#' Calculate transfer steps from layouts
#'
#' @param daughter A daughter layout, standardized and repaired
#' @param mother A mother layout, standardized and repaired
#' @param .echo_drop_nL The transfer volume of the Echo instrument to be used, in nL. Defaults to 25.
#' @param .dilutant_name The name of the compound in the mother to be used for dilutions. Defaults to "DMSO".
#'
#' @return A tibble containing all transfer steps, and final conditions representing any necessary rounding
#'
#' @importFrom plyr round_any
#' @importFrom dplyr bind_rows rename row_number
#' @importFrom tidyr nest unnest
#' @importFrom purrr map_dbl
#' @importFrom rlang abort
#' @importFrom utils globalVariables
#'
#' @export
calculate_transfers <- #_______(primary function) Write all transfer steps from layouts, rounding where necessary_______#
function(daughter, mother, .echo_drop_nL = 25, .dilutant_name = "DMSO") {
compound_transfers <- #__compound transfers__
concentrations_to_transfers(daughter, mother, .echo_drop_nL) %>% # rounds where necessary based on .echo_drop_nL
mutate(dilution_vol = .data$daughter_final_vol - .data$mother_vol) %>%
distribute_shared(.echo_drop_nL) %>%
mutate(transfer_type = "compound_transfer")
dilution_transfers <- #__calc dilution transfers separately, to respect necessary rounding and desired final volume__
make_dilutions_plate(compound_transfers, mother, .echo_drop_nL = .echo_drop_nL, .dilutant_name = .dilutant_name) %>%
mutate(transfer_type = "dilution_transfer")
transfers <- #__All transfer steps and final conditions__#
bind_rows(compound_transfers, dilution_transfers) %>%
select(-c(.data$dilution_vol, .data$mother_dil)) %>% # helper column for dilution_transfers
mutate(across(where(is.numeric), round, 2)) %>% # for readability
filter(.data$mother_vol > 0) %>%
select(c(.data$`Destination Well`, .data$`Source Well`, .data$compound, .data$daughter_conc, .data$mother_conc, .data$daughter_final_vol, .data$mother_vol, .data$final_conc, .data$rounded_up, .data$rounded_up_perc, .data$transfer_type)) # return in reader-friendly order
}
#' Monitor depletion of source plate wells
#'
#' @param transfers A tibble containing the final calculated transfers.
#' @param max_uL_pull The maximum volume that can be pulled from any single mother well. Defaults to 35.
#'
#' @return A tibble containing Source Wells, compounds, and total volume pulled from each well
#'
#' @importFrom dplyr distinct if_else
#'
#' @export
monitor_source_depletion <- #_______(helper function) Monitor overdrawing of source plate wells, and warn_______#
function(transfers, max_uL_pull = 35) {
#_____Catch argument errors____
if (!is.numeric(max_uL_pull)) {
abort_bad_argument("max_uL_pull",
must = "be numeric",
not = typeof(max_uL_pull)) }
# catching errors in transfers input would require a different abort and I don't want to write that right now
#_____Calculate the depletion of each source well____
source_depletions <- transfers %>%
group_by(.data$`Source Well`) %>%
mutate(uL_used = round(sum(.data$mother_vol)/1000, 3)) %>%
select(.data$`Source Well`, .data$compound, .data$uL_used) %>%
distinct() %>%
mutate(over_drawn = if_else(.data$uL_used > max_uL_pull,
true = "Overdrawn", # helps expressively throw error, for shinyAlerts()
false = "Ok"))
#_____Catch source overdraws and warn user____
if ("Overdrawn" %in% source_depletions$over_drawn) {
overdrawn <- source_depletions %>% filter(.data$over_drawn == "Overdrawn")
warning(glue::glue("Warning!
The requested transfers will overdraw {glue::glue_collapse(nrow(overdrawn))} well(s) in the mother plate.
Specified maximum transfer volume is {max_uL_pull} uL per source well.
Well(s) {glue::glue_collapse(overdrawn %>% pull(.data$`Source Well`), sep = ', ')} have the following excessive volumes (uL) transferred:
{glue::glue_collapse(overdrawn %>% pull(.data$uL_used), sep = ', ')},
corresponding to compounds {glue::glue_collapse(overdrawn %>% pull(.data$compound) %>% unique(), sep = ', ')}.
To fix this, add more wells of any over-drawn compounds to your mother plate, or reduce their use in the daughter.")
)
}
source_depletions %>% ungroup()# return for visualization and/or download
}
#' Concentrations to transfers
#'
#' @param daughter repaired daughter layout
#' @param mother repaired mother layout
#' @param .echo_drop_nL the droplet size for the echo, in nL. Defaults to 25.
#'
#' @return tibble of calcualted transfers, withoutdistribution across source
#'
#' @importFrom dplyr group_by left_join mutate if_else filter
#' @importFrom tidyr nest
#' @importFrom plyr round_any
#'
#' @export
concentrations_to_transfers <- #_______(helper function) Convert concentrations to transfer steps, rounding where necessary_______#
function(daughter, mother, .echo_drop_nL = 25) { # if there are compounds in the daughter not in the mother
#_____Catch argument errors____
if (!is.numeric(.echo_drop_nL)) {
abort_bad_argument(".echo_drop_nL",
must = "be numeric",
not = typeof(.echo_drop_nL)) }
by_compound_m <- #__INTERMEDIATE: mother layout, nested by compound__
mother %>%
group_by(.data$compound, .data$mother_conc) %>%
nest()
transfers <- #__OUTPUT: all calculated transfer steps__#
daughter %>%
left_join( . , by_compound_m, by = "compound") %>%
mutate(mother_dil = (.data$daughter_conc/.data$mother_conc) * ( .data$daughter_final_vol),
mother_vol = plyr::round_any(.data$mother_dil, .echo_drop_nL, ceiling),
final_conc = (.data$mother_conc*.data$mother_vol)/.data$daughter_final_vol,
rounded_up = .data$final_conc - .data$daughter_conc,
rounded_up_perc = if_else(.data$daughter_conc == 0, true = 0, false = round(100*.data$rounded_up/.data$daughter_conc, 1))) %>%
filter(is.na(.data$mother_vol) == FALSE)
}
#' Calculate dilution trasnfers
#'
#' @param transfers tibble containing compound transferd
#' @param mother repaired mother layout
#' @param .echo_drop_nL droplet size of echo in nL. Defaults to 25.
#' @param .dilutant_name name of the compound used for diluations. Defaults to "DMSO"
#'
#' @return a transfer tibble, specifically for the dilutions.
#'
#' @importFrom dplyr filter mutate select distinct ungroup
#'
#' @export
make_dilutions_plate <- #_______(helper function) Calculate dilution transfers separately to avoid rounding errors_______#
function(transfers, mother, .echo_drop_nL = 25, .dilutant_name = "DMSO") {
#_____Catch argument errors____
if (!.dilutant_name %in% mother$compound) {
abort_bad_argument(".dilutant_name",
must = "be a compound in the mother plate. {.dilutant_name} not found in mother.",
not = NULL) }
# .echo_drop_nL used only in concentrations_to_transfer, errors caught there
dil_mother <- #__INTERMEDIATE: mother, with dilutant only__
mother %>%
filter(.data$compound == .dilutant_name) %>%
mutate(mother_conc = 1) # just needs to match daughter for concentrations_to_transfers
dil_daughter <- #__INTERMEDIATE: daughter, with dilutions only__
transfers %>%
filter(.data$dilution_vol > 0) %>%
mutate(daughter_final_vol = .data$dilution_vol) %>%
select(.data$`Destination Well`, .data$daughter_final_vol, ) %>%
mutate(compound = .dilutant_name,
daughter_conc = 1) %>% # just needs to match mother for concentrations_to_transfers
distinct() # remove duplicates for each transfer
dil_transfer <- #__OUTPUT: dilution transfers__#
concentrations_to_transfers(dil_daughter, dil_mother, .echo_drop_nL) %>%
distribute_shared(.echo_drop_nL) %>%
ungroup()
}
#' Distribute transfers over multiple mother source wells
#'
#'
#' @param transfers a tibble of transfers
#' @param .echo_drop_nL droplet size of echo in nL. Defaults to 25.
#'
#' @return a modified tibble of transfers, with transfers distributed across common mother wells.
#'
#' @importFrom dplyr mutate group_by row_number cur_group_id n select ungroup ungroup
#' @importFrom purrr map_dbl
#' @importFrom tidyr unnest
#'
#' @export
distribute_shared <- #_______(helper function) Distribute transfers over common source wells_______#
function(transfers, .echo_drop_nL = 25) {
#_____Catch argument errors____
if (!is.numeric(.echo_drop_nL)) {
abort_bad_argument(".echo_drop_nL",
must = "be numeric",
not = typeof(.echo_drop_nL)) }
distributed <- #__OUTPUT: transfers, distributed over common source wells__#
transfers %>%
mutate(n_wells = map_dbl(.data$data, nrow)) %>% # the number of wells of mother
mutate(per_well = (.data$mother_vol/.data$n_wells) - (.data$mother_vol/.data$n_wells)%%.echo_drop_nL, # how many transfers per well?
extra_transfer = .data$mother_vol - .data$per_well*.data$n_wells) %>% # how many left over transfers, after even division over wells?
rename(mother_vol_total = .data$mother_vol,
mother_vol = .data$per_well) %>%
unnest(cols = c(.data$data)) %>% # this unnesting step adds the divided transfer volume to all mother source wells
group_by(.data$compound, .data$mother_conc, .data$`Destination Well`, .add = TRUE) %>%
mutate(mother_vol = if_else(row_number() == cur_group_id()%%n(), .data$mother_vol + .data$extra_transfer, .data$mother_vol)) %>% # add the extra transfer to just one of the wells, cycling through wells
# mutate(mother_vol = if_else(row_number() == 1, .data$mother_vol + .data$extra_transfer, .data$mother_vol)) %>% # add the extra transfer to just one of the wells
select(-c(.data$extra_transfer, .data$n_wells, .data$extra_transfer, .data$mother_vol_total)) %>% # drop unneeded column to match input
ungroup()
}
utils::globalVariables("where") # workaround: tidyselect::where() is not an exported function
# See: https://github.com/r-lib/tidyselect/issues/201#issuecomment-650547846
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.