API reference: c4.survival¶
The code behind c4 survival and c4 prs. For command-line use, see
Command line. Covariates, endpoints and
VCFs come from the cohort's survival: block, described in
the data catalog.
from c4.survival import Survival
surv = Survival.from_cohort("demo")
results = surv.run_survival(["4:1455432:A>T"], build="b38", endpoint="OS")
How the model works¶
Who is in the model. The tables named in the survival: block are merged
on patient_id, and the first table defines the study population. Each patient
is linked to their VCF sample through the id_map (for dna: imputed, the
imputed_id column). Patients marked drop=1 are left out.
Per variant (run_survival). Dosages are extracted with bcftools. If the
variants are in another build than the VCFs, they are lifted over first. Dosage
counts the ALT allele as you wrote it; if the VCF has REF and ALT the other
way round, the dosage is flipped to 2 - dosage and the variant gets a
warning. Each variant then gets its own Cox proportional hazards model
(lifelines CoxPHFitter):
time, event ~ dosage + covariates
- The model is fitted on complete cases only: rows missing the time, event,
dosage or any covariate are dropped. So
Ncan differ between variants. - Covariates whose names start with
PCare standardised to mean 0, SD 1 on that sample. Other covariates are used as they are. HRis the hazard ratio per extra copy of the ALT allele.ALT_AFis that allele's frequency in the sample;MAFis the smaller ofALT_AFand1 - ALT_AF.- A variant that can't be tested still gets a row: its HR is blank and
errorsays why (not in the VCF, liftover failed, no complete cases, model didn't converge).
Polygenic score (run_prs). Each weight's effect allele is matched to the
variant's REF or ALT. Weights matching neither are dropped, and so are
palindromic A/T and C/G SNPs unless keep_ambiguous=True. The score is the sum
of effect × effect-allele dosage; where a person's dosage is missing, that
variant's mean is used instead. The score is standardised on the complete-case
sample and goes into the same Cox model in place of the dosage, so HR_per_SD
is per standard deviation of the score. With n_quantiles, a second model
compares the top quantile against the bottom one.
Reference¶
COVARIATES = [*[f'PC{i}' for i in (range(1, 6))], 'female', 'Age_IOstart']
module-attribute
¶
DEFAULT_ENDPOINTS = {'OS': ('OS_mo', 'OS_event'), 'PFS': ('PFS_mo', 'PFS_event')}
module-attribute
¶
Survival
¶
from_cohort(name, catalog=None)
classmethod
¶
Build from a cohort's survival: block in the data catalog.
The tables listed under tables are loaded and merged on patient_id,
and each patient's sample name in the dna VCFs is looked up in the
cohort's id_map. Covariates and endpoints come from the block, and
liftover chains from the catalog's resources.
chain_for(from_build)
¶
Chain file for lifting variants from from_build to the VCF build, if one is known.
prep_survival(variant_list, build, dosage)
¶
Parse variants, liftover if needed, extract dosages, align alleles.
Returns:
| Type | Description |
|---|---|
DataFrame
|
A tuple |
dict
|
column plus one column per variant string, counting the ALT allele |
tuple[DataFrame, dict]
|
as written. variant_meta maps each variant string to |
tuple[DataFrame, dict]
|
|
last_dosage_table()
¶
(dosages, info) from the last run_survival/run_prs, for saving: one row per VCF sample (with patient_id when known) and one column per variant, plus the per-variant match table. None before any run.
run_survival(variant_list, build, endpoint, dosage=True, keep_data=False)
¶
Fit one Cox PH model per variant: time/event ~ dosage + covariates.
Each model uses the complete cases for that variant, endpoint and the covariates; PC covariates are standardised on that sample. The HR is per copy of the ALT allele as written in the variant string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
variant_list
|
list
|
Variant strings, e.g. |
required |
build
|
str
|
Genome build of the variants (e.g. "b37", "b38"). Lifted to the VCF build when they differ. |
required |
endpoint
|
str
|
Endpoint name, e.g. "OS" or "PFS". |
required |
dosage
|
bool
|
Use the VCF's DS field; False derives dosage from GT. |
True
|
keep_data
|
bool
|
Also return, per variant, the complete-case DataFrame (time, event, dosage) its model was fitted on, e.g. for a Kaplan-Meier plot. |
False
|
Returns:
| Type | Description |
|---|---|
|
DataFrame with one row per variant: variant, vcf_id, HR, |
|
|
HR_lower_95, HR_upper_95, p, MAF, ALT_AF, N, N_events, warning, |
|
|
error. With |
run_prs(weights, build, endpoint, dosage=True, keep_ambiguous=False, n_quantiles=0)
¶
Build a weighted polygenic risk score (PRS) and test its association with a survival endpoint via Cox PH.
The score is the sum of effect x effect-allele dosage over matched variants; a missing dosage is filled with that variant's mean. The score is standardised on the complete-case sample, so the HR is per SD.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
DataFrame
|
Columns variant_id ( |
required |
build
|
str
|
Genome build of the weight variants (e.g. "b37", "b38"). |
required |
endpoint
|
str
|
Endpoint name, e.g. "OS" or "PFS". |
required |
dosage
|
bool
|
Use the VCF's DS field; False derives dosage from GT. |
True
|
keep_ambiguous
|
bool
|
Keep palindromic (A/T, C/G) SNPs instead of dropping them. |
False
|
n_quantiles
|
int
|
If > 1, also fit top vs bottom quantile. |
0
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
A tuple |
dict
|
weight with whether it was used and, if not, why ( |
tuple[DataFrame, dict]
|
summary is a dict with HR_per_SD, HR_lower_95, HR_upper_95, p, N, |
tuple[DataFrame, dict]
|
N_events, variant tallies (n_matched, n_not_in_vcf, ...), |
tuple[DataFrame, dict]
|
|
tuple[DataFrame, dict]
|
with quantiles, |
read_prs_weights(path)
¶
Read a PRS weights file: tab-separated, no header, exactly three columns (variant_id, effect_allele, effect).
Raises ValueError, naming the offending line, if a line has a different number of columns, a value is missing, or an effect isn't a number.