Skip to content

mecfs_bio.build_system.task.ppp_ldsc.ppp_ldsc_context

Shared, protein-invariant context for LDSC over the UKB-PPP database.

Every per-protein slim file stores beta/se in the SAME variant-index row order, so the alignment of index variants to LD scores, the regression LD scores (ld), the total reference-SNP count M, and the genome-sorted order used for contiguous jackknife blocks are all identical across proteins. We therefore build them ONCE and reuse them for every protein (and, later, for cross-trait rg and LCV).

The context also carries each retained variant's chromosome and hg38 position so a per- protein cis mask (variants within +/- a window of the protein's gene) can be computed, and its row position in the slim files so a protein's beta/se can be gathered by positional index.

Built by intersecting the variant index (rsID) with the reference LD scores (SNP), then optionally dropping strand-ambiguous variants and the extended MHC region (on the index's primary hg38 POS), and sorting by (chromosome, position).

The reference LD scores arrive as a consolidated dataframe (ConsolidateLDScoresTask), which also carries the common-variant count M_5_50 the regression normalizes by, so this module reads dataframes rather than the LDSC authors' per-chromosome file layout.

Classes:

  • PppLdscContext

    The shared regression SNP set, genome-sorted. All arrays are parallel (one entry per

Functions:

  • build_cis_mask

    Boolean over the context SNP set, True for CIS variants (to be excluded): those on

  • build_ppp_ldsc_context

    Intersect the variant index with the LD scores and assemble the shared context.

PppLdscContext

The shared regression SNP set, genome-sorted. All arrays are parallel (one entry per retained variant, in (chromosome, position) order).

We do not carry a separate regression-weight LD score (wLD): with sep_weights=False the weight LD score equals ld, and the batched kernel reuses ld directly. Reinstate a wld array here only if a future path (e.g. externally supplied regression weights) needs it.

Attributes:

chrom instance-attribute

chrom: ndarray

ld instance-attribute

ld: ndarray

m instance-attribute

m: float

n_snps property

n_snps: int

pos instance-attribute

pos: ndarray

row_pos instance-attribute

row_pos: ndarray

build_cis_mask

build_cis_mask(
    context: PppLdscContext,
    gene_chrom: int,
    gene_start: int,
    gene_end: int,
    window_bp: int,
) -> np.ndarray

Boolean over the context SNP set, True for CIS variants (to be excluded): those on the gene's chromosome within window_bp of the gene body [start, end].

Source code in mecfs_bio/build_system/task/ppp_ldsc/ppp_ldsc_context.py
def build_cis_mask(
    context: PppLdscContext,
    gene_chrom: int,
    gene_start: int,
    gene_end: int,
    window_bp: int,
) -> np.ndarray:
    """Boolean over the context SNP set, True for CIS variants (to be excluded): those on
    the gene's chromosome within window_bp of the gene body [start, end]."""
    return (
        (context.chrom == gene_chrom)
        & (context.pos >= gene_start - window_bp)
        & (context.pos <= gene_end + window_bp)
    )

build_ppp_ldsc_context

build_ppp_ldsc_context(
    index_df: DataFrame,
    ld_df: DataFrame,
    *,
    drop_strand_ambiguous: bool = True,
    exclude_mhc: bool = True,
) -> PppLdscContext

Intersect the variant index with the LD scores and assemble the shared context.

index_df: the variant index, with columns CHR, POS (hg38), rsID, and is_strand_ambiguous, in its canonical row order (row i aligns to row i of every slim file). ld_df: consolidated reference LD scores with columns CHR, SNP (rsID), L2 and M_5_50.

M is taken from the LD scores BEFORE the join, since it counts reference variants and must not shrink to the subset the index happens to cover.

Source code in mecfs_bio/build_system/task/ppp_ldsc/ppp_ldsc_context.py
def build_ppp_ldsc_context(
    index_df: pl.DataFrame,
    ld_df: pl.DataFrame,
    *,
    drop_strand_ambiguous: bool = True,
    exclude_mhc: bool = True,
) -> PppLdscContext:
    """Intersect the variant index with the LD scores and assemble the shared context.

    index_df: the variant index, with columns CHR, POS (hg38), rsID, and
        is_strand_ambiguous, in its canonical row order (row i aligns to row i of every
        slim file).
    ld_df: consolidated reference LD scores with columns CHR, SNP (rsID), L2 and M_5_50.

    M is taken from the LD scores BEFORE the join, since it counts reference variants and must
    not shrink to the subset the index happens to cover.
    """
    m_total = total_m_5_50(narwhals.from_native(ld_df, eager_only=True))
    joined = (
        index_df.with_row_index(_ROW_POS_COL)
        .join(
            ld_df.select(LD_SCORE_RSID_COL, LD_SCORE_LD_SCORE_COL),
            left_on=GWASLAB_RSID_COL,
            right_on=LD_SCORE_RSID_COL,
            how="inner",
        )
        .sort([GWASLAB_CHROM_COL, GWASLAB_POS_COL])
    )
    if drop_strand_ambiguous:
        joined = joined.filter(~pl.col(PPP_INDEX_IS_STRAND_AMBIGUOUS_COL))
    if exclude_mhc:
        mhc = extended_mhc_interval("38")
        in_mhc = (
            (pl.col(GWASLAB_CHROM_COL) == mhc.chrom)
            & (pl.col(GWASLAB_POS_COL) >= mhc.start)
            & (pl.col(GWASLAB_POS_COL) <= mhc.end)
        )
        joined = joined.filter(~in_mhc)

    return PppLdscContext(
        row_pos=joined[_ROW_POS_COL].to_numpy().astype(np.int64),
        ld=joined[LD_SCORE_LD_SCORE_COL].to_numpy().astype(float),
        chrom=joined[GWASLAB_CHROM_COL].to_numpy().astype(np.int64),
        pos=joined[GWASLAB_POS_COL].to_numpy().astype(np.int64),
        m=m_total,
    )