An interactive R Shiny application for querying statistical evidence of drug effects, drug–drug interactions, and directed temporal protein dependencies in a large-scale perturbation proteomics dataset.
Live application: https://ulme.shinyapps.io/DrugProt/
Drug-Prot lets you specify a set of proteins of interest and returns, for that set: corrected p-values for the effect of each of 63 single drugs and 59 drug combinations on protein expression at 6, 24, and 48 hours post-treatment; and a directed temporal dependency network linking the queried proteins to any other protein in the dataset. Because multiple-testing correction is applied only over the queried set, focused analyses gain substantial power relative to proteome-wide searches.
All statistical evidence (approximately 60 million p-values across 5,392 proteins, 122 treatments, and three time points) is precomputed, so queries are near-instantaneous and the underlying proteomic dataset is never needed at query time.
- What the app provides
- Repository contents
- Requirements
- Data files
- Installation and running locally
- Using the application
- Data model
- Storage backends
- Deployment
- Reproducing the evidence
- Citation
- License
- Contact
Drug effects. For the selected protein set, the minimum corrected p-value over the set and chosen time points for each single drug and each experimentally available drug pair, shown as an interactive 122 × 122 heatmap (diagonal: single-drug evidence; off-diagonal: pairwise interaction evidence). Drug pairs without experimental data are coded with a sentinel value of 2 and rendered in black. Companion tables list significant single drugs and significant drug pairs in order of increasing p-value.
Protein network. A directed temporal dependency subgraph linking the queried proteins to other proteins in the dataset. Two views are provided: a temporal graph, in which each node is a (protein, time point) pair and edges run from t = 6 to t = 24 and from t = 24 to t = 48; and a summary graph collapsing the temporal graph across time. Both are rendered as zoomable, draggable, force-directed networks. Edge direction reflects the temporal ordering of the measurements and is not protected against unmeasured confounding.
Downloads. Corrected drug-effect p-values and protein–protein edge p-values for the queried set as CSV, and the summary and temporal graphs as standalone interactive HTML.
| Path | Description |
|---|---|
app.R |
The Shiny application: UI definition and server logic. |
dbAccess_parquet.R |
Data access layer backed by Parquet files read through DuckDB. This is the backend the deployed app uses. |
dbAccess.R |
Alternative data access layer backed by a single SQLite database. Exposes the same five helper functions with identical signatures, so it is a drop-in replacement. |
examples/exampleSet.txt |
Example protein-set upload file (nuclear envelope / lamina-associated proteins), one HGNC symbol per line. |
www/drugprot-logo.svg |
Application logo. |
DrugProt.Rproj |
RStudio project file. |
LICENSE |
BSD 3-Clause License. |
Note that the data files themselves are not in this repository — see Data files below.
- R (≥ 4.1 recommended)
- R packages:
shiny,shinydashboard,shinyWidgetsplotly,networkD3,gridreadxlDBI,duckdb(Parquet backend)RSQLite(only if using the SQLite backend)
install.packages(c(
"shiny", "shinydashboard", "shinyWidgets",
"plotly", "networkD3", "readxl",
"DBI", "duckdb"
))
# only for the SQLite backend:
# install.packages("RSQLite")The precomputed statistical evidence is distributed separately from the code because of its size. Download the archive from:
https://doi.org/10.5281/zenodo.21508013
The archive contains raw, uncorrected p-values and effect estimates for every protein, drug, and time point, together with a README describing the columns.
Place the Parquet files in a directory named parquet/ at the root of this
repository (alongside app.R), so that the layout is:
DrugProt/
├── app.R
├── dbAccess_parquet.R
├── parquet/
│ ├── proteins.parquet
│ ├── treatments.parquet
│ ├── exp_times.parquet
│ ├── drug_order.parquet
│ ├── meta.parquet
│ ├── drug_lookup.parquet
│ ├── drug_pvalue.parquet
│ ├── drug_coef.parquet
│ ├── protein_coef.parquet
│ └── protein_pvalue.parquet
└── ...
protein_pvalue.parquet is by far the largest file (~58 million rows); the
others are small.
git clone https://github.com/markusul/DrugProt.git
cd DrugProt
# download and unpack the Parquet data into ./parquet/ (see above)Then, from R in the repository root:
shiny::runApp()The application opens with the About tab, which describes what Drug-Prot does and how to interpret the results. Go to Settings to select proteins.
The connection is opened in app.R with:
db <- dp_connect("parquet", in_memory_network = FALSE, memory_limit = "3GB")in_memory_network = FALSEkeeps the ~58M-row network table as a lazy Parquet view on disk. This minimises resident memory and is the setting used for the hosted deployment.in_memory_network = TRUEmaterialises that table into memory once at startup (~1.5–2 GB resident), which makes repeated network queries noticeably faster. Use this if you have the RAM available locally, and raisememory_limitaccordingly.
Results are identical either way.
Select proteins (Settings tab), in one of three ways:
- Type or select from the searchable picker of all 5,392 measured proteins.
- Click an example button — LMNA & RAB7A (the two most IC50-predictive proteins from the paper) or 26 IC50 proteins (the full predictive set).
- Upload a plain-text file with one protein identifier per line. See
examples/exampleSet.txtfor the expected format.
Proteins are named by HGNC gene symbol (e.g. RAB7A, LMNA, MAP2K1).
Proteins quantified together as a group are joined with a slash
(e.g. CALM1/CALM2/CALM3). Uploaded names must match those in the dataset.
Configure the inference (Settings tab):
- Significance level α — the threshold below which a corrected p-value is called significant (default 0.05).
- Correction method for drug effects — default Holm, controlling the family-wise error rate.
- Correction method for protein effects — default Benjamini–Hochberg, controlling the false discovery rate, which suits the much larger number of protein–protein tests.
All correction methods available in R's p.adjust are offered. Tests are
two-sided against the null of no effect.
View results in the Drug Effects and Protein Network tabs. Each tab includes a collapsible "How to read..." box explaining the display.
The backend exposes ten tables (as Parquet files or SQLite tables). Proteins, treatments, and time points are referenced throughout by integer index; the lookup tables map those indices to names.
| Table | Contents |
|---|---|
proteins |
protein (index), name (HGNC symbol) |
treatments |
treatment (index), raw_label, display |
exp_times |
idx, hours (6, 24, 48) |
drug_order |
ord, raw_label — display ordering for the heatmap |
drug_lookup |
id, name — resolves drug IDs appearing in interaction labels |
meta |
key, value — nProtein, nTreatment, nTimes |
drug_pvalue |
protein, treatment, time_idx, pvalue — raw p-values for drug effects |
drug_coef |
protein, treatment, time_idx, coef — estimated drug effects |
protein_pvalue |
source, target, time_idx, pvalue — raw p-values for temporal dependencies |
protein_coef |
source, target, time_idx, coef — estimated dependency coefficients |
Both data access layers return raw, uncorrected p-values. All statistical
decisions — multiple-testing correction, α thresholding, minimum-over-time
aggregation, edge signing — happen in the Shiny reactives, not in the data
layer. The alpha argument to dp_net_pvals() is a performance pre-filter to
avoid returning tens of millions of irrelevant rows, not a statistical choice.
Two storage conventions are worth noting. Sentinel p-values marking drug pairs
without experimental data are dropped at build time rather than stored, so
absence from protein_pvalue means "no experiment". Only nonzero entries of
protein_coef are stored, so dp_protein_bhat() returns zero for any
(target, source) pair not present in the table.
Both dbAccess_parquet.R and dbAccess.R provide:
dp_connect(...) # open a connection
dp_metadata(con) # names, labels, counts
dp_drug_selPvecs(con, selection, nTreatment, nTimes) # drug p-values for a protein set
dp_net_pvals(con, selection, alpha, nProtein, nNetTimes) # network p-values for a protein set
dp_drug_dEff(con, treatment_idx, nProtein, nTimes) # drug coefficients for one treatment
dp_protein_bhat(con, target, time_idx, nProtein) # dependency coefficients for one targetThe Parquet backend additionally provides dp_disconnect(con) and
dp_protein_coef_bulk(con, targets, time_idx).
The application ships with two interchangeable backends:
- Parquet + DuckDB (
dbAccess_parquet.R) — the default and the one used in deployment. DuckDB reads the Parquet files directly with predicate and column pushdown, so a query touches only the row groups and columns it needs. - SQLite (
dbAccess.R) — a single-file alternative. Network queries rely on SQLite's multi-index OR optimisation overidx_net_source/idx_net_targetto avoid scanning the full ~58M-row table.
To switch, change which file app.R sources near the top:
# source("dbAccess.R")
# db <- dp_connect()
# meta <- dp_metadata(db)
source("dbAccess_parquet.R")
db <- dp_connect("parquet", in_memory_network = FALSE, memory_limit = "3GB")
meta <- dp_metadata(db)The function names and signatures are identical, so nothing else changes.
The hosted instance runs on shinyapps.io. rsconnect/ contains the deployment
configuration. To deploy your own instance:
library(rsconnect)
deployApp()The Parquet directory must be included in the bundle. Note that the app's
memory footprint is dominated by the network table; in_memory_network = FALSE
is recommended for hosted deployment.
This repository contains the query application only. The code that fits the statistical models and assembles the queryable database is in a separate repository:
https://github.com/markusul/DrugProt-Paper
The full pipeline runs as follows:
Perturbation proteomics data (Sun et al., 2026)
│
├─ model fitting: de-sparsified Lasso via the hdi package,
│ producing p-values and coefficients as .RData under results/
│
├─ R/buildDatabase.R → data/drugprot.sqlite
│ Collects drug-effect p-values, protein-network p-values, and
│ drug/protein coefficients into long, indexed SQLite tables.
│ Sentinel p-values (== 2, "no experiment") are dropped rather
│ than stored; only nonzero protein coefficients are kept.
│
├─ R/sqliteToParquet.R → data/parquet/
│ Writes one compressed Parquet file per SQLite table.
│
└─ copy data/parquet/ into this repository as ./parquet/
(the app calls dp_connect("parquet", ...); see Data files above)
The underlying perturbation proteomics dataset is described in Sun et al. (2029), https://doi.org/10.1038/s41586-026-11001-9.
If you use Drug-Prot, please cite:
Ulmer, M., Sun, R., Qian, L., Aebersold, R., Guo, T., and Bühlmann, P. (2026). Drug-Prot: A query system for statistical inference of drug effects and interactions in dynamic proteomic networks.
Please also cite the underlying dataset:
Sun, R., Qian, L., Li, Y., et al. (2026). An operational perturbation proteomics-based virtual cell model. Nature. https://doi.org/10.1038/s41586-026-11001-9
- Zhang, C.-H. and Zhang, S. S. (2014). Confidence intervals for low dimensional parameters in high dimensional linear models. J. R. Stat. Soc. B 76(1):217–242. (de-sparsified Lasso)
- van de Geer, S., Bühlmann, P., Ritov, Y., and Dezeure, R. (2014). On asymptotically optimal confidence regions and tests for high-dimensional models. Ann. Statist. 42(3):1166–1202.
- Dezeure, R., Bühlmann, P., Meier, L., and Meinshausen, N. (2015). High-dimensional inference: confidence intervals, p-values and R-software hdi. Statist. Sci. 30(4):533–558.
- Bühlmann, P. (2013). Statistical significance in high-dimensional linear models. Bernoulli 19(4):1212–1242. (group p-values)
- Holm, S. (1979). A simple sequentially rejective multiple test procedure. Scand. J. Statist. 6(2):65–70. (FWER control for drug effects)
- Benjamini, Y. and Hochberg, Y. (1995). Controlling the false discovery rate. J. R. Stat. Soc. B 57(1):289–300. (FDR control for protein effects)
BSD 3-Clause License. See LICENSE.
Markus Ulmer — markus.ulmer@stat.math.ethz.ch Seminar for Statistics, ETH Zürich
