| Title: | Analysis of Designed Agricultural Experiments |
|---|---|
| Description: | A 'shiny' application and supporting functions for the analysis of designed agricultural experiments, following the procedures set out by Gomez and Gomez (1984, ISBN:9780471870920). Handles completely randomised, randomised complete block, Latin square, factorial (up to four factors), split-plot and strip-plot designs, and pooled (combined) analysis over environments including factorial treatments, after Yates and Cochran (1938) <doi:10.1017/S0021859600050978>; analyses several response variables simultaneously; recommends and applies variance-stabilising transformations using the profile likelihood of Box and Cox (1964) <doi:10.1111/j.2517-6161.1964.tb00553.x>; reports standard errors and critical differences for every legitimate comparison, including the four distinct comparisons of a split plot, the mixed ones using the approximation of Satterthwaite (1946) <doi:10.2307/3002019>; and produces publication-format tables of means, diagnostic plots and a written interpretation. |
| Authors: | Immad A. Shah [aut] (ORCID: <https://orcid.org/0000-0003-2761-5112>), Uzair Javid Khan [aut, cre] (ORCID: <https://orcid.org/0009-0001-6368-0977>), M. Iqbal Jeelani [aut] (ORCID: <https://orcid.org/0000-0002-2974-2871>), Imran Khan [ctb] |
| Maintainer: | Uzair Javid Khan <[email protected]> |
| License: | GPL (>= 3) |
| Version: | 2.0.1 |
| Built: | 2026-10-10 18:49:06 UTC |
| Source: | https://github.com/uzairkhan11w/doepro |
Fits the analysis of variance for a single response variable and returns the means, standard errors and critical differences for every legitimate comparison the design allows.
analyze(d, design, map, alpha = 0.05)analyze(d, design, map, alpha = 0.05)
d |
A data frame in long format: one row per plot, with columns for the design factors and the response. Text factor columns are put in natural order (D0, D60, D120; T1, T2, ..., T10), with a label carrying no number, such as Control, placed first; a column that is already a factor keeps its levels, so supply a factor to fix an order such as Vegetative, Flowering, Maturity. |
design |
The design code. One of the values of |
map |
A named list mapping roles to column names of |
alpha |
The significance level. It sets the critical differences, the grouping letters and the significance of every F-test. Defaults to 0.05. |
The error term is chosen to match the design. A split plot is fitted with
Error(rep/main) and a strip plot with Error(rep/(A+B)), so the
main-plot and sub-plot comparisons are each tested against their own error;
the two mixed comparisons use a weighted t. In a pooled (combined)
analysis over environments, each treatment effect is tested against its own
interaction with the environment, and each environment by treatment
interaction against the pooled error.
Unequal replication is handled exactly in the designs with a single error
term (CRD, RCBD, Latin square and the factorials): each mean gets its own
standard error and each pair of means its own standard error of a difference
and critical difference. When blocks are incomplete or factorial cells are
unequal, the means are adjusted (least-squares) means and every term is
tested adjusted for all the others (Type III). Designs with several error
strata (split plot, strip plot and the pooled analyses) can only be analysed
exactly when they are complete; with a plot missing analyze() stops
and says which combinations are missing.
A list with, among others:
anovaThe analysis of variance table.
effectsOne entry per effect. Each holds means, the
table of means with columns Mean, N, SD, SE
(the standard error of that mean), the grouping letters and, when the
means are adjusted, Raw_mean; sed_mat and cd_mat,
the standard error of the difference and the critical difference for
every pair of means; sem, sed and cd, single
values that are NA when replication is unequal and they differ
from mean to mean (equal_rep is then FALSE, and
se_range, sed_range and cd_range give the range);
mse and df, the error mean square and degrees of freedom
the effect is tested against; p, F and alpha. The
interaction of a split plot, strip plot or pooled analysis also has
slice, the factor within whose levels its means are compared:
its SE, sed and cd apply to those comparisons,
sed_mat and cd_mat are NA across levels of
slice, and extra gives the standard errors and critical
differences of the other comparisons.
mse, dfe
The error mean square and its degrees of freedom.
balancedWhether every cell of the design holds the same number of observations.
cv, grand, resid, lm
The coefficient of variation, grand mean, residuals and the fitted linear model.
run_all to analyse several responses at once, and
build_report to render the result.
d <- demo_data("RCBD") res <- analyze(d, "RCBD", list(response = "Yield", treat = "Variety", block = "Block")) res$anova res$effects[["Variety"]]$meansd <- demo_data("RCBD") res <- analyze(d, "RCBD", list(response = "Yield", treat = "Variety", block = "Block")) res$anova res$effects[["Variety"]]$means
Turns the result of run_all into a complete, self-contained HTML
report: the analysis of variance, the tables of means with their standard
errors and critical differences, the checks of the assumptions, and a
plain-English interpretation. The report carries the DOEpro citation in its
footer.
build_report(rr, letters_on = TRUE, detailed = TRUE, screen = FALSE)build_report(rr, letters_on = TRUE, detailed = TRUE, screen = FALSE)
rr |
The object returned by |
letters_on |
Show the grouping letters. Letters are suppressed automatically for any effect whose F-test is not significant, whatever this is set to. |
detailed |
If |
screen |
If |
A character string of HTML, of length one.
rr <- run_all(demo_data("CRD"), "CRD", list(treat = "Treatment"), "Yield") html <- build_report(rr) substr(html, 1, 60) # writeLines(html, "report.html")rr <- run_all(demo_data("CRD"), "CRD", list(treat = "Treatment"), "Yield") html <- build_report(rr) substr(html, 1, 60) # writeLines(html, "report.html")
Looks through every column for the problems that most often spoil an
analysis of field data: numbers written with a decimal comma, decimal points
and commas mixed in one column, thousands separators, per cent signs and
units typed after numbers, numbers stored as text, entries that are not
numbers, empty cells and the markers people type for them (such as n/a or
Excel's #DIV/0!), labels that differ only in spacing, capitals or
punctuation, and empty or repeated rows. Each problem is described with the
rows it affects and, where a correction is safe, what the correction would
be. Nothing is changed; see fix_data.
check_data(d)check_data(d)
d |
A data frame, as loaded. |
A list with issues, a list with one element per problem
(column; level: "correct" for a correction
fix_data will make, "check" for something only the
user can settle, "note" for information; message;
rows, as row numbers of the data; fixable), and
numeric and labels, the columns read as numbers and as
labels.
d <- data.frame(Variety = c("V1", "V1", "V2", "V2"), Yield = c("5,6", "6.1", "n/a", "12a")) chk <- check_data(d) vapply(chk$issues, `[[`, "", "message")d <- data.frame(Variety = c("V1", "V1", "V2", "V2"), Yield = c("5,6", "6.1", "n/a", "12a")) chk <- check_data(d) vapply(chk$issues, `[[`, "", "message")
Clusters the rows, or the means of each level of a grouping column, on the
chosen numeric columns (hclust), from the rows with a
value of every one. By default each variable is standardised first, so no
variable dominates through its units. Text entries are read as the data
check reads them; see check_data.
cluster_data( d, vars, group = NULL, k = NULL, distance = c("euclidean", "manhattan"), linkage = c("ward", "complete", "average", "single"), scale = TRUE ) ## S3 method for class 'doepro_cluster' print(x, ...)cluster_data( d, vars, group = NULL, k = NULL, distance = c("euclidean", "manhattan"), linkage = c("ward", "complete", "average", "single"), scale = TRUE ) ## S3 method for class 'doepro_cluster' print(x, ...)
d |
A data frame. |
vars |
The names of two or more numeric columns. |
group |
Optional: the name of a column whose levels are clustered by their means, instead of clustering the rows. |
k |
The number of clusters, or |
distance |
|
linkage |
|
scale |
|
x |
A result of |
... |
Not used. |
The number of clusters, unless given, is the one from 2 to 10 (or one fewer than the items) with the largest average silhouette width (Rousseeuw, 1987): for each item, how much closer it lies to the other items of its own cluster than to those of the nearest other cluster, from -1 to 1. The cophenetic correlation measures how faithfully the tree keeps the distances between the items. Clusters are numbered in the order they appear in the tree from left to right.
A list of class doepro_cluster: items (the labels of
the items clustered), X (their values), cluster (each
item's cluster), sizes, means (each cluster's mean of each
variable), silhouette (each item's width), by_k (the
average silhouette width for each number of clusters tried),
suggested, k, cophenetic, tree (the
hclust result), and the settings.
set.seed(1) d <- data.frame(Variety = rep(paste0("V", 1:9), each = 3)) d$Yield <- rep(c(30, 31, 29, 40, 42, 41, 35, 36, 34), each = 3) + rnorm(27) d$Height <- rep(c(80, 82, 79, 95, 97, 96, 88, 87, 90), each = 3) + rnorm(27) cl <- cluster_data(d, c("Yield", "Height"), group = "Variety") cl$k cl$clusterset.seed(1) d <- data.frame(Variety = rep(paste0("V", 1:9), each = 3)) d$Yield <- rep(c(30, 31, 29, 40, 42, 41, 35, 36, 34), each = 3) + rnorm(27) d$Height <- rep(c(80, 82, 79, 95, 97, 96, 88, 87, 90), each = 3) + rnorm(27) cl <- cluster_data(d, c("Yield", "Height"), group = "Variety") cl$k cl$cluster
For every pair of the variables named: the number of rows with both values
(N), the coefficient (Pearson's r, Spearman's rho or Kendall's tau-b), its
two-sided p-value, a significance mark at the chosen level (* at
alpha, ** at alpha / 5, NS otherwise), for
Pearson's r a confidence interval at level 1 - alpha (Fisher's
approximation), the strength of the association, and a one-line reading.
Text entries are read as the data check reads them: an entry that is not
plainly a number counts as missing; see check_data.
correlate_data( d, vars, method = c("pearson", "spearman", "kendall"), alpha = 0.05 )correlate_data( d, vars, method = c("pearson", "spearman", "kendall"), alpha = 0.05 )
d |
A data frame. |
vars |
The names of two or more numeric columns. |
method |
|
alpha |
The significance level, between 0 and 0.5. |
Pearson's p-value is the usual t test. Spearman's and Kendall's are exact
whenever the distinct orderings of one variable's values can be listed (up
to 50,000, which covers eight rows and many more when values are tied);
with ties and more orderings than that, Spearman's at any size and
Kendall's up to 20 rows are estimated from 20,000 random orderings
(200,000 near a significance threshold; a fixed seed, and the user's
random number stream is left as it was), and Kendall's above 20 rows use
the large-sample approximation, which holds its level there; without ties
they come from cor.test, exact for Kendall's tau below
50 rows and Spearman's rho at nine, and a large-sample approximation
beyond.
Strength uses Cohen's 0.1, 0.3 and 0.5 for r, carried to rho and tau
through their relation to r for bivariate normal data (0.10, 0.29, 0.48 for
rho; 0.06, 0.19, 0.33 for tau).
A data frame with one row per pair: Variable1,
Variable2, N, Coefficient, p, Mark,
CI_lower and CI_upper (Pearson only; NA otherwise),
Strength, P_method ("exact", "estimate" from
random orderings, or "approximation"), Approximate
(TRUE unless exact), Held (rows left out only because an
entry waits for a data-check correction) and Reading. A pair that cannot be
computed has NA coefficient and p-value and says why in
Reading.
d <- demo_data("RCBD") set.seed(1) d$Height <- 60 + 0.8 * d$Yield + rnorm(nrow(d), 0, 2) correlate_data(d, c("Yield", "Height")) correlate_data(d, c("Yield", "Height"), method = "spearman")d <- demo_data("RCBD") set.seed(1) d$Height <- 60 + 0.8 * d$Yield + rnorm(nrow(d), 0, 2) correlate_data(d, c("Yield", "Height")) correlate_data(d, c("Yield", "Height"), method = "spearman")
Generates a small, balanced example dataset for a design, so you can try DOEpro without your own data. These are the datasets behind the Load example button in the application.
demo_data(which)demo_data(which)
which |
The example to build: |
A data frame in long format, ready to pass to analyze.
head(demo_data("RCBD")) str(demo_data("SPLIT"))head(demo_data("RCBD")) str(demo_data("SPLIT"))
For each variable: the number of values (N), the number missing, the mean,
standard deviation (SD), minimum, first quartile (Q1), median, third
quartile (Q3), maximum, coefficient of variation (CV, per cent) and
skewness, optionally for each level of a grouping column. Quartiles are
calculated as Excel's QUARTILE.INC does (quantile(type = 7)) and
skewness as Excel's SKEW does (the adjusted Fisher-Pearson coefficient).
Text entries are read as the data check reads them: an entry that is not
plainly a number (such as 5,6 or 12a) counts as missing; see
check_data.
describe_data(d, vars, group = NULL)describe_data(d, vars, group = NULL)
d |
A data frame. |
vars |
The names of the numeric columns to describe. |
group |
Optionally, the name of a column whose levels split each variable into groups. Rows without a label in it are left out. |
A data frame with one row per variable (and group): Variable,
Group (when grouped), N, Missing, Mean,
SD, Min, Q1, Median, Q3, Max,
CV and Skewness. CV is NA when any value is negative
or the mean is not positive, where it has no useful meaning; skewness
needs three values that are not all equal. Missing counts empty cells,
entries that are not numbers and infinite values.
describe_data(demo_data("RCBD"), "Yield") describe_data(demo_data("RCBD"), "Yield", group = "Variety")describe_data(demo_data("RCBD"), "Yield") describe_data(demo_data("RCBD"), "Yield", group = "Variety")
A named character vector: the names are the labels shown in the application
and the values are the design codes passed to analyze.
DESIGNSDESIGNS
A named character vector of length 11.
A named character vector of length 11. Each element is the design
code passed to the design argument of analyze or
run_all; the corresponding name is the label shown in the
application.
DESIGNS DESIGNS[["Split Plot Design"]]DESIGNS DESIGNS[["Split Plot Design"]]
Maximum-likelihood factor analysis (factanal) of the
chosen columns, from the rows with a value of every one, with the
Kaiser-Meyer-Olkin measure of sampling adequacy (overall and for each
variable) and Bartlett's test of sphericity. The number of factors, unless
given, comes from Horn's parallel analysis of the eigenvalues of the
correlation matrix (the same as on the principal components tab), within
the most the data allow. Text entries are read as the data check reads
them; see check_data.
fa_data( d, vars, factors = NULL, rotation = c("varimax", "promax", "none"), alpha = 0.05 ) ## S3 method for class 'doepro_fa' print(x, ...)fa_data( d, vars, factors = NULL, rotation = c("varimax", "promax", "none"), alpha = 0.05 ) ## S3 method for class 'doepro_fa' print(x, ...)
d |
A data frame. |
vars |
The names of three or more numeric columns. |
factors |
The number of factors, or |
rotation |
|
alpha |
The significance level for Bartlett's test and the test that the number of factors is enough. |
x |
A result of |
... |
Not used. |
Each factor's sign is arbitrary; here it is chosen so that its largest
loading is positive, and the factors are ordered by the variance they
carry. With rotation = "promax" the loadings are pattern loadings
and the factors may correlate; their correlations are returned.
A list of class doepro_fa: kmo (overall) and
msa (each variable), bartlett (statistic, df, p),
eigen (eigenvalues of the correlation matrix with their
parallel-analysis thresholds), suggested and factors,
loadings (variables by factors), communality,
uniqueness, variance (each factor's sum of squared
loadings, proportion and cumulative proportion), phi (the factor
correlations), fit (the test that the number of factors is
enough), scores (regression scores, rows by factors), and the
settings.
set.seed(1) f1 <- rnorm(60); f2 <- rnorm(60) d <- data.frame(Height = f1 + rnorm(60, 0, 0.5), Tillers = f1 + rnorm(60, 0, 0.5), Yield = f1 + rnorm(60, 0, 0.6), Protein = f2 + rnorm(60, 0, 0.5), Oil = f2 + rnorm(60, 0, 0.5), Moisture = f2 + rnorm(60, 0, 0.6)) fa <- fa_data(d, names(d)) fa$kmo round(fa$loadings, 2)set.seed(1) f1 <- rnorm(60); f2 <- rnorm(60) d <- data.frame(Height = f1 + rnorm(60, 0, 0.5), Tillers = f1 + rnorm(60, 0, 0.5), Yield = f1 + rnorm(60, 0, 0.6), Protein = f2 + rnorm(60, 0, 0.5), Oil = f2 + rnorm(60, 0, 0.5), Moisture = f2 + rnorm(60, 0, 0.6)) fa <- fa_data(d, names(d)) fa$kmo round(fa$loadings, 2)
Makes only the corrections whose meaning is certain, entry by entry: decimal commas become points, thousands separators are removed, a per cent sign or a unit carried by every value in a column is dropped, entries such as n/a or - are emptied, extra spaces in labels are removed, and empty rows are deleted. A column becomes a column of numbers once every entry in it is a number. Entries whose meaning is not certain, such as 12a, nil, or 1,250 where the column does not show which notation it uses, are left exactly as they are, and labels that differ in capitals or punctuation are not merged.
fix_data(d)fix_data(d)
d |
A data frame, as loaded. |
A list with data, the corrected data frame (row names kept,
so row numbers still match the data as loaded), and log, a
character vector saying what was changed.
d <- data.frame(Variety = c("V1", "V1", "V2", "V2"), Yield = c("5,6", "6.1", "n/a", "7,2")) fix_data(d)d <- data.frame(Variety = c("V1", "V1", "V2", "V2"), Yield = c("5,6", "6.1", "n/a", "7,2")) fix_data(d)
Principal components of the chosen columns, from the rows with a value of
every one (prcomp). By default each variable is
standardised first, so the analysis works on their correlations and no
variable dominates through its units; with scale = FALSE it works
on their covariances, which suits variables measured in the same units.
Text entries are read as the data check reads them: an entry that is not
plainly a number counts as missing; see check_data.
pca_data(d, vars, scale = TRUE) ## S3 method for class 'doepro_pca' print(x, ...)pca_data(d, vars, scale = TRUE) ## S3 method for class 'doepro_pca' print(x, ...)
d |
A data frame. |
vars |
The names of two or more numeric columns. |
scale |
|
x |
A result of |
... |
Not used. |
Each component's sign is arbitrary (reversing it changes nothing); here it is chosen so that the variable with the largest loading on it loads positively. The loadings are the correlations between each variable and each component. Components are kept by Horn's parallel analysis: those, from the first, whose eigenvalue exceeds the 95th percentile of the eigenvalues of random normal data of the same size (with the same variances for a covariance analysis), simulated with a fixed seed. Kaiser's rule (an eigenvalue above the average, which is 1 for correlations) is shown beside it.
A list of class doepro_pca: eigen (a data frame:
Component, Eigenvalue, Proportion,
Cumulative, PA_threshold), loadings (variables by
components, correlations), vectors (the eigenvectors),
scores (rows by components), rows (the rows used),
kaiser and retained (the number of components each rule
keeps), and the settings.
set.seed(1) d <- data.frame(Height = rnorm(30, 100, 10)) d$Tillers <- 0.1 * d$Height + rnorm(30) d$Yield <- 0.3 * d$Height + 0.5 * d$Tillers + rnorm(30, 0, 3) d$Protein <- rnorm(30, 12, 1) p <- pca_data(d, c("Height", "Tillers", "Yield", "Protein")) p$eigen round(p$loadings, 2)set.seed(1) d <- data.frame(Height = rnorm(30, 100, 10)) d$Tillers <- 0.1 * d$Height + rnorm(30) d$Yield <- 0.3 * d$Height + 0.5 * d$Tillers + rnorm(30, 0, 3) d$Protein <- rnorm(30, 12, 1) p <- pca_data(d, c("Height", "Tillers", "Yield", "Protein")) p$eigen round(p$loadings, 2)
Fits response on the predictors by least squares
(lm), using the rows with a value of every variable.
One predictor gives a simple linear regression, two or more a multiple
regression. Text entries are read as the data check reads them: an entry
that is not plainly a number counts as missing; see
check_data.
regress_data(d, response, predictors, alpha = 0.05) ## S3 method for class 'doepro_regression' print(x, ...)regress_data(d, response, predictors, alpha = 0.05) ## S3 method for class 'doepro_regression' print(x, ...)
d |
A data frame. |
response |
The name of the numeric column to be predicted. |
predictors |
The names of one or more numeric columns to predict it from. |
alpha |
The significance level, between 0 and 0.5. |
x |
A result of |
... |
Not used. |
The coefficient table gives each estimate with its standard error, t
statistic, two-sided p-value, a significance mark (* at
alpha, ** at alpha / 5, NS otherwise) and a
confidence interval at level 1 - alpha; with two or more
predictors, each slope's variance inflation factor. The model summary
gives N, R-squared, adjusted R-squared, the residual mean square (MSE, the
residual sum of squares divided by its N - k - 1 degrees of freedom), its
square root (RMSE, the residual standard error), the mean absolute residual
(MAE), and the F test of the whole model. Three checks on the residuals
go with them: Shapiro-Wilk's test of normality; the Breusch-Pagan test
(Koenker's studentised form) of a spread that changes with the predictors
(the squared residuals on the predictors, k degrees of freedom); and a test
of a curve, Ramsey's RESET (with one predictor its square added to the
model, with several the square of the fitted values). With up to 200 rows
the p-values of Shapiro-Wilk and Breusch-Pagan come from 2,000 sets of
normal data simulated on the same predictor values (10,000 near a
significance threshold), since for residuals from few rows the usual ones
can be far off; with one residual degree of freedom both are not
available.
A list of class doepro_regression: equation (the
fitted equation as printed), coefficients (a data frame:
Term, Estimate, SE, t, p, Mark,
CI_lower, CI_upper and, with two or more predictors,
VIF), model (a one-row data frame: N, R2,
Adj_R2, MSE, RMSE, MAE, F,
df1, df2, p, Mark), anova (the
regression, residual and total sums of squares), checks (the
three residual checks: Check, Test, Statistic,
df, p, Simulated (whether the p-value came from
simulation) and Verdict, which gives the reason when a check is
not available), rows (the rows used), fit
(the lm fit, on columns y, x1, x2, ...: the
predictors centred and scaled by centre and scale),
built (predictors that are powers or products of others), and
the settings. With an exact fit (every residual zero) the standard
errors, tests and intervals are NA.
d <- demo_data("RCBD") set.seed(1) d$Height <- 60 + 0.8 * d$Yield + rnorm(nrow(d), 0, 2) r <- regress_data(d, "Height", "Yield") r$equation r$coefficients r$modeld <- demo_data("RCBD") set.seed(1) d$Height <- 60 + 0.8 * d$Yield + rnorm(nrow(d), 0, 2) r <- regress_data(d, "Height", "Yield") r$equation r$coefficients r$model
Runs analyze for every response variable in turn, using the same
design and mapping, and optionally applying a variance-stabilising
transformation to each. This is what the application calls when more than one
response is selected.
run_all(d, design, map, responses, alpha = 0.05, trans = NULL, dtype = "auto")run_all(d, design, map, responses, alpha = 0.05, trans = NULL, dtype = "auto")
d |
A data frame in long format. |
design |
The design code; see |
map |
A named list mapping roles to columns; see |
responses |
A character vector of response column names. |
alpha |
The significance level. Defaults to 0.05. |
trans |
Either |
dtype |
Passed to the transformation adviser; |
A list with fits (one entry per response, each containing the
fitted analysis in final), together with the design, the mapping and
the significance level.
d <- demo_data("FRCBD") rr <- run_all(d, "FRCBD", list(factors = c("Nitrogen", "Variety"), block = "Block"), responses = "Yield") rr$fits[["Yield"]]$final$anovad <- demo_data("FRCBD") rr <- run_all(d, "FRCBD", list(factors = c("Nitrogen", "Variety"), block = "Block"), responses = "Yield") rr$fits[["Yield"]]$final$anova
Launches the DOEpro Shiny application, which analyses designed agricultural experiments: paste or upload your data, choose the design, and press Run analysis.
run_DOEpro(...)run_DOEpro(...)
... |
Further arguments passed to |
The application returns the analysis of variance, tables of means with standard errors and critical differences, checks of the assumptions with advice on transformations, post-hoc comparisons, diagnostic plots, a plain-English interpretation, and a downloadable report.
An object of class shiny.appobj. Called for its side effect of
starting the application.
if (interactive()) { run_DOEpro() }if (interactive()) { run_DOEpro() }
A named list of the variance-stabilising transformations. Each entry holds a
label (lab), the transformation (f) and its inverse
(inv), which is what lets DOEpro report means on the original scale of
measurement alongside the transformed ones.
TRANSTRANS
A named list of length 9. The names are the keys used in the
trans argument of run_all: "none",
"log", "log1", "sqrt", "sqrt0.5",
"arcsine", "arcsine01", "reciprocal" and
"boxcox".
A named list of length 9. Each element is itself a list with
lab, the label shown in the application; f, a function
applying the transformation; and b, a function applying its
inverse, which is what allows means to be reported on the original scale
of measurement.
names(TRANS) vapply(TRANS, `[[`, character(1), "lab")names(TRANS) vapply(TRANS, `[[`, character(1), "lab")