High-throughput metabolic phenotype microarray data is still relatively new in mammalian cell culture research. As such, many questions remain:
This package aims to provide:
RawRead.csv and
PlateInfo.csv files into R.Important Limitations:
The read_folder() function reads every
RawRead.csv and PlateInfo.csv file in
the specified folder and all sub directories.
Always double-check that:
PlateInfo.csv files have the same column
order and same variable names.Also: R is case-sensitive.
H460 ≠ h460.
0Gy ≠ 0gy.
This can cause missing data or errors later — so be careful!
Before you can analyze, you need to organize your files properly.
Analysis).RawRead.csv and
PlateInfo.csv files into this folder.PlateInfo.csv file in Excel or a text
editor.Description, Sample) to store your
experimental conditions.variable1_variable2_variable3
e.g. H460_ctrl_2hWhy? Because read_folder() splits this column into
separate variables (e.g., cell.line,
condition, time.h). If the format is
inconsistent, it will fail or create duplicates.
Arguments of read_folder():
What follows next is the R code and the corresponding output. We included example data in this package to be able to demonstrate the workflow. Everything following a “#” in code are comments that should help understand what the code does.
If you are using RStudio an object (data frame) should now be loaded to the Environment tab. You can click on it’s name to inspect the dataframe or type
head(RawRead)
#> # A tibble: 6 × 102
#> Id PlateId Wavelength Actual Temperature C…¹ Target Temperature C…²
#> <chr> <chr> <dbl> <dbl> <dbl>
#> 1 80aaa046-c9d… 6e1e3d… 590 37.3 37
#> 2 99780dd0-b47… 6e1e3d… 740 37.3 37
#> 3 51011480-f92… 6e1e3d… 590 37.1 37
#> 4 4a0b5a5c-886… 6e1e3d… 740 37.1 37
#> 5 a28b7969-fc1… 6e1e3d… 590 37 37
#> 6 be0de81b-576… 6e1e3d… 740 37 37
#> # ℹ abbreviated names: ¹`Actual Temperature Celsius`,
#> # ²`Target Temperature Celsius`
#> # ℹ 97 more variables: `Read At` <dttm>, A01 <dbl>, A02 <dbl>, A03 <dbl>,
#> # A04 <dbl>, A05 <dbl>, A06 <dbl>, A07 <dbl>, A08 <dbl>, A09 <dbl>,
#> # A10 <dbl>, A11 <dbl>, A12 <dbl>, B01 <dbl>, B02 <dbl>, B03 <dbl>,
#> # B04 <dbl>, B05 <dbl>, B06 <dbl>, B07 <dbl>, B08 <dbl>, B09 <dbl>,
#> # B10 <dbl>, B11 <dbl>, B12 <dbl>, C01 <dbl>, C02 <dbl>, C03 <dbl>, …which prints the first rows of the dataframe to your console.
Let’s look at the corresponding example PlateInfo.csv file.
data("PlateInfo") # loads PlateInfo example file from MetPhenoR
head(PlateInfo) # shows first few rows of PlateInfo dataframe
#> # A tibble: 6 × 13
#> PlateId `Project Name` `Project Code` `Plate Type` Sample `Loaded At`
#> <chr> <chr> <chr> <chr> <chr> <dttm>
#> 1 c2aa34c… example_proje… exp PM-M01 A_ctr… 2025-05-30 13:50:36
#> 2 6e1e3d1… example_proje… exp PM-M01 A_ctr… 2025-05-30 13:50:36
#> 3 6237378… example_proje… exp PM-M01 A_tre… 2025-05-30 13:50:36
#> 4 011a375… example_proje… exp PM-M02 A_tre… 2025-05-30 13:50:36
#> 5 3b477cc… example_proje… exp PM-M02 A_tre… 2025-05-30 13:50:36
#> 6 7e309fc… example_proje… exp PM-M02 A_ctr… 2025-05-30 13:50:36
#> # ℹ 7 more variables: `Incubation Hours` <dbl>,
#> # `Incubation Temperature Celsius` <dbl>, `Incubated Offline` <lgl>,
#> # `Cycle Time Minutes` <dbl>, Position <chr>, Protocol <lgl>,
#> # Description <lgl>In this example file the column Sample contains the experimental information.
head(PlateInfo$Sample) # shows first few rows of column storing experimental information
#> [1] "A_ctrl_inhibitor_2h" "A_ctrl_ctrl_2h"
#> [3] "A_treatment_ctrl_2h" "A_treatment_ctrl_2h"
#> [5] "A_treatment_inhibitor_2h" "A_ctrl_ctrl_2h"In the next I show an example code to load you data from a folder named “metabolic_phenotyping” on drive A: and assign it to an object called data.
# data <- read_folder(data_path = "A:/metabolic_phenotyping", # folder path
# sample_column = "Sample", # which column in PlateInfo contains experimental information
# variables = c("cell.line","condition","time.h") # sets the variable names for the following workflow)This function does five things:
Let’s load an example of multiple processed RawReads and PlateInfo files and look at the different elements:
data("processed_data") # load example output of read_folder() function
head(processed_data$data) # show first lines of element "data" in object "processed_data"
#> # A tibble: 6 × 23
#> PlateId Project.Name Project.Code Plate.Type Sample cell.line treatment
#> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 c2aa34c0-d80f… example_pro… exp PM-M01 A_ctr… A ctrl
#> 2 c2aa34c0-d80f… example_pro… exp PM-M01 A_ctr… A ctrl
#> 3 c2aa34c0-d80f… example_pro… exp PM-M01 A_ctr… A ctrl
#> 4 c2aa34c0-d80f… example_pro… exp PM-M01 A_ctr… A ctrl
#> 5 c2aa34c0-d80f… example_pro… exp PM-M01 A_ctr… A ctrl
#> 6 c2aa34c0-d80f… example_pro… exp PM-M01 A_ctr… A ctrl
#> # ℹ 16 more variables: inhibitor <chr>, time <chr>, Loaded.At <dttm>,
#> # Incubation.Hours <dbl>, Incubation.Temperature.Celsius <dbl>,
#> # Incubated.Offline <lgl>, Cycle.Time.Minutes <dbl>, Position <chr>,
#> # Protocol <lgl>, Description <lgl>, Actual.Temperature.Celsius <dbl>,
#> # Target.Temperature.Celsius <dbl>, Read.At <dttm>, Hour <dbl>, Well <chr>,
#> # diff_OD_590_740 <dbl>You will find that you set variables are now not only stored in your sample_column but also separate variables. This comes in especially handy for custom analysis and visualization.
processed_data$meta # display meta information: folder path, sample column, variables
#> $data_path
#> [1] "~/Documents/packages/Odin data/ODIN Data 03.06.2025/"
#>
#> $sample_column
#> [1] "Sample"
#>
#> $variables
#> [1] "cell.line" "treatment" "inhibitor" "time"Now that our data is read in let’s check if we read in the data correctly.
Now that the data is in, let’s inspect it.
qc_data(processed_data) # execute function qc_data() on read in data stored in object "processed_data" to create plotThis plot shows:
What to look for:
If you see multiple replicates or grey wells, check:
RawRead.csv or
PlateInfo.csv?Sample column formatted consistently?To get a first overview we first visualize the latest time point (i.e. end of read) in 96-well format. These plots should roughly represent how your plate looks like after the OD recording. In the title the specific experiment is automatically labelled and one plot is created per distinct 96-well plate in the read in data:
#>
#> $`A_ctrl_ctrl_2h_PM-M02`
#>
#> $`A_ctrl_inhibitor_2h_PM-M01`
#>
#> $`A_ctrl_inhibitor_2h_PM-M02`
#>
#> $`A_treatment_ctrl_2h_PM-M01`
#>
#> $`A_treatment_ctrl_2h_PM-M02`
#>
#> $`A_treatment_inhibitor_2h_PM-M01`
#>
#> $`A_treatment_inhibitor_2h_PM-M02`
To make sense of your data, you need to know what chemical is in each well.
The package includes built-in annotations for:
data("PM_M01_M02") # load annotation file for PM-M01 and PM-M02
head(PM_M01_M02) # inspect for few rows
#> # A tibble: 6 × 11
#> Plate.Type Well Chemical `Kegg Link` Info KEGG module_id module_name
#> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 PM-M01 A01 Negative Contr… <NA> C-So… <NA> <NA> <NA>
#> 2 PM-M01 A02 Negative Contr… <NA> C-So… <NA> <NA> <NA>
#> 3 PM-M01 A03 Negative Contr… <NA> C-So… <NA> <NA> <NA>
#> 4 PM-M01 A04 a-Cyclodextrin https://ww… C-So… C009… <NA> <NA>
#> 5 PM-M01 A05 Dextrin https://ww… C-So… C007… <NA> <NA>
#> 6 PM-M01 A06 Glycogen https://ww… C-So… C001… <NA> <NA>
#> # ℹ 3 more variables: upper_hierarchy <chr>, middle_hierarchy <chr>,
#> # lower_hierarchy <chr>
processed_data <- annotate_chemical(processed_data, annotation = PM_M01_M02) # annotate Chemical Information
head(processed_data$data)
#> # A tibble: 6 × 25
#> PlateId Project.Name Project.Code Plate.Type Sample cell.line treatment
#> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 c2aa34c0-d80f… example_pro… exp PM-M01 A_ctr… A ctrl
#> 2 c2aa34c0-d80f… example_pro… exp PM-M01 A_ctr… A ctrl
#> 3 c2aa34c0-d80f… example_pro… exp PM-M01 A_ctr… A ctrl
#> 4 c2aa34c0-d80f… example_pro… exp PM-M01 A_ctr… A ctrl
#> 5 c2aa34c0-d80f… example_pro… exp PM-M01 A_ctr… A ctrl
#> 6 c2aa34c0-d80f… example_pro… exp PM-M01 A_ctr… A ctrl
#> # ℹ 18 more variables: inhibitor <chr>, time <chr>, Loaded.At <dttm>,
#> # Incubation.Hours <dbl>, Incubation.Temperature.Celsius <dbl>,
#> # Incubated.Offline <lgl>, Cycle.Time.Minutes <dbl>, Position <chr>,
#> # Protocol <lgl>, Description <lgl>, Actual.Temperature.Celsius <dbl>,
#> # Target.Temperature.Celsius <dbl>, Read.At <dttm>, Hour <dbl>, Well <chr>,
#> # diff_OD_590_740 <dbl>, Chemical <chr>, control <chr>Now you’ll see two new columns:
Chemical: e.g., “Glucose”, “Lactate”, “Acetate”control: “negative” (e.g. No Chemical in well or
Tween), “positive” (e.g. Glucose) or NA (all other metabolites and
chemical)If you want to save your annotated data at this point, e.g. for further analysis with other software, you can write a csv file with:
# write.csv(processed_data$data, # just stores the dataframe but not meta information about original file path
# file = "A:\results") # your file pathIf you want to store your complete object to keep the meta information (i.e. original folder path, …) you can store as an .RData object that only can be read by R.
Want to use your own plate annotation?
Create a CSV file with columns: Plate.Type,
Well, Chemical and control
(should only contain “positive”, “negative” or no entry).
Use read_csv() and pass it to
annotate_chemical().
In real experiments, we’ve seen:
This step helps you identify and filter problematic curves.
Classify_growth_curves() adds an element classification to the our object processed_data. The classification of OD curves can be tweaked with parameters. For details type ?classify_growth_curve_types in R or click on the function name and press F1, which opens the help page.
processed_data <- classify_growth_curve_types(processed_data) # classify growth curves
head(processed_data$classification)
#> $derivative_data
#> # A tibble: 241,384 × 27
#> cell.line treatment inhibitor time Plate.Type Well PlateId Project.Name
#> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 2 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 3 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 4 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 5 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 6 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 7 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 8 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 9 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 10 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> # ℹ 241,374 more rows
#> # ℹ 19 more variables: Project.Code <chr>, Sample <chr>, Loaded.At <dttm>,
#> # Incubation.Hours <dbl>, Incubation.Temperature.Celsius <dbl>,
#> # Incubated.Offline <lgl>, Cycle.Time.Minutes <dbl>, Position <chr>,
#> # Protocol <lgl>, Description <lgl>, Actual.Temperature.Celsius <dbl>,
#> # Target.Temperature.Celsius <dbl>, Read.At <dttm>, Hour <dbl>,
#> # diff_OD_590_740 <dbl>, Chemical <chr>, control <chr>, spline <dbl>, …
#>
#> $well_summary
#> # A tibble: 768 × 17
#> cell.line treatment inhibitor time Plate.Type Well n_points has_drop
#> <chr> <chr> <chr> <chr> <chr> <chr> <int> <lgl>
#> 1 A ctrl ctrl 2h PM-M01 A01 143 FALSE
#> 2 A ctrl ctrl 2h PM-M01 A02 143 FALSE
#> 3 A ctrl ctrl 2h PM-M01 A03 143 FALSE
#> 4 A ctrl ctrl 2h PM-M01 A04 143 FALSE
#> 5 A ctrl ctrl 2h PM-M01 A05 143 FALSE
#> 6 A ctrl ctrl 2h PM-M01 A06 143 FALSE
#> 7 A ctrl ctrl 2h PM-M01 A07 143 FALSE
#> 8 A ctrl ctrl 2h PM-M01 A08 143 FALSE
#> 9 A ctrl ctrl 2h PM-M01 A09 143 FALSE
#> 10 A ctrl ctrl 2h PM-M01 A10 286 FALSE
#> # ℹ 758 more rows
#> # ℹ 9 more variables: has_growth <lgl>, all_flat <lgl>, max_od <dbl>,
#> # derivative_sd <dbl>, od_range <dbl>, qc_low_points <lgl>,
#> # qc_high_noise <lgl>, qc_flag <lgl>, curve_type <chr>The classification can visualized with the plot_growth_curve() function, that creates one plot per 96-well plate:
plot_growth_curve(processed_data)
#> $data
#> # A tibble: 241,384 × 25
#> PlateId Project.Name Project.Code Plate.Type Sample cell.line treatment
#> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 c2aa34c0-d80… example_pro… exp PM-M01 A_ctr… A ctrl
#> 2 c2aa34c0-d80… example_pro… exp PM-M01 A_ctr… A ctrl
#> 3 c2aa34c0-d80… example_pro… exp PM-M01 A_ctr… A ctrl
#> 4 c2aa34c0-d80… example_pro… exp PM-M01 A_ctr… A ctrl
#> 5 c2aa34c0-d80… example_pro… exp PM-M01 A_ctr… A ctrl
#> 6 c2aa34c0-d80… example_pro… exp PM-M01 A_ctr… A ctrl
#> 7 c2aa34c0-d80… example_pro… exp PM-M01 A_ctr… A ctrl
#> 8 c2aa34c0-d80… example_pro… exp PM-M01 A_ctr… A ctrl
#> 9 c2aa34c0-d80… example_pro… exp PM-M01 A_ctr… A ctrl
#> 10 c2aa34c0-d80… example_pro… exp PM-M01 A_ctr… A ctrl
#> # ℹ 241,374 more rows
#> # ℹ 18 more variables: inhibitor <chr>, time <chr>, Loaded.At <dttm>,
#> # Incubation.Hours <dbl>, Incubation.Temperature.Celsius <dbl>,
#> # Incubated.Offline <lgl>, Cycle.Time.Minutes <dbl>, Position <chr>,
#> # Protocol <lgl>, Description <lgl>, Actual.Temperature.Celsius <dbl>,
#> # Target.Temperature.Celsius <dbl>, Read.At <dttm>, Hour <dbl>, Well <chr>,
#> # diff_OD_590_740 <dbl>, Chemical <chr>, control <chr>
#>
#> $meta
#> $meta$data_path
#> [1] "~/Documents/packages/Odin data/ODIN Data 03.06.2025/"
#>
#> $meta$sample_column
#> [1] "Sample"
#>
#> $meta$variables
#> [1] "cell.line" "treatment" "inhibitor" "time"
#>
#>
#> $classification
#> $classification$derivative_data
#> # A tibble: 241,384 × 27
#> cell.line treatment inhibitor time Plate.Type Well PlateId Project.Name
#> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 2 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 3 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 4 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 5 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 6 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 7 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 8 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 9 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> 10 A ctrl ctrl 2h PM-M01 A01 6e1e3d13-5… example_pro…
#> # ℹ 241,374 more rows
#> # ℹ 19 more variables: Project.Code <chr>, Sample <chr>, Loaded.At <dttm>,
#> # Incubation.Hours <dbl>, Incubation.Temperature.Celsius <dbl>,
#> # Incubated.Offline <lgl>, Cycle.Time.Minutes <dbl>, Position <chr>,
#> # Protocol <lgl>, Description <lgl>, Actual.Temperature.Celsius <dbl>,
#> # Target.Temperature.Celsius <dbl>, Read.At <dttm>, Hour <dbl>,
#> # diff_OD_590_740 <dbl>, Chemical <chr>, control <chr>, spline <dbl>, …
#>
#> $classification$well_summary
#> # A tibble: 768 × 17
#> cell.line treatment inhibitor time Plate.Type Well n_points has_drop
#> <chr> <chr> <chr> <chr> <chr> <chr> <int> <lgl>
#> 1 A ctrl ctrl 2h PM-M01 A01 143 FALSE
#> 2 A ctrl ctrl 2h PM-M01 A02 143 FALSE
#> 3 A ctrl ctrl 2h PM-M01 A03 143 FALSE
#> 4 A ctrl ctrl 2h PM-M01 A04 143 FALSE
#> 5 A ctrl ctrl 2h PM-M01 A05 143 FALSE
#> 6 A ctrl ctrl 2h PM-M01 A06 143 FALSE
#> 7 A ctrl ctrl 2h PM-M01 A07 143 FALSE
#> 8 A ctrl ctrl 2h PM-M01 A08 143 FALSE
#> 9 A ctrl ctrl 2h PM-M01 A09 143 FALSE
#> 10 A ctrl ctrl 2h PM-M01 A10 286 FALSE
#> # ℹ 758 more rows
#> # ℹ 9 more variables: has_growth <lgl>, all_flat <lgl>, max_od <dbl>,
#> # derivative_sd <dbl>, od_range <dbl>, qc_low_points <lgl>,
#> # qc_high_noise <lgl>, qc_flag <lgl>, curve_type <chr>
#>
#>
#> $growth_curve_plots
#> $growth_curve_plots$`A_ctrl_ctrl_2h_PM-M01`#>
#> $growth_curve_plots$`A_ctrl_ctrl_2h_PM-M02`
#>
#> $growth_curve_plots$`A_ctrl_inhibitor_2h_PM-M01`
#>
#> $growth_curve_plots$`A_ctrl_inhibitor_2h_PM-M02`
#>
#> $growth_curve_plots$`A_treatment_ctrl_2h_PM-M01`
#>
#> $growth_curve_plots$`A_treatment_ctrl_2h_PM-M02`
#>
#> $growth_curve_plots$`A_treatment_inhibitor_2h_PM-M01`
#>
#> $growth_curve_plots$`A_treatment_inhibitor_2h_PM-M02`
Left panel: 96-well layout with color-coded curve types.
Right panel: Individual curves for non-growing types.
You can choose to store these plots in your “processed_data” object (for documentation) by assigning the results to your object. This creates a new element in processed_data called growth_curve_plots, in which every distinct plate is stored separately with it corresponding name compounded of the experimental variables and the plate type.
Limitation As we only saw the before mentioned growth types MetPhenoR is also only able to detect these.
processed_data <- plot_growth_curve(processed_data) # store plots
processed_data$growth_curve_plots$`A_treatment_inhibitor_2h_PM-M01` # just show one plotIf you make the decision to omit a complete 96-well plate (e.g. due to flat_low OD curves in the control Well, unexplainable OD drops) the function filter_data() can remove one plate from processed_data.
Let’s first recapitulate which conditions we have:
If we made the decision that the 96-well plate with plate type “PM-M01” in cell line A, at treatment “treatment” and inhibitor “inhibitor” and at time point 2h is not suited for further analysis we can omit this plate with:
processed_data <- filter_data(processed_data,
plate_type = "PM-M01",
filters = list(
cell.line = "A",
treatment = "treatment",
inhibitor = "inhibitor",
time = "2h"
)
)We can re-use the qc_data function to check if we omitted the correct plate:
The omitted plate has now no observations (no color).
Now that your data is clean, we can compute the AUC for each well. The compute_auc_ci() function may run for a couple seconds depending on the size of your dataset and stores the results in the element auc_results in object processed_data.
Depending on your inspection of the curve types and after potentially omitting complete plates from analysis you can decide which curve types to keep. In our example data set I demonstrate how to omit the curves with OD drops (and therefore keep “growing”, “flat_low” and “slow_growing”). The default setting is only to keep growing.
processed_data <- compute_auc_ci(processed_data,
target_curve_type = c( # keep these curve types for AUC calculation
"growing",
"flat_low",
"slow_growing"
),
n_boot = 10 # low number of bootstraps for vignette
)
head(processed_data$auc_results)
#> # A tibble: 6 × 12
#> Plate.Type Well cell.line treatment inhibitor time AUC AUC_low AUC_high
#> <chr> <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
#> 1 PM-M01 A01 A ctrl ctrl 2h 11.5 11.4 11.5
#> 2 PM-M01 A01 A ctrl inhibitor 2h 11.0 10.9 11.0
#> 3 PM-M01 A01 A treatment ctrl 2h 9.61 9.58 9.63
#> 4 PM-M01 A02 A ctrl ctrl 2h 10.4 10.4 10.5
#> 5 PM-M01 A02 A ctrl inhibitor 2h 11.1 11.0 11.2
#> 6 PM-M01 A02 A treatment ctrl 2h 10.8 10.7 10.8
#> # ℹ 3 more variables: n_points <int>, Chemical <chr>, control <chr>The AUC column contains the area under the curve.
AUC_low and AUC_high are the 95% confidence
intervals (from bootstrapping).
Export for external analysis:
To get an idea about the variance of control wells (and wells containing Tween) of the same biological condition between different 96-well plates (e.g. Control wells on PM-M01 and PM-M02) we can employ the function plot_controls_tween_all_plate(). In case of anti-cancer-drug plates only the control wells are displayed. If you used custom annotation and labelled your control wells as either “negative” or “positive” in the ‘control’ column of your custom annotation file they will be displayed here.
This returns a plot with:
In the upper plot in this case we can spot that the variance between control wells of different plates is pretty low. We can also get an idea about the variance in the positive control of Wells containing Glucose (Glucose = red squares). In the upper case the variance is low in the lower-left plot and high in the upper right plot.
Look for high variability, e.g., if
0Gy_treatment_inhibitor shows large spread, you may need to
re-evaluate that condition.
If we want to get a first glimpse on how the AUCs of different biological conditions compare on the same plate we can use the plot_control_tween_per_plate() function.
plot_controls_tween_per_plate(processed_data,
selected_plate = "PM-M01"
) # which Plate Type to compare
#> `height` was translated to `width`.In the above plot we can e.g. see that the variance of control wells is comparably small in all biological conditions, but that the AUCs in Wells containing Glucose differ between conditions.
After we inspected the variance of the control Wells we can proceed with visualizing all Wells on one plate type in all biological conditions:
plot_auc(processed_data,
selected_plate = "PM-M02", # Plate Type to display
facet_vars = c("treatment") # which two biological conditions should be compared, first variable goes on y-axis panel, second on x-axis panel
)
#> [[1]]#>
#> [[2]]
In the resulting plots the metabolites are ordered by their mean AUC across all conditions which results in ordering from highest to lowest along the y-axis while still preserving the ability to easily compare metabolites between conditions.
The dashed lines show the range between minimum and maximum OD of the ‘negative’ control wells to preserve an indicator for data variability. In the second plot on the left (PM-M02, ctrl) we can e.g. observe that the AUC of most wells lies within the range of AUC wells observed in control wells.
The above visualization does not scale for comparison between multiple experimental conditions. One way to facilitate comparison of more conditions it to normalize the AUC to the mean of the control wells of the respective 96-well plate. The caveat is that we loose the information about variability of control wells, so decide after data inspection if you should proceed. We can do this with the function compute_auc_ratio(). The results are stored in element auc_ratio results of processed_data
processed_data <- compute_auc_ratio(processed_data)
head(processed_data$auc_ratio)
#> # A tibble: 6 × 17
#> Plate.Type Well cell.line treatment inhibitor time AUC AUC_low AUC_high
#> <chr> <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
#> 1 PM-M01 A01 A ctrl ctrl 2h 11.5 11.4 11.5
#> 2 PM-M01 A01 A ctrl inhibitor 2h 11.0 10.9 11.0
#> 3 PM-M01 A01 A treatment ctrl 2h 9.61 9.58 9.63
#> 4 PM-M01 A02 A ctrl ctrl 2h 10.4 10.4 10.5
#> 5 PM-M01 A02 A ctrl inhibitor 2h 11.1 11.0 11.2
#> 6 PM-M01 A02 A treatment ctrl 2h 10.8 10.7 10.8
#> # ℹ 8 more variables: n_points <int>, Chemical <chr>, control <chr>,
#> # mean_ctrl_AUC <dbl>, sd_ctrl_AUC <dbl>, mean_ctrl_AUC_low <dbl>,
#> # mean_ctrl_AUC_high <dbl>, AUC_ratio <dbl>The AUC ratios can of course be exported for to a csv file with:
We can visualize the AUC ratios as a heatmap with plot_auc_ratio_heatmap(). Depending on which biological conditions you want to compare you can set the x_var argument to the variable that should be displayed on the x-axis.
plot_auc_ratio_heatmap(processed_data,
plate_type = "PM-M01", # which plate type to visualize
x_var = "treatment" # which variable should go on x-axis, all other go in x-axis panels
) Missing values due to filtering or dropping of OD curve types will be displayed in grey.
In some cases, e.g. a very strong signal from Glucose making distinction of other Wells impossible, it might be helpful to display the AUC ratio on the log-scale. Please note that this distorts your perception of results with higher visual emphasis on small AUC ratios compared to natural scale.
plot_auc_ratio_heatmap(processed_data,
plate_type = "PM-M01",
x_var = "treatment",
scale_type = "log10" # switch from natural to log10 scale
)If we do not only want to visualize per plate type but leverage the fact that metabolite or drugs on different plates might belong to one biological meaningfull group we can annotate metadata. In case of the metabolic plates PM-M01 and PM-M02 MetPhenoR contains the annotation of metabolites to different hierarchies (module_name = pathway plus different hierarchical orders of pathways) of the KEGG database.
head(PM_M01_M02)
#> # A tibble: 6 × 11
#> Plate.Type Well Chemical `Kegg Link` Info KEGG module_id module_name
#> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 PM-M01 A01 Negative Contr… <NA> C-So… <NA> <NA> <NA>
#> 2 PM-M01 A02 Negative Contr… <NA> C-So… <NA> <NA> <NA>
#> 3 PM-M01 A03 Negative Contr… <NA> C-So… <NA> <NA> <NA>
#> 4 PM-M01 A04 a-Cyclodextrin https://ww… C-So… C009… <NA> <NA>
#> 5 PM-M01 A05 Dextrin https://ww… C-So… C007… <NA> <NA>
#> 6 PM-M01 A06 Glycogen https://ww… C-So… C001… <NA> <NA>
#> # ℹ 3 more variables: upper_hierarchy <chr>, middle_hierarchy <chr>,
#> # lower_hierarchy <chr>In case of plates PM-M11 to PM-14 it contains various information from ‘Affected Pathway’ to ‘Clinical Status’.
We can annotate the metadata with:
This adds additional columns to the element auc_ratio of processed_data, which can be exported as previously shown. In case of custom annotation you just need a data frame with one column named Chemical and as many columns containing information on the Chemicals as needed.
We can now group our heat map by any column of the metadata that we find suitable. In case of many distinct entries in our chosen column it might be helpful to only display certain entries in one specific plot. We can again choose the x-axis variable and whether we want to display the AUC ratio on the natural or log10 scale.
plot_auc_ratio_by_group(processed_data,
group_var = "middle_hierarchy", # column in metadata
x_var = "treatment",
group_filter = c(
"Nucleotide metabolism", # which entries in 'group_var' to display
"Amino acid metabolism"
)
)Another summary visualization are violin plots implemented in function plot_auc_ratio_violin() which makes comparisons of AUC ratios of many groups at the same time possible. In this plot we loose the information which metabolite or drug belongs to which point in the violin, but for detailed inspection the heat maps are available.
plot_auc_ratio_violin(processed_data,
group_var = "middle_hierarchy",
color_var = "treatment"
) # which variable should be displayed on color scale
#> Warning: Groups with fewer than two datapoints have been dropped.
#> ℹ Set `drop = FALSE` to consider such groups for position adjustment purposes.
#> Groups with fewer than two datapoints have been dropped.
#> ℹ Set `drop = FALSE` to consider such groups for position adjustment purposes.
#> Warning in max(data$density, na.rm = TRUE): no non-missing arguments to max;
#> returning -Inf
#> Warning in max(data[["n"]]): no non-missing arguments to max; returning -Inf
#> Warning: Computation failed in `stat_ydensity()`.
#> Caused by error in `$<-.data.frame`:
#> ! replacement has 1 row, data has 0
#> Warning: Groups with fewer than two datapoints have been dropped.
#> ℹ Set `drop = FALSE` to consider such groups for position adjustment purposes.
#> Warning in max(data$density, na.rm = TRUE): no non-missing arguments to max;
#> returning -Inf
#> Warning in max(data[["n"]]): no non-missing arguments to max; returning -Inf
#> Warning: Computation failed in `stat_ydensity()`.
#> Caused by error in `$<-.data.frame`:
#> ! replacement has 1 row, data has 0
#> Warning in min(x): no non-missing arguments to min; returning Inf
#> Warning in max(x): no non-missing arguments to max; returning -Inf
#> Warning in min(x): no non-missing arguments to min; returning Inf
#> Warning in max(x): no non-missing arguments to max; returning -InfWe now have the AUC ratio on the x-axis and every point represents one metabolite/drug associated with the group on the y-axis. We can choose the variable that will be color coded and all other experimental variables are displayed in the x-axis facets. We can also again switch from natural to log-scale.
plot_auc_ratio_violin(processed_data,
group_var = "middle_hierarchy",
color_var = "inhibitor", # switch color variable
scale_type = "log10" # log 10
)
#> Warning: Groups with fewer than two datapoints have been dropped.
#> ℹ Set `drop = FALSE` to consider such groups for position adjustment purposes.
#> Groups with fewer than two datapoints have been dropped.
#> ℹ Set `drop = FALSE` to consider such groups for position adjustment purposes.
#> Warning in max(data$density, na.rm = TRUE): no non-missing arguments to max;
#> returning -Inf
#> Warning in max(data[["n"]]): no non-missing arguments to max; returning -Inf
#> Warning: Computation failed in `stat_ydensity()`.
#> Caused by error in `$<-.data.frame`:
#> ! replacement has 1 row, data has 0
#> Warning: Groups with fewer than two datapoints have been dropped.
#> ℹ Set `drop = FALSE` to consider such groups for position adjustment purposes.
#> Warning in max(data$density, na.rm = TRUE): no non-missing arguments to max;
#> returning -Inf
#> Warning in max(data[["n"]]): no non-missing arguments to max; returning -Inf
#> Warning: Computation failed in `stat_ydensity()`.
#> Caused by error in `$<-.data.frame`:
#> ! replacement has 1 row, data has 0
#> Warning in min(x): no non-missing arguments to min; returning Inf
#> Warning in max(x): no non-missing arguments to max; returning -Inf
#> Warning in min(x): no non-missing arguments to min; returning Inf
#> Warning in max(x): no non-missing arguments to max; returning -InfsessionInfo()
#> R version 4.6.2 (2026-10-29)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04.1 LTS
#>
#> Matrix products: default
#> BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] MetPhenoR_1.3 rmarkdown_2.32
#>
#> loaded via a namespace (and not attached):
#> [1] sass_0.4.10 utf8_1.2.6 generics_0.1.4 tidyr_1.3.2
#> [5] stringi_1.8.9 hms_1.1.4 digest_0.6.39 magrittr_2.0.5
#> [9] evaluate_1.0.5 grid_4.6.2 RColorBrewer_1.1-3 fastmap_1.2.0
#> [13] jsonlite_2.0.0 purrr_1.2.2 viridisLite_0.4.3 scales_1.4.0
#> [17] tweenr_2.0.3 jquerylib_0.1.4 cli_3.6.6 rlang_1.3.0
#> [21] polyclip_1.10-7 splines_4.6.2 withr_3.0.3 cachem_1.1.0
#> [25] yaml_2.3.12 otel_0.3.0 tools_4.6.2 tzdb_0.5.0
#> [29] dplyr_1.2.1 ggplot2_4.0.3 buildtools_1.0.0 vctrs_0.7.3
#> [33] R6_2.6.1 lifecycle_1.0.5 stringr_1.6.0 MASS_7.3-66
#> [37] pkgconfig_2.0.3 pillar_1.11.1 bslib_0.12.0 gtable_0.3.6
#> [41] glue_1.8.1 ggforce_0.5.0 xfun_0.62 tibble_3.3.1
#> [45] tidyselect_1.2.1 sys_3.4.3 knitr_1.52 farver_2.1.2
#> [49] patchwork_1.3.2 htmltools_0.5.9 labeling_0.4.3 maketools_1.3.2
#> [53] readr_2.2.0 compiler_4.6.2 S7_0.2.2