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,289 @@
|
|
|
1
|
+
"""Wrangle packaged MAFs for position-based gallery examples."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import re
|
|
7
|
+
from typing import TYPE_CHECKING, TypedDict, cast
|
|
8
|
+
|
|
9
|
+
from genome_spy.datasets import load_dataset
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_COMPLEMENTARY_SUBSTITUTIONS = {
|
|
16
|
+
"A>G": "T>C",
|
|
17
|
+
"T>C": "T>C",
|
|
18
|
+
"C>T": "C>T",
|
|
19
|
+
"G>A": "C>T",
|
|
20
|
+
"A>T": "T>A",
|
|
21
|
+
"T>A": "T>A",
|
|
22
|
+
"A>C": "T>G",
|
|
23
|
+
"T>G": "T>G",
|
|
24
|
+
"C>A": "C>A",
|
|
25
|
+
"G>T": "C>A",
|
|
26
|
+
"C>G": "C>G",
|
|
27
|
+
"G>C": "C>G",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_DNMT3A_DOMAINS = (
|
|
31
|
+
{"name": "Dnmt3b_related", "start": 290, "end": 377, "color": "#e78973"},
|
|
32
|
+
{"name": "ADDz_Dnmt3a", "start": 476, "end": 612, "color": "#8094ee"},
|
|
33
|
+
{"name": "AdoMet_MTases", "start": 634, "end": 905, "color": "#f7c57a"},
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Dnmt3aLollipopData(TypedDict):
|
|
38
|
+
"""Chart tables and metadata returned for the DNMT3A lollipop."""
|
|
39
|
+
|
|
40
|
+
gene: str
|
|
41
|
+
transcript: str
|
|
42
|
+
protein_length: int
|
|
43
|
+
total_samples: int
|
|
44
|
+
mutated_samples: int
|
|
45
|
+
mutation_rate: float
|
|
46
|
+
features: pd.DataFrame
|
|
47
|
+
domains: pd.DataFrame
|
|
48
|
+
backbone: pd.DataFrame
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class BrcaRainfallData(TypedDict):
|
|
52
|
+
"""Chart tables and metadata returned for the BRCA rainfall plot."""
|
|
53
|
+
|
|
54
|
+
sample: str
|
|
55
|
+
reference_build: str
|
|
56
|
+
points: pd.DataFrame
|
|
57
|
+
change_points: pd.DataFrame
|
|
58
|
+
y_max: float
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Pik3caLollipopData(TypedDict):
|
|
62
|
+
"""Prepared inputs for the TCGA-BRCA PIK3CA lollipop plot."""
|
|
63
|
+
|
|
64
|
+
proteinLength: int
|
|
65
|
+
mutations: list[dict[str, object]]
|
|
66
|
+
domains: list[dict[str, object]]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def pik3ca_lollipop_data() -> Pik3caLollipopData:
|
|
70
|
+
"""Load prepared TCGA-BRCA PIK3CA mutations and protein domains.
|
|
71
|
+
|
|
72
|
+
Description:
|
|
73
|
+
Returns the chart-ready named datasets extracted from the official
|
|
74
|
+
GenomeSpy PIK3CA lollipop specification. Statistical aggregation and
|
|
75
|
+
protein-domain curation are intentionally kept out of the gallery
|
|
76
|
+
example so it can focus on declarative visualization.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Protein length, recurrent mutation rows, and domain rows.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
DatasetNotFoundError: If the packaged resource is unavailable.
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
>>> pik3ca_lollipop_data()["mutations"][0]["mutation"]
|
|
86
|
+
'E81K'
|
|
87
|
+
"""
|
|
88
|
+
return cast(
|
|
89
|
+
Pik3caLollipopData,
|
|
90
|
+
load_dataset("pik3ca_tcga_brca_lollipop", as_format="json"),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def dnmt3a_lollipop_data() -> Dnmt3aLollipopData:
|
|
95
|
+
"""Prepare DNMT3A mutation positions from the packaged TCGA LAML MAF.
|
|
96
|
+
|
|
97
|
+
Description:
|
|
98
|
+
Loads the MAF's ``Hugo_Symbol``, ``Protein_Change``,
|
|
99
|
+
``Variant_Classification``, and ``Tumor_Sample_Barcode`` columns.
|
|
100
|
+
DNMT3A protein changes are parsed to amino-acid positions and grouped
|
|
101
|
+
into one feature per position. Domains and the protein backbone are
|
|
102
|
+
small display annotations maintained here because they are not in the
|
|
103
|
+
upstream MAF.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
A mapping containing mutation features, domain/backbone tables, and
|
|
107
|
+
sample-rate metadata.
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
ImportError: If pandas is not installed.
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
>>> dnmt3a_lollipop_data()["features"].head()
|
|
114
|
+
"""
|
|
115
|
+
import pandas as pd
|
|
116
|
+
|
|
117
|
+
maf = load_dataset("tcga_laml_maf", as_format="dataframe")
|
|
118
|
+
gene_variants = maf.loc[maf["Hugo_Symbol"].eq("DNMT3A")].copy()
|
|
119
|
+
gene_variants["label"] = gene_variants["Protein_Change"].str.removeprefix("p.")
|
|
120
|
+
gene_variants["position"] = gene_variants["label"].map(_protein_position)
|
|
121
|
+
parsed = gene_variants.dropna(subset=["position"]).copy()
|
|
122
|
+
parsed["position"] = parsed["position"].astype(int)
|
|
123
|
+
|
|
124
|
+
# Count calls and choose the most frequent class at each position. Sorting
|
|
125
|
+
# class names descending preserves the previous deterministic tie-break.
|
|
126
|
+
features = parsed.groupby("position", as_index=False).agg(
|
|
127
|
+
count=("label", "size"),
|
|
128
|
+
labels=("label", lambda values: sorted(values.dropna().unique())),
|
|
129
|
+
)
|
|
130
|
+
dominant_class = (
|
|
131
|
+
parsed.groupby(["position", "Variant_Classification"], as_index=False)
|
|
132
|
+
.size()
|
|
133
|
+
.sort_values(
|
|
134
|
+
["position", "size", "Variant_Classification"],
|
|
135
|
+
ascending=[True, False, False],
|
|
136
|
+
kind="stable",
|
|
137
|
+
)
|
|
138
|
+
.drop_duplicates("position")
|
|
139
|
+
.rename(columns={"Variant_Classification": "class"})[["position", "class"]]
|
|
140
|
+
)
|
|
141
|
+
features = features.merge(dominant_class, on="position", validate="one_to_one")
|
|
142
|
+
features["label"] = features["labels"].map(" / ".join)
|
|
143
|
+
features["is_hotspot"] = features["position"].eq(882)
|
|
144
|
+
|
|
145
|
+
total_samples = int(maf["Tumor_Sample_Barcode"].nunique())
|
|
146
|
+
mutated_samples = int(parsed["Tumor_Sample_Barcode"].nunique())
|
|
147
|
+
return {
|
|
148
|
+
"gene": "DNMT3A",
|
|
149
|
+
"transcript": "NM_022552",
|
|
150
|
+
"protein_length": 912,
|
|
151
|
+
"total_samples": total_samples,
|
|
152
|
+
"mutated_samples": mutated_samples,
|
|
153
|
+
"mutation_rate": round(mutated_samples / total_samples * 100, 2),
|
|
154
|
+
"features": features,
|
|
155
|
+
"domains": pd.DataFrame(_DNMT3A_DOMAINS),
|
|
156
|
+
"backbone": pd.DataFrame([{"start": 1, "end": 912, "color": "#a8b5b6"}]),
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def brca_rainfall_data() -> BrcaRainfallData:
|
|
161
|
+
"""Prepare a BRCA rainfall plot from the packaged maftools MAF.
|
|
162
|
+
|
|
163
|
+
Description:
|
|
164
|
+
Loads the sample, variant-type, chromosome, position, allele, and gene
|
|
165
|
+
columns. The sample with the most calls is selected, SNPs are ordered
|
|
166
|
+
within chromosomes, substitutions are converted to pyrimidine
|
|
167
|
+
orientation, and inter-event distances are calculated. The kataegis
|
|
168
|
+
detector follows maftools' six-mutation moving-window rule.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
A mapping containing rainfall points, detected clusters, and display
|
|
172
|
+
metadata.
|
|
173
|
+
|
|
174
|
+
Raises:
|
|
175
|
+
ImportError: If pandas is not installed.
|
|
176
|
+
|
|
177
|
+
Example:
|
|
178
|
+
>>> brca_rainfall_data()["points"].head()
|
|
179
|
+
"""
|
|
180
|
+
import pandas as pd
|
|
181
|
+
|
|
182
|
+
maf = load_dataset("brca_maf", as_format="dataframe")
|
|
183
|
+
sample_counts = (
|
|
184
|
+
maf.groupby("Tumor_Sample_Barcode").size().sort_values(ascending=False)
|
|
185
|
+
)
|
|
186
|
+
sample = str(sample_counts.index[0])
|
|
187
|
+
snps = maf.loc[
|
|
188
|
+
maf["Tumor_Sample_Barcode"].eq(sample) & maf["Variant_Type"].eq("SNP")
|
|
189
|
+
].copy()
|
|
190
|
+
chromosome_order = sorted(snps["Chromosome"].unique(), key=_chromosome_key)
|
|
191
|
+
snps["Chromosome"] = pd.Categorical(
|
|
192
|
+
snps["Chromosome"], categories=chromosome_order, ordered=True
|
|
193
|
+
)
|
|
194
|
+
snps = snps.sort_values(["Chromosome", "Start_Position"], kind="stable")
|
|
195
|
+
snps["distance"] = snps.groupby("Chromosome", observed=True)[
|
|
196
|
+
"Start_Position"
|
|
197
|
+
].diff()
|
|
198
|
+
substitution = snps["Reference_Allele"] + ">" + snps["Tumor_Seq_Allele2"]
|
|
199
|
+
snps["con_class"] = substitution.map(_COMPLEMENTARY_SUBSTITUTIONS)
|
|
200
|
+
snps["log10_distance"] = snps["distance"].map(
|
|
201
|
+
lambda distance: (
|
|
202
|
+
round(math.log10(distance + 1), 4) if pd.notna(distance) else math.nan
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
points = (
|
|
207
|
+
snps.dropna(subset=["distance", "con_class"])
|
|
208
|
+
.rename(
|
|
209
|
+
columns={
|
|
210
|
+
"Chromosome": "chrom",
|
|
211
|
+
"Start_Position": "pos",
|
|
212
|
+
"Hugo_Symbol": "gene",
|
|
213
|
+
}
|
|
214
|
+
)[["chrom", "pos", "gene", "distance", "log10_distance", "con_class"]]
|
|
215
|
+
.copy()
|
|
216
|
+
)
|
|
217
|
+
points["chrom"] = "chr" + points["chrom"].astype(str).str.removeprefix("chr")
|
|
218
|
+
points["distance"] = points["distance"].astype(int)
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
"sample": sample,
|
|
222
|
+
# The trimmed maftools example omits NCBI_Build, but its coordinates
|
|
223
|
+
# and Hugo symbols match hg19 (for example, chr8:124090377 TBC1D31).
|
|
224
|
+
"reference_build": "hg19",
|
|
225
|
+
"points": points.reset_index(drop=True),
|
|
226
|
+
"change_points": _detect_kataegis(points),
|
|
227
|
+
"y_max": round(float(points["log10_distance"].max()) + 0.2, 2),
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _protein_position(label: object) -> int | None:
|
|
232
|
+
if not isinstance(label, str):
|
|
233
|
+
return None
|
|
234
|
+
match = re.search(r"\d+", label)
|
|
235
|
+
return int(match.group()) if match else None
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _chromosome_key(chromosome: object) -> tuple[int, str]:
|
|
239
|
+
value = str(chromosome).removeprefix("chr")
|
|
240
|
+
if value.isdigit():
|
|
241
|
+
return int(value), ""
|
|
242
|
+
return {"X": 23, "Y": 24, "MT": 25}.get(value, 26), value
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _detect_kataegis(points: pd.DataFrame) -> pd.DataFrame:
|
|
246
|
+
import pandas as pd
|
|
247
|
+
|
|
248
|
+
records: list[dict[str, object]] = []
|
|
249
|
+
for chromosome, chromosome_points in points.groupby(
|
|
250
|
+
"chrom", observed=True, sort=False
|
|
251
|
+
):
|
|
252
|
+
chromosome_points = chromosome_points.reset_index(drop=True)
|
|
253
|
+
start_index = 0
|
|
254
|
+
end_index = 6
|
|
255
|
+
while end_index <= len(chromosome_points):
|
|
256
|
+
# maftools starts a cluster when six consecutive mutations average
|
|
257
|
+
# at most 1,000 bp apart, then extends that cluster greedily.
|
|
258
|
+
queue = chromosome_points.iloc[start_index:end_index]
|
|
259
|
+
if len(queue) < 6:
|
|
260
|
+
break
|
|
261
|
+
if queue["distance"].mean() > 1000:
|
|
262
|
+
start_index += 1
|
|
263
|
+
end_index += 1
|
|
264
|
+
continue
|
|
265
|
+
|
|
266
|
+
while end_index <= len(chromosome_points):
|
|
267
|
+
queue = chromosome_points.iloc[start_index:end_index]
|
|
268
|
+
if queue["distance"].mean() > 1000:
|
|
269
|
+
break
|
|
270
|
+
end_index += 1
|
|
271
|
+
|
|
272
|
+
cluster = chromosome_points.iloc[start_index : end_index - 1]
|
|
273
|
+
records.append(
|
|
274
|
+
{
|
|
275
|
+
"chrom": str(chromosome),
|
|
276
|
+
"start": int(cluster["pos"].min()),
|
|
277
|
+
"end": int(cluster["pos"].max()),
|
|
278
|
+
"count": len(cluster),
|
|
279
|
+
"mean_distance": round(float(cluster["distance"].mean()), 2),
|
|
280
|
+
"arrow_y": round(
|
|
281
|
+
max(float(cluster["log10_distance"].min()) - 0.25, 0.1),
|
|
282
|
+
4,
|
|
283
|
+
),
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
start_index = end_index
|
|
287
|
+
end_index += 6
|
|
288
|
+
|
|
289
|
+
return pd.DataFrame(records)
|