tAge 1.5.0
Breaking changes
flag_outliers()replacesremove_outliers(), astage.pp.flag_outliersdoes in the Python package, with the same numbers. It flags, in the phenoData, samples whose log2(CPM + 1) profile (genes with mean CPM >= 10) is far from the median profile of their stratum – robust z of1 - rabove 5 – and leaves the removal to the caller (eset[, !eset$tage_outlier]).method = "pca_iqr"is the paper’s PC1/PC2 1.5-IQR rule on the same input. The removed detectors ran PCA on log10(raw counts), where PC1 follows sequencing depth; in strata of a dozen samples the 1.5-IQR rule removed normal samples in most strata.robustbaseis no longer suggested.tage_module_heatmap()andload_module_functions()no longer default to the rodent module set: passmodule_set(ormodule_functions). A module colour names a different module in each set – 7 of the 8 colours shared by the rodent and multispecies sets differ, “blue” being Respiration/Mitochondrial translation in one and Myogenesis/Muscle contraction in the other – so multispecies and human module clocks were labelled with rodent functions whenever the argument was left out.module_functions = character(0)labels the rows by module name only.
Clock registry
The registry is the Zenodo record 22166800: the 60 composite clocks and, new, the published module clocks.
list_module_clocks()lists the 78 elastic net module clocks of the rodent and multispecies sets (one per co-expression module plusallmodulegenes, chronological and mortality, Scaled normalisation) with their annotated function and the archive each set is published as.download_clocks()accepts its output: the archive is downloaded once, unpacked intodest_dir, andpathpoints inside it. Downloads are checked to be a pickle or a zip archive. Module clocks are scaled like the composite clock of the same outcome.Module sets are named
"rodent","multispecies"and"human":load_module_functions(module_set = )andtage_module_heatmap(module_set = )replace theversion/modules_versionarguments. The bundled annotation files areModule_to_function_map_<set>.csv.Bundled data files renamed:
Gene_list_rodent_clocks.txt(the gene list of the rodent clocks, read byload_gene_list()) andmetadata/Orthologs_monkey_to_mouse.csv.
New features
-
predict_tAge()takes the clock table fromlist_clocks()/list_module_clocks()with apathcolumn (e.g. fromdownload_clocks()): every row is applied to the representation itsscalingnames and gets a column named after the model file, the names the Python package uses. A named list of paths still works; with several paths per representation it used to fail with “‘length = 3’ in coercion to ‘logical(1)’” and now names its columns by model file, while one path per representation keeps the<representation>_<mode>_tAgenames.modeandreturn_stddefault toNULL: each model’s type comes from the table, the registry or its file name, and Bayesian ridge clocks keep their_sdcolumn.tAge_by_group()follows.predict_tAge_one()refuses more than one path with a clear message.
Bug fixes
tage_clock_forest()labels each axis with the unit of its clocks: fromunitskeyed by prediction column, else from the"tage_units"attributepredict_tAge()sets (years for primates, percent withnormalized_age = "percent"), else fromunitskeyed by outcome.unitsused to be ignored – the defaults came first in the lookup, sounits = c(Chronological = "years")still printed “months” – and every chronological axis read “months”, human data included. Without any unit information a chronological axis now reads “age units”.tage_adjust_covariates()gainsgroup_column. With it, the covariate effects removed are those of the model the statistics fit (value ~ group + covariates, one model per stratum,lmor the weighted meta-regression), so the difference between group means of the adjusted values is the tested estimate. The covariate-only model, still the default, attributes part of the group effect to covariates that are unevenly distributed across groups (tested KO - WT 1.22, plotted 0.91 in an unbalanced example).tage_boxplot()passes its groups, so its covariate-adjusted points now show what its brackets test.aggregate_pseudobulk()adds cells until a pseudobulk sample reachescoverage_thresholdand then starts the next one, asaggregate_on_obs_columns()and the Python package do. It used to cut the cumulative read count at multiples of the threshold, so the first sample and about half of the others held fewer reads than the threshold, and a cell with more reads than the threshold left empty samples (0 cells, 0 counts) behind. Both functions gaindrop_incomplete(as in Python) to drop the leftover last sample, and share one implementation that sums with a sparse indicator matrix.aggregate_on_obs_columns()now respectsverbose.tAge_preprocessing()refuses input that is not raw counts – missing, negative or non-integer values (TPM, CPM, log data), with the tolerance the Python package uses – and acontrol_group_columnthat does not exist or acontrol_group_labelthat no sample carries; both used to fall back to centring on all samples with a warning. A stratum ofsplit_bywithout reference samples is still centred on itself with a warning.control_subtraction()refuses a column that does not exist.map_genes()keeps Entrez IDs as text from integers when summing identifiers that collapse onto one gene. Grouping on the numbers named round IDs in scientific notation (rat 500000 became"5e+05"), so they matched nothing in the ortholog table and were dropped; rat 500000 is the ortholog of the clock gene 64945. Duplicates are summed withrowsum().The statistics leave out a categorical covariate that has a single level in the data a model is fitted on (e.g.
Sexin an all-male stratum) instead of skipping the stratum onlm()’s “contrasts can be applied only to factors with 2 or more levels”. The Python package already fitted these strata; the two now agree.
tAge 1.4.0
New features
Species maximum lifespans are the values the clocks were trained with (AnAge): rat 3.8 years (was 4.2) and human 122 years (was 122.5); mouse 4 and macaque 39 years are unchanged. Chronological-age predictions for rat samples are therefore ~10% lower than before, human ones ~0.4%.
One figure style, shared with the Python package:
theme_tage(), the colour tokens intage_colors(),tage_series_colors()for group colours (the reference group in neutral grey, comparisons in colour-vision-safe slots) andscale_fill_tage_diverging()for signed effects.tage_boxplot(),tage_clock_forest(),tage_module_heatmap(), the outlier PCA plot andplot_eset_density()all draw with it: left-aligned title with subtitle and caption, recessive axes, one colour per clock outcome, blue-grey-red scale centred on zero.tage_boxplot()gainssubtitleandcaption; itstheme_typeis deprecated.tAge_preprocessing(split_by = )preprocesses each level of a phenoData column (tissue, dataset, cell type) on its own – gene filtering, normalisation and reference centring within the stratum, on the stratum’s own controls – and combines the results. This is how the clocks were trained and applied in the paper; a single reference pooled across tissues mixes tissue differences into the signal.tAge_by_group()is now this pluspredict_tAge(), and no longer swallows errors per stratum. The bulk vignette preprocesses the two-tissue Klotho example this way, with the paper’s 25% gene-detection threshold.Gene identifiers are detected:
gene_mapping_type = "auto"(the new default ofmap_genes()andtAge_preprocessing()) picks Ensembl, gene symbol or Entrez as the type with the most matches in the species’ gene table; Ensembl version suffixes are stripped when that is what makes the IDs match. Entrez input is new. Nothing matching is an error that lists the match counts.The species is recorded in the ExpressionSets returned by
tAge_preprocessing();predict_tAge()/predict_tAge_one()take it from there (species = NULL).speciesis documented as the species of the samples, used only to rescale chronological-age clocks; an unknown species is an error instead of a silent factor of 1.tage_species()lists the supported species with their maximum lifespans and default units.predict_tAge()gainsage_units(“auto”: months for rodents, years for primates; or “months” / “years”) andnormalized_age(“fraction”, the default, or “percent” as in the paper and TACO). The result carries a"tage_units"attribute naming the unit of every prediction column. Clocks outside the registry are recognised from their file names (Chronoage, Hazard / Mortality, Relage / NormalizedAge); unknown names are left on the native scale with a warning.remove_outliers(method = )adds the paper’s two rules next to the Mahalanobis default:"pca_iqr"(PC1 or PC2 beyond 1.5 IQR, bulk data) and"spearman_median"(Spearman correlation with the group’s median profile below 0.5, meta-dataset).
Bug fixes
-
control_subtraction()warns when the requested control label matches no sample (it used to fall back to all samples silently unlessverbose).
Housekeeping
- The Python bridge no longer silences every
UserWarningfor the whole session; the scikit-learn version warning is suppressed around the model load only. - Base-package functions are imported explicitly (
R CMD checkNOTEs);tage_boxplot()no longer callslibrary().pngandrobustbaseare listed in Suggests. The test helper downloads models throughdownload_clocks(). -
tage_boxplot()needsggpubronly for the bracket layer it draws with it; the pkgdown workflow installs it for the vignettes. -
download_clocks()writes to<file>.partand renames only once the transfer is complete and checked, so an interrupted session cannot leave a truncated model under the real name. Non-ASCII characters in R sources are written as\uescapes (R CMD checkwarning).
tAge 1.3.1
Bug fixes
map_genes(species = "monkey")failed on every input with “subscript out of bounds”: macaque genes without a mouse ortholog were looked up with[[. Mapping is now vectorised and drops those genes, as for the other species.tage_clock_forest(clocks_meta = list_clocks(...))could not find the prediction columns:predict_tAge()names them<normalisation>_<mode>_tAge, not by model file. Registry rows are now matched throughscalingandtype(Scaled + EN ->scaled_diff_EN_tAge), labelled from the registry fields when there is nonamecolumn, and the registry’s “Normalized age” outcome is recognised. Two rows landing on one column is an error with an explanation.tage_adjust_covariates()with a Bayesian ridgese_columnand nosplit_byreturned zero-centred residuals; the values are now put back on the tAge scale like every other branch.tage_compare_groups(),tage_regress_continuous(),tage_module_stats()andtage_adjust_covariates()no longer drop a stratum in silence. Every skipped clock/stratum is reported with a warning naming it and the reason (reference group absent, fewer than two groups, collinear covariates, a failedlm/rma.unifit, …).download_clocks()raises R’s download timeout while it runs (newtimeoutargument, default 3600 s; the default 60 s aborted every Bayesian ridge model, 0.9-2.4 GB each), removes partial files instead of leaving them to be reported as “already present”, and rejects files that are not pickles (Zenodo error pages).Normalized-age panels of
tage_clock_forest()are labelled “fraction of maximum lifespan”, the scale the package actually returns.
tAge 1.3.0
New features
Two publication-style figures built directly on the statistics, so a figure and the table behind it cannot drift apart:
tage_clock_forest()draws one clock per row with its confidence interval, filled when it survives the multiplicity correction;tage_module_heatmap()draws module effects as modules × strata with a star per significant cell. Both return the statistics as the"tage_stats"attribute, and both accept a precomputed table viastats =.load_module_functions()reads the bundled module-to-function annotation used for the row labels.The statistics gain
ci_low/ci_highand aconf_levelargument. The critical value follows the test: normal for the Bayesian ridge meta-regression, t otherwise.Statistical tests matching the TACO / tClock reference application:
tage_compare_groups()for marginal-mean contrasts between groups,tage_regress_continuous()for slopes against a numeric predictor,tage_module_stats()for module-clock heatmaps,tage_adjust_covariates()for the matching plotting values, andtage_significance_stars()for the*** ** * ^labels. Elastic net clocks uselm+emmeans; Bayesian ridge clocks usemetafor::rma.uniweighted by the per-sample prediction standard deviation, reporting z-tests.emmeansandmetaforare new imports.predict_tAge()andpredict_tAge_one()gainreturn_std, on by default formode = "BR", adding a<normalisation>_BR_tAge_sdcolumn. These are the weights the Bayesian ridge tests need, and were previously discarded.tage_boxplot()defaults tostat_method = "emmeans", annotating brackets fromtage_compare_groups()and supporting covariates, per-stratum models and Bayesian ridge weighting. Passing any otherstat_methodkeeps the previousggpubr::stat_compare_means()behaviour.
tAge 1.1.0
Corrects three bugs that produced wrong predictions in 1.0.0 / 1.0.1. Analyses run with those versions should be repeated with 1.1.0.
Bug fixes
Species rescaling is now applied only to chronological-age clocks. Previously every prediction was multiplied by the species maximum-lifespan factor, which turned mortality output (
log10hazard ratio) and normalized-age output into meaningless numbers. The clock type is taken from the model file name, matching the TACO reference application.Reference centring is never skipped.
control_subtraction()used to return the data unchanged when no reference group was given. All distributed clocks are relative (_scaleddiff/_yugenediff) models trained on reference-centred expression, so predictions made without centring were invalid.column_nameandcontrol_labelnow default toNULL, which centres on all samples (overall per-gene median) — the TACO default.Genes absent from the input are padded with
NAinstead of0when aligning to the clock gene list. The trained model’s imputer then fills them with the training-set median for that gene, which is the correct neutral value.predict_tAge()coerces the reticulate result to adata.frame, fixing a failure on reticulate/pandas versions that return a bare vector for a single-column result.tage_boxplot()is exported.
New features
- Clock registry:
list_clocks()browses the pre-trained models (filter by type, outcome, species, tissue and scaling) anddownload_clocks()fetches them from Zenodo record 18763485, returning the table with apathcolumn. The registry ships with the package asinst/extdata/clocks_metadata.csv.
Documentation
Two vignettes replace the former Jupyter notebooks:
vignette("tage-bulk")for bulk RNA-seq andvignette("tage-singlecell")for the pseudobulk single-cell workflow.pkgdown site at https://gladyshev-lab.github.io/tAge/.
README rewritten: units of each clock outcome, the role of reference groups in relative clocks, supported species, and licensing.
scale_eset()documents that scaling is per sample (column-wise, each sample scaled across genes). Per-gene standardisation happens separately inside the trained model, using training-set statistics.
tAge 1.0.1
- README and LICENSE updated for the MGB Open Access License 1.0.
- Model availability notice and placeholder paths corrected in the tutorials.
tAge 1.0.0
- First release, accompanying Tyshkovskiy et al. (2026), Nature.
- Superseded by 1.1.0 — see the bug fixes above before using any results from this version.