#' Calculate FP per cell values
#'
#' Takes as input plate reader data processed with `process_plate()` and uses
#' normalised/calibrated values to calculate per-cell values. Adds column(s) for
#' FP/cell as either: (a) `normalisedFP/normalisedOD` (RFU/OD), (b)
#' `calibratedFP/calibratedOD` (molecules/cell). Plots results and returns a
#' dataframe. Note that technically, units of molecules are 'molecules of
#' equivalent FP' and cells are 'particles of equivalent microspheres'.
#'
#' @param data_csv path to a CSV file containing processed plate reader data
#' @param timecourse logical. Is the data timecourse/kinetic data and does it
#' include a variable called 'time'?
#' @param flu_channels the column names for the NORMALISED fluorescence data
#' @param flu_labels the column names for the CALIBRATED fluorescence data
#' @param remove_wells list of coordinates of wells to be removed from analysis
#' (e.g. empty wells)
#' @param get_rfu_od logical. if TRUE, uses `normalised_FP` and `normalised_OD`
#' to calculate FP per cell as FP/OD in relative fluorescence units/relative
#' OD (RFU/OD).
#' @param get_mol_cell logical. if TRUE, uses `calibrated_FP` and
#' `calibrated_OD` to calculate FP per cell as FP/OD in relative fluorescence
#' units/relative OD (molecules/cell).
#' @param plate_type type of plate. numeric, i.e. '96' for 96-well plate.
#' Defines the rows and columns used for plotting figures. Defaults to '96'.
#' @param outfolder path to folder where output files should be saved. Defaults
#' to current working directory.
#'
#' @return a data.frame with columns for each FP/cell calculation
#' @export
#' @importFrom rlang .data
#' @importFrom dplyr %>%
#'
#' @examples
#' \dontrun{
#' pc_data_mCherry <- calc_fppercell(
#' data_csv = "mcherry_parsed_processed.csv",
#' flu_channels = c("red1red1"), flu_labels = c("mCherry"),
#' remove_wells = c("A11"),
#' get_rfu_od = TRUE, get_mol_cell = TRUE,
#' outfolder = file.path("plots")
#' )
#' }
calc_fppercell <- function(data_csv,
timecourse = TRUE,
flu_channels, # colnames of normalised values (rfu), eg c("red1red1", "red1red2")
flu_labels, # colnames of calibrated values (molecules), eg c("mCherry", "mScarlet")
remove_wells,
get_rfu_od = TRUE, # get_rfu_pems = FALSE, # pointless
get_mol_cell = FALSE, # used to be get_mefl_pems
plate_type = 96,
outfolder = "."){
# Get parsed data -------------------------------------------------
pr_data <- utils::read.csv(data_csv, check.names = FALSE)
pr_data
percell_data <- pr_data # copy over data
percell_data
# Remove wells eg blank wells -------------------------------------------------
# Blank wells tend to distort scales as FP/OD leads to division by close to zero values = huge values
if(!is.null(remove_wells)){
## to mutate blank wells to NA would be ideal, but complex
## to remove blank wells, do this
percell_data <- percell_data %>%
dplyr::filter(!.data$well %in% remove_wells)
percell_data
}
# Location for saved plots -------------------------------------------------
# make folder if it doesn't exist already
ifelse(test = !dir.exists(file.path(outfolder)), yes = dir.create(file.path(outfolder)), no = FALSE)
# RFU/OD calculation -------------------------------------------------
if (get_rfu_od){
# If get_rfu_od = TRUE, calculate FP(relative fluor units)/OD...
# normalised_OD required
# normalised_FPs required
# This will be normalised_FP/normalised_OD (rfu/od).
for (flu_idx in seq_len(length(flu_channels))) {
# For each FP...
# Calculate FP/OD -----------------------------
# cols to use
cellmeasure <- "normalised_OD"
if(!any(names(percell_data) %in% cellmeasure)){
# if normalised_OD column doesn't exist
message("Normalised_OD needed to calculate FP/OD in rfu/od.
Measure missing, skipping all rfu/od calculations.")
break
}
flumeasure <- paste0("normalised_", flu_channels[flu_idx])
if(!any(names(percell_data) %in% flumeasure)){
# if normalised_FP column doesn't exist
message(flumeasure, " needed to calculate FP/OD in rfu/od.
Measure missing, skipping FP.")
next
}
# Calc
if(isFALSE(timecourse)){
percell_data <- percell_data %>%
dplyr::mutate(v1 = (.data[[flumeasure]]) / (.data[[cellmeasure]]) )
# divides norm GFP by norm OD
} else if (isTRUE(timecourse)){
percell_data <- percell_data %>%
dplyr::group_by(.data$time) %>%
dplyr::mutate(v1 = (.data[[flumeasure]]) / (.data[[cellmeasure]]) )
# for each timepoint, divides norm GFP by norm OD
}
# Rename last column:
names(percell_data)[ncol(percell_data)] <- paste0("normalised", flu_channels[flu_idx], "_perOD")
names(percell_data)
# Plot FP/OD ---------------------------------------------------------
# plot
if(isFALSE(timecourse)){
# find all rows and columns of plate type
rows <- fpcountr::find_rows(plate_type = plate_type)
columns <- fpcountr::find_columns(plate_type = plate_type)
# heatmap - normalised fluor per OD
max_value <- max(percell_data[[paste0("normalised", flu_channels[flu_idx], "_perOD")]], na.rm = TRUE)
plt_flu <- ggplot2::ggplot(data = percell_data,
ggplot2::aes(x = .data$column, y = row, fill = .data[[paste0("normalised", flu_channels[flu_idx], "_perOD")]])) +
ggplot2::geom_tile() +
ggplot2::scale_x_discrete("", position = "top",
# limits = factor(unique(percell_data$column))) + # where not all rows used, only displays fraction of plate
# limits = factor(seq(1,12))) + # hardcoded for 96-well plates
limits = factor(columns)) +
ggplot2::scale_y_discrete("",
# limits = rev(unique(percell_data$row))) + # where not all rows used, only displays fraction of plate
# limits = rev(c("A", "B", "C", "D", "E", "F", "G", "H"))) + # hardcoded for 96-well plates
limits = rev(rows)) +
viridis::scale_fill_viridis(paste0(flu_channels[flu_idx], "/OD (rfu/od)"), # paste0("normalised ", flu_channels[flu_idx], " per OD (rfu/OD)"),
discrete = FALSE, limits = c(0, max_value),
alpha = 0.4,
na.value = "white") +
# ggplot2::geom_text(ggplot2::aes(label = scales::scientific(.data[[paste0("normalised", flu_channels[flu_idx], "_perOD")]], 2)), na.rm = TRUE, size = 2.5) +
ggplot2::geom_text(ggplot2::aes(label = round(.data[[paste0("normalised", flu_channels[flu_idx], "_perOD")]], 2)), na.rm = TRUE, size = 2.5) +
ggplot2::theme_bw() + # base_size = 8
ggplot2::theme(
aspect.ratio = 8/12,
panel.grid = ggplot2::element_blank()
)
plt_flu
} else if(isTRUE(timecourse)){
# find all rows and columns of plate type, and add them as factors
percell_data$row <- factor(percell_data$row, levels = fpcountr::find_rows(plate_type = plate_type))
percell_data$column <- factor(percell_data$column, levels = fpcountr::find_columns(plate_type = plate_type))
plt_flu <- ggplot2::ggplot(percell_data) +
ggplot2::geom_line(ggplot2::aes(x = .data$time,
y = .data[[paste0("normalised", flu_channels[flu_idx], "_perOD")]]),
linewidth = 0.5) +
ggplot2::scale_x_continuous("time") +
ggplot2::scale_y_continuous(name = paste0(flu_channels[flu_idx], "/OD (rfu/od)"), # labels = scales::label_scientific(digits = 2)
) +
ggplot2::labs(caption = "") +
ggplot2::scale_colour_discrete("") +
ggplot2::facet_grid(row~column, drop = FALSE) + # keep wells with missing values
ggplot2::theme_bw(base_size = 8) +
ggplot2::theme(
aspect.ratio = 1,
legend.position = "none",
axis.text.x = ggplot2::element_text(angle = 90, vjust = 0.5),
panel.grid.minor = ggplot2::element_blank()
)
plt_flu
}
plotname <- paste0("normalised_", flu_channels[flu_idx], "_perOD.pdf")
ggplot2::ggsave(file.path(outfolder, plotname),
plot = plt_flu,
height = 16, width = 24, units = "cm")
} # then repeat for each FP
} # get_rfu_od
# FP/Cell in RFU/CELL --------------------------------------------------
# pointless
# if(get_rfu_pems){
# # If get_rfu_pems = TRUE, calculate FP(rfu)/Cell(pems)...
#
# # calibrated_OD required
# # normalised_FPs required
# # This will be normalised_FP/calibrated_OD (rfu/pems).
#
# for (flu_idx in seq_len(length(flu_channels))) {
# # For each FP...
#
# # Calculate FP/Cell -----------------------------
#
# # cols to use
# cellmeasure <- "calibrated_OD"
# if(!any(names(percell_data) %in% cellmeasure)){
# # if calibrated_OD column doesn't exist
# message("Calibrated_OD needed to calculate FP/cell in rfu/pems.
# Measure missing, skipping all rfu/pems calculations.")
# break
# }
# flumeasure <- paste0("normalised_", flu_channels[flu_idx])
# if(!any(names(percell_data) %in% flumeasure)){
# # if normalised_FP column doesn't exist
# message(flumeasure, " needed to calculate FP/cell in rfu/pems.
# Measure missing, skipping FP.")
# next
# }
#
# percell_data <- percell_data %>%
# dplyr::group_by(.data$time) %>%
# dplyr::mutate(v1 = (.data[[flumeasure]]) / (.data[[cellmeasure]]) )
# # for each timepoint, divides norm GFP by calib OD
#
# # Rename last column:
# names(percell_data)[ncol(percell_data)] <- paste0("normalised", flu_channels[flu_idx], "_perCell")
# names(percell_data)
#
# # Plot FP/Cell -----------------------------
#
# # plot
# plt_flu <- ggplot2::ggplot(percell_data) +
# ggplot2::geom_line(ggplot2::aes(x = .data$time,
# y = .data[[paste0("normalised", flu_channels[flu_idx], "_perCell")]]),
# linewidth = 0.5) +
# ggplot2::scale_x_continuous("time") +
# ggplot2::scale_y_continuous(name = paste0(flu_channels[flu_idx], "/cell (rfu/pems)"),
# labels = scales::label_scientific()) +
# # pems = particles of equiv microspheres
# ggplot2::labs(caption = "") +
# ggplot2::scale_colour_discrete("") +
# ggplot2::facet_grid(row~column) +
# ggplot2::theme_bw(base_size = 8) +
# ggplot2::theme(
# aspect.ratio = 1,
# legend.position = "none",
# axis.text.x = ggplot2::element_text(angle = 90, vjust = 0.5),
# panel.grid.minor = ggplot2::element_blank()
# )
# plt_flu
# plotname <- paste0("normalised", flu_channels[flu_idx], "_perCell.pdf")
# ggplot2::ggsave(file.path(outfolder, plotname),
# plot = plt_flu,
# height = 16, width = 24, units = "cm")
# } # then repeat for each FP
#
# } # get_rfu_pems
# FP/Cell in molecules/cell --------------------------------------------------
if(get_mol_cell){
# If get_mol_cell = TRUE, calculate FP(molecules)/Cell...
# calibrated_OD required
# calibrated_FPs required
# This will be calibrated_GFP/calibrated_OD (molecules/cell).
for (flu_idx in seq_len(length(flu_labels))) {
# For each FP...
# Calculate FP/Cell -----------------------------
# cols to use
cellmeasure <- "calibrated_OD"
if(!any(names(percell_data) %in% cellmeasure)){
# if calibrated_OD column doesn't exist
message("Calibrated_OD needed to calculate FP/cell in molecules/cell.
Measure missing, skipping all molecules/cell calculations.")
break
}
flumeasure <- paste0("calibrated_", flu_labels[flu_idx])
if(!any(names(percell_data) %in% flumeasure)){
# if calibrated_FP column doesn't exist
message(flumeasure, " needed to calculate FP/cell in molecules/cell.
Measure missing, skipping FP.")
next
}
if(isFALSE(timecourse)){
percell_data <- percell_data %>%
dplyr::mutate(v1 = (.data[[flumeasure]]) / (.data[[cellmeasure]]) )
# divides norm/calib GFP to norm/calib OD
} else if(isTRUE(timecourse)){
percell_data <- percell_data %>%
dplyr::group_by(.data$time) %>%
dplyr::mutate(v1 = (.data[[flumeasure]]) / (.data[[cellmeasure]]) )
# for each timepoint, divides norm/calib GFP to norm/calib OD
}
# Rename last column:
names(percell_data)[ncol(percell_data)] <- paste0("calibrated", flu_labels[flu_idx], "_perCell")
names(percell_data)
# Plot FP/Cell -----------------------------
# plot
if(isFALSE(timecourse)){
# find all rows and columns of plate type
rows <- fpcountr::find_rows(plate_type = plate_type)
columns <- fpcountr::find_columns(plate_type = plate_type)
# heatmap - calibrated fluor per cell
max_value <- max(percell_data[[paste0("calibrated", flu_labels[flu_idx], "_perCell")]], na.rm = TRUE)
plt_flu_calib <- ggplot2::ggplot(data = percell_data,
ggplot2::aes(x = .data$column, y = row, fill = .data[[paste0("calibrated", flu_labels[flu_idx], "_perCell")]])) +
ggplot2::geom_tile() +
ggplot2::scale_x_discrete("", position = "top",
# limits = factor(unique(percell_data$column))) + # where not all rows used, only displays fraction of plate
# limits = factor(seq(1,12))) + # hardcoded for 96-well plates
limits = factor(columns)) +
ggplot2::scale_y_discrete("",
# limits = rev(unique(percell_data$row))) + # where not all rows used, only displays fraction of plate
# limits = rev(c("A", "B", "C", "D", "E", "F", "G", "H"))) + # hardcoded for 96-well plates
limits = rev(rows)) +
viridis::scale_fill_viridis(paste0(flu_labels[flu_idx], "/cell (molecules/cell)"), # paste0("calibrated ", flu_channels[flu_idx], " per cell (molecules/cell)"),
discrete = FALSE, limits = c(0, max_value),
alpha = 0.4,
na.value = "white") +
ggplot2::geom_text(ggplot2::aes(label = scales::scientific(.data[[paste0("calibrated", flu_labels[flu_idx], "_perCell")]], 2)), na.rm = TRUE, size = 2.5) +
ggplot2::theme_bw() + # base_size = 8
ggplot2::theme(
aspect.ratio = 8/12,
panel.grid = ggplot2::element_blank()
)
plt_flu_calib
} else if(isTRUE(timecourse)){
# find all rows and columns of plate type, and add them as factors
percell_data$row <- factor(percell_data$row, levels = fpcountr::find_rows(plate_type = plate_type))
percell_data$column <- factor(percell_data$column, levels = fpcountr::find_columns(plate_type = plate_type))
plt_flu_calib <- ggplot2::ggplot(percell_data) +
ggplot2::geom_line(ggplot2::aes(x = .data$time,
y = .data[[paste0("calibrated", flu_labels[flu_idx], "_perCell")]]),
linewidth = 0.5) +
ggplot2::scale_x_continuous("time") +
ggplot2::scale_y_continuous(name = paste0(flu_labels[flu_idx], "/cell (molecules/cell)"),
labels = scales::label_scientific()) +
ggplot2::labs(caption = "") +
ggplot2::scale_colour_discrete("") +
ggplot2::facet_grid(row~column, drop = FALSE) + # keep wells with missing values
ggplot2::theme_bw(base_size = 8) +
ggplot2::theme(
aspect.ratio = 1,
legend.position = "none",
axis.text.x = ggplot2::element_text(angle = 90, vjust = 0.5),
panel.grid.minor = ggplot2::element_blank()
)
plt_flu_calib
}
plotname <- paste0(flu_labels[flu_idx], "_calibrated_percell.pdf") # bring plot name in line w process_plate()
ggplot2::ggsave(file.path(outfolder, plotname),
plot = plt_flu_calib,
height = 16, width = 24, units = "cm")
} # then repeat for each FP
} # get_mol_cell
# Save CSV --------------------------------------------------
filename <- gsub(".csv", "_percell.csv", basename(data_csv))
utils::write.csv(x = percell_data,
file = file.path(outfolder, filename),
row.names = FALSE)
return(percell_data)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.