genome-spy-python 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- genome_spy/__init__.py +199 -0
- genome_spy/_chart_authoring.py +231 -0
- genome_spy/_conditions.py +72 -0
- genome_spy/_embed.py +87 -0
- genome_spy/_expressions.py +271 -0
- genome_spy/_parameters.py +267 -0
- genome_spy/_render.py +207 -0
- genome_spy/_utils.py +75 -0
- genome_spy/_widget.py +262 -0
- genome_spy/api.py +198 -0
- genome_spy/arrow.py +155 -0
- genome_spy/channels.py +193 -0
- genome_spy/chart.py +1240 -0
- genome_spy/data.py +56 -0
- genome_spy/data_transformers.py +267 -0
- genome_spy/datasets/__init__.py +189 -0
- genome_spy/datasets/_airway.py +219 -0
- genome_spy/datasets/_annotations.py +37 -0
- genome_spy/datasets/_gistic.py +43 -0
- genome_spy/datasets/_grammar.py +66 -0
- genome_spy/datasets/_hapmap.py +180 -0
- genome_spy/datasets/_mutation.py +289 -0
- genome_spy/datasets/_oncoprint.py +523 -0
- genome_spy/datasets/data/airway_metadata.csv +9 -0
- genome_spy/datasets/data/airway_scaledcounts.csv +38695 -0
- genome_spy/datasets/data/brca.maf.gz +0 -0
- genome_spy/datasets/data/hapmap_gwas.csv +14413 -0
- genome_spy/datasets/data/mutation_impact_reference.json +27 -0
- genome_spy/datasets/data/oncoprint_dataset3.json +266 -0
- genome_spy/datasets/data/p53_sequence_comparison.json.gz +0 -0
- genome_spy/datasets/data/pik3ca_mutations.json +1 -0
- genome_spy/datasets/data/pik3ca_tcga_brca_lollipop.json +38 -0
- genome_spy/datasets/data/refseq_gene_bodies.csv.gz +0 -0
- genome_spy/datasets/data/tal1_alphagenome_reference.json.gz +0 -0
- genome_spy/datasets/data/tcga.tsv +146 -0
- genome_spy/datasets/data/tcga_laml.maf.gz +0 -0
- genome_spy/datasets/data/tcga_laml_annot.tsv +201 -0
- genome_spy/datasets/data/tcga_laml_combined_oncoplot.json.gz +0 -0
- genome_spy/datasets/data/tcga_ov_gistic_lesions.tsv.gz +0 -0
- genome_spy/datasets/data/tcga_ov_gistic_scores.tsv.gz +0 -0
- genome_spy/helpers.py +185 -0
- genome_spy/jupyter.py +5 -0
- genome_spy/py.typed +0 -0
- genome_spy/schema/__init__.py +784 -0
- genome_spy/schema/_kwds.py +1394 -0
- genome_spy/schema/_typing.py +186 -0
- genome_spy/schema/capabilities.json +593 -0
- genome_spy/schema/channels.py +8943 -0
- genome_spy/schema/composition.py +1064 -0
- genome_spy/schema/core.py +51821 -0
- genome_spy/schema/ergonomics.py +2056 -0
- genome_spy/schema/expressions.py +476 -0
- genome_spy/schema/genome-spy-schema.json +33657 -0
- genome_spy/schema/lazy.py +326 -0
- genome_spy/schema/mixins.py +11684 -0
- genome_spy/schemapi.py +264 -0
- genome_spy/static/widget.js +345 -0
- genome_spy_python-0.1.0.dist-info/METADATA +185 -0
- genome_spy_python-0.1.0.dist-info/RECORD +64 -0
- genome_spy_python-0.1.0.dist-info/WHEEL +4 -0
- genome_spy_python-0.1.0.dist-info/licenses/LICENSE +21 -0
- genome_spy_python-0.1.0.dist-info/licenses/LICENSES/ALTAIR-BSD-3-Clause.txt +27 -0
- genome_spy_python-0.1.0.dist-info/licenses/LICENSES/GALLERY-DATA-MIT.txt +22 -0
- genome_spy_python-0.1.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +42 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""Internal helpers for the packaged airway RNA-seq examples."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from genome_spy.datasets import load_dataset
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_AIRWAY_GENE_SYMBOLS = {
|
|
16
|
+
"ENSG00000109906": "ZBTB16",
|
|
17
|
+
"ENSG00000116711": "PLA2G4A",
|
|
18
|
+
"ENSG00000145777": "TSLP",
|
|
19
|
+
"ENSG00000152583": "SPARCL1",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def airway_paired_logcounts(
|
|
24
|
+
*, min_base_mean: float = 10.0
|
|
25
|
+
) -> tuple[pd.Series, pd.DataFrame, pd.DataFrame]:
|
|
26
|
+
"""Load packaged airway counts as paired treated/control log-counts.
|
|
27
|
+
|
|
28
|
+
Description:
|
|
29
|
+
Reads the scaled-count table and sample metadata, filters genes by
|
|
30
|
+
mean count, transforms counts with ``log2(count + 1)``, and pivots the
|
|
31
|
+
eight samples into matching treated and control matrices by cell type.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
min_base_mean: Minimum mean count required to retain a gene.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
The retained base means, treated log-count matrix, and control
|
|
38
|
+
log-count matrix. All three share the same gene index.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ImportError: If pandas is not installed.
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
>>> base_mean, treated, control = airway_paired_logcounts()
|
|
45
|
+
>>> treated.shape == control.shape
|
|
46
|
+
True
|
|
47
|
+
"""
|
|
48
|
+
counts = load_dataset("airway_scaledcounts", as_format="dataframe").set_index(
|
|
49
|
+
"ensgene"
|
|
50
|
+
)
|
|
51
|
+
metadata = load_dataset("airway_metadata", as_format="dataframe").set_index("id")
|
|
52
|
+
|
|
53
|
+
sample_ids = metadata.index.to_list()
|
|
54
|
+
count_matrix = counts.loc[:, sample_ids].copy()
|
|
55
|
+
base_mean = count_matrix.mean(axis=1)
|
|
56
|
+
count_matrix = count_matrix.loc[base_mean >= min_base_mean]
|
|
57
|
+
base_mean = base_mean.loc[count_matrix.index]
|
|
58
|
+
|
|
59
|
+
long_counts = (
|
|
60
|
+
np.log2(count_matrix + 1.0)
|
|
61
|
+
.reset_index()
|
|
62
|
+
.melt(id_vars="ensgene", var_name="id", value_name="log_count")
|
|
63
|
+
.merge(
|
|
64
|
+
metadata.loc[:, ["celltype", "dex"]].reset_index(),
|
|
65
|
+
on="id",
|
|
66
|
+
how="left",
|
|
67
|
+
validate="many_to_one",
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
paired = long_counts.pivot(
|
|
71
|
+
index="ensgene",
|
|
72
|
+
columns=["celltype", "dex"],
|
|
73
|
+
values="log_count",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
celltype_order = metadata["celltype"].drop_duplicates().to_list()
|
|
77
|
+
control = paired.xs("control", axis=1, level="dex").loc[:, celltype_order]
|
|
78
|
+
treated = paired.xs("treated", axis=1, level="dex").loc[:, celltype_order]
|
|
79
|
+
return base_mean.loc[paired.index], treated, control
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def airway_differential_expression(
|
|
83
|
+
*,
|
|
84
|
+
min_base_mean: float = 10.0,
|
|
85
|
+
max_genes: int = 12_000,
|
|
86
|
+
log2fc_cutoff: float = 1.0,
|
|
87
|
+
pvalue_cutoff: float = 0.01,
|
|
88
|
+
padj_alpha: float = 0.1,
|
|
89
|
+
) -> tuple[pd.DataFrame, dict[str, list[float]]]:
|
|
90
|
+
"""Build the chart-ready airway differential-expression table.
|
|
91
|
+
|
|
92
|
+
Description:
|
|
93
|
+
Uses the paired treated/control log-count matrices from
|
|
94
|
+
:func:`airway_paired_logcounts`, computes paired t-tests and Benjamini-
|
|
95
|
+
Hochberg adjusted p-values, then adds the transformed fields and
|
|
96
|
+
significance classification shared by the MA and volcano examples.
|
|
97
|
+
A small curated set of genes also receives chart-ready callout labels
|
|
98
|
+
and label offsets.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
min_base_mean: Minimum mean count required before testing.
|
|
102
|
+
max_genes: Keep at most this many genes by base mean for plotting.
|
|
103
|
+
log2fc_cutoff: Absolute fold-change threshold for significance labels.
|
|
104
|
+
pvalue_cutoff: Raw p-value threshold for significance labels.
|
|
105
|
+
padj_alpha: FDR level passed to the multiple-testing correction.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
A chart-ready result table and plotting domains.
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
ImportError: If pandas, SciPy, or statsmodels is not installed.
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
>>> data, domains = airway_differential_expression(max_genes=200)
|
|
115
|
+
>>> {"log2fc", "pvalue", "padj"} <= set(data)
|
|
116
|
+
True
|
|
117
|
+
"""
|
|
118
|
+
import pandas as pd
|
|
119
|
+
from scipy.stats import ttest_rel
|
|
120
|
+
from statsmodels.stats.multitest import fdrcorrection
|
|
121
|
+
|
|
122
|
+
base_mean, treated, control = airway_paired_logcounts(min_base_mean=min_base_mean)
|
|
123
|
+
log2fc = treated.subtract(control).mean(axis=1)
|
|
124
|
+
test = ttest_rel(treated.to_numpy(), control.to_numpy(), axis=1, nan_policy="omit")
|
|
125
|
+
pvalue = np.asarray(test.pvalue, dtype=float)
|
|
126
|
+
pvalue = np.where(
|
|
127
|
+
~np.isfinite(pvalue) & np.isclose(log2fc.to_numpy(), 0.0), 1.0, pvalue
|
|
128
|
+
)
|
|
129
|
+
pvalue = np.where(~np.isfinite(pvalue), 0.0, pvalue)
|
|
130
|
+
_rejected, padj = fdrcorrection(pvalue, alpha=padj_alpha)
|
|
131
|
+
|
|
132
|
+
data = pd.DataFrame(
|
|
133
|
+
{
|
|
134
|
+
"ensgene": treated.index,
|
|
135
|
+
"base_mean": base_mean.loc[treated.index].to_numpy(),
|
|
136
|
+
"log2fc": log2fc.to_numpy(),
|
|
137
|
+
"pvalue": pvalue,
|
|
138
|
+
"padj": padj,
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
data["neglog10_pvalue"] = -np.log10(np.clip(data["pvalue"], 1e-300, 1.0))
|
|
142
|
+
data["neglog10_padj"] = -np.log10(np.clip(data["padj"], 1e-300, 1.0))
|
|
143
|
+
data["log10_base_mean"] = np.log10(data["base_mean"])
|
|
144
|
+
passes = (data["pvalue"] < pvalue_cutoff) & (data["log2fc"].abs() >= log2fc_cutoff)
|
|
145
|
+
data["direction"] = np.where(
|
|
146
|
+
passes & (data["log2fc"] > 0),
|
|
147
|
+
"up in dex",
|
|
148
|
+
np.where(passes & (data["log2fc"] < 0), "down in dex", "n.s."),
|
|
149
|
+
)
|
|
150
|
+
data = data.nlargest(max_genes, "base_mean").sort_values("log10_base_mean")
|
|
151
|
+
|
|
152
|
+
log2fc_extent = float(np.ceil(data["log2fc"].abs().max() * 2) / 2)
|
|
153
|
+
volcano_y_max = float(np.ceil(data["neglog10_pvalue"].quantile(0.995) / 5) * 5)
|
|
154
|
+
data["neglog10_pvalue_plot"] = np.minimum(data["neglog10_pvalue"], volcano_y_max)
|
|
155
|
+
volcano_x_extent = float(np.ceil(data["log2fc"].abs().max() * 2) / 2)
|
|
156
|
+
domains = {
|
|
157
|
+
"ma_x": [
|
|
158
|
+
float(np.floor(data["log10_base_mean"].min() * 2) / 2),
|
|
159
|
+
float(np.ceil(data["log10_base_mean"].max() * 2) / 2),
|
|
160
|
+
],
|
|
161
|
+
"ma_y": [-log2fc_extent, log2fc_extent],
|
|
162
|
+
"volcano_x": [-volcano_x_extent, volcano_x_extent],
|
|
163
|
+
"volcano_y": [0.0, volcano_y_max],
|
|
164
|
+
"pvalue_cutoff": [-float(np.log10(pvalue_cutoff))],
|
|
165
|
+
}
|
|
166
|
+
data["gene_symbol"] = data["ensgene"].map(_AIRWAY_GENE_SYMBOLS)
|
|
167
|
+
_add_airway_annotation_positions(data)
|
|
168
|
+
return data, domains
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _add_airway_annotation_positions(data: pd.DataFrame) -> None:
|
|
172
|
+
"""Add sparse label endpoints used by the airway gallery examples."""
|
|
173
|
+
# Pixel offsets keep annotation spacing stable while the reader zooms. The
|
|
174
|
+
# side tells the text mark to extend away from the leader endpoint.
|
|
175
|
+
volcano_offsets = {
|
|
176
|
+
"ZBTB16": (-30, -32),
|
|
177
|
+
"PLA2G4A": (-30, -38),
|
|
178
|
+
"TSLP": (-36, 28),
|
|
179
|
+
}
|
|
180
|
+
ma_offsets = {
|
|
181
|
+
"ZBTB16": (48, 16),
|
|
182
|
+
"PLA2G4A": (-36, 16),
|
|
183
|
+
"SPARCL1": (48, 15),
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
data["volcano_label"] = data["gene_symbol"].where(
|
|
187
|
+
data["gene_symbol"].isin(volcano_offsets)
|
|
188
|
+
)
|
|
189
|
+
data["ma_label"] = data["gene_symbol"].where(data["gene_symbol"].isin(ma_offsets))
|
|
190
|
+
|
|
191
|
+
volcano_dx = data["gene_symbol"].map(
|
|
192
|
+
{symbol: offset[0] for symbol, offset in volcano_offsets.items()}
|
|
193
|
+
)
|
|
194
|
+
volcano_dy = data["gene_symbol"].map(
|
|
195
|
+
{symbol: offset[1] for symbol, offset in volcano_offsets.items()}
|
|
196
|
+
)
|
|
197
|
+
ma_dx = data["gene_symbol"].map(
|
|
198
|
+
{symbol: offset[0] for symbol, offset in ma_offsets.items()}
|
|
199
|
+
)
|
|
200
|
+
ma_dy = data["gene_symbol"].map(
|
|
201
|
+
{symbol: offset[1] for symbol, offset in ma_offsets.items()}
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
data["volcano_x_offset"] = volcano_dx
|
|
205
|
+
data["volcano_y_offset"] = volcano_dy
|
|
206
|
+
data["volcano_label_side"] = data["gene_symbol"].map(
|
|
207
|
+
{
|
|
208
|
+
symbol: "left" if offset[0] < 0 else "right"
|
|
209
|
+
for symbol, offset in volcano_offsets.items()
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
data["ma_x_offset"] = ma_dx
|
|
213
|
+
data["ma_y_offset"] = ma_dy
|
|
214
|
+
data["ma_label_side"] = data["gene_symbol"].map(
|
|
215
|
+
{
|
|
216
|
+
symbol: "left" if offset[0] < 0 else "right"
|
|
217
|
+
for symbol, offset in ma_offsets.items()
|
|
218
|
+
}
|
|
219
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Internal helpers for the packaged RefSeq gene-body annotations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Literal
|
|
6
|
+
|
|
7
|
+
from genome_spy.datasets import load_dataset
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def refseq_gene_bodies(assembly: Literal["hg19", "hg38"]) -> pd.DataFrame:
|
|
14
|
+
"""Load an assembly-wide RefSeq gene-body annotation table.
|
|
15
|
+
|
|
16
|
+
Description:
|
|
17
|
+
Loads gene bodies independently prepared from assembly-matched UCSC
|
|
18
|
+
RefSeq records. Coordinates are zero-based and half-open. The number
|
|
19
|
+
of contributing transcript records is intended only for label
|
|
20
|
+
prioritization.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
assembly: UCSC assembly identifier.
|
|
24
|
+
Returns:
|
|
25
|
+
A chart-ready table containing every packaged gene body for the
|
|
26
|
+
requested assembly.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ImportError: If pandas is not installed.
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
>>> genes = refseq_gene_bodies("hg38")
|
|
33
|
+
>>> "STK3" in set(genes["symbol"])
|
|
34
|
+
True
|
|
35
|
+
"""
|
|
36
|
+
genes = load_dataset("refseq_gene_bodies", as_format="dataframe")
|
|
37
|
+
return genes.loc[genes["assembly"].eq(assembly)].reset_index(drop=True)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Load the complete packaged TCGA OV GISTIC2 example results."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, TypedDict
|
|
6
|
+
|
|
7
|
+
from genome_spy.datasets import load_dataset
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TcgaOvGisticData(TypedDict):
|
|
14
|
+
"""Chart tables returned for the TCGA OV GISTIC landscape."""
|
|
15
|
+
|
|
16
|
+
scores: pd.DataFrame
|
|
17
|
+
lesions: pd.DataFrame
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def tcga_ov_gistic_data() -> TcgaOvGisticData:
|
|
21
|
+
"""Load the TCGA OV GISTIC2 data displayed by the gallery example.
|
|
22
|
+
|
|
23
|
+
Description:
|
|
24
|
+
The tables are the complete ``scores.gistic`` and
|
|
25
|
+
``all_lesions.conf_99.txt`` files used by GenomeSpy's TCGA OV example.
|
|
26
|
+
They originate from the TCGA OV-TP GISTIC2 Level 4 archive published by
|
|
27
|
+
the Broad GDAC Firehose on 2016-01-28 and use hg19 coordinates. The
|
|
28
|
+
copies are retained locally so rendering does not depend on
|
|
29
|
+
GenomeSpy's external data host.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Score and lesion tables for the displayed genomic interval.
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
ImportError: If pandas is not installed.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> tcga_ov_gistic_data()["scores"].head()
|
|
39
|
+
"""
|
|
40
|
+
return {
|
|
41
|
+
"scores": load_dataset("tcga_ov_gistic_scores", as_format="dataframe"),
|
|
42
|
+
"lesions": load_dataset("tcga_ov_gistic_lesions", as_format="dataframe"),
|
|
43
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Small deterministic tables used by grammar gallery examples."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def sincos_data() -> pd.DataFrame:
|
|
11
|
+
"""Return a compact table for point and composition examples."""
|
|
12
|
+
return pd.DataFrame({"x": range(31)})
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def heatmap_data() -> pd.DataFrame:
|
|
16
|
+
"""Return a deterministic grid with a smooth quantitative value."""
|
|
17
|
+
rows = [
|
|
18
|
+
{"x": x, "y": y, "z": math.sin(x / 8) + math.cos(y / 10 - 0.5)}
|
|
19
|
+
for y in range(20)
|
|
20
|
+
for x in range(40)
|
|
21
|
+
]
|
|
22
|
+
return pd.DataFrame(rows)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def ranged_rule_data() -> pd.DataFrame:
|
|
26
|
+
"""Return intervals for the ranged-rule example."""
|
|
27
|
+
return pd.DataFrame(
|
|
28
|
+
[
|
|
29
|
+
{"y": "A", "x": 2, "x2": 7},
|
|
30
|
+
{"y": "B", "x": 0, "x2": 3},
|
|
31
|
+
{"y": "B", "x": 5, "x2": 6},
|
|
32
|
+
{"y": "C", "x": 4, "x2": 8},
|
|
33
|
+
{"y": "D", "x": 1, "x2": 5},
|
|
34
|
+
]
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def link_data() -> pd.DataFrame:
|
|
39
|
+
"""Return interval pairs for the link-mark example."""
|
|
40
|
+
return pd.DataFrame(
|
|
41
|
+
[
|
|
42
|
+
{"x": 1, "x2": 5, "y": 2},
|
|
43
|
+
{"x": 3, "x2": 8, "y": 4},
|
|
44
|
+
{"x": 5, "x2": 11, "y": 6},
|
|
45
|
+
{"x": 7, "x2": 13, "y": 8},
|
|
46
|
+
{"x": 9, "x2": 16, "y": 10},
|
|
47
|
+
]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def sequence_logo_data() -> pd.DataFrame:
|
|
52
|
+
"""Return base counts for a small sequence-logo example."""
|
|
53
|
+
return pd.DataFrame(
|
|
54
|
+
[
|
|
55
|
+
{"pos": 1, "base": "A", "count": 2},
|
|
56
|
+
{"pos": 1, "base": "C", "count": 3},
|
|
57
|
+
{"pos": 1, "base": "T", "count": 5},
|
|
58
|
+
{"pos": 2, "base": "A", "count": 7},
|
|
59
|
+
{"pos": 2, "base": "C", "count": 3},
|
|
60
|
+
{"pos": 3, "base": "A", "count": 10},
|
|
61
|
+
{"pos": 4, "base": "T", "count": 9},
|
|
62
|
+
{"pos": 4, "base": "G", "count": 1},
|
|
63
|
+
{"pos": 5, "base": "G", "count": 8},
|
|
64
|
+
{"pos": 6, "base": "G", "count": 7},
|
|
65
|
+
]
|
|
66
|
+
)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Prepared HapMap tables used by association-plot examples."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from genome_spy.datasets import load_dataset
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def hapmap_manhattan_data(
|
|
16
|
+
*, genome_wide_p: float = 5e-8, suggestive_p: float = 1e-5
|
|
17
|
+
) -> tuple[pd.DataFrame, pd.DataFrame, dict[str, float | list[float]]]:
|
|
18
|
+
"""Build chart tables for the packaged HapMap Manhattan example.
|
|
19
|
+
|
|
20
|
+
Description:
|
|
21
|
+
Loads ``CHR``, ``BP``, ``P``, and annotation columns from the HapMap
|
|
22
|
+
GWAS table, removes invalid p-values, adds chromosome labels and
|
|
23
|
+
``-log10(P)``, and selects the eight smallest p-values for labels.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
genome_wide_p: P-value used for the genome-wide guide line.
|
|
27
|
+
suggestive_p: P-value used for the suggestive guide line.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
The transformed point table, top-hit table, and y-axis metadata.
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
ImportError: If pandas is not installed.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
>>> points, top_hits, domains = hapmap_manhattan_data()
|
|
37
|
+
>>> "neglog" in points
|
|
38
|
+
True
|
|
39
|
+
"""
|
|
40
|
+
data = load_dataset("hapmap_gwas", as_format="dataframe")
|
|
41
|
+
data = data[data["P"] > 0].copy()
|
|
42
|
+
data["chrom"] = np.where(
|
|
43
|
+
data["CHR"] == 23,
|
|
44
|
+
"chrX",
|
|
45
|
+
"chr" + data["CHR"].astype(str),
|
|
46
|
+
)
|
|
47
|
+
data["neglog"] = -np.log10(data["P"])
|
|
48
|
+
data["chrom_group"] = np.where(data["CHR"] % 2 == 0, "even", "odd")
|
|
49
|
+
top_hits = data.nsmallest(8, "P")
|
|
50
|
+
y_domain = [0.0, float(np.ceil(data["neglog"].max()))]
|
|
51
|
+
return (
|
|
52
|
+
data,
|
|
53
|
+
top_hits,
|
|
54
|
+
{
|
|
55
|
+
"y_domain": y_domain,
|
|
56
|
+
"genome_wide_y": float(-np.log10(genome_wide_p)),
|
|
57
|
+
"suggestive_y": float(-np.log10(suggestive_p)),
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def hapmap_volcano_data(
|
|
63
|
+
*, effect_cutoff: float = 0.5, pvalue_cutoff: float = 1e-5
|
|
64
|
+
) -> tuple[pd.DataFrame, dict[str, float | list[float]]]:
|
|
65
|
+
"""Build the chart-ready HapMap volcano table and domains.
|
|
66
|
+
|
|
67
|
+
Description:
|
|
68
|
+
Loads the HapMap association table, transforms p-values to ``-log10(P)``,
|
|
69
|
+
and classifies points by effect direction and the supplied thresholds.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
effect_cutoff: Minimum absolute effect size for a labelled point.
|
|
73
|
+
pvalue_cutoff: Maximum p-value for a labelled point.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
The transformed point table and plotting domains.
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
ImportError: If pandas is not installed.
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
>>> data, domains = hapmap_volcano_data()
|
|
83
|
+
>>> "association" in data
|
|
84
|
+
True
|
|
85
|
+
"""
|
|
86
|
+
data = load_dataset("hapmap_gwas", as_format="dataframe")
|
|
87
|
+
data = data[data["P"] > 0].copy()
|
|
88
|
+
data["neglog"] = -np.log10(data["P"])
|
|
89
|
+
passes = (data["P"] < pvalue_cutoff) & (data["EFFECTSIZE"].abs() >= effect_cutoff)
|
|
90
|
+
data["association"] = np.where(
|
|
91
|
+
passes & (data["EFFECTSIZE"] > 0),
|
|
92
|
+
"risk",
|
|
93
|
+
np.where(passes & (data["EFFECTSIZE"] < 0), "protective", "n.s."),
|
|
94
|
+
)
|
|
95
|
+
x_extent = float(np.ceil(data["EFFECTSIZE"].abs().max() * 10) / 10)
|
|
96
|
+
return data, {
|
|
97
|
+
"x_domain": [-x_extent, x_extent],
|
|
98
|
+
"y_domain": [0.0, float(np.ceil(data["neglog"].max()))],
|
|
99
|
+
"effect_cutoff": effect_cutoff,
|
|
100
|
+
"neglog_pvalue_cutoff": float(-np.log10(pvalue_cutoff)),
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def hapmap_qq_data(
|
|
105
|
+
*, bins: int = 45
|
|
106
|
+
) -> tuple[pd.DataFrame, pd.DataFrame, dict[str, float]]:
|
|
107
|
+
"""Build the chart-ready HapMap QQ and deviation tables.
|
|
108
|
+
|
|
109
|
+
Description:
|
|
110
|
+
Sorts valid HapMap p-values, pairs observed and expected quantiles, and
|
|
111
|
+
bins their deviation for the lower summary strip.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
bins: Number of expected-quantile bins used for the deviation summary.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
The QQ table, binned deviation table, and plotting limits.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
ImportError: If pandas is not installed.
|
|
121
|
+
|
|
122
|
+
Example:
|
|
123
|
+
>>> qq, deviation, domains = hapmap_qq_data()
|
|
124
|
+
>>> {"expected", "observed"} <= set(qq)
|
|
125
|
+
True
|
|
126
|
+
"""
|
|
127
|
+
import pandas as pd
|
|
128
|
+
|
|
129
|
+
pvals = np.sort(
|
|
130
|
+
load_dataset("hapmap_gwas", as_format="dataframe")
|
|
131
|
+
.query("P > 0")["P"]
|
|
132
|
+
.to_numpy()
|
|
133
|
+
)
|
|
134
|
+
ranks = np.arange(1, len(pvals) + 1)
|
|
135
|
+
data = pd.DataFrame(
|
|
136
|
+
{
|
|
137
|
+
"expected": -np.log10((ranks - 0.5) / len(pvals)),
|
|
138
|
+
"observed": -np.log10(pvals),
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
data["delta"] = data["observed"] - data["expected"]
|
|
142
|
+
data["pattern"] = np.where(
|
|
143
|
+
data["delta"] > 0.45, "Tail enrichment", "Null-like bulk"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
edges = np.linspace(0.0, float(data["expected"].max()), bins + 1)
|
|
147
|
+
deviation = (
|
|
148
|
+
data.assign(
|
|
149
|
+
bin=pd.cut(
|
|
150
|
+
data["expected"],
|
|
151
|
+
bins=edges,
|
|
152
|
+
include_lowest=True,
|
|
153
|
+
duplicates="drop",
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
.groupby("bin", observed=True, as_index=False)
|
|
157
|
+
.agg(delta_mean=("delta", "mean"))
|
|
158
|
+
)
|
|
159
|
+
intervals = deviation["bin"]
|
|
160
|
+
deviation["x0"] = intervals.map(lambda interval: float(interval.left))
|
|
161
|
+
deviation["x1"] = intervals.map(lambda interval: float(interval.right))
|
|
162
|
+
deviation["zero"] = 0.0
|
|
163
|
+
deviation["direction"] = np.where(
|
|
164
|
+
deviation["delta_mean"] >= 0,
|
|
165
|
+
"Observed > expected",
|
|
166
|
+
"Observed < expected",
|
|
167
|
+
)
|
|
168
|
+
deviation = deviation[["x0", "x1", "zero", "delta_mean", "direction"]]
|
|
169
|
+
limit = float(max(data["expected"].max(), data["observed"].max())) * 1.02
|
|
170
|
+
delta_limit = float(max(abs(deviation["delta_mean"]).max(), 0.25)) * 1.1
|
|
171
|
+
return (
|
|
172
|
+
data,
|
|
173
|
+
deviation,
|
|
174
|
+
{
|
|
175
|
+
"limit": limit,
|
|
176
|
+
"delta_limit": delta_limit,
|
|
177
|
+
"annotation_x": round(limit * 0.54, 2),
|
|
178
|
+
"tail_y": round(limit * 0.93, 2),
|
|
179
|
+
},
|
|
180
|
+
)
|