---
title: "Choosing normalisation and imputation methods"
author: "Sergio Ciordia"
package: NADIA
output:
BiocStyle::html_document:
toc: true
toc_float: true
number_sections: true
vignette: >
%\VignetteIndexEntry{Choosing normalisation and imputation methods}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>", crop = NULL)
options(width = 100)
```
# Introduction
Normalisation and imputation are not merely technical details. Normalisation
changes the scale on which samples are compared, whereas imputation determines
which values replace the measurements that were not observed. Both choices can
therefore affect the proteins identified as differentially abundant.
There is no method that is best for every dataset. A useful choice should be
based on the characteristics of the experiment, the missing-value pattern and
diagnostics calculated from the data. This vignette follows that reasoning in
four steps:
1. describe the normalisation methods available in NADIA;
2. compare candidate normalisation methods using quantitative metrics and
diagnostic plots;
3. describe and compare the available imputation strategies; and
4. apply the selected combination and examine how alternative choices affect
the differential abundance results.
NADIA exposes 13 normalisation labels and 20 imputation strategies. At the
level of selectable methods, this gives **13 × 20 = 260** nominal
normalisation--imputation combinations. Running and interpreting every
combination is possible, but it is rarely an efficient first step. The metrics
in this vignette provide a staged screen: first rank plausible normalisation
methods using replicate agreement and condition separation, then compare
imputation methods on the selected---or a small number of plausible---normalised
assays. The resulting ranks reduce the search space; they do not by themselves
prove that the highest-ranked combination is biologically correct.
The value 260 is a catalogue-level count rather than 260 fully interchangeable
completed matrices: `log2` and `log2Norm` are numerically redundant, and `limpa`
and `none` have special downstream interpretations. The count nevertheless
illustrates why a principled shortlist is preferable to choosing a pipeline by
trial and error.
The examples use `nadia_dia`, a preprocessed DIA dataset included with NADIA.
The comparison is performed without an external ground truth. If an experiment
contains spike-in proteins with known abundances or expected fold changes, the
best normalisation--imputation combination is easier to establish. Such an
experiment supplies an external reference against which the complete pipeline
can be benchmarked, rather than relying only on internal properties of the
processed matrix. Proteins added at known different abundances show whether the
pipeline recovers the expected changes. Proteins kept at the same abundance, by
contrast, show how often the pipeline reports a difference when none is
expected. In that setting, the benchmarking results should take priority over
the internal ranks described here; see `vignette("benchmarking")`.
# Installation and packages
While NADIA is under development, it can be installed from GitHub:
```{r install, eval = FALSE}
if (!requireNamespace("remotes", quietly = TRUE))
install.packages("remotes")
remotes::install_github("sciordia/NADIA", build_vignettes = TRUE)
```
Only NADIA and `SummarizedExperiment` need to be attached for the code in this
vignette. Method-specific packages are called internally when required.
```{r packages, message = FALSE}
library(NADIA)
library(SummarizedExperiment)
data(nadia_dia)
```
# Normalisation
## What normalisation does
Protein intensities may contain systematic differences caused by sample
preparation, injected amount, instrument response or other non-biological
sources. Normalisation aims to reduce these unwanted differences while
preserving the biological variation that the experiment was designed to
measure.
An effective method should therefore satisfy two goals at the same time:
- replicate samples from the same condition should become more comparable;
- genuine differences between conditions should remain visible.
Making every sample look identical would reduce technical dispersion, but it
could also remove biological signal. For this reason, no single diagnostic is
sufficient on its own.
## Normalisation methods available in NADIA
NADIA provides 13 normalisation options, which are selected with the
`norm_method` argument. Regardless of the option chosen, the resulting
protein-abundance matrix is expressed on the log2 scale.
::: {.wide-reference-table .normalisation-methods-table}
| norm_method | Principle |
|---|---|
| `log2` | Applies the log2 transformation and no additional normalisation. It is the natural baseline for comparison. |
| `log2Norm` | Applies the same log2 transformation. It is retained as a named benchmark method for compatibility. |
| `GlobalMedian` | Divides each sample by its total intensity and rescales the totals to their median before log2 transformation. |
| `GlobalMean` | Divides each sample by its total intensity and rescales the totals to their mean before log2 transformation. |
| `eqmedians` | Log2-transforms the data and shifts every sample so that all sample medians are equal. This approach is based on the equal-median normalisation used by MSstats. |
| `medianNorm` | Divides raw intensities by the sample median, rescales to the mean of the sample medians and then applies log2. |
| `meanNorm` | Divides raw intensities by the sample mean, rescales to the mean of the sample means and then applies log2. |
| `vsn` | Uses variance-stabilising normalisation from the `vsn` package to reduce the dependence between variance and mean intensity. |
| `quantile` | Maps the empirical distribution of every sample to a common quantile distribution using `limma`. |
| `quantile.robust` | Uses the median, rather than the mean, across sample quantiles to construct a reference distribution that is less sensitive to extreme samples. |
| `Rlr` | Fits a robust linear regression between each sample and the protein-wise median profile, then removes the fitted offset and scale. This approach is based on the global robust linear regression normalisation implemented in NormalyzerDE. |
| `MAD` | Aligns both the median and the median absolute deviation of every sample to the corresponding global values. |
| `cycloess` | Applies cyclic loess normalisation with `limma`, correcting intensity-dependent differences between samples. |
:::
`log2` and `log2Norm` produce the same values. Consequently,
`nm_run_normalizations(methods = "all")` retains the `log2` baseline and skips
the redundant `log2Norm` assay, leaving 12 distinct assays for comparison.
## How normalisation methods are evaluated
NADIA combines quantitative measures of within-condition agreement with a
measure of between-condition separation. Four of the measures follow the
intragroup evaluation used by the PRONE package: PCV, PMAD, PEV and intragroup
correlation.
::: {.wide-reference-table .metrics-reference-table}
| Rank | What NADIA calculates | Preferred direction |
|---|---|---:|
| `Rank_PCV` | For each protein and condition, the coefficient of variation is calculated as $100 \times SD / |mean|$. Values are averaged across conditions and summarised by the median across proteins. | Lower |
| `Rank_PMAD` | The median absolute deviation is calculated within each condition for every protein, averaged across conditions and summarised by the median across proteins. | Lower |
| `Rank_PEV` | The within-condition variance is calculated for every protein, averaged across conditions and summarised by the median across proteins. | Lower |
| `Rank_Cor` | Pairwise correlations are calculated between replicate samples within each condition and summarised by their median. | Higher |
| `Rank_Sep` | PCA is performed on complete protein profiles. A one-way ANOVA F-ratio measures separation of the PC1 sample scores by condition relative to variation within conditions. | Higher |
:::
`Rank_Final` is the mean of these five ranks. The lowest value is preferred.
This combined criterion rewards agreement among replicates without allowing
dispersion alone to decide the result. That qualification is important: the
PRONE evaluation showed that reducing intragroup variation does not necessarily
identify the method that performs best in downstream analyses.
The ranking should be read together with the diagnostic plots returned by
`normalization_metrics()`:
- `boxplot` and `density` compare sample intensity distributions;
- `pcv`, `pmad` and `pev` show within-condition dispersion across proteins;
- `correlation` shows agreement between replicate samples;
- `pca` and `mds` show whether replicates cluster and conditions separate;
- `scatter` compares two samples directly, and `qq` assesses one sample against
a normal distribution;
- `metrics` displays additional multivariate diagnostics; and
- `final_ranking` displays the combined ranking.
These plots answer different questions. A method should not be selected from a
single attractive PCA or a single low dispersion value.
## Comparing normalisation methods with NADIA
The first step builds a baseline `SummarizedExperiment`. The second applies the
candidate methods, storing each result as a separate assay. Four methods are
used here to keep the example concise; omitting `methods` runs all distinct
benchmark assays.
```{r norm-run, message = FALSE}
se_norm <- nm_prepare_se(nadia_dia, verbose = FALSE)
se_norm <- nm_run_normalizations(
se_norm,
methods = c("log2Norm", "cycloess", "Rlr", "quantile"),
verbose = FALSE)
assayNames(se_norm)
```
`normalization_metrics()` calculates the diagnostics and returns both tables
and plots. `output_dir = NULL` prevents files from being written to disk.
```{r norm-metrics, message = FALSE, warning = FALSE}
nm <- normalization_metrics(
se_norm,
output_dir = NULL,
verbose = FALSE)
nm$final_rank
```
The two similarly named elements have different roles but contain the same
ranking. `nm$final_rank` is the numerical table: its five component columns are
`Rank_PCV`, `Rank_PMAD`, `Rank_PEV`, `Rank_Cor` and `Rank_Sep`, and
`Rank_Final` is their mean. `nm$final_ranking` is the bar-chart representation
of `Rank_Final` from that table; it does not calculate an additional ranking.
The separately returned `pc1_rank` and `mds1_rank` tables are optional
diagnostics and are not included in `Rank_Final`.
The first row is the preferred method under the combined criterion. In this
example, `cycloess` has the lowest `Rank_Final`: it ranks first for PCV, PMAD and
correlation while retaining good separation between conditions. `Rlr` is
second, and the unnormalised `log2` baseline ranks last.
The final ranking plot provides the same comparison visually. Shorter bars
indicate a better mean rank.
```{r norm-ranking, fig.width = 7, fig.height = 4.5, fig.alt = "Final ranking of candidate normalisation methods"}
nm$final_ranking
```
The PCA remains useful as a complementary check because it shows the individual
samples rather than reducing the comparison to one score.
```{r norm-pca, fig.width = 8, fig.height = 6, fig.alt = "PCA of samples after each candidate normalisation method"}
nm$pca
```
The selected normalisation method can be extracted directly:
```{r norm-winner}
norm_winner <- nm$final_rank$Method[1]
norm_winner
```
# Imputation
## Why values are missing
Imputation replaces missing entries with estimates so that analyses requiring a
complete matrix can be performed. The estimate should reflect a plausible reason
for the value being absent. NADIA uses the following practical distinction:
- **MAR (missing at random):** the protein is present, but its intensity was not
quantified successfully—for example because of poor chromatography, precursor
interference or a missed match. Under this assumption, the gap is not itself
evidence that the unknown intensity was low. Its value can therefore be
estimated from patterns in the available measurements, such as similar
protein profiles or related samples.
- **MNAR (missing not at random):** the protein is absent or its abundance is
below the detection limit. Missingness depends on the unobserved abundance
being low and therefore carries biological or measurement information.
Replacing such a gap with a typical neighbouring value may erase that
evidence, so a low-value or detection-aware method is generally more
plausible.
```{r mar-mnar-diagram, echo = FALSE, fig.align = "center", out.width = "100%", fig.cap = "Practical distinction between MAR and MNAR missing values in proteomics. MAR values are reconstructed from observed data structure, whereas MNAR values call for a low-value or detection-aware model.", fig.alt = "Two-panel diagram comparing MAR and MNAR missing values. MAR shows a missing replicate among similar observed intensities and recommends structure-based imputation. MNAR shows low values falling below a detection limit and recommends low-value or detection-aware imputation. A note states that an individual missing value does not reveal its cause and that both mechanisms can coexist."}
knitr::include_graphics("figures/MAR_MNAR_Missingness.svg")
```
Click the figure to enlarge it; press Esc or click outside the image to close it.
An individual `NA` does not reveal why the value is missing. MAR and MNAR are
possible explanations that must be evaluated using the experimental design, the
overall distribution of missing values and their relationship with observed
intensity. Both mechanisms may occur in the same dataset.
### How NADIA represents missing intensities
By the time the data enter the analysis, an unreported intensity is represented
as `NA`. How that `NA` is created depends on the input format. In a long
Spectronaut report, a protein that was not quantified in a particular run is
usually absent from the report; when NADIA constructs the wide `protein_quant`
table, the corresponding protein–run cell becomes `NA`. Some wide reports use
zero instead to mark an unreported intensity. Before normalisation,
`normalize_proteomics()` converts these zeros to `NA`, so downstream functions
handle both situations consistently.
## Imputation methods available in NADIA
NADIA provides 20 selectable strategies. The grouping below describes their
main intent and does not imply that every missing value can be classified
unambiguously.
::: {.wide-reference-table .imputation-methods-table}
| Group | imp_method | Principle |
|---|---|---|
| Structure-based, generally used for MAR | `bpca` | Bayesian principal-component imputation using `pcaMethods`. |
| | `knn` | Estimates a value from similar proteins using k-nearest neighbours. |
| | `mice` | Multiple imputation by chained equations; NADIA averages the completed matrices. |
| | `missForest` | Non-parametric random-forest imputation using relationships among variables. |
| | `Impseq` | Sequential model-based imputation from `rrcovNA`. |
| | `Impseqrob` | Robust sequential imputation that reduces sensitivity to outlying profiles. |
| | `MLE` | Maximum-likelihood imputation under a multivariate normal model. |
| | `nbavg` | Replaces a gap with the mean of the nearest observed positions in the same protein profile. |
| Low-value, generally used for MNAR | `QRILC` | Quantile-regression imputation for left-censored data. |
| | `MinDet` | Uses a low, sample-specific quantile; the default is the first percentile. |
| | `MinProb` | Draws low values around a sample-specific low quantile. |
| | `PI` | Perseus-style sampling from a narrowed, down-shifted normal distribution. |
| | `min` | Replaces every gap with the global observed minimum. |
| | `halfmin` | Uses half the global minimum on the original scale, equivalent to one unit below the minimum on the log2 scale; this convention is used by DIA-NN. |
| | `zero` | Replaces every gap with zero on the log2 scale. |
| | `with` | Replaces every gap with a user-supplied constant. |
| Hybrid | `combo` | Classifies missing cells from condition-wise presence patterns, then applies separate MAR and MNAR methods. Defaults are `Impseqrob` and `min`. |
| | `softHybrid` | Blends estimates from MAR and MNAR methods continuously using protein missingness and mean intensity. |
| Detection model | `limpa` | Estimates a detection-probability curve and returns abundance estimates with uncertainty for downstream analysis with `de_method = "limpa"`. |
| No imputation | `none` | Leaves missing values unchanged. |
:::
`limpa` and `none` require special interpretation. `limpa` is intended to carry
its estimated uncertainty into a `limpa` differential abundance model, whereas
`none` deliberately does not produce a complete matrix. They should not be
treated as ordinary constant-fill methods.
## How imputation methods are evaluated
When the true missing values are unknown, NADIA follows the classic
computational evaluation framework used by NAguideR:
1. retain proteins that contain no missing values;
2. hide a known subset of their observed cells;
3. impute the artificial gaps with every candidate method; and
4. compare the estimates with the values that were hidden.
NADIA provides two ways to decide how many values are hidden and where the
artificial gaps are placed:
- With `pattern = "random"`, the user sets the proportion explicitly through
`na_prop`. For example, `na_prop = 0.20` hides 20% of the values in the
complete-case matrix. The selected cells do not depend on intensity and are
treated as MAR for the benchmark. This option is useful for a controlled
experiment or for comparing performance at several predefined missingness
levels.
- With `pattern = "from_data"`, `na_prop` is ignored. NADIA estimates the
proportion of proteins affected by missing values and the distribution of
gaps across samples from the original assay, then reproduces those features
when masking the complete-case matrix. The resulting percentage of artificial
gaps is therefore approximately the percentage observed in the experiment;
small differences can occur because cell counts must be rounded.
The second option follows the data-derived principle used in the
[NAguideR evaluation](https://doi.org/10.1093/nar/gkaa498), in which missing
values were generated in a complete matrix at a proportion similar to that of
the original dataset. For selecting an imputation method for one particular
experiment, `pattern = "from_data"` is generally the more representative
starting point because it preserves the experiment's overall missingness burden
and its distribution across samples. In `nadia_dia`, for example, the original
assay contains approximately 8.0% missing cells and `pattern = "from_data"`
generates approximately 8.1% artificial gaps, whereas
`pattern = "random", na_prop = 0.20` generates 20%.
Both options hide values that were originally observed, so the true values are
known and imputation error can be calculated. Consequently, neither option
directly reproduces MNAR missingness caused by low abundance or a detection
limit. `pattern = "from_data"` is more representative of the observed NA
frequency and sample distribution, but it is not a direct benchmark of MNAR
imputation.
NADIA calculates four metrics:
::: {.wide-reference-table .metrics-reference-table}
| Metric | Interpretation | Preferred direction |
|---|---|---:|
| `NRMSE` | Normalised root mean squared error over the artificially masked cells. | Lower |
| `SOR` | Sum of per-protein error ranks. A method receives the worst rank for a protein if it fails to impute its artificial gaps. | Lower |
| `PSS` | Procrustes sum of squares comparing the PCA structures of the true and imputed matrices. | Lower |
| `ACC_OI` | Pearson correlation between the true and imputed values pooled over the artificially masked cells. | Higher |
:::
Because the four metrics have different scales, NADIA does not average their raw
values. Instead, it orders the methods from best to worst for each metric and
assigns rank 1 to the best method, rank 2 to the second, and so on. `Rank_Mean`
is the mean of these four ranks, with each metric contributing equally. The
method with the lowest `Rank_Mean` therefore has the best overall performance
for the simulated gaps.
Occasionally, a metric cannot be calculated for a particular method. In that
case, NADIA calculates `Rank_Mean` from the available ranks without assigning
the best or worst rank to the missing metric. Therefore, before selecting a
method solely on the basis of `Rank_Mean`, check whether any of its metric values
are `NA`. A favourable mean rank based on fewer metrics may not be directly
comparable with one calculated from the complete set of metrics.
`ACC_OI` compares the true and imputed values only at the cells that NADIA hid
for the simulation. This prevents the many unchanged cells from making the
correlation appear artificially high. Pearson correlation requires variation in
both sets of values. A constant method such as `min` assigns the same value to
every hidden cell, so its imputed values have no variation and `ACC_OI` cannot
be calculated. In this situation, `ACC_OI = NA` means *not evaluable*; it does
not indicate either good or poor imputation.
## Comparing imputation methods with NADIA
Imputation must be evaluated on a normalised assay. The code below uses the
normalisation selected above and compares representative structure-based,
low-value and hybrid strategies. It uses a controlled 20% random mask for this
illustration. For an experiment-specific evaluation, use
`pattern = "from_data"` and omit `na_prop` from the call.
```{r imp-run, message = FALSE, warning = FALSE}
se_imp <- im_prepare_se(
nadia_dia,
norm_method = norm_winner,
verbose = FALSE)
im <- imputation_metrics(
se_imp,
assay_name = norm_winner,
methods = c("Impseqrob", "knn", "MinDet", "QRILC", "min"),
combo_methods = list(
Impseqrob_min = list(
mar_method = "Impseqrob",
mnar_method = "min")),
na_prop = 0.20,
pattern = "random",
output_dir = NULL,
verbose = FALSE)
im$metrics_table[, c(
"Method", "NRMSE", "SOR", "PSS", "ACC_OI", "Rank_Mean")]
```
`Impseqrob` has the lowest `Rank_Mean` in this example. It produces the smallest
NRMSE, the best SOR and the highest correlation with the hidden values. `knn`
preserves the multivariate structure particularly well, as indicated by its low
PSS, but ranks second overall. The low-value MNAR methods perform poorly because
the simulation hides values at random rather than preferentially hiding low
intensities. That result should not be used to conclude that MNAR imputation is
unnecessary in the original matrix.
The metrics plot shows the values on their original scales:
```{r imp-metrics-plot, fig.width = 8, fig.height = 6, fig.alt = "Imputation quality metrics for the candidate methods"}
im$metrics
```
The heatmap shows one imputation method per row and one evaluation metric per
column. The number printed in each cell is the method's rank for that metric:
rank 1 is the best result. Lower ranks are shown in blue and higher ranks in
red; a grey cell marked `NA` indicates that the metric could not be calculated.
The final column, `Rank_Mean`, is the average of the available metric ranks.
Methods are ordered by this value, so the method in the first row has the lowest
`Rank_Mean` and the best overall result for the simulated gaps.
```{r imp-ranking, fig.width = 7.5, fig.height = 5, fig.alt = "Heatmap of imputation method ranks"}
im$ranking
```
The winner can again be extracted from the first row:
```{r imp-winner}
imp_winner <- im$metrics_table$Method[1]
imp_winner
```
# Applying the selected combination
The selected normalisation and imputation methods can now be passed to the main
workflow. For this example the data-driven combination is `cycloess` followed by
`Impseqrob`.
```{r apply-winners, message = FALSE}
res_selected <- process_proteomics(
nadia_dia,
norm_method = norm_winner,
imp_method = imp_winner,
verbose = FALSE)
table(res_selected$DEPs_results$Change)
```
The normalised matrix and the imputed matrix are retained as separate assays, so
the processing history can be inspected without overwriting earlier stages.
```{r selected-assays}
assayNames(res_selected$se_proc)
```
# Effect on differential abundance results
A method-selection metric is a guide, not a guarantee that every downstream
result is correct. A useful final check is to repeat the analysis with plausible
alternative pipelines and compare their consequences.
```{r sensitivity, message = FALSE, warning = FALSE}
variants <- list(
"cycloess + Impseqrob" = list(
norm_method = "cycloess", imp_method = "Impseqrob"),
"cycloess + combo" = list(
norm_method = "cycloess", imp_method = "combo"),
"cycloess + knn" = list(
norm_method = "cycloess", imp_method = "knn"),
"quantile + combo" = list(
norm_method = "quantile", imp_method = "combo"),
"log2 + min" = list(
norm_method = "log2", imp_method = "min"))
# Some imputation methods print progress on stdout; capture.output keeps it out.
invisible(capture.output(
counts <- do.call(rbind, lapply(names(variants), function(label) {
fit <- do.call(
process_proteomics,
c(list(nadia_dia, verbose = FALSE), variants[[label]]))
tab <- table(factor(
fit$DEPs_results$Change,
levels = c("Up", "Down", "No Change")))
data.frame(
Pipeline = label,
Up = tab[["Up"]],
Down = tab[["Down"]],
No_Change = tab[["No Change"]])
}))))
counts
```
The input matrix, differential abundance method and decision thresholds are the
same in every run, but the numbers called Up and Down change substantially.
These are protein--comparison results: a protein tested in several contrasts can
contribute more than one row. The comparison does not establish which pipeline
is biologically correct. It shows that normalisation and imputation are
consequential modelling choices and must be evaluated, reported and reproduced.
# Practical decision guide
For a dataset without external ground truth, the following sequence provides a
defensible starting point:
1. inspect sample distributions and the missing-value pattern;
2. compare several plausible normalisation methods using both the final rank and
the diagnostic plots;
3. examine whether missingness appears predominantly scattered or related to
low abundance and condition-wise dropout;
4. compare imputation methods, remembering that the artificial-masking
benchmark assesses reconstruction of MAR values rather than true MNAR
missingness caused by low abundance or a detection limit;
5. apply the selected combination and repeat the differential abundance
analysis with reasonable alternatives as a sensitivity analysis; and
6. report the method names, relevant parameters, package version and session
information.
When a spike-in or another known reference is available, it should take priority
over these proxy criteria because it permits evaluation of the expected
biological changes directly.
# References and software
- Arend L, Adamowicz K, Schmidt JR, *et al.* (2025). [Systematic evaluation of
normalization approaches in tandem mass tag and label-free protein
quantification data using PRONE](https://doi.org/10.1093/bib/bbaf201).
*Briefings in Bioinformatics*, 26(3), bbaf201. PRONE is available from
[Bioconductor](https://bioconductor.org/packages/PRONE) and
[GitHub](https://github.com/daisybio/PRONE).
- Wang S, Li W, Hu L, Cheng J, Yang H, Liu Y (2020). [NAguideR: performing and
prioritizing missing value imputations for consistent bottom-up proteomic
analyses](https://doi.org/10.1093/nar/gkaa498). *Nucleic Acids Research*,
48(14), e83. Source code is available from the
[NAguideR GitHub repository](https://github.com/wangshisheng/NAguideR).
- Li M, Cobbold SA, Smyth GK (2025). [Quantification and differential analysis
of mass spectrometry proteomics data with probabilistic recovery of
information from missing values](https://doi.org/10.1101/2025.04.28.651125).
*bioRxiv*, 2025.04.28.651125. This work describes the statistical framework
implemented in `limpa`; the software is available from
[Bioconductor](https://bioconductor.org/packages/limpa) and
[GitHub](https://github.com/SmythLab/limpa).
- Shi Y, Davis S, Charles PD, Taylor S, Dombi E, Berridge G, Ebner D, Fischer R
(2026). [SoftHybrid: A Hybrid Imputation Algorithm Optimised for Single-Cell
Proteomics Data](https://doi.org/10.64898/2026.01.13.699212). *bioRxiv*,
2026.01.13.699212. This work describes the `softHybrid` imputation strategy.
# Session information
```{r session}
sessionInfo()
```