Edit on GitHub

starplast.deposits

Per-gene tables derived from the deposits added in the 2026-09 data audit.

Each source here is a published deposit -- a supplementary workbook, a GEO processed table -- that needs a computation between the file and the column: a mean of two replicate screens, a log, a moderated contrast. The computation lives here and runs against the raw deposit under the dataset root; derive_all writes the result as a small keyed table into the cache (starplast/data/deposit_<key>.tsv), and the merge reads only that. So the map rebuilds without the raw deposits, and the raw deposits re-derive every shipped number.

notebooks/derive_deposits_2026_09.ipynb runs every derivation below with the sanity checks that admitted it (built by scripts/derive_deposits.py).

What each one is, and the check it had to pass:

  • In vivo heart and brain fitness (Giuliano et al. 2024, PMID 38977907). The four tissues already shipped were cited to the 2019 platform paper; they are this study's composite scores, identical to the supplement to 5e-8. The same sheet carries heart and brain, the only in vivo heart and brain CRISPR fitness that exists.
  • Serum restriction (Bitew et al. 2025, PMID 41407671): two independent genome-wide screens in 10% and 1% serum. Fitness in either serum agrees with the fibroblast screen at rho 0.79-0.85 -- it is fibroblast fitness again -- while the 10%-minus-1% differential is orthogonal to it (rho 0.06): a new axis, lipid dependence. GRA38, the paper's gene, is -6.66 in 10% and +0.33 in 1%.
  • Translation efficiency, GSE302107: the most reproducible TE in the organism (replicates rho 0.973, against 0.87 and 0.67 for the two already shipped); agrees with them at 0.78-0.79. Shipped as log2 of the authors' linear footprint/RNA ratio, the scale the other TE columns use.
  • mRNA decay, GSE329845: wild-type log2(actinomycin D / vehicle) after 4 h from a moderated linear model with replicate blocking. No spike-ins, so it is decay RELATIVE to the median transcript, not a half-life. Replicates agree at rho 0.96, ribosomal-protein mRNAs are stable. Genome-wide (6,406 genes) where the shipped column is the 412-gene unstable tail of another study; the two do not correlate (rho -0.03), which the tail's selection explains.
  • Parasite density (Giuliano et al., Cell 2026, PMID 42580337; instruction 56): one library split into low (MOI 0.3) and high (MOI 3) density. The arms are fibroblast fitness again (r = 0.995 between them, the paper's number); the high-versus-low contrast is the new axis. All 31 of the paper's density-inhibited mutants are among the 32 genes its contrast puts at Bonferroni p < 0.05 on the depleted side (the 32nd, TGGT1_264610, sits at 0.048 on half a clone), and its 12 high-confidence ones reproduce exactly from the stated rule (four-fold, adj. p < 1e-4). The second pass, 2026-09-26 (instruction 52), added six Plasmodium deposits and the checks that admitted them:

  • The spatial proteome (PMID 42218142): the first subcellular localization in the Plasmodium table, 1,646 of 3,000 proteins into 24 niches, reproduced exactly, with RAP1, MAHRP1, ACP, EXP2 and GAPDH each where they belong. The same paper's field pN/pS and between-species dN/dS come with it.

  • mRNA synthesis and decay rates (PMID 29985403): 4-thiouracil labelling every hour of the blood-stage cycle. Fluxes in transcripts per minute, NOT half-lives, so not comparable with the Toxoplasma actinomycin columns; the paper's claim of transcription in every stage reproduces, and the timing agrees with an independent expression series.
  • The blood-stage proteome and Hsp90 dependence (bioRxiv 10.64898/2026.08.28.747854): abundance in a DMSO control and what two chemically distinct Hsp90 inhibitors do to it. The paper's 131 chaperone-dependent proteins reproduce exactly from its stated rule.
  • RNA dependence (PMID 38355719): 898 of 3,671 proteins shift towards the light fractions when RNA is removed -- the paper's number, from its own q-values.
  • The sexually committed proteome (PMID 41482054): commitment, a cycle before the gametocyte proteome already in the map.
  • The resistome (PMID 39607932): 724 clones evolved against 118 compounds. Per gene, how many selections and compounds mutated it -- the ranking is PfMDR1, PfATP4, PfCRT, PI4K -- plus field variation over 5,970 Pf6 isolates.

  • Host: the human fibroblast response to Toxoplasma (GSE335016, moderated contrast of infected against uninfected on log FPKM; CXCL8, IL6 and the interferon genes up, GAPDH flat), baseline mouse bone-marrow macrophage expression (GSE267544, M0 arm only -- LPS+IFN-gamma is not infection), and the human hepatocyte response to P. falciparum (GSE263643, donor and treatment blocked; weak: 28 genes at padj < 0.05, and the uninfected wells' mock material is not described).

def bh(p) -> numpy.ndarray:

Benjamini-Hochberg adjusted p-values; NaN stays NaN.

def moderated_t(Y, X, contrast) -> tuple:

limma's lmFit + eBayes (Smyth 2004, no intensity trend) for one contrast.

Y genes x samples on a log scale, X samples x parameters. Returns (coefficient, t, p, info). Squeezing each gene's variance toward the common prior is what makes three-replicate contrasts usable: a gene whose three values happen to agree is not declared certain.

GIULIANO = ('DNA', 'CRISPR_screen', '38977907', 'Supplementary Data 5 (Genome-wide mouse screen scores).xlsx')
GIULIANO_SHEET = 'Genome-Wide Differential'
GIULIANO_COLUMNS = {'PE differential composite score': 'fit_invivo_PE', 'Liver differential composite score': 'fit_invivo_liver', 'Spleen differential composite score': 'fit_invivo_spleen', 'Lung differential composite score': 'fit_invivo_lung', 'Heart differential composite score': 'fit_invivo_heart', 'Brain differential composite score': 'fit_invivo_brain'}
GIULIANO_NEW = ('fit_invivo_heart', 'fit_invivo_brain')
def giuliano_invivo(root: str, all_tissues: bool = True) -> pandas.DataFrame:

Composite in vivo fitness per tissue, GT1 accessions. Empty if the deposit is absent.

SERUM = ('DNA', 'CRISPR_screen', '41407671', '41467_2025_66137_MOESM4_ESM.xlsx')
SERUM_SHEET = '10%vs1%FBS_CRISPR-screen_DATA'
def serum_restriction(root: str) -> pandas.DataFrame:

Fitness in lipid-rich (10% serum) and lipid-limited (1%) medium, and the difference.

Two independent screens (Exp1 at passage 8; Exp2 at passages 4, 5 and 8), averaged where they measured the same passage, because replicate screens of one condition are replicates. Sign as published: negative fitness = depleted = the gene is needed; a negative differential means the gene is needed in lipid-RICH medium.

DENSITY = ('DNA', 'CRISPR_screen', '42580337', 'mmc2.xlsx')
DENSITY_DIFFERENTIAL = 'gRNA-UMI L2FC(High/Low) Differential Score'
def density_screen(root: str) -> pandas.DataFrame:

Fitness at low and high parasite density, and what high density adds (Giuliano et al., Cell 2026, PMID 42580337, Table S1).

One genome-wide library, selected for four passages at MOI 1 and then split for four more at MOI 0.3 (low density) and MOI 3 (high). The two arms are the authors' gene scores at passage 8, the mean gRNA log2 fold change against the input library: negative = depleted = needed, exactly as in the fibroblast screen they restate (r = 0.995 between the arms). The dependence column is the authors' own contrast, computed on barcoded gRNA-UMI clones directly between the arms, log2(high / low): NEGATIVE means the gene is needed at high density. Its statistic is the Bonferroni-adjusted two-sided t-test against the non-targeting clones, shipped as -log10(adj. p). fit_density_dim marks the 31 density-inhibited mutants of the paper's hit sheet (1) among the genes the contrast scored (0); genes it did not score carry no call.

GSE302107 = ('translation', 'riboseq', 'GSE302107', 'GSE302107_RPKM_and_TE.xlsx')
def riboseq_302107(root: str) -> pandas.DataFrame:

log2 translation efficiency per replicate (footprint RPKM over total-RNA RPKM).

A zero ratio is a gene with no footprints counted, which is 'not measured' rather than an infinitely low efficiency, so it becomes NaN rather than -inf.

GSE329845 = ('transcription', 'RNAseq', 'GSE329845', 'GSE329845_Processed_Data.csv.gz')
def mrna_decay_329845(root: str, return_fit: bool = False):

Wild-type mRNA remaining after 4 h of actinomycin D, log2 relative to vehicle.

Median-of-ratios size factors over all twelve libraries (DESeq2's), genes kept at a mean of 10 normalized counts in wild-type vehicle, then a moderated linear model on log2(count + 1) with replicate as a block. Relative to the median transcript: with no spike-in, a global drop in RNA is normalized away, so 0 is 'as stable as the typical mRNA', not 'fully stable'.

HFF = ('host', 'fibroblast_infection', 'GSE335016', 'GSE335016_245740-HOMO-gene_fpkm.txt.gz')
def hff_tg_infection(root: str) -> pandas.DataFrame:

Human foreskin fibroblasts infected with wild-type Toxoplasma against uninfected, log2.

Moderated t on log2(FPKM + 1) over the three arms (uninfected, wild-type, TGGT1_245740 knockout; three replicates each), genes with a mean FPKM of 1. Only the wild-type contrast is shipped: the knockout answers a question about one parasite gene, not about infection.

BMDM = ('host', 'bmdm', 'GSE267544', 'GSE267544_fpkm_BMDM.txt.gz')
def bmdm_baseline(root: str) -> pandas.DataFrame:

Unstimulated (M0) mouse bone-marrow macrophage expression, TPM from FPKM, 3 replicates.

Keyed by Ensembl, never by symbol: the deposit's symbols are old (Emr1 for Adgre1, Irg1 for Acod1) and a symbol join would silently lose exactly the macrophage markers.

HEPATOCYTE = ('host', 'hepatocyte_infection', 'GSE263643', 'GSE263643_deseq2-normalized_counts.txt.gz')
def hepatocyte_pf_infection(root: str) -> pandas.DataFrame:

Primary human hepatocytes infected with P. falciparum against uninfected, log2.

The paper never makes this comparison; it is made here from its DESeq2-normalized counts with treatment and donor as blocks (freshly isolated cells excluded), genes with a mean of 10. The infected wells hold few infected cells, so the contrast is diluted and weak by construction.

GLUCOSE = ('DNA', 'CRISPR_screen', 'glucose_limitation_2025', 'TableS1.xlsx')
def glucose_limitation(root: str) -> pandas.DataFrame:

Fitness with glucose or with glutamine withdrawn (Uboldi et al., bioRxiv 2025, Table S1).

After three passages in complete medium the library was split into glucose-only and glutamine-only medium for two more, three replicates each. Each arm is measured against the post-selection library, so negative means depleted there. The dependence column is the authors' edgeR contrast, glutamine-only minus glucose-only: negative means the gene is needed when glucose is absent. GDH1 ranks first of 8,155 and PEPCK second, which is the paper's result, and ribosomal proteins are needed in complete medium (median -2.21 against -0.02).

Noisy: the three replicate differentials agree at rho 0.10-0.17 only, so the FDR ships beside the value and neither should be read gene by gene without it.

UTR5 = ('translation', 'riboseq', 'GSE302108', 'SupplementaryData4_TE_info_and_UTRfeatures.xlsx')
def utr5_architecture(root: str) -> pandas.DataFrame:

Each gene's 5' untranslated region, reannotated from long reads (Peters et al., bioRxiv 2025, Supplementary Data 4): length, upstream AUGs and ORFs, overlapping ORFs, in-frame extensions, and how closely the start context matches a Kozak sequence.

Sequence properties rather than a measurement of the gene, and they behave as the biology says they should: upstream AUGs go with LOW translation efficiency (rho -0.47 against the mean of the TE columns) and Kozak strength with high (+0.22). The study's reporter assay is not a per-gene column -- its 30,235 scored sequences are variants of twelve endogenous UTRs.

BRADY = ('transcription', 'scRNAseq', '41580398', '41467_2026_68489_MOESM3_ESM.xlsx')
def bradyzoite_subtypes(root: str) -> pandas.DataFrame:

Average expression in each of five in vivo bradyzoite subtypes (Ulu et al., Nat Commun 2026, Supplementary Data 1): single cells from brain cysts of mice infected for 30 days.

The subtypes are the point -- a tissue cyst is not one transcriptional state. Every group has BAG1, LDH2, ENO1, SRS9 and CST1 above its 92nd percentile and SAG1 below the 41st, so all five are bradyzoites; SRS22A is 3.27 in Group B against 0.34-0.66 elsewhere, which is the paper's Group B signature.

IRON = ('translation', 'proteomics', '41925342')
def iron_depletion(root: str) -> pandas.DataFrame:

What 24 hours without iron does to each protein, and to each transcript (Hanna et al., mBio 2026, Tables S1 and S2). Positive means higher when iron is withheld.

The proteome is genome-scale: 5,052 protein groups, three replicates each side. The RNA contrast covers only the 3,113 genes of the paper's joint analysis, which is a significance-filtered subset, so a missing RNA value means 'not reported' rather than 'unchanged'. The paper's 61 iron-sulfur proteins shift down on balance (median -0.04 against +0.02; 12 of the 16 that change significantly fall), a modest effect. Despite the paper's title there is no ribosome profiling in it -- translation is measured by microscopy -- so this fills protein abundance under stress, not translation.

TURBOID = ('post_translation', 'BioID', 'organelle_surface_turboid_2026', 'media-1.xlsx')
TURBOID_BAITS = {'apicoplast': ('Apicoplast - and + biotin', 'Apicoplast vs spatial reference'), 'mitochondrion': ('Mitochodrion - and + biotin', 'Mitoch vs spatial reference'), 'er': ('ER - and + biotin', 'ER vs spatial reference')}
def organelle_surface(root: str) -> pandas.DataFrame:

What sits against the cytosolic face of the apicoplast, mitochondrion and ER (Parker & Huet, bioRxiv 2026, Table S1): TurboID on baits anchored in each outer membrane, tail out.

Per bait, the enrichment of each protein with biotin over without, and whether it is one of the paper's stringent hits -- also enriched over a cytosolic TurboID, so not merely abundant near everything. Against hyperLOPIT the stringent mitochondrial hits are mitochondrial at odds 13.8 and the ER hits ER at 7.6; the apicoplast bait shows no enrichment for apicoplast proteins (odds 1.2, p = 0.8), so that bait reports proximity, not location.

A protein a bait never detected keeps a missing stringent flag rather than a zero: that bait did not test it.

K562 = ('host', 'k562_rhoptry_screen', 'v2_media-1.xlsx')
def k562_rhoptry_screen(root: str) -> pandas.DataFrame:

Which HUMAN genes a Toxoplasma rhoptry needs in order to discharge into the cell it invades (Valleau et al., bioRxiv 2025, Table S1): a genome-wide CRISPR screen in K562 read out by a fluorescent reporter of discharge.

Cells were sorted into those still susceptible to discharge and those that had become resistant; each was compared with the unsorted population. A POSITIVE combined score means knocking the gene out makes discharge fail, so the host gene is required for it. The screen recovers its own biology: SLC35A2, the transporter the paper is about, ranks first of 20,010, and a panel of twenty N-glycan genes has a median rank of 135 with 85% in the top tenth, while B4GALT1, FUT8 and ST6GAL1 -- glycosylation the paper argues is NOT involved -- are not hits.

A host table, so rows are keyed by reviewed UniProt accession. The deposit gives gene symbols; a symbol that resolves to no reviewed human protein (155 of them are microRNA loci) is dropped.

MELTOME = ('translation', 'proteomics', '41315737', 'pxd056075_tm_gene.tsv')
def pf_melting_temperature(root: str) -> pandas.DataFrame:

The temperature at which each P. falciparum protein comes out of solution (Pazicky et al., Nat Microbiol 2025, Supplementary Table 1; PRIDE PXD056075).

Intact-cell CETSA over ten temperatures, seven points of the blood-stage cycle, three replicates. No melting temperature is published anywhere -- the deposit is raw mass spectrometry and the paper tabulates abundances -- so it is FITTED, by scripts/fit_meltome.py, with the acceptance the shipped Toxoplasma column already uses (R-squared at least 0.8, melting point between 30 and 80 degrees). That script writes the table this reads; the fit is not repeated inside the build.

It behaves as a melting point must: replicates agree at rho 0.84 with a spread of 1.15 degrees, subunits of one complex melt together (0.19 degrees across the T-complex, 0.42 across the proteasome's alpha ring, against 5.6 for size-matched random sets, p = 9e-5), glycolytic enzymes and the proteasome are thermostable at about 62.5 degrees while HSP70 and HSP90 are labile at 50.4. It also agrees with the independent Toxoplasma measurement through orthology (+0.28). Median 53.0 degrees against 55.2 for Toxoplasma.

One column, not seven: a protein's melting point barely moves across the cycle (3.7 degrees of range against 1.15 of replicate noise), so per-stage columns would claim a resolution the data does not have.

FERTILITY = ('DNA', 'transmission_screen', '39541984', 'mmc2.xlsx')
def pb_fertility(root: str) -> pandas.DataFrame:

Whether a gene is needed for male or for female fertility, carried from Plasmodium berghei (Russell et al., Cell Syst 2024, Table S1 sheet A): barcoded knockouts crossed and scored through mosquito transmission.

Measured in the rodent parasite and transferred by orthology -- the table names the falciparum ortholog itself, so nothing is guessed here. Negative means the mutant lost fertility on that side. The result is sex-specific and the controls prove it: HAP2, P48/45, P230, CDPK4 and MAPK2 fail in males only, P47, NEK4 and DMC1 in females only, and the redundant P25/P28 pair is correctly not called. The union of reduced mutants is 348, the paper's own number.

A transfer between species, so it belongs to the orthology axis for leakage: an ortholog's measurement is not an independent measurement of this gene.

PF_LATENCY = ('transcription', 'scRNAseq', 'pf_latency_2026')
def pf_latency(root: str) -> pandas.DataFrame:

What a parasite entering a drug-tolerant latent state transcribes (bioRxiv 2026, Supplementary Tables 7 and 24): single cells after artemisinin or nutrient deprivation.

Positive means higher in the latent cells. The paper's own 200-gene latency classifier ships beside it as a membership flag, so a user can ask 'is my gene one of the ones that defines this state' without rebuilding the classifier.

Its FDR is deliberately NOT shipped: cells were treated as replicates, so 87% of genes reach padj 0.05 and the column would say 'significant' about almost everything. Absence is not evidence either -- ribosomal-protein genes and most var and rifin genes are missing from the table altogether, so a gene without a value was not necessarily unchanged.

PF_M6A = ('transcription', 'm6a', 'pf_m6a_2026', 'SupplementaryTables_media-2.xlsx')
PF_M6A_SHEETS = {28: 'Supplementary Table 6', 32: 'Supplementary Table 7', 36: 'Supplementary Table 8'}
PF_M6A_MIN_DEPTH = 20
def pf_m6a(root: str) -> pandas.DataFrame:

How many methylated adenosines a transcript carries, and how fully they are methylated (bioRxiv 2026, Supplementary Tables 6-8): nanopore direct RNA sequencing at three time points.

Sites are the union over the time points, because a site detected at one hour is a site. The stoichiometry is the weighted average methylation of the canonical sites, averaged over the time points where the transcript was read at least 20 times -- below that the fraction is a coin-flip. Where a gene has several transcripts, the deepest one represents it.

Only the control condition is used. The deposit's two groups are unlabelled and the second has about half the methylation of the first, which is consistent with the paper's knock-sideways of the methyltransferase; that inference is good enough to pick the control arm but not good enough to ship a difference column, so no difference is shipped.

PF_GAMETOCYTE = ('translation', 'proteomics', 'pf_gametocyte_2026', 'Extended Datasets.xlsx')
def pf_gametocyte_proteome(root: str) -> pandas.DataFrame:

What a mature (stage V) gametocyte's proteome contains, and what it is still making (bioRxiv 2026, Extended Data Tables 1 and 3): label-free quantification, and click chemistry on newly made protein.

The abundance column is clean -- replicates agree at about 0.99. Newly made means found with the label and in no control: the deposit's group 4, which is the paper's 705 proteins. The translatome is shipped only as membership, not as an enrichment: there is one pooled sample per condition and most of the proteins it names are absent from the controls, so a ratio would be a ratio to nothing. A gene the proteome quantified but the translatome did not name is a zero rather than a blank, because that experiment did look for it.

PF_TPP = ('post_translation', 'thermal', 'pf_tpp_2026', 'SupplTable3_media-6.csv')
def pf_target_engagement(root: str) -> pandas.DataFrame:

How many antimalarials measurably engage each protein (bioRxiv 2026, Supplementary Table 3): thermal proteome profiling against 25 compounds, as a dose series at fixed temperatures.

Counts, not a continuous response, and that is the finding rather than a simplification: the continuous response agrees between replicates at r 0.0-0.55, so a per-compound number would be mostly noise, while the call of hit or not reproduces the paper (99 stabilised hits, and cladosporine-KRS, MMV665915-ACS10 and KAF156-prohibitin all in the top thirteen). The denominator ships with the count: a protein seen in five assays and hit twice is not the same evidence as one seen in 25 and hit twice.

NOT a melting temperature. This design has no temperature series, so it cannot give one; the melting points come from the separate MAP-X meltome.

PF_FEBRILE = ('post_translation', 'phosphosites', 'pf_febrile_2026', 'elife-107860-supp1-v1.xlsx')
PF_FEBRILE_FC = 'Log2FC [WT (Post Heat Stress) /WT (No Heat Stress)]'
def pf_febrile_phospho(root: str) -> pandas.DataFrame:

How each protein's phosphorylation changes in a fever (eLife 2026, Supplementary File 1): infected red cells held at 39 degrees, then returned to 37 and harvested.

Per protein, the strongest change at any of its sites and how many sites moved in each direction. The paper's regulated set reproduces exactly at 143 up and 53 down -- but those are counts of ROWS, and a row is a site at one phosphorylation multiplicity, so a site seen doubly and singly phosphorylated is counted twice. Collapsed to unique sites, each represented by its strongest change, it is 128 up and 51 down, which is what the columns here count. The response is specific rather than global: 60% of the proteins with a rising site are exported to the host cell, against 7% of everything quantified.

Thirty-nine degrees, not forty, whatever the preprint's abstract says -- the methods give the temperature the cultures were held at. A phosphosite whose peptide could belong to more than one gene is dropped rather than assigned to the first.

PF_CHROMATIN = ('post_translation', 'BioID', 'pf_chromatin_proxiome_2026')
PF_CHROMATIN_BAITS = {'hp1': ('TableS1_HP1_proximity_proteome_media-3.xlsx', 'Sheet C. HP1 protA-turboID', 't-test Difference', '-Log t-test p value', 'Unique peptides', 1.5), 'h3k27ac': ('TableS5_euchromatin_proximity_proteome_media-7.xlsx', 'Sheet B. H3K27ac', 't-test Difference', '-Log t-test p value', 'Unique peptides', 1.5), 'h3k4me3': ('TableS5_euchromatin_proximity_proteome_media-7.xlsx', 'Sheet C. H3K4me3', 't-test Difference', '-Log t-test p value', 'Unique peptides', 0.8), 'centromere': ('TableS7_centromeric_proximity_proteome_media-9.xlsx', 'Sheet A. CENH3 (mT)-DiQ-BioIDs', 'T-test Difference', '-Log T-test p-value', 'Unique peptides', 1.0)}
PF_CHROMATIN_MIN_P = 1.3
PF_CHROMATIN_MIN_PEPTIDES = 4
def pf_chromatin_proxiome(root: str) -> pandas.DataFrame:

Which proteins sit in each chromatin environment (bioRxiv 2026, Tables S1, S5 and S7): proximity labelling from baits that mark heterochromatin, active chromatin and the centromere.

Per bait, how enriched a protein is over the bait's own control and whether it clears the paper's threshold for that bait. The high-confidence counts reproduce the paper exactly (H3K27ac 99, H3K4me3 48, heterochromatin 61), and the positive controls land where they must: HP1, HDA2, GDV1 and AP2-HC in heterochromatin, CENH3 at the centromere, BDP1 and GCN5 in active chromatin. SIR2A is in none of them -- it was not detected by any bait, which is absence of evidence, not evidence of absence.

A protein group spanning more than one gene is dropped rather than assigned to the first of them; where one gene appears twice in a bait, the row with more unique peptides represents it. A protein a bait never detected keeps a missing flag, not a zero.

PF_ACCESSION = '^PF3D7_(?:\\d{7}|API\\d{5}|MIT\\d{5})$'
PF_LOPIT = ('post_translation', 'LOPIT', '42218142')
PF_LOPIT_SUMMARY = '41467_2026_73664_MOESM3_ESM.xlsx'
PF_LOPIT_VARIATION = '41467_2026_73664_MOESM5_ESM.xlsx'
PF_LOPIT_LOCATION = 'Final location (S1-S2)'
PF_LOPIT_SCORE = 'svm.score (S1-S2)'
def pf_spatial_proteome(root: str) -> pandas.DataFrame:

Where each P. falciparum protein sits in a late schizont (Chisholm et al., Nat Commun 2026, Supplementary Data 1 and 3): hyperLOPIT over density gradients, classified by a support-vector machine into 24 cellular niches.

The first subcellular localization of any kind in the Plasmodium table, which until now had no compartment at all. lopit_pf_location is the label and lopit_pf_svm_score the classifier's confidence in it; unknown becomes a MISSING label rather than a 25th niche, because the map's first rule is that absence must not read as a measurement.

Reproduces the paper exactly: 3,000 proteins, 1,646 classified, 24 niches. The markers land where they must -- RAP1 in the rhoptries, MAHRP1 in the Maurer's clefts, ACP in the apicoplast, EXP2 and HSP101 at the parasitophorous vacuole membrane, GAPDH in the cytosol.

The same paper's variation table is a separate deposit (pf_field_variation) rather than more columns here, because holding out a localization must not also withdraw a dN/dS: the closure excludes columns that share provenance, and a genomic analysis printed in the same paper is not an output of this gradient experiment.

def pf_field_variation(root: str) -> pandas.DataFrame:

How variable each gene is in the field, and how fast it has evolved between species (Chisholm et al., Nat Commun 2026, Supplementary Data 3).

field_pnps_adj and field_variant_fraction are computed over FIELD isolates, which is the question the map had no data for -- the SNP columns already shipped are PlasmoDB's counts over laboratory strains, and the two agree at rho 0.29, a related quantity on a different population rather than a copy of it. dnds_laverania and dnds_plasmodium are ratios against orthologs in other species: selection since the species split, not variation inside this one.

PF_4TU = ('transcription', 'RNAseq', '29985403', '41467_2018_4966_MOESM5_ESM.xlsx')
def pf_mrna_dynamics(root: str) -> pandas.DataFrame:

How fast each transcript is made and how fast it disappears (Painter et al., Nat Commun 2018, Supplementary Data 2): 4-thiouracil labelling every hour through the 48-hour blood-stage cycle, with the rates read off a model of total and nascent RNA.

Transcripts per minute at the gene's own peak, so these are FLUXES and not half-lives: an abundant transcript decays faster in transcripts per minute than a scarce stable one, which is why the decay column correlates with abundance (rho 0.42) and why it is not comparable with the Toxoplasma actinomycin columns, which are fractions remaining. Signs as published: transcription positive (4,414 of 4,416), decay negative (4,443 of 4,462).

The paper's claim is active transcription in every stage of the cycle, and the deposit carries it: 616 to 962 genes take their peak transcription in each of the six windows. The timing checks out against a series this study had nothing to do with -- of the genes whose transcription peaks in a ring window, 87% to 92% also peak in the shipped ring expression column.

The peak-stage labels are in the deposit and are deliberately NOT shipped: which stage a gene's transcription peaks in is what the stage expression series in the table already says, and a second copy of it would be a column the leakage closure has to guess at.

PF_HSP90 = ('translation', 'proteomics', 'pf_hsp90_2026')
PF_HSP90_TABLE = 'TableS1_media-2.xlsx'
PF_HSP90_HITS = 'TableS2_media-3.xlsx'
PF_HSP90_REPORT = '20240914_094033_10643_hu_pf_loc_091424_Report_protein_abundances.tsv'
PF_HSP90_MIN_DROP = -0.5
PF_HSP90_MAX_P = 0.05
def pf_hsp90_chemoproteome(root: str) -> pandas.DataFrame:

The asexual blood-stage proteome, and what falls when Hsp90 is inhibited (Ibrasheva et al., bioRxiv 2026, Tables S1 and S2; PRIDE PXD079493).

Three columns for three questions. proteome_blood_log2 is how much of each protein a DMSO-treated culture contains -- log2 of the mean of the four control replicates' normalized abundances -- which is the first protein abundance in the map for the stage the parasite spends its life in. The two fold changes are what geldanamycin and XL888 do to it, two inhibitors with different scaffolds; their continuous responses agree only at rho 0.17, which is why the hit call demands BOTH -- what survives that conjunction is the chaperone's, while either column alone is largely one compound's. hsp90_dependent is the paper's own hit call, reproduced from the fold changes and p-values by the rule its methods state.

The deposit gives UniProt accessions and the map is keyed by gene, so the mapping comes from the deposit's OWN Spectronaut report (PG.UniProtIds beside PG.Genes), never from a lookup that could have drifted: 3,050 of 3,265 rows resolve, and a protein group naming more than one gene is dropped rather than assigned to the first.

The PRIDE record says 133 hits and the supplementary table lists 131. The 131 are what is reproducible, so they are what ships, and the difference is recorded rather than reconciled.

PF_RDEEP = ('post_translation', 'RDeeP', '38355719', '41467_2024_45519_MOESM4_ESM.xlsx')
PF_RDEEP_SHEET = 'Left-shifting'
PF_RDEEP_HEADER = 5
PF_RDEEP_MAX_Q = 0.05
def pf_rna_dependence(root: str) -> pandas.DataFrame:

Whether a protein needs RNA to stay in the complex it is in (Hollin et al., Nat Commun 2024, Supplementary Data 2): R-DeeP -- the same lysate run down a sucrose gradient with and without RNase, 25 fractions each, and a protein whose distribution shifts towards the light fractions when the RNA is gone was held there by RNA.

A question nothing in the map asked. It is not RNA binding: a protein can shift because its PARTNER binds RNA, which is why the study calls them RNA-DEPENDENT rather than RNA-binding.

rna_dependent is 1 for the 898 significantly left-shifted proteins and 0 for the rest of the 3,671 the run quantified -- a zero here means tested and not shifted, which is why the flag ships beside the q-value rather than as a list. The 898 at q < 0.05 is the paper's own number, reproduced from the deposit.

The classes come out the right way round: RNA helicases are enriched among the dependent (28 of 61, odds 2.7, p 2e-4) and the proteasome, a large complex that needs no RNA, has 0 of 14. Ribosomal proteins are DEPLETED rather than enriched (23 of 137, odds 0.6) -- a ribosome is mostly RNA and does not come apart in this assay the way an mRNA-bridged complex does -- so this column must not be read as 'binds RNA'.

PF_COMMITTED = ('translation', 'proteomics', '41482054', 'mmc4.xlsx')
def pf_committed_proteome(root: str) -> pandas.DataFrame:

What changes in a parasite that has committed to becoming a gametocyte but still looks asexual (Venugopal et al., Mol Cell Proteomics 2026, Table S3): parasites sorted on MSRP1, a marker this study establishes, and compared with their uncommitted siblings.

Commitment, not the gametocyte -- the map already has a stage V gametocyte proteome, and this is the decision that precedes it by a cycle. Positive means more abundant in the committed cells. The columns are the study's combined contrast over both reporter lines and both sorting directions (logfc.all, fdr.all), because the four arms are the same comparison done four ways.

MSRP1 itself is +1.68 at FDR 0, which is a positive control and nothing more: it is the protein the sort was done on. The check that means something is the paper's finding that merozoite surface proteins separate the two populations: MSP1 (+0.34, FDR 0) and MSP2 (+0.67) are up in the committed cells.

PF_RESISTOME = ('DNA', 'in_vitro_evolution', '39607932')
PF_RESISTOME_SNVS = 'SupplementaryData3_SNVs-INDELs.xlsx'
PF_RESISTOME_GENES = 'SupplementaryData6_Pf6Genes.xlsx'
PF_RESISTOME_TARGETS = 'SupplementaryData5_CompoundTarget.xlsx'
PF_RESISTOME_SUPPORTED = ('Overrepresented SNVs', 'Overrepresented SNVs and CNVs')
PF_RESISTOME_LEGACY = {'mal_mito_1': 'PF3D7_MIT01400', 'mal_mito_2': 'PF3D7_MIT02100', 'mal_mito_3': 'PF3D7_MIT02300'}
PF_RESISTOME_CODING = ('missense_variant', 'frameshift_variant', 'stop_gained', 'inframe_deletion', 'disruptive_inframe_deletion', 'inframe_insertion', 'disruptive_inframe_insertion', 'stop_lost', 'start_lost', 'splice_acceptor_variant', 'splice_donor_variant')
def pf_resistome(root: str) -> pandas.DataFrame:

Which genes mutate when the parasite is selected for drug resistance, and how variable they are in the field (Luth et al., Science 2024, Supplementary Data 3 and 6): 724 clones, each evolved in vitro against one of 118 compounds and sequenced whole-genome.

Four columns. Three are counts over CODING variants only: how many independently selected clones carry one, how many distinct compounds those selections used, and how many distinct variants there are. The fourth, resistance_target_compounds, is the paper's own call -- for how many compounds is this gene the classified target or resistance gene, counting only the classes its hypergeometric test supports -- and it is the column that answers the slot, because a COUNT of mutations is not a claim that the gene confers resistance.

The difference is visible at the top of each column and is the reason both ship. By raw selections the leaders are AP2-G (13 compounds) and PfEMP1, which mutate under prolonged culture whatever the drug: losing gametocyte production is cheap in a flask. By the paper's classification the leaders are PfATP4 and PfMDR1 (5 compounds each), the prodrug-activating esterase (4), then cytochrome b, CARL, PI4K beta and the Niemann-Pick C1-related protein at 3, with DHODH, PfCRT, the tRNA ligases and DHFR-TS behind them -- the canonical antimalarial resistance set, arrived at without being told what it should contain.

A gene with no selected mutation gets NO value rather than a zero. That is deliberate and it is the opposite of the choice made for the R-DeeP flag: a screen that tested every gene can say 'not a hit', whereas 118 compounds are not a test of the other 4,600 genes.

Beside them, the same paper's field variation from 5,970 Pf6 isolates: pf6_field_dnds and the non-synonymous SNV count. The deposit writes -1 where a ratio could not be computed, which becomes missing here -- carried through as a number it would be a strong negative selection signal that nobody measured.

@dataclass(frozen=True)
class Deposit:

One deposit: which registry entry it belongs to, whose table it lands in, and how it is read.

A dataclass rather than a dict so that adding a deposit is adding a line, and so the organism cannot be forgotten -- a Plasmodium table merged into the Toxoplasma one would join zero rows and look exactly like a dataset with no coverage.

Deposit(key: str, organism: str, derive: Callable, combine: str = 'mean')
key: str
organism: str
derive: Callable
combine: str = 'mean'
DEPOSITS = (Deposit(key='crispr_invivo_composite', organism='Tg', derive=<function giuliano_invivo>, combine='mean'), Deposit(key='crispr_serum_restriction', organism='Tg', derive=<function serum_restriction>, combine='mean'), Deposit(key='crispr_glucose_limitation', organism='Tg', derive=<function glucose_limitation>, combine='mean'), Deposit(key='crispr_parasite_density', organism='Tg', derive=<function density_screen>, combine='mean'), Deposit(key='gse302107_riboseq', organism='Tg', derive=<function riboseq_302107>, combine='mean'), Deposit(key='gse302108_utr5', organism='Tg', derive=<function utr5_architecture>, combine='mean'), Deposit(key='mrna_decay_gse329845', organism='Tg', derive=<function mrna_decay_329845>, combine='mean'), Deposit(key='brady_subtypes', organism='Tg', derive=<function bradyzoite_subtypes>, combine='mean'), Deposit(key='iron_depletion_proteome', organism='Tg', derive=<function iron_depletion>, combine='mean'), Deposit(key='organelle_surface_turboid', organism='Tg', derive=<function organelle_surface>, combine='mean'), Deposit(key='pf_meltome', organism='Pf', derive=<function pf_melting_temperature>, combine='mean'), Deposit(key='pf_pb_fertility_transfer', organism='Pf', derive=<function pb_fertility>, combine='mean'), Deposit(key='pf_latency_transcriptome', organism='Pf', derive=<function pf_latency>, combine='mean'), Deposit(key='pf_m6a_nanopore', organism='Pf', derive=<function pf_m6a>, combine='mean'), Deposit(key='pf_gametocyte_proteome', organism='Pf', derive=<function pf_gametocyte_proteome>, combine='mean'), Deposit(key='pf_target_engagement', organism='Pf', derive=<function pf_target_engagement>, combine='mean'), Deposit(key='pf_febrile_phospho', organism='Pf', derive=<function pf_febrile_phospho>, combine='mean'), Deposit(key='pf_chromatin_proxiome', organism='Pf', derive=<function pf_chromatin_proxiome>, combine='mean'), Deposit(key='pf_spatial_proteome', organism='Pf', derive=<function pf_spatial_proteome>, combine='mean'), Deposit(key='pf_field_variation', organism='Pf', derive=<function pf_field_variation>, combine='mean'), Deposit(key='pf_mrna_dynamics', organism='Pf', derive=<function pf_mrna_dynamics>, combine='mean'), Deposit(key='pf_hsp90_chemoproteome', organism='Pf', derive=<function pf_hsp90_chemoproteome>, combine='mean'), Deposit(key='pf_rna_dependence', organism='Pf', derive=<function pf_rna_dependence>, combine='mean'), Deposit(key='pf_committed_proteome', organism='Pf', derive=<function pf_committed_proteome>, combine='mean'), Deposit(key='pf_resistome', organism='Pf', derive=<function pf_resistome>, combine='mean'), Deposit(key='host_hff_tg_infection', organism='Hs', derive=<function hff_tg_infection>, combine='mean'), Deposit(key='host_bmdm_baseline', organism='Mm', derive=<function bmdm_baseline>, combine='mean'), Deposit(key='host_hepatocyte_pf_infection', organism='Hs', derive=<function hepatocyte_pf_infection>, combine='mean'), Deposit(key='host_k562_rhoptry_screen', organism='Hs', derive=<function k562_rhoptry_screen>, combine='mean'))
def table_path(base: str, key: str) -> str:

Where a derived deposit table is cached, so the merge can run without the raw files.

def derive_all( dataset_root: str, base: str, keys=None, log=<built-in function print>) -> dict:

Derive every deposit present under dataset_root and write its keyed table into the cache.

A deposit absent from the root is skipped and its shipped table, if any, left alone -- a machine without the raw files must not erase what one with them derived.

def parasite_columns( base: str, organism: str, ids, resolve=None, log=<built-in function print>) -> pandas.DataFrame:

Every shipped deposit column for one organism, aligned to ids.

Accessions go through resolve (the identity layer: GT1 to ME49, old to current). Where rows land on one gene: an accession that is the gene itself (TGGT1_224540 for TGME49_224540) wins over the A/B halves of a locus GT1 splits (TGGT1_224540A/B); only a gene reached by halves alone gets their mean. Averaging all three once moved TGME49_224540 from -3.99 to +0.20 -- the halves are different gene models, not replicates of the whole.

A LABEL column (a compartment name, a stage) has no mean, so where two rows land on one gene it is carried only if they agree; where they disagree the gene gets no label rather than the first row's. Added for the Plasmodium spatial proteome, whose measurement IS a label -- before that every deposit column was numeric and mean(numeric_only=True) dropped anything else in silence.

def host_columns(base: str, organism: str) -> pandas.DataFrame:

One host species' deposits merged on its own protein key.