--- title: "growkar-introduction" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{growkar-introduction} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r vignette-setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} library(growkar) library(dplyr) library(knitr) data(yeast_growth_data) ``` ## Background High-throughput microbial growth assays are widely used to characterize phenotypic responses to genetic perturbations, environmental conditions, and drug treatments. These assays generate time-resolved measurements across large numbers of samples, analogous to other high-throughput experimental platforms in functional genomics. While tools exist for transcriptomic and epigenomic data analysis within the Bioconductor ecosystem, there is a relative lack of standardized infrastructure for analyzing growth-based phenotypic data and integrating it with omics datasets. The `growkar` package addresses this gap by providing a scalable and reproducible framework for high-throughput microbial phenotyping from growth assays. It uses Bioconductor data structures to represent assay measurements and derived phenotypes, while accepting tidy and wide plate-reader exports as import adapters. The primary workflow is: 1. Import assay data and build a standardized `SummarizedExperiment`. 2. Validate the SE structure before downstream analysis. 3. Explore growth curves visually. 4. Compute empirical summaries and detect the exponential phase. 5. Fit logistic or Gompertz models where a full-curve summary is useful. ## Class design `growkar` uses two S4 classes and no S3 classes: - **`GrowthExperiment`** is the data container. It is an S4 class that extends `SummarizedExperiment`, adding a validity method that enforces the canonical growth-assay layout (an `od` assay, numeric `time` in `rowData()`). Because it *is* a `SummarizedExperiment`, every Bioconductor method works on it unchanged, and `as(x, "SummarizedExperiment")` is always available for handing objects to other packages. - **`GrowthFit`** represents a *model result* — a logistic or Gompertz fit for one sample. It is not a data container; instances are stored in `metadata(ge)` beside the experiment they were derived from. The package defines no S3 classes and registers no S3 methods. Build a `GrowthExperiment` with the constructor or with standard S4 coercion: ```{r growth-experiment-construct} # Constructor (control over column-name resolution): ge <- GrowthExperiment(yeast_growth_data) ge # Equivalent standard coercion: ge2 <- as(yeast_growth_data, "GrowthExperiment") identical(ge, ge2) # It is a SummarizedExperiment, so it hands off cleanly to other packages: se <- as(ge, "SummarizedExperiment") is(ge, "SummarizedExperiment") ``` Tidy tables and wide plate-reader exports are handled purely as *import adapters* by `as_tidy_growth_data()`, which resolves vendor column labels and normalizes them to `sample`, `time`, and `od` before the container is built. Once data are in the container, tidy manipulation is delegated to the tidyomics stack rather than reimplemented — see [Interoperability with tidyomics](#interoperability-with-tidyomics) below. ## Bioconductor container workflow Derived summaries are stored in `metadata()` of the same object, so growth phenotypes travel with the container: ```{r summarized-experiment} ge <- growth_metrics(ge, method = "rolling_window", average_replicates = TRUE) S4Vectors::metadata(ge)$growth_metrics ``` This workflow is useful when growth phenotypes need to be carried forward into other Bioconductor analyses or linked to omics-derived sample annotations. The canonical layout in `growkar` is: - `assay(ge, "od")`: OD matrix with timepoints in rows and samples in columns - `rowData(ge)`: timepoint metadata - `colData(ge)`: sample metadata - `metadata(ge)`: derived results such as growth metrics, phase windows, and fits `validate_growth_experiment()` checks that an object follows this layout: ```{r validate-se} validate_growth_experiment(ge) ``` Helper accessors make these components easier to inspect: ```{r se-accessors} growth_assay(ge)[1:3, 1:3] timepoints(ge) sample_data(ge) ``` ## Importing plate-reader exports `as_tidy_growth_data()` is the import adapter. It accepts wide plate-reader exports (time in the first column, samples in the remaining columns) as well as long tables, resolves common instrument column labels such as `Time [h]` and `OD600`, and infers `condition`/`replicate` from suffixed sample names. ```{r tidy-conversion} tidy_growth <- as_tidy_growth_data(yeast_growth_data) head(tidy_growth) ``` The canonical columns are `sample`, `time`, and `od`. Additional metadata such as `condition` and `replicate` are carried alongside them. ```{r validate-input} validate_growth_data(tidy_growth) ``` Imported data are then converted into the canonical container: ```{r summarized-experiment-direct} GrowthExperiment(yeast_growth_data) ``` ## Interoperability with tidyomics `growkar` does not reimplement tidy verbs or a tidy display layer for `SummarizedExperiment`. It depends on [tidySummarizedExperiment](https://bioconductor.org/packages/tidySummarizedExperiment) and follows its conventions, including the `.feature` and `.sample` labels, so `growkar` objects can be filtered, mutated, summarized, and plotted with the standard tidyverse grammar while the underlying object remains a `SummarizedExperiment` usable by any other Bioconductor package. ```{r tidyomics} library(tidySummarizedExperiment) # The SE prints and behaves as a tibble abstraction, without being converted. se se |> filter(condition == "Cg") |> filter(time <= 6) |> select(.feature, .sample, od, condition, replicate) se |> group_by(condition) |> summarise(max_od = max(od), .groups = "drop") ``` `growkar` results stored in `metadata(se)` are ordinary tibbles, so they slot directly into the same downstream workflow. ## Exploratory plotting and quality control Graphing is optional in `growkar`: `ggplot2` is declared in `Suggests`, so the data-structure and analysis layers install without a graphics stack. The `plot_*()` functions check for it at call time and return standard `ggplot` objects that can be customized further. Qualitative palettes come from `grDevices::palette.colors()` in base R. ```{r plot-averaged-curves, eval = requireNamespace("ggplot2", quietly = TRUE)} plot_growth_curve( se, average_replicates = TRUE, colour_col = "condition", palette_name = "Dark2" ) ``` Replicate-level faceting remains available when averaging is disabled: ```{r plot-replicate-facets, eval = requireNamespace("ggplot2", quietly = TRUE)} plot_growth_curve( se, average_replicates = FALSE, colour_col = "condition", facet_col = "replicate", palette_name = "Dark2" ) ``` ## Empirical summaries ```{r summarize-metrics} metrics <- summarize_growth_metrics( se, method = "rolling_window", average_replicates = TRUE ) knitr::kable(metrics, digits = 3) ``` For a single sample, the empirical growth-rate estimate can be inspected in more detail: ```{r single-sample-growth-rate} gr <- compute_growth_rate(se, method = "rolling_window") knitr::kable(head(gr), digits = 3) ``` The doubling time helper is useful when you already have growth-rate values: ```{r doubling-time-helper} knitr::kable( tibble( sample = gr$sample, growth_rate = gr$mu, doubling_time = compute_doubling_time(gr$mu) ), digits = 3 ) ``` ## Exponential phase detection `detect_exponential_phase()` returns the ranked candidate windows and metadata describing whether the chosen interval required any degraded fallback. ```{r detect-phase} phase_tbl <- detect_exponential_phase(se) knitr::kable(head(phase_tbl), digits = 3) ``` ## Model fitting and diagnostics For a full-curve model-based summary, fit one of the supported parametric models. `fit_growth_curve()` returns a `GrowthFit` S4 object. ```{r fit-logistic-model} sample_id <- unique(gr$sample)[1] fit_input <- as_tidy_growth_data(se) |> filter(sample == sample_id) cg_fit <- fit_growth_curve(fit_input, model = "logistic") cg_fit isVirtualClass("GrowthFit") extract_params(cg_fit) summary(cg_fit) ``` `GrowthFit` supports the standard modelling generics and a small set of accessors, so slots never need to be touched directly: ```{r growth-fit-accessors} fit_sample(cg_fit) fit_model(cg_fit) fit_status(cg_fit) fit_converged(cg_fit) coef(cg_fit) head(fitted(cg_fit)) nobs(cg_fit) ``` Failed fits remain machine-readable and do not crash downstream helpers: ```{r failed-fit-example} flat_fit <- fit_growth_curve( tibble( sample = "flat", time = 0:4, od = rep(0.2, 5) ), model = "logistic" ) summary(flat_fit) ``` Across a plate, fits are stored in `metadata()` of the same `SummarizedExperiment`: ```{r fit-plate} se <- fit_growth_models(se, model = "logistic") S4Vectors::metadata(se)$growth_model_parameters ``` Observed and fitted values can be visualized together: ```{r plot-fitted-curve, eval = requireNamespace("ggplot2", quietly = TRUE)} plot_fitted_curve(cg_fit) ``` ## Supported API The supported SE-native interface includes: - `as_tidy_growth_data()` - `GrowthExperiment()` - `validate_growth_experiment()` - `compute_growth_rate()` - `summarize_growth_metrics()` - `detect_exponential_phase()` - `fit_growth_curve()` ## Limitations and interpretation notes - Growth-rate estimates rely on log-linear behavior and can be degraded for very sparse or flat curves. - Zero OD values are allowed in the data, but log-based methods may warn and ignore them. - A failed model fit still returns a `GrowthFit` object with status and diagnostics rather than throwing a cryptic `nls` error. ## Session information ```{r session-information} sessionInfo() ```