prcomp() return the mirror image of the same
embedding on some LAPACK/BLAS builds (it did on Linux CI), which the test
read as an order dependence. Axes are now oriented with the same
canonicalisation the 1.1.4 snapshots use, and a new test checks that a sign
flip passes while a moved point still fails. No user-visible behaviour
changes; only the test suite is affected.create_vista(),
run_edger_analysis(), benchmark_vista_equivalence() and
validate_vista_deep() gain edger_test = c("LRT", "QL"). "QL" runs
glmQLFit()/glmQLFTest() (edgeR v4 pipeline), which the edgeR authors
recommend for bulk RNA-seq; the likelihood-ratio test treats the dispersion
as known and is liberal with few replicates. The test used is recorded in
cutoffs(v)$edger_test, and the report config accepts edger_test:; for an
edgeR object made before 1.1.6, which does not record the test, the report
states the likelihood-ratio test and marks it as inferred. The equivalence
check runs its own QL reference. The vignettes now set edger_test.edger_test will default to "QL". It stays "LRT" in this release
so that no results change without warning. A call that runs edgeR
(method = "edger" or "both") without setting edger_test, or with
edger_test = NULL, prints a message, classed vista_default_change and
throttled like the deprecation warnings, saying so. Set
edger_test = "LRT" to keep current results after the change, or
edger_test = "QL" to use the new default now; either silences the message.
DESeq2 and limma-voom analyses are not affected. See ?VISTA-deprecated.tximport abundance or lengths could be analysed as counts.
read_vista_counts(format = "tximport", counts_from = "abundance") imported
TPM, and counts_from = "length" transcript lengths, as if they were counts,
and edgeR and limma tested them without complaint. Both now raise an error.
tximport counts were analysed without transcript-length correction.
tximport's default countsFromAbundance = "no" leaves estimated counts
unscaled and expects the DE method to use length offsets, which VISTA does
not. Such input that carries abundance and length (also when it does not
say how it was scaled) is now converted to length-scaled TPM counts
(tximport's countsFromAbundance = "lengthScaledTPM", recommended when the
method does not use offsets), with a message. With tx2gene, transcript-level
input is summarised to genes as tximport's summarizeToGene() does, which
rebuilds gene counts from abundance and length even when the transcript
counts were already scaled; summing them, as VISTA did, undid the gene-level
length correction. The results agree with tximport's own to within
numerical precision (relative differences below 1e-14). abundance and
length are matched to counts by name, and missing or negative values, a
sample with zero total abundance, or a tx2gene that matches no transcript
are clear errors, as is an unrecognised countsFromAbundance value. Without
abundance/length, VISTA warns and uses the counts as given. Were you
affected? If you imported tximport output made with the default
countsFromAbundance = "no", a hand-built list without that field, or
transcript-level "lengthScaledTPM"/"dtuScaledTPM" output with tx2gene
(scaledTPM sums to the same gene counts); re-run those analyses.
DESeq2 stopped on estimated counts. Non-integer counts from tximport or
RSEM made create_vista(method = "deseq2") fail with "some values in assay
are not integers". DESeq2 now receives them rounded, with a message, as
DESeqDataSetFromTximport() does. Filtering is applied to the counts as
given, as for edgeR and limma, so method = "both" keeps one set of genes,
and its counts(v) holds DESeq2's rounded counts whichever source is
active.
Values that are not counts could be analysed without comment. All three
engines now refuse negative values, and warn (class
vista_not_counts_warning) about non-integer values with an unmistakable
sign of not being counts: every sample summing to exactly one million (TPM,
CPM) or small, almost all fractional values (log scale). These are warnings
because no rule on the values alone separates counts from normalised data:
estimated counts from libraries of about a million reads must not be
refused, and TMM-CPM, FPKM or filtered TPM cannot be recognised. Supply raw
or estimated counts. Integer counts are unaffected.
RSEM TPM or FPKM could be imported as counts. Where count_column
selects the values read from RSEM output, only RSEM's count columns,
expected_count (the default) and posterior_mean_count, are now accepted.
A merged table whose sample columns are named in sample_names is read as
given.
Untested genes were stored with pvalue = 1. DESeq2 reports NA for
genes it does not test (Cook's-distance count outliers); VISTA replaced that
with 1 when it stored the comparison, beside a padj that stayed NA, so an
untested gene read as tested and the row disagreed with itself. On airway
this affected 51 genes. Tables from comparisons() and those written by
export_vista_assets() now show NA, as the engine reported, and so do the
per-backend pvalue_deseq2/pvalue_edger columns of a consensus table.
Plots and rankings already substituted their own value for a missing
p-value, so figures are unchanged.
Filtering a consensus table on padj returned the union, not the
intersection. With consensus_mode = "intersection", a gene called by only
one backend is not called, but its padj was that backend's significant
value, contrary to the documented "less-significant of the two". Selecting
padj <= 0.05 on the table, as get_foldchange_scatter() and the volcano
thresholds do, therefore picked up every single-backend gene. Such genes now
carry the less-significant pvalue and padj of the two backends, and their
log2fc follows consensus_log2fc (the mean of the two by default) instead
of the calling backend's estimate, as for every other uncalled gene. On
airway this changes log2fc for all 119 single-backend genes (by up to 0.36)
and the p-values of most of them. In union mode, where they are called, they
keep the calling backend's values, as before. regulation was always correct
and is unchanged.
DEG-count plots dropped comparisons with no DEGs.
get_deg_count_barplot(), get_deg_count_pieplot() and
get_deg_count_donutplot() left out a comparison whose summary was all
"Other", asking for it by name aborted with "not found in DEG summary", and
when no requested comparison had any DEG they returned NULL with a warning.
Such comparisons are now drawn at zero. Comparison order is unchanged. A
summary that uses none of the labels Up, Down or Other (possible with
as_vista()) is left out with a warning naming the labels found, rather than
drawn as zero.
limma MA plots put the wrong quantity on the x axis. For limma the
table's baseMean is AveExpr, already log2-CPM, and get_ma_plot()
preferred it to the object's mean expression, so the axis showed
log10(log2-CPM + 1). It now uses the object's mean expression for every
engine, as its own comment intended.
An explicit intercept was not removed for edgeR and limma. For
design_formula = "~ 1 + cell + group", VISTA prefixed 0 +, which + 1
then undid, so the model kept its intercept. VISTA now reads the intercept
from the formula's terms. The fitted model spans the same space, so results
differ only within numerical precision.
benchmark_vista_equivalence() no longer shares code with what it
checks. Its reference pipelines used VISTA's own helpers for sample
matching, design construction, contrasts, DEG calls and row alignment, so a
defect in any of them was reproduced on both sides and passed -- which is
how the first and third defects above went unnoticed. The reference now
calls only base R, stats and the engines, re-implements the documented
contract from scratch (see ?benchmark_vista_equivalence), derives contrasts
from the design itself, reads VISTA's tables through its exported accessors
and matches rows by identifier. It compares every numeric column the engine
reports, the retained gene set and the set of comparisons; the summary gains
gene_sets_identical, comparison_sets_identical,
all_columns_within_tolerance and columns_compared.
baseMean_within_tolerance is NA for edgeR, which reports no baseMean
(it was a vacuous TRUE); a numeric column VISTA stores but the reference
does not report (columns_not_in_reference) counts as a mismatch; and a
mismatch between a value and NA is reported as an infinite difference
instead of 0. The MA, PCA and DEG-count checks now read the data
get_ma_plot(), get_pca_plot() and the DEG-count plots draw, and the PCA
check compares every sample-sheet column; the volcano check still rebuilds
its inputs from the stored table.reference element has a new shape. Its comparisons are
data frames in VISTA's column names (gene_id, log2fc, stat, pvalue,
padj, plus baseMean and lfcSE for DESeq2, logCPM for edgeR, and
baseMean (limma's AveExpr), B, P.Value and adj.P.Val for limma),
not the engines' own tables. row_data is removed (use
rowMeans(reference$norm_counts)), group_column is added, and each
deg_summary table has exactly two rows, Up and Down.VISTA::/VISTA:::, or by name in a string. They inject defects into VISTA's own helpers that the
benchmark must report: a flipped contrast sign, swapped Up/Down calls,
values under the wrong gene labels, samples matched to the wrong metadata
rows, a dropped covariate, untested genes stored as p = 1, and a
comparison dropped from DEG-count plot data. The 1.1.5 check caught two of
the first five; those five were chosen to target the helpers it shared, so
this shows the new check can fail where the old one could not, not a
detection rate.metadata(x)$provenance, as ?VISTA-class already
described. The stored object behind example_vista() was regenerated to
carry them.?create_vista documents how a sample sheet without sample_names is
matched to count columns, and what the consensus padj is: a conservative
ranking score, not an FDR-adjusted p-value of the consensus. In simulations
the intersection lowered, but did not control, the single backends' false
discovery rate, and the union raised it.min_counts and min_replicates in benchmark_vista_equivalence() and
validate_vista_deep() are now described as they behave: min_replicates
counts samples across the whole experiment, not per group.get_foldchange_matrix() gains display_id, display_from and
display_orgdb, matching [get_foldchange_heatmap()]. genes may now be
given as display labels, and the returned matrix is labelled with them.
Because symbols are not unique, duplicated labels are made unique with a
warning rather than left to collide -- duplicated rownames would make
m["SYM", ] return whichever row came first and silently hide the other.
Rows keep the object's identifiers unless display_id is supplied, so
existing calls are unaffected.get_foldchange_heatmap(annotate_columns = TRUE) annotated nothing. The
columns of a fold-change heatmap are comparisons, but the annotation was
built by filtering colData for samples named in those columns -- an
intersection that is always empty -- so the track carried no levels and no
colours. It now annotates by comparison, coloured from the object's
comparison palette, and accepts column_anno_colors like the expression
heatmap.display_id supplied without a display_orgdb failed with
missing value where TRUE/FALSE needed. The internal comparison
to_type == from_type is NA when from_type is NA and logical(0)
when it is NULL, and || can evaluate neither. It now names the missing
argument.get_expression_lineplot() accepted display_from/display_orgdb and
ignored them -- its private copy of the identifier mapping handled only the
rowData() path. All gene-taking plots now share one resolver.prcomp() and cmdscale() may return an axis or its negation for
identical input, decided by the LAPACK/BLAS build, so the snapshots could
fail on one machine and pass on another with nothing in the package
changed. Each axis is now canonicalised before comparison. No user-visible
behaviour changes; only the test suite is affected.Statistical annotations failed under ggpubr 1.0.0. Passing
stats_group = TRUE produced a plot that constructed normally and then
died when drawn, with could not find function "create_p_label". ggpubr
1.0.0 builds p.label = "p.format" into an expression calling that
package-internal helper, which is not visible from the environment of the
mapping VISTA supplies, so the annotation layer could not be evaluated.
VISTA now names the statistic's own computed columns via
ggplot2::after_stat(). The rendered label text is unchanged on every
ggpubr version, and an unrecognised p.label still falls through to
ggpubr rather than silently dropping the annotation.
Affects get_expression_boxplot(), get_expression_violinplot(),
get_expression_barplot(), get_expression_raincloud(),
get_foldchange_boxplot() and get_foldchange_raincloud() — anywhere
stats_group = TRUE was combined with the default p.label = "p.format".
Users on ggpubr < 1.0.0 were unaffected.
crop = NULL). BiocStyle's crop hook shells
out to magick; without it every figure emitted a warning.Targeted at Bioconductor 3.24 (VISTA 1.2.0). This release fixes several defects that produced silently wrong results, makes VISTA behave like a proper Bioconductor object, and begins a deprecation cycle for a number of inconsistent argument names. No existing script stops working: every renamed argument still functions and warns.
Check whether you were affected:
get_expression_matrix(genes = ..., summarise = TRUE) mislabelled rows.
Rows were subset in assay order but labelled in the order genes was
supplied, so whenever those differed every row carried another gene's
identifier and values.
Scope: no other VISTA function called get_expression_matrix(), so no
plot, heatmap, DEG table, export or generated report was affected. Internal
group averaging goes through norm_counts(summarise = TRUE), which labels
rows from the matrix itself and was never affected by this. Only code that
called get_expression_matrix() directly could be wrong.
Were you affected? Only if you passed genes and summarise = TRUE
and your gene vector was in some order other than the object's own row
order. Passing genes already in rownames(x) order (or omitting genes)
always produced correct output. Note that ranked inputs — for example
get_genes_by_regulation(top_n = ...), which sorts by absolute fold change —
are normally not in row order, so those calls were affected. Re-run them.
The bug dates back to the first release, so it is present in 1.0.0 and every 0.99.x.
GSEA ranked vectors slid scores onto the wrong genes. get_gsea()
reassigned names(rank_vec) from a mapping helper that drops unmapped and
duplicate identifiers, so the shorter vector was padded with NA and every
score after the first gap moved to a different gene. The resulting NES
values and leading-edge gene lists were meaningless.
Scope: only set_type = "kegg" was affected in practice. That path always
converts to ENTREZID, and any real dataset contains identifiers with no
ENTREZ mapping, so the vector always shortened. set_type = "msigdb"
converted an identifier type to itself, which short-circuits before
querying the annotation database and cannot shorten. set_type = "go"
never reassigned the names at all. Verified on a 22-gene input containing
two identifiers absent from the OrgDb: "kegg" returned 20 names for 22
scores, "msigdb" returned 22.
No other VISTA function called get_gsea(), so nothing else in the package
consumed a corrupted ranking. Re-run any get_gsea(set_type = "kegg")
results; MSigDB and GO results are unaffected.
get_corr_heatmap() drew a staircase, not a triangle. The triangle
mask was computed against input order while the axes were re-levelled to
clustered order, under the default cluster_by = "correlation".
Multi-file count importers bound columns positionally. The STAR, HTSeq and RSEM importers took gene identifiers from the first file only and never checked that row i described the same gene in the others, so files with equal row counts but different ordering were silently misaligned and every count for those samples was attributed to the wrong gene.
Scope: only read_vista_counts() given a vector of two or more file
paths with format of "star", "htseq" or "rsem", and only when
those files did not already list the same genes in the same order. A single
file, a data frame, a matrix, "featurecounts" and "tximport" all take a
name-keyed path and were never affected. Files written by one pipeline run
normally do share an order, so a uniform set of inputs was fine; the risk is
mixing files produced at different times or by different pipeline versions,
where HTSeq's trailing __no_feature/__ambiguous rows or STAR's four
leading N_* rows may differ.
Imports now match by gene identifier and error, naming the file and up to five missing identifiers, when a file does not cover the reference gene set. This can surface an error where a script previously appeared to work; it was producing wrong numbers. Anything built from a multi-file STAR, HTSeq or RSEM import should be re-imported and re-analysed.
run_vista_report() reported its own defaults as your parameters. When a
prebuilt object was supplied via vista_rds, the report printed
deseq2 / 1 / 0.05 / padj regardless of how the object was built.
run_cell_deconvolution()'s SYMBOL retry told xCell2 the wrong identifier
type. When the first scoring attempt failed, VISTA retried with a
SYMBOL-keyed matrix built from rowData, but carried the original
gene_id_type (for example "ensembl") into the retry -- handing xCell2
symbols while declaring them Ensembl IDs. The retry therefore either failed
with the real error masked behind a generic message, or scored against a
near-empty gene overlap.
Deconvolution was broken for ENSG...:SYMBOL rownames — the common case
reached automatically by gene_id_type = "auto".
Fixed get_ma_plot() reading a non-existent metadata key for its threshold
fallback, get_expression_lollipop() failing to resolve symbols through an
OrgDb, comparison colours collapsing to character(0) and crashing
get_foldchange_lineplot(), NaN columns when summarising over a factor
with unused levels, and YAML/filename escaping in report and asset output.
Hardened the DE-table alignment helper, which reindexes every comparison
onto the counts matrix and so underpins the whole package. It now rejects a
duplicated reference gene list instead of emitting rows whose names R had
silently de-duplicated (g2 -> g2.1) away from the gene_id column they
came from, coerces tibble input to a plain data frame — a tibble ignores
rownames<-, so every gene read as absent and was replaced by an NA row
with a perfectly correct label — and asserts as a post-condition that the
returned rownames and gene_id column both match the reference exactly.
No shipped code path reached the tibble case; both are closed against future
callers, because a wrong answer here is invisible in the output's shape.
get_pathway_heatmap(max_genes)
and get_enrichment_chord(max_genes) truncated the pathway gene set
positionally. Pathway membership is a set, and clusterProfiler's ordering of
it is arbitrary and, verified here, differs between R sessions -- so the same
call could plot a different subset of genes on different runs. The heatmap now
ranks candidates by expression variance (most informative rows first) and the
chord by pathway participation, both breaking ties on the gene identifier.run_cell_deconvolution() accepted scores whose
row count matched the sample count and stamped the object's sample names onto
them, even when the scores carried different labels of their own -- which
would attach every sample's cell fractions to the wrong sample. Alignment is
now by name whenever names are available in either orientation; contradictory
labels are an error; and position is used only when the scores carry no
labels at all, with a message saying so.min_replicates samples reach min_counts);
they previously ignored min_counts and used a hardcoded cpm > 1. They
also filter before calcNormFactors(), per the edgeR user's guide. edgeR
and limma results shift slightly relative to 1.1.1.min_replicates is documented accurately: it counts samples across the
whole experiment, never within each group.counts assay, reachable with counts(v).
as_deseq_dataset() converts a VISTA object back into a DESeqDataSet.
Roughly doubles the assay footprint; opt out with
create_vista(keep_raw_counts = FALSE).[ method. Subsetting now keeps metadata consistent: row subsetting
reindexes every DE table and recounts DEG summaries, column subsetting
prunes orphaned colour maps and warns when a comparison loses its samples.
Previously v[1:10, ] left the DE tables describing all original genes and
validObject() still returned TRUE.show() method. Objects now display as class: VISTA with their
grouping, comparisons, active DE source, cutoffs and schema version.updateObject() migrates objects from older metadata schemas.?VISTA resolves, and there is a package overview page.... are now rejected with a did-you-mean
suggestion. A typo such as get_expression_heatmap(v, gene = my_genes)
previously plotted the default gene set instead of erroring.get_foldchange_lineplot() gains return_type. Its default stays "both"
(the list it has always returned); pass "plot" for a bare ggplot.get_enrichment_chord() gains return_type. Its default stays "data"
because it draws to the active device and has always returned its table
invisibly; "plot" returns a recorded plot that save_vista_plot() can now
write to a file. VISTA enables the device display list before drawing, since
png() and pdf() leave it off and would otherwise yield an empty
recording that silently saved nothing.All still work and warn once per session. See ?VISTA-deprecated for the
full table and timelines; each becomes defunct in 1.4.0.
| Function | Deprecated | Use instead |
|---|---|---|
| get_deg_count_pieplot(), get_deg_count_donutplot() | label | label_type |
| get_corr_heatmap() | cluster_by | order_by |
| get_corr_heatmap() | show_corr_values, col_corr_values | label, label_color |
| get_celltype_group_dotplot() | error | errorbar |
| four get_expression_*() plots | comparisons | stat_comparisons |
| get_pca_plot(), get_mds_plot(), get_umap_plot() | top_n_genes | top_n |
| get_expression_lollipop(), get_foldchange_lollipop() | line_size | linewidth |
| get_volcano_plot() | col_up, col_down, col_other(s), lab_size | colors, label_size |
| get_pca_plot() | sample.seed | removed; it never had any effect |
| get_expression_barplot(), get_expression_lollipop() | facet_scale | facet_scales |
| get_expression_violinplot(), get_expression_lineplot() | value_transform | log_transform |
return_type values are unified on c("plot", "data", "both"). The legacy
spellings still work and warn: "heatmap"/"clusters" (both heatmaps),
"matrix" (get_celltype_heatmap()), "genes" (get_pathway_heatmap()).
Arguments that select the shape of a purely tabular result --
get_pathway_genes(), get_genes_by_regulation(), read_vista_counts(),
derive_vista_metadata() -- keep their own vocabularies, because they have no
plot component.
Undocumented gene caps (20 for embeddings, 15 for lollipop, 25 for barplot)
are now the max_genes argument, with the existing values as defaults.
is.matrix(), nrow(), class -- all of which the broken output satisfied.NA padding across numeric, integer, character, logical, factor, Date and
POSIXct columns, and the reordering guarantee itself. Both faults fixed above
were found by these tests.Depends: R (>= 4.6.0); viridis dropped from Imports (unused);
BiocGenerics added. grDevices, graphics, stats, tools and utils
are now declared — all five were already used via :: without being listed.
The maintainer's ORCID iD is recorded in Authors@R.print.vista removed — no object could carry that class.get_volcano_plot() now inherits log2fc_cutoff, pval_cutoff, and the new
p_value_type argument from cutoffs(x) instead of hard-coding the raw
pvalue column at 0.05. Previously the volcano could colour a different set
of genes than deg_summary(), get_deg_count_barplot(), and
get_genes_by_regulation() reported for the same object. Pass the arguments
explicitly to restore the old behaviour
(p_value_type = "pvalue", pval_cutoff = 0.05). The y-axis label now
reflects which p-value column is plotted.
Consensus DE tables (method = "both") no longer overwrite pvalue and
padj with 1 for every gene that is not called by both backends. Each gene
now carries the less-significant of the two backend values, so the columns
stay continuous and usable for volcano/MA plots and ranking. Per-backend
values remain available in pvalue_deseq2/pvalue_edger/padj_deseq2/
padj_edger, and DEG calls are unchanged.
get_ma_plot() read its threshold fallback from the non-existent
metadata(x)$cutoffs key, silently using package defaults instead of the
object's stored cutoffs.
get_pathway_heatmap(max_genes)
and get_enrichment_chord(max_genes) truncated the pathway gene set
positionally. Pathway membership is a set, and clusterProfiler's ordering of
it is arbitrary and, verified here, differs between R sessions -- so the same
call could plot a different subset of genes on different runs. The heatmap now
ranks candidates by expression variance (most informative rows first) and the
chord by pathway participation, both breaking ties on the gene identifier.run_cell_deconvolution() accepted scores whose
row count matched the sample count and stamped the object's sample names onto
them, even when the scores carried different labels of their own -- which
would attach every sample's cell fractions to the wrong sample. Alignment is
now by name whenever names are available in either orientation; contradictory
labels are an error; and position is used only when the scores carry no
labels at all, with a message saying so.The edgeR and limma-voom backends now filter genes with the same predicate as
the DESeq2 backend — a gene is kept when at least min_replicates samples
reach min_counts. They previously ignored min_counts at this step and used
a hard-coded cpm > 1 threshold, so the same arguments produced different
feature sets across backends.
The edgeR and limma-voom backends now filter before calling
edgeR::calcNormFactors(), per the edgeR user's guide; normalization factors
were previously estimated from genes that were about to be discarded. Both
changes shift edgeR/limma results slightly relative to 1.1.0.
min_replicates is documented accurately: it counts samples across the whole
experiment, not within each group, which is what all backends have always
implemented.create_vista() now documents the consensus table's column semantics,
including the support column.get_enrichment_network() from the README; no such
function exists. Corrected the citation block and listed the missing
expression-plot entries.is.matrix(), nrow(), class -- all of which the broken output satisfied..prepare_sample_metadata() and
.filter_genes(), which existed in both utils-internal.R and
viz_related.R, with collation order silently deciding which one ran..plot_pca(), .plot_mds(),
.prepare_mds_dataframe(), .prepare_corr_matrix(), .plot_corr_heatmap(),
and .cluster_log2fc_matrix() along with their generated man pages.vignettes/guides/ and vignettes/workflows/
are no longer shipped in the source tarball.read_vista_counts(), read_vista_metadata(), and
match_vista_inputs() to standardize common RNA-seq input formats without
changing the existing create_vista() API.derive_vista_metadata() to bootstrap starter sample metadata from
count-derived sample names using split- or regex-based parsing.example_vista() now uses a precomputed default object to reduce example,
test, and package-check runtime while preserving the existing API.Submitted to Bioconductor 2026-02-11
VISTA (Visualization Toolkit for Transcriptomic Analysis) provides a unified S4-based framework for differential expression analysis of RNA-seq data, wrapping DESeq2 and edgeR workflows with consistent metadata management and rich visualization capabilities.
VISTA class extending SummarizedExperiment for standardized data management@importFrom declarations across all modulesgenes input explicitly, support
minimal-call defaults, and allow custom colours for multi-column annotations"Other"
slice and support configurable label text colourget_genes_by_regulation() now supports top-gene ranking by abs(log2fc)
and optional annotated table outputuse_group_colors argument
while keeping use_vista_colors as a deprecated compatibility alias