perturbMatchData is an ExperimentHub
package that provides a collection of human reference perturbation
expression signatures as HDF5-backed SummarizedExperiment
objects. Each signature summarises the differential expression observed
after a single-gene perturbation (knockdown, knockout or overexpression)
in an RNA-seq experiment.
The signatures were assembled with an automated Nextflow pipeline and
summarised in two ways: using the characteristic
direction method and using limma (log2
fold-change together with a pi score,
log2FC * -log10(padj)). Both are provided with and without
SVA batch correction, giving four datasets in total.
These reference databases are used by the perturbMatch package to identify the upstream regulators most likely to drive a query gene expression signature, and can also be used directly to ask what happens to gene expression when a given gene is perturbed.
if (!requireNamespace("BiocManager", quietly = TRUE)) {
install.packages("BiocManager")
}
BiocManager::install("perturbMatchData")library(perturbMatchData)
library(SummarizedExperiment)
#> Loading required package: MatrixGenerics
#> Loading required package: matrixStats
#>
#> Attaching package: 'MatrixGenerics'
#> The following objects are masked from 'package:matrixStats':
#>
#> colAlls, colAnyNAs, colAnys, colAvgsPerRowSet, colCollapse,
#> colCounts, colCummaxs, colCummins, colCumprods, colCumsums,
#> colDiffs, colIQRDiffs, colIQRs, colLogSumExps, colMadDiffs,
#> colMads, colMaxs, colMeans2, colMedians, colMins, colOrderStats,
#> colProds, colQuantiles, colRanges, colRanks, colSdDiffs, colSds,
#> colSums2, colTabulates, colVarDiffs, colVars, colWeightedMads,
#> colWeightedMeans, colWeightedMedians, colWeightedSds,
#> colWeightedVars, rowAlls, rowAnyNAs, rowAnys, rowAvgsPerColSet,
#> rowCollapse, rowCounts, rowCummaxs, rowCummins, rowCumprods,
#> rowCumsums, rowDiffs, rowIQRDiffs, rowIQRs, rowLogSumExps,
#> rowMadDiffs, rowMads, rowMaxs, rowMeans2, rowMedians, rowMins,
#> rowOrderStats, rowProds, rowQuantiles, rowRanges, rowRanks,
#> rowSdDiffs, rowSds, rowSums2, rowTabulates, rowVarDiffs, rowVars,
#> rowWeightedMads, rowWeightedMeans, rowWeightedMedians,
#> rowWeightedSds, rowWeightedVars
#> Loading required package: GenomicRanges
#> Loading required package: stats4
#> Loading required package: BiocGenerics
#> Loading required package: generics
#>
#> Attaching package: 'generics'
#> The following objects are masked from 'package:base':
#>
#> as.difftime, as.factor, as.ordered, intersect, is.element, setdiff,
#> setequal, union
#>
#> Attaching package: 'BiocGenerics'
#> The following objects are masked from 'package:stats':
#>
#> IQR, mad, sd, var, xtabs
#> The following object is masked from 'package:utils':
#>
#> data
#> The following objects are masked from 'package:base':
#>
#> anyDuplicated, aperm, append, as.data.frame, basename, cbind,
#> colnames, dirname, do.call, duplicated, eval, evalq, Filter, Find,
#> get, grep, grepl, is.unsorted, lapply, Map, mapply, match, mget,
#> order, paste, pmax, pmax.int, pmin, pmin.int, Position, rank,
#> rbind, Reduce, rownames, sapply, saveRDS, scale, sequence, table,
#> tapply, transform, unique, unsplit, which.max, which.min
#> Loading required package: S4Vectors
#>
#> Attaching package: 'S4Vectors'
#> The following object is masked from 'package:utils':
#>
#> findMatches
#> The following objects are masked from 'package:base':
#>
#> expand.grid, I, unname
#> Loading required package: IRanges
#> Loading required package: Seqinfo
#> Loading required package: Biobase
#> Welcome to Bioconductor
#>
#> Vignettes contain introductory material; view with
#> 'browseVignettes()'. To cite Bioconductor, see
#> 'citation("Biobase")', and for packages 'citation("pkgname")'.
#>
#> Attaching package: 'Biobase'
#> The following object is masked from 'package:MatrixGenerics':
#>
#> rowMedians
#> The following objects are masked from 'package:matrixStats':
#>
#> anyMissing, rowMediansThe four datasets are:
| Accessor | Method | Batch correction |
|---|---|---|
perturbMatch_chrdir() |
Characteristic direction | No |
perturbMatch_chrdir_sva() |
Characteristic direction | SVA |
perturbMatch_limma() |
limma (log2FC / pi score) | No |
perturbMatch_limma_sva() |
limma (log2FC / pi score) | SVA |
Most work starts from the accessors in the previous section, which
handle retrieval for you. The underlying records can also be browsed
like any other ExperimentHub resource, which is useful for checking what
is hosted and what is already in the local cache.
perturbMatch_reference() returns the records prepared by
this package:
library(ExperimentHub)
#> Loading required package: AnnotationHub
#> Loading required package: BiocFileCache
#> Loading required package: dbplyr
#>
#> Attaching package: 'AnnotationHub'
#> The following object is masked from 'package:Biobase':
#>
#> cache
perturbMatch_reference()
#> ExperimentHub with 9 records
#> # snapshotDate(): 2026-09-04
#> # $dataprovider: NCBI GEO
#> # $species: Homo sapiens
#> # $rdataclass: DFrame, character
#> # additional mcols(): taxonomyid, genome, description,
#> # coordinate_1_based, maintainer, rdatadateadded, preparerclass, tags,
#> # rdatapath, sourceurl, sourcetype
#> # retrieve records with, e.g., 'object[["EH10451"]]'
#>
#> title
#> EH10451 | perturbMatch_rowData
#> EH10452 | perturbMatch_chrdir_colData
#> EH10453 | perturbMatch_chrdir_sva_colData
#> EH10454 | perturbMatch_limma_colData
#> EH10455 | perturbMatch_limma_sva_colData
#> EH10456 | perturbMatch_chrdir_assays
#> EH10457 | perturbMatch_chrdir_sva_assays
#> EH10458 | perturbMatch_limma_assays
#> EH10459 | perturbMatch_limma_sva_assays
## querying the hub directly also matches on the other metadata fields
eh <- ExperimentHub()
query(eh, "perturbMatchData")
#> ExperimentHub with 9 records
#> # snapshotDate(): 2026-09-04
#> # $dataprovider: NCBI GEO
#> # $species: Homo sapiens
#> # $rdataclass: DFrame, character
#> # additional mcols(): taxonomyid, genome, description,
#> # coordinate_1_based, maintainer, rdatadateadded, preparerclass, tags,
#> # rdatapath, sourceurl, sourcetype
#> # retrieve records with, e.g., 'object[["EH10451"]]'
#>
#> title
#> EH10451 | perturbMatch_rowData
#> EH10452 | perturbMatch_chrdir_colData
#> EH10453 | perturbMatch_chrdir_sva_colData
#> EH10454 | perturbMatch_limma_colData
#> EH10455 | perturbMatch_limma_sva_colData
#> EH10456 | perturbMatch_chrdir_assays
#> EH10457 | perturbMatch_chrdir_sva_assays
#> EH10458 | perturbMatch_limma_assays
#> EH10459 | perturbMatch_limma_sva_assaysEach dataset is stored as a record of signature matrices and a record of signature annotation, alongside one gene annotation record shared by all four. Every record carries a stable ExperimentHub accession, so a dataset can also be composed by hand from those accessions.
Use the named accessors to assemble a dataset as a
SummarizedExperiment. Each resource is downloaded once and
subsequent calls read from the local ExperimentHub cache. The signature
matrices are large, 790 MB for chrdir and 673 MB for
chrdir_sva, and 3.1 GB and 2.6 GB for limma
and limma_sva, which carry three assays each. The following
chunk is therefore shown rather than run when this vignette is
built:
To see which perturbations are available without retrieving those matrices, ask for the annotation alone. This is what the rest of this vignette uses:
se <- perturbMatch_chrdir(withAssays = FALSE)
#> see ?perturbMatchData and browseVignettes('perturbMatchData') for documentation
#> downloading 1 resources
#> retrieving 1 resource
#> loading from cache
#> see ?perturbMatchData and browseVignettes('perturbMatchData') for documentation
#> downloading 1 resources
#> retrieving 1 resource
#> loading from cache
se
#> class: SummarizedExperiment
#> dim: 39376 6802
#> metadata(9): dataset method ... assay_names assay_bytes
#> assays(0):
#> rownames(39376): 100287102 653635 ... 4576 4571
#> rowData names(18): GeneID Symbol ... GOProcess GOComponent
#> colnames(6802): GSE100009_ZZZ3_KD_1 GSE100092_HECTD1_KD_1 ...
#> GSE99687_SMAD5_KO_1 GSE99825_CTCFL_KD_1
#> colData names(14): sig_id pert_symbol ... cell_line_efo_label
#> target_gene_percentileThe accessors wrap the standard ExperimentHub interface, so the resources can also be retrieved by accession and composed by hand. Look the accessions up by title rather than writing them out, so that they follow the deposit:
recs <- perturbMatch_reference()
ids <- setNames(names(recs), recs$title)
ids
#> perturbMatch_rowData perturbMatch_chrdir_colData
#> "EH10451" "EH10452"
#> perturbMatch_chrdir_sva_colData perturbMatch_limma_colData
#> "EH10453" "EH10454"
#> perturbMatch_limma_sva_colData perturbMatch_chrdir_assays
#> "EH10455" "EH10456"
#> perturbMatch_chrdir_sva_assays perturbMatch_limma_assays
#> "EH10457" "EH10458"
#> perturbMatch_limma_sva_assays
#> "EH10459"What distinguishes the four datasets, including which HDF5 datasets
the assay resource holds and the scoring metric, is attached to the
colData resource, so composing an object by hand means
carrying it across:
eh <- ExperimentHub()
h5 <- eh[[ids[["perturbMatch_chrdir_assays"]]]]
colData <- eh[[ids[["perturbMatch_chrdir_colData"]]]]
se <- SummarizedExperiment(
assays = list(chrdir = HDF5Array::HDF5Array(h5, "chrdir")),
rowData = eh[[ids[["perturbMatch_rowData"]]]],
colData = colData
)
metadata(se) <- metadata(colData)Each object is a SummarizedExperiment with genes in the
rows and perturbation signatures in the columns. The assays are
HDF5-backed delayed arrays, so the data is read from disk on demand
rather than held in memory.
## Signature-level metadata (one row per perturbation)
head(colData(se))
#> DataFrame with 6 rows and 14 columns
#> sig_id pert_symbol pert_id pert_type
#> <character> <character> <character> <character>
#> GSE100009_ZZZ3_KD_1 GSE100009_ZZZ3_KD_1 ZZZ3 ZZZ3_KD KD
#> GSE100092_HECTD1_KD_1 GSE100092_HECTD1_KD_1 HECTD1 HECTD1_KD KD
#> GSE100183_EP400_KD_1 GSE100183_EP400_KD_1 EP400 EP400_KD KD
#> GSE100183_MYCL_KD_1 GSE100183_MYCL_KD_1 MYCL MYCL_KD KD
#> GSE100260_LIPA_KO_1 GSE100260_LIPA_KO_1 LIPA LIPA_KO KO
#> GSE100261_MTHFD2_KD_1 GSE100261_MTHFD2_KD_1 MTHFD2 MTHFD2_KD KD
#> accession control_samples case_samples
#> <character> <character> <character>
#> GSE100009_ZZZ3_KD_1 GSE100009 GSM3100231,GSM310023.. GSM3100233,GSM310023..
#> GSE100092_HECTD1_KD_1 GSE100092 GSM2671101,GSM2671102 GSM2671097,GSM267109..
#> GSE100183_EP400_KD_1 GSE100183 GSM2674232,GSM267423.. GSM2674235,GSM267423..
#> GSE100183_MYCL_KD_1 GSE100183 GSM2674232,GSM267423.. GSM2674244,GSM267424..
#> GSE100260_LIPA_KO_1 GSE100260 GSM2676347,GSM267634.. GSM2676345,GSM267634..
#> GSE100261_MTHFD2_KD_1 GSE100261 GSM2676365,GSM267636.. GSM2676368,GSM267636..
#> technology cell_line model
#> <character> <character> <character>
#> GSE100009_ZZZ3_KD_1 RNA-seq H1299 Qwen3-finetuned, Mag..
#> GSE100092_HECTD1_KD_1 RNA-seq T47D curated
#> GSE100183_EP400_KD_1 RNA-seq MKL-1 curated
#> GSE100183_MYCL_KD_1 RNA-seq MKL-1 curated
#> GSE100260_LIPA_KO_1 RNA-seq iPSC-derived Macroph.. curated
#> GSE100261_MTHFD2_KD_1 RNA-seq Aortic Endothelial C.. Qwen3-finetuned, Mag..
#> entrez_gene_id cell_line_efo_id cell_line_efo_label
#> <numeric> <character> <character>
#> GSE100009_ZZZ3_KD_1 26009 EFO:0003043 NCI-H1299
#> GSE100092_HECTD1_KD_1 25831 NA NA
#> GSE100183_EP400_KD_1 57634 NA NA
#> GSE100183_MYCL_KD_1 4610 NA NA
#> GSE100260_LIPA_KO_1 3988 NA NA
#> GSE100261_MTHFD2_KD_1 10797 EFO:0022506 immortalized human a..
#> target_gene_percentile
#> <numeric>
#> GSE100009_ZZZ3_KD_1 0.977568
#> GSE100092_HECTD1_KD_1 36.482872
#> GSE100183_EP400_KD_1 1.205720
#> GSE100183_MYCL_KD_1 3.056862
#> GSE100260_LIPA_KO_1 0.788644
#> GSE100261_MTHFD2_KD_1 1.117109
## Gene annotation data from NCBI
head(rowData(se))
#> DataFrame with 6 rows and 18 columns
#> GeneID Symbol Description Synonyms
#> <numeric> <character> <character> <character>
#> 100287102 100287102 DDX11L1 DEAD/H-box helicase .. NA
#> 653635 653635 WASH7P WASP family homolog .. FAM39F|WASH5P
#> 102466751 102466751 MIR6859-1 microRNA 6859-1 hsa-mir-6859-1
#> 107985730 107985730 MIR1302-2HG MIR1302-2 host gene NA
#> 100302278 100302278 MIR1302-2 microRNA 1302-2 MIRN1302-2|hsa-mir-1..
#> 645520 645520 FAM138A family with sequence.. F379|FAM138F
#> GeneType EnsemblGeneID Status ChrAcc ChrStart
#> <character> <character> <character> <character> <character>
#> 100287102 pseudo ENSG00000290825 active NC_000001.11 11874
#> 653635 pseudo NA active NC_000001.11 14362
#> 102466751 ncRNA ENSG00000278267 active NC_000001.11 17369
#> 107985730 ncRNA NA active NC_000001.11 29926
#> 100302278 ncRNA ENSG00000284332 active NC_000001.11 30366
#> 645520 ncRNA ENSG00000237613 active NC_000001.11 34611
#> ChrStop Orientation Length GOFunctionID GOProcessID
#> <character> <character> <numeric> <character> <character>
#> 100287102 14409 positive 1652 NA NA
#> 653635 29370 negative 1769 NA NA
#> 102466751 17436 negative 68 NA NA
#> 107985730 31295 positive 538 NA NA
#> 100302278 30503 positive 138 NA GO:0035195
#> 645520 36081 negative 1130 NA NA
#> GOComponentID GOFunction GOProcess GOComponent
#> <character> <character> <character> <character>
#> 100287102 NA NA NA NA
#> 653635 NA NA NA NA
#> 102466751 NA NA NA NA
#> 107985730 NA NA NA NA
#> 100302278 NA NA miRNA-mediated gene .. NA
#> 645520 NA NA NA NA
## How the scoring matrix should be derived from the assay(s)
metadata(se)$ref_metric
#> [1] "chrdir"The signature matrix itself is only present when the object was built
with withAssays = TRUE, so this chunk is again shown rather
than run:
The limma datasets carry three assays
(log2fc, padj and the combined pi
score) and the chrdir datasets a single chrdir
assay. The metadata(se)$ref_metric field records which
assays downstream tools such as
perturbMatch::calcEnrichment() should use.
The scripts used to build these objects from the signature results are included with the package.
list.files(system.file("scripts", package = "perturbMatchData"))
#> [1] "make-data.R" "make-metadata.R" "run_nextflow.sh" "utils.R"run_nextflow.sh records the LLM-GeoSig run that
annotated the GEO series and computed the signatures from the GEO
RNA-seq counts matrices, make-data.R assembles each
SummarizedExperiment and make-metadata.R
writes the ExperimentHub metadata.
The case and control groupings, the perturbed gene and the
perturbation type were assigned by large language models rather than
curated by hand, using the agreement of two models and a curated subset
for calibration. Treat the colData annotation as automated
annotation: it is accurate enough to rank regulators across thousands of
signatures, but any individual signature should be checked against its
GEO series before it is relied on.
The assembled resources are deposited on Zenodo under the concept DOI 10.5281/zenodo.21792179, which resolves to the current version of the deposit. The ExperimentHub records point at a fixed versioned record, so the files retrieved by a given release of this package do not change.
Please cite the Zenodo deposit above alongside the methods the signatures were computed with.
The source counts are the NCBI-generated RNA-seq counts matrices for each GEO series, described at https://www.ncbi.nlm.nih.gov/geo/info/rnaseqcounts.html.
Clark NR, Hu KS, Feldmann AS, Kou Y, Chen EY, Duan Q, Ma’ayan A. The characteristic direction: a geometrical approach to identify differentially expressed genes. BMC Bioinformatics. 2014;15:79. doi:[10.1186/1471-2105-15-79](https://doi.org/10.1186/1471-2105-15-79)
Ritchie ME, Phipson B, Wu D, Hu Y, Law CW, Shi W, Smyth GK. limma powers differential expression analyses for RNA-sequencing and microarray studies. Nucleic Acids Research. 2015;43(7):e47. doi:[10.1093/nar/gkv007](https://doi.org/10.1093/nar/gkv007)
Xiao Y, Hsiao TH, Suresh U, Chen HIH, Wu X, Wolf SE, Chen Y. A novel significance score for gene selection and ranking. Bioinformatics. 2014;30(6):801-807. doi: 10.1093/bioinformatics/btr671
Leek JT, Storey JD. Capturing heterogeneity in gene expression studies by surrogate variable analysis. PLoS Genetics. 2007;3(9):e161. doi:[10.1371/journal.pgen.0030161](https://doi.org/10.1371/journal.pgen.0030161)
sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04.1 LTS
#>
#> Matrix products: default
#> BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats4 stats graphics grDevices utils datasets methods
#> [8] base
#>
#> other attached packages:
#> [1] ExperimentHub_3.3.2 AnnotationHub_4.3.2
#> [3] BiocFileCache_3.3.0 dbplyr_2.6.0
#> [5] SummarizedExperiment_1.43.0 Biobase_2.73.2
#> [7] GenomicRanges_1.65.4 Seqinfo_1.3.2
#> [9] IRanges_2.47.5 S4Vectors_0.51.10
#> [11] BiocGenerics_0.59.12 generics_0.1.4
#> [13] MatrixGenerics_1.25.0 matrixStats_1.5.0
#> [15] perturbMatchData_0.99.0 BiocStyle_2.41.0
#>
#> loaded via a namespace (and not attached):
#> [1] KEGGREST_1.53.6 xfun_0.61 bslib_0.12.0
#> [4] httr2_1.3.0 lattice_0.23-1 vctrs_0.7.3
#> [7] tools_4.6.1 curl_8.0.0 tibble_3.3.1
#> [10] AnnotationDbi_1.75.2 RSQLite_3.53.3 blob_1.3.0
#> [13] BiocBaseUtils_1.15.1 pkgconfig_2.0.3 Matrix_1.7-6
#> [16] lifecycle_1.0.5 compiler_4.6.1 Biostrings_2.81.9
#> [19] htmltools_0.5.9 sys_3.4.3 buildtools_1.0.0
#> [22] sass_0.4.10 yaml_2.3.12 crayon_1.5.3
#> [25] pillar_1.11.1 jquerylib_0.1.4 DelayedArray_0.39.6
#> [28] cachem_1.1.0 abind_1.4-8 tidyselect_1.2.1
#> [31] digest_0.6.39 purrr_1.2.2 dplyr_1.2.1
#> [34] BiocVersion_3.24.0 maketools_1.3.2 fastmap_1.2.0
#> [37] grid_4.6.1 cli_3.6.6 SparseArray_1.13.2
#> [40] magrittr_2.0.5 S4Arrays_1.13.0 withr_3.0.3
#> [43] filelock_1.0.3 rappdirs_0.3.4 bit64_4.8.6
#> [46] rmarkdown_2.32 XVector_0.53.0 httr_1.4.9
#> [49] bit_4.6.0 otel_0.2.0 png_0.1-9
#> [52] memoise_2.0.1 evaluate_1.0.5 knitr_1.52
#> [55] rlang_1.3.0 glue_1.8.1 DBI_1.3.0
#> [58] BiocManager_1.30.27 jsonlite_2.0.0 R6_2.6.1