File size: 3,202 Bytes
925ee3b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
"""Enrichment analysis for AU-rich element (ARE) and NMD target genes."""

from __future__ import annotations

from pathlib import Path

import numpy as np
import pandas as pd
from anndata import AnnData
from scipy import stats

from .._constants import GAMMA
from .._utils import get_layer, require_layers

_DATA_DIR = Path(__file__).parent / "data"


def _load_gene_list(filename: str) -> set[str]:
    """Load a gene list from a bundled text file (one gene per line)."""
    path = _DATA_DIR / filename
    with open(path) as f:
        return {line.strip() for line in f if line.strip()}


def _enrichment_test(
    adata: AnnData,
    gene_set: set[str],
    label: str,
    min_gamma_fraction: float = 0.1,
) -> dict:
    """Mann-Whitney U test: do genes in gene_set have higher gamma?

    Only tests genes with sufficient non-zero gamma signal.
    """
    require_layers(adata, GAMMA)

    gamma = get_layer(adata, GAMMA)
    median_gamma = np.median(gamma, axis=0)

    # Filter to genes with reliable gamma estimates
    nonzero_frac = (gamma > 0).mean(axis=0)
    reliable = nonzero_frac >= min_gamma_fraction

    gene_names = adata.var_names.tolist()
    in_set = np.array([g in gene_set for g in gene_names])

    # Apply reliability filter
    in_set_reliable = in_set & reliable
    background_reliable = (~in_set) & reliable

    n_in = in_set_reliable.sum()
    n_out = background_reliable.sum()

    if n_in < 2 or n_out < 2:
        return {
            "label": label,
            "n_genes_in_set": int(n_in),
            "n_genes_in_set_unfiltered": int(in_set.sum()),
            "n_genes_background": int(n_out),
            "median_gamma_in_set": np.nan,
            "median_gamma_background": np.nan,
            "U_statistic": np.nan,
            "p_value": np.nan,
        }

    gamma_in = median_gamma[in_set_reliable]
    gamma_out = median_gamma[background_reliable]

    U, p = stats.mannwhitneyu(gamma_in, gamma_out, alternative="greater")

    return {
        "label": label,
        "n_genes_in_set": int(n_in),
        "n_genes_in_set_unfiltered": int(in_set.sum()),
        "n_genes_background": int(n_out),
        "median_gamma_in_set": float(np.median(gamma_in)),
        "median_gamma_background": float(np.median(gamma_out)),
        "U_statistic": float(U),
        "p_value": float(p),
    }


def are_enrichment(adata: AnnData) -> dict:
    """Test whether ARE genes have higher gamma than background.

    AU-rich elements (AREs) in 3' UTRs promote mRNA degradation.
    Genes with AREs should have higher degradation rates (gamma).

    Returns
    -------
    dict with test statistics including ``U_statistic`` and ``p_value``.
    """
    gene_set = _load_gene_list("are_genes.txt")
    return _enrichment_test(adata, gene_set, "ARE")


def nmd_enrichment(adata: AnnData) -> dict:
    """Test whether NMD target genes have higher gamma than background.

    Nonsense-mediated mRNA decay (NMD) targets should show higher
    degradation rates.

    Returns
    -------
    dict with test statistics including ``U_statistic`` and ``p_value``.
    """
    gene_set = _load_gene_list("nmd_genes.txt")
    return _enrichment_test(adata, gene_set, "NMD")