2 Correct data

First, we define functions to 1) get background-corrected values of our samples, 2) compute the standard concentrations, and 3) to get tidy data frames of the standard and the biological samples.

# Gets samples, performs background correction
# Works only on single rows of the 96-well plate
# The number of columns is user defined (set n.cols)
#' @param data input data frame
#' @param row.idx row index in the input data frame of the sample
#' @param col.dix column index in the input data frame of the first cell of the sample
#' @param n.cols number of columns to get in the input data frame
#' @return a data frame with background-corrected values
correct.sample <- function(data, row.idx, col.idx, n.cols) {
  row.end <- row.idx + 1 # we select only two rows (450 nm and 620 nm measurements)
  col.end <- col.idx + (n.cols - 1) # the number of columns is user defined
  sample <- data %>%
    .[row.idx:row.end, col.idx:col.end]
  # Subtract background OD value for each sample
  # Note: in our example data, 450 nm OD results are always on top of 620 nm OD results.
  corr.sample <- sample[1, ] - sample[2, ]
  # Return background-corrected OD value
  return(as.tibble(corr.sample))
}

# Compute dilution series
#' @param max.conc.std maximal concentration
#' @param n.dilutions number of dilutions performed
#' @param dilution.fact dilution factor
#' @return a vector with concentrations
comp.conc <- function(
  max.conc.std = max.conc.std, 
  n.dilutions = n.dilutions, 
  dilution.fact = dilution.fact) {
  dilutions <- sapply(
    seq(0, (n.dilutions - 1)),
    function(x) max.conc.std / (dilution.fact^x)
  )
  return(dilutions)
}

# Get background-corrected standard
# TODO: add documentation
get.standard <- function(data = data,
                         row.idx = row.idx.std,
                         col.idx = col.idx.std,
                         n.dilutions = n.dilutions,
                         n.repl = n.repl,
                         dilution.fact = dilution.fact,
                         max.conc.std = max.conc.std, 
                         concentration = concentrations) {
  # Assumes that the standard dilutions were pipetted in vertical order
  row.end <- row.idx + (n.dilutions * 2) - 1 
  col.end <- col.idx + (n.repl - 1)
  standard <- data %>% .[row.idx:row.end, col.idx:col.end]
  corr.standard <- purrr::map_df(
    seq(1, row.end, by = 2),
    ~ correct.sample(data = standard, ., col.idx = 1, n.cols = n.repl)
  ) %>%
    na.omit() %>%
    setNames(seq(n.repl)) %>%
    dplyr::mutate(concentration = concentrations) %>%
    tidyr::gather(key = replicate, value = absorbance, -concentration)

  return(corr.standard)
}

# TODO: add documentation
get.donors <- function(row.idx = row.idx.donor.1,
                       col.idx = col.idx.donor.1,
                       n.cols = n.repl.donors * length(tpoints)) {
  row.end <- row.idx + 1 # 450 nm and 620 nm
  col.end <- col.idx + (n.cols - 1)
  sample <- data %>% .[row.idx:row.end, col.idx:col.end]
  corr.donors <- correct.sample(
    data = sample, row.idx = 1, col.idx = 1, n.cols = n.cols
  )
  return(corr.donors)
} 

Now we can create a data frame with the corrected data of the standard:

concentrations <- comp.conc(
  max.conc.std = max.conc.std,
  n.dilutions = n.dilutions,
  dilution.fact = dilution.fact
)

standard <- get.standard(
  data = data,
  row.idx = row.idx.std,
  col.idx = col.idx.std,
  n.dilutions = n.dilutions,
  n.repl = n.repl.std,
  dilution.fact = dilution.fact,
  max.conc.std = max.conc.std, 
  concentration = concentrations
)
# Inspect result
standard
## # A tibble: 16 x 3
##    concentration replicate absorbance
##            <dbl> <chr>          <dbl>
##  1       1       1              1.69 
##  2       0.5     1              1.29 
##  3       0.25    1              0.865
##  4       0.125   1              0.689
##  5       0.0625  1              0.26 
##  6       0.0312  1              0.128
##  7       0.0156  1              0.059
##  8       0.00781 1              0.033
##  9       1       2              1.70 
## 10       0.5     2              1.32 
## 11       0.25    2              0.884
## 12       0.125   2              0.671
## 13       0.0625  2              0.262
## 14       0.0312  2              0.121
## 15       0.0156  2              0.062
## 16       0.00781 2              0.032

Here we select the wells with the blanks:

# Get blanks
blanks <- correct.sample(data, row.idx.blank, col.idx.blank, n.cols = 2) %>%
  setNames(seq(1, dim(.)[2])) %>%
  tidyr::gather(key = replicate, value = blank)
# Inspect blanks
blanks
## # A tibble: 2 x 2
##   replicate   blank
##   <chr>       <dbl>
## 1 1         0.00400
## 2 2         0.0050

And finally we create a data frame with the corrected data of our biological samples. In our case, we have a time series with five data points for four donors:

donors <- purrr::map_df(
  seq(row.idx.donor.1, (row.idx.donor.1 + 2 * (n.donors - 1)), by = 2),
  ~ get.donors(., col.idx = col.idx.donor.1, n.cols = 10)
) %>%
  # Add names that indicate time point and replicate ID separated by a dot
  setNames(c(0.1, 0.2, 7.1, 7.2, 30.1, 30.2, 60.1, 60.2, 180.1, 180.2)) %>%
  dplyr::mutate(donor = seq(1, n.donors)) %>%
  dplyr::mutate(donor = factor(donor, unique(donor))) %>%
  tidyr::gather(key = "time", value = "absorbance", -donor) %>%
  tidyr::separate(time, c("time", "replicate")) %>%
  dplyr::mutate(time = as.integer(time)) %>%
  dplyr::group_by(donor, time)
donors.av <- donors %>% 
  dplyr::summarise(absorbance.av = mean(absorbance)) %>%
  dplyr::ungroup()

# Inspect data
donors.av
## # A tibble: 20 x 3
##    donor  time absorbance.av
##    <fct> <int>         <dbl>
##  1 1         0         0.336
##  2 1         7         0.596
##  3 1        30         0.710
##  4 1        60         0.804
##  5 1       180         0.482
##  6 2         0         0.534
##  7 2         7         0.729
##  8 2        30         0.825
##  9 2        60         0.790
## 10 2       180         0.660
## 11 3         0         0.347
## 12 3         7         1.25 
## 13 3        30         0.837
## 14 3        60         0.768
## 15 3       180         0.563
## 16 4         0         0.932
## 17 4         7         1.17 
## 18 4        30         1.01 
## 19 4        60         0.845
## 20 4       180         0.950

Before we proceed with the analysis, let’s plot the data and see whether it looks reasonable.