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,523 @@
|
|
|
1
|
+
"""Wrangle the packaged MAF and pyoncoprint tables for oncoprints.
|
|
2
|
+
|
|
3
|
+
The source files are deliberately kept in their upstream tabular form. The
|
|
4
|
+
functions below only add the ordering, aggregation, and display fields that the
|
|
5
|
+
GenomeSpy examples need.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING, TypedDict
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from genome_spy.datasets import load_dataset
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
import pandas as pd
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
_NON_SYNONYMOUS_CLASSES = (
|
|
21
|
+
"Frame_Shift_Del",
|
|
22
|
+
"Frame_Shift_Ins",
|
|
23
|
+
"Splice_Site",
|
|
24
|
+
"Translation_Start_Site",
|
|
25
|
+
"Nonsense_Mutation",
|
|
26
|
+
"Nonstop_Mutation",
|
|
27
|
+
"In_Frame_Del",
|
|
28
|
+
"In_Frame_Ins",
|
|
29
|
+
"Missense_Mutation",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
_LUAD_REPLACEMENTS = {
|
|
33
|
+
"amp_rec": "Amplification",
|
|
34
|
+
"homdel_rec": "Deep Deletion",
|
|
35
|
+
"splice": "Splice Mutation (putative driver)",
|
|
36
|
+
"splice_rec": "Splice Mutation (putative passenger)",
|
|
37
|
+
"sv": "Structural Variant (putative driver)",
|
|
38
|
+
"sv_rec": "Structural Variant (putative passenger)",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
_LUAD_MUTATION_CLASSES = (
|
|
42
|
+
"Amplification",
|
|
43
|
+
"Deep Deletion",
|
|
44
|
+
"Splice Mutation (putative driver)",
|
|
45
|
+
"Splice Mutation (putative passenger)",
|
|
46
|
+
"Structural Variant (putative driver)",
|
|
47
|
+
"Structural Variant (putative passenger)",
|
|
48
|
+
"Inframe Mutation (putative driver)",
|
|
49
|
+
"Missense Mutation (putative driver)",
|
|
50
|
+
"Missense Mutation (putative passenger)",
|
|
51
|
+
"Truncating mutation (putative driver)",
|
|
52
|
+
"Truncating mutation (putative passenger)",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
_LUAD_HEATMAP_GROUPS = (
|
|
56
|
+
"mRNA expression z-scores relative to diploid samples (RNA Seq V2 RSEM)",
|
|
57
|
+
"Methylation (HM27 and HM450 merge)",
|
|
58
|
+
"Microbiome Signatures (log RNA Seq CPM)",
|
|
59
|
+
)
|
|
60
|
+
_LUAD_HEATMAP_GROUP_ORDER = dict(zip(_LUAD_HEATMAP_GROUPS, range(3), strict=True))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class LamlOncoplotData(TypedDict):
|
|
64
|
+
"""Chart tables and limits returned for the LAML oncoplot."""
|
|
65
|
+
|
|
66
|
+
samples: pd.DataFrame
|
|
67
|
+
genes: pd.DataFrame
|
|
68
|
+
events: pd.DataFrame
|
|
69
|
+
grid: pd.DataFrame
|
|
70
|
+
sample_tmb: pd.DataFrame
|
|
71
|
+
gene_counts: pd.DataFrame
|
|
72
|
+
total_samples: int
|
|
73
|
+
altered_samples: int
|
|
74
|
+
tmb_limit: int
|
|
75
|
+
count_limit: int
|
|
76
|
+
sample_domain: list[float]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class LuadOncoprintData(TypedDict):
|
|
80
|
+
"""Chart tables and limits returned for the LUAD oncoprint."""
|
|
81
|
+
|
|
82
|
+
samples: pd.DataFrame
|
|
83
|
+
genes: pd.DataFrame
|
|
84
|
+
events: pd.DataFrame
|
|
85
|
+
grid: pd.DataFrame
|
|
86
|
+
sample_burden: pd.DataFrame
|
|
87
|
+
mutation_spectrum: pd.DataFrame
|
|
88
|
+
msi: pd.DataFrame
|
|
89
|
+
stage: pd.DataFrame
|
|
90
|
+
gene_counts: pd.DataFrame
|
|
91
|
+
heatmap_rows: pd.DataFrame
|
|
92
|
+
heatmap_cells: pd.DataFrame
|
|
93
|
+
sample_domain: list[float]
|
|
94
|
+
gene_order: list[str]
|
|
95
|
+
burden_limit: int
|
|
96
|
+
spectrum_limit: int
|
|
97
|
+
count_limit: int
|
|
98
|
+
msi_limit: float
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def laml_oncoplot_data() -> LamlOncoplotData:
|
|
102
|
+
"""Prepare the TCGA LAML MAF for the oncoplot example.
|
|
103
|
+
|
|
104
|
+
Description:
|
|
105
|
+
Loads the 2,207-row TCGA LAML MAF and keeps the nine nonsynonymous
|
|
106
|
+
``Variant_Classification`` values used by the canonical maftools plot.
|
|
107
|
+
The ten genes with the most altered samples are selected, samples are
|
|
108
|
+
sorted by their gene-presence pattern, and repeated calls for one
|
|
109
|
+
sample/gene pair are displayed as ``Multi_Hit``. The returned tables
|
|
110
|
+
are the mutation matrix, sample burden, gene counts, and their drawing
|
|
111
|
+
grid; ``genes`` also contains the percentage label used by the chart.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
A mapping of chart table names to DataFrames and scalar display limits.
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
ImportError: If pandas is not installed.
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
>>> data = laml_oncoplot_data()
|
|
121
|
+
>>> data["events"].head()
|
|
122
|
+
"""
|
|
123
|
+
import pandas as pd
|
|
124
|
+
|
|
125
|
+
maf = load_dataset("tcga_laml_maf", as_format="dataframe")
|
|
126
|
+
annotations = load_dataset("tcga_laml_annotations", as_format="dataframe")
|
|
127
|
+
|
|
128
|
+
# The maftools oncoplot excludes silent and non-coding calls.
|
|
129
|
+
variants = maf.loc[
|
|
130
|
+
maf["Variant_Classification"].isin(_NON_SYNONYMOUS_CLASSES)
|
|
131
|
+
].copy()
|
|
132
|
+
# A stable sort keeps the alphabetical groupby order among genes with the
|
|
133
|
+
# same sample count, so the top-ten selection is reproducible.
|
|
134
|
+
altered_by_gene = (
|
|
135
|
+
variants.groupby("Hugo_Symbol")["Tumor_Sample_Barcode"]
|
|
136
|
+
.nunique()
|
|
137
|
+
.sort_values(ascending=False, kind="stable")
|
|
138
|
+
)
|
|
139
|
+
gene_order = altered_by_gene.head(10).index.tolist()
|
|
140
|
+
top_variants = variants[variants["Hugo_Symbol"].isin(gene_order)].copy()
|
|
141
|
+
|
|
142
|
+
# Sort samples by the presence pattern of the selected genes, then append
|
|
143
|
+
# samples with no selected-gene calls in their original MAF order.
|
|
144
|
+
presence = (
|
|
145
|
+
top_variants.assign(_present=1)
|
|
146
|
+
.pivot_table(
|
|
147
|
+
index="Tumor_Sample_Barcode",
|
|
148
|
+
columns="Hugo_Symbol",
|
|
149
|
+
values="_present",
|
|
150
|
+
aggfunc="max",
|
|
151
|
+
fill_value=0,
|
|
152
|
+
)
|
|
153
|
+
.reindex(columns=gene_order)
|
|
154
|
+
)
|
|
155
|
+
sample_order = presence.sort_values(
|
|
156
|
+
gene_order,
|
|
157
|
+
ascending=[False] * len(gene_order),
|
|
158
|
+
kind="stable",
|
|
159
|
+
).index.tolist()
|
|
160
|
+
all_samples = maf["Tumor_Sample_Barcode"].drop_duplicates().tolist()
|
|
161
|
+
sample_order.extend(sample for sample in all_samples if sample not in sample_order)
|
|
162
|
+
sample_positions = {sample: index for index, sample in enumerate(sample_order)}
|
|
163
|
+
gene_positions = {gene: index for index, gene in enumerate(gene_order)}
|
|
164
|
+
|
|
165
|
+
# Aggregate repeated calls without a row-wise Python loop.
|
|
166
|
+
events = (
|
|
167
|
+
top_variants.groupby(["Tumor_Sample_Barcode", "Hugo_Symbol"], sort=False)
|
|
168
|
+
.agg(
|
|
169
|
+
hit_count=("Variant_Classification", "size"),
|
|
170
|
+
first_class=("Variant_Classification", "first"),
|
|
171
|
+
)
|
|
172
|
+
.reset_index()
|
|
173
|
+
.rename(
|
|
174
|
+
columns={
|
|
175
|
+
"Tumor_Sample_Barcode": "sample",
|
|
176
|
+
"Hugo_Symbol": "gene",
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
events["class"] = events["first_class"].where(
|
|
181
|
+
events["hit_count"].eq(1), "Multi_Hit"
|
|
182
|
+
)
|
|
183
|
+
events = events.drop(columns=["hit_count", "first_class"])
|
|
184
|
+
events["sample_order"] = events["sample"].map(sample_positions)
|
|
185
|
+
events["gene_order"] = events["gene"].map(gene_positions)
|
|
186
|
+
|
|
187
|
+
annotation_lookup = annotations.set_index("Tumor_Sample_Barcode")
|
|
188
|
+
sample_top_gene_counts = events.groupby("sample")["gene"].nunique()
|
|
189
|
+
tmb_totals = variants.groupby("Tumor_Sample_Barcode").size()
|
|
190
|
+
samples = pd.DataFrame({"sample": sample_order})
|
|
191
|
+
samples["sample_order"] = range(len(samples))
|
|
192
|
+
samples["fab_classification"] = (
|
|
193
|
+
samples["sample"].map(annotation_lookup["FAB_classification"]).fillna("NA")
|
|
194
|
+
)
|
|
195
|
+
samples["altered_genes"] = (
|
|
196
|
+
samples["sample"].map(sample_top_gene_counts).fillna(0).astype(int)
|
|
197
|
+
)
|
|
198
|
+
samples["tmb_total"] = samples["sample"].map(tmb_totals).fillna(0).astype(int)
|
|
199
|
+
|
|
200
|
+
mutation_events = top_variants.groupby("Hugo_Symbol").size()
|
|
201
|
+
genes = pd.DataFrame({"gene": gene_order})
|
|
202
|
+
genes["gene_order"] = genes["gene"].map(gene_positions)
|
|
203
|
+
genes["altered_samples"] = genes["gene"].map(altered_by_gene).astype(int)
|
|
204
|
+
genes["mutation_events"] = genes["gene"].map(mutation_events).astype(int)
|
|
205
|
+
genes["percent_altered"] = (
|
|
206
|
+
(genes["altered_samples"] / len(sample_order) * 100).round().astype(int)
|
|
207
|
+
)
|
|
208
|
+
genes["label"] = genes["percent_altered"].map(lambda value: f"{value}%")
|
|
209
|
+
|
|
210
|
+
grid = _sample_gene_grid(sample_order, gene_order, sample_positions, gene_positions)
|
|
211
|
+
sample_tmb = (
|
|
212
|
+
variants.groupby(["Tumor_Sample_Barcode", "Variant_Classification"], sort=False)
|
|
213
|
+
.size()
|
|
214
|
+
.reset_index(name="count")
|
|
215
|
+
.rename(
|
|
216
|
+
columns={
|
|
217
|
+
"Tumor_Sample_Barcode": "sample",
|
|
218
|
+
"Variant_Classification": "class",
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
sample_tmb["sample_order"] = sample_tmb["sample"].map(sample_positions)
|
|
223
|
+
gene_counts = (
|
|
224
|
+
events.groupby(["gene", "gene_order", "class"], sort=False)
|
|
225
|
+
.size()
|
|
226
|
+
.reset_index(name="count")
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
sample_categories = pd.CategoricalDtype(sample_order, ordered=True)
|
|
230
|
+
gene_categories = pd.CategoricalDtype(gene_order, ordered=True)
|
|
231
|
+
for frame in (samples, events, grid, sample_tmb):
|
|
232
|
+
frame["sample"] = frame["sample"].astype(sample_categories)
|
|
233
|
+
for frame in (genes, events, grid, gene_counts):
|
|
234
|
+
frame["gene"] = frame["gene"].astype(gene_categories)
|
|
235
|
+
|
|
236
|
+
count_limit = int(genes["altered_samples"].max())
|
|
237
|
+
return {
|
|
238
|
+
"samples": samples,
|
|
239
|
+
"genes": genes,
|
|
240
|
+
"events": events,
|
|
241
|
+
"grid": grid,
|
|
242
|
+
"sample_tmb": sample_tmb,
|
|
243
|
+
"gene_counts": gene_counts,
|
|
244
|
+
"total_samples": len(samples),
|
|
245
|
+
"altered_samples": int(samples["altered_genes"].gt(0).sum()),
|
|
246
|
+
"tmb_limit": int(samples["tmb_total"].max()),
|
|
247
|
+
"count_limit": count_limit,
|
|
248
|
+
"sample_domain": [-0.5, len(sample_order) - 0.5],
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def luad_oncoprint_data() -> LuadOncoprintData:
|
|
253
|
+
"""Prepare the pyoncoprint TCGA LUAD table for visualization.
|
|
254
|
+
|
|
255
|
+
Description:
|
|
256
|
+
Loads the wide ``tcga.tsv`` example table (145 tracks and 507 sample
|
|
257
|
+
columns). Recurrence tracks are melted and combined by gene/sample,
|
|
258
|
+
abbreviations are expanded to the display classes used by the example,
|
|
259
|
+
genes and samples are ranked by recurrence, and the clinical and
|
|
260
|
+
heatmap tracks are reshaped into long chart tables. The returned event
|
|
261
|
+
table contains every mutation class; the example filters it into the
|
|
262
|
+
rectangle and star layers at render time.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
A mapping of chart table names to DataFrames and scalar display limits.
|
|
266
|
+
|
|
267
|
+
Raises:
|
|
268
|
+
ImportError: If pandas is not installed.
|
|
269
|
+
|
|
270
|
+
Example:
|
|
271
|
+
>>> data = luad_oncoprint_data()
|
|
272
|
+
>>> data["events"].head()
|
|
273
|
+
"""
|
|
274
|
+
import pandas as pd
|
|
275
|
+
|
|
276
|
+
source = load_dataset("pyoncoprint_tcga", as_format="dataframe")
|
|
277
|
+
sample_columns = source.columns[2:].tolist()
|
|
278
|
+
recurrence_rows = source.loc[
|
|
279
|
+
source["track_type"].isin(
|
|
280
|
+
["MUTATIONS", "CNA", "STRUCTURAL_VARIANT", "PROTEIN", "MRNA"]
|
|
281
|
+
)
|
|
282
|
+
].copy()
|
|
283
|
+
recurrence_rows.loc[:, sample_columns] = recurrence_rows[sample_columns].replace(
|
|
284
|
+
_LUAD_REPLACEMENTS
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Melt the source's repeated gene rows, then retain source order while
|
|
288
|
+
# joining multiple alteration tracks into one cell per gene/sample.
|
|
289
|
+
recurrence_long = recurrence_rows.melt(
|
|
290
|
+
id_vars=["track_name", "track_type"],
|
|
291
|
+
value_vars=sample_columns,
|
|
292
|
+
var_name="sample",
|
|
293
|
+
value_name="class",
|
|
294
|
+
).dropna(subset=["class"])
|
|
295
|
+
recurrence_long["class"] = recurrence_long["class"].astype(str)
|
|
296
|
+
recurrence_long = recurrence_long[recurrence_long["class"].ne("")].copy()
|
|
297
|
+
recurrence = (
|
|
298
|
+
recurrence_long.groupby(["track_name", "sample"], sort=False)["class"]
|
|
299
|
+
.agg(",".join)
|
|
300
|
+
.unstack(fill_value="")
|
|
301
|
+
.reindex(columns=sample_columns, fill_value="")
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Gene ranking counts each distinct alteration class once per gene/sample.
|
|
305
|
+
score_rows = (
|
|
306
|
+
recurrence.rename_axis("gene")
|
|
307
|
+
.reset_index()
|
|
308
|
+
.melt(id_vars="gene", var_name="sample", value_name="classes")
|
|
309
|
+
)
|
|
310
|
+
score_rows["class"] = score_rows["classes"].str.split(",")
|
|
311
|
+
score_rows = score_rows.explode("class")
|
|
312
|
+
score_rows = score_rows[score_rows["class"].ne("")].drop_duplicates(
|
|
313
|
+
["gene", "sample", "class"]
|
|
314
|
+
)
|
|
315
|
+
gene_scores = (
|
|
316
|
+
score_rows.groupby("gene").size().reindex(recurrence.index, fill_value=0)
|
|
317
|
+
)
|
|
318
|
+
# Negate instead of reversing so that ties keep their input order.
|
|
319
|
+
gene_order = gene_scores.index[
|
|
320
|
+
np.argsort(-gene_scores.to_numpy(), kind="stable")
|
|
321
|
+
].tolist()
|
|
322
|
+
recurrence = recurrence.reindex(gene_order)
|
|
323
|
+
|
|
324
|
+
mutation_weights = {
|
|
325
|
+
mutation_class: weight
|
|
326
|
+
for weight, mutation_class in enumerate(
|
|
327
|
+
reversed(_LUAD_MUTATION_CLASSES), start=1
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
weighted_events = score_rows[
|
|
331
|
+
score_rows["class"].isin(_LUAD_MUTATION_CLASSES)
|
|
332
|
+
].copy()
|
|
333
|
+
weighted_events["weight"] = weighted_events["class"].map(mutation_weights)
|
|
334
|
+
weighted = (
|
|
335
|
+
weighted_events.pivot_table(
|
|
336
|
+
index="gene",
|
|
337
|
+
columns="sample",
|
|
338
|
+
values="weight",
|
|
339
|
+
aggfunc="sum",
|
|
340
|
+
fill_value=0,
|
|
341
|
+
)
|
|
342
|
+
.reindex(index=gene_order[::-1], columns=sample_columns, fill_value=0)
|
|
343
|
+
.to_numpy()
|
|
344
|
+
)
|
|
345
|
+
# np.lexsort uses the final row as the primary key; reversing the gene
|
|
346
|
+
# order makes the highest-ranked gene the primary sample-sorting key.
|
|
347
|
+
sorted_sample_indices = np.lexsort(weighted)[::-1]
|
|
348
|
+
sorted_samples = np.asarray(sample_columns)[sorted_sample_indices].tolist()
|
|
349
|
+
|
|
350
|
+
events = weighted_events[["gene", "sample", "class"]].copy()
|
|
351
|
+
old_sample_positions = {
|
|
352
|
+
sample: index for index, sample in enumerate(sorted_samples)
|
|
353
|
+
}
|
|
354
|
+
events["old_sample_order"] = events["sample"].map(old_sample_positions)
|
|
355
|
+
events["gene_order"] = events["gene"].map(
|
|
356
|
+
{gene: index for index, gene in enumerate(gene_order)}
|
|
357
|
+
)
|
|
358
|
+
active_old_orders = sorted(events["old_sample_order"].unique())
|
|
359
|
+
active_samples = [sorted_samples[index] for index in active_old_orders]
|
|
360
|
+
old_to_new = {old: new for new, old in enumerate(active_old_orders)}
|
|
361
|
+
sample_positions = {sample: index for index, sample in enumerate(active_samples)}
|
|
362
|
+
events = events[events["sample"].isin(active_samples)].copy()
|
|
363
|
+
events["sample_order"] = events["old_sample_order"].map(old_to_new)
|
|
364
|
+
events = events.drop(columns="old_sample_order")
|
|
365
|
+
|
|
366
|
+
sample_burden = (
|
|
367
|
+
events.groupby(["sample", "sample_order", "class"], sort=False)
|
|
368
|
+
.size()
|
|
369
|
+
.reset_index(name="count")
|
|
370
|
+
)
|
|
371
|
+
gene_counts = (
|
|
372
|
+
events.groupby(["gene", "gene_order", "class"], sort=False)
|
|
373
|
+
.size()
|
|
374
|
+
.reset_index(name="count")
|
|
375
|
+
)
|
|
376
|
+
altered_by_gene = events.groupby("gene")["sample"].nunique()
|
|
377
|
+
|
|
378
|
+
samples = pd.DataFrame(
|
|
379
|
+
{"sample": active_samples, "sample_order": range(len(active_samples))}
|
|
380
|
+
)
|
|
381
|
+
genes = pd.DataFrame(
|
|
382
|
+
{
|
|
383
|
+
"gene": gene_order,
|
|
384
|
+
"gene_order": range(len(gene_order)),
|
|
385
|
+
"altered_samples": [
|
|
386
|
+
int(altered_by_gene.get(gene, 0)) for gene in gene_order
|
|
387
|
+
],
|
|
388
|
+
}
|
|
389
|
+
)
|
|
390
|
+
genes["label"] = genes["altered_samples"].map(
|
|
391
|
+
lambda count: f"{round(count / len(sample_columns) * 100)}%"
|
|
392
|
+
)
|
|
393
|
+
grid = _sample_gene_grid(
|
|
394
|
+
active_samples,
|
|
395
|
+
gene_order,
|
|
396
|
+
sample_positions,
|
|
397
|
+
{gene: index for index, gene in enumerate(gene_order)},
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
clinical = source[source["track_type"].eq("CLINICAL")].copy()
|
|
401
|
+
spectrum_source = clinical[
|
|
402
|
+
clinical["track_name"].str.startswith("Mutation spectrum")
|
|
403
|
+
].copy()
|
|
404
|
+
spectrum_source["class"] = spectrum_source["track_name"].str.extract(r"\(([^)]+)\)")
|
|
405
|
+
mutation_spectrum = (
|
|
406
|
+
spectrum_source.drop(columns=["track_name", "track_type"])
|
|
407
|
+
.set_index("class")
|
|
408
|
+
.T.rename_axis("sample")
|
|
409
|
+
.reset_index()
|
|
410
|
+
.melt(id_vars="sample", var_name="class", value_name="count")
|
|
411
|
+
.dropna(subset=["count"])
|
|
412
|
+
)
|
|
413
|
+
mutation_spectrum["count"] = mutation_spectrum["count"].astype(int)
|
|
414
|
+
mutation_spectrum = mutation_spectrum[
|
|
415
|
+
mutation_spectrum["sample"].isin(active_samples)
|
|
416
|
+
& mutation_spectrum["count"].gt(0)
|
|
417
|
+
].copy()
|
|
418
|
+
mutation_spectrum["sample_order"] = mutation_spectrum["sample"].map(
|
|
419
|
+
sample_positions
|
|
420
|
+
)
|
|
421
|
+
mutation_spectrum = mutation_spectrum.sort_values(
|
|
422
|
+
["sample_order", "class"], kind="stable"
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
clinical_tracks = clinical.set_index("track_name")
|
|
426
|
+
|
|
427
|
+
def clinical_track(name: str, value_name: str) -> pd.DataFrame:
|
|
428
|
+
row = clinical_tracks.loc[name, sample_columns]
|
|
429
|
+
frame = row.rename(value_name).rename_axis("sample").reset_index()
|
|
430
|
+
frame = frame[frame["sample"].isin(active_samples)].copy()
|
|
431
|
+
frame["sample_order"] = frame["sample"].map(sample_positions)
|
|
432
|
+
return frame.sort_values("sample_order")
|
|
433
|
+
|
|
434
|
+
msi = clinical_track("MSI MANTIS Score", "value")
|
|
435
|
+
msi["value"] = msi["value"].fillna(0).astype(float)
|
|
436
|
+
stage = clinical_track(
|
|
437
|
+
"American Joint Committee on Cancer Tumor Stage Code", "stage"
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
heatmap_source = source[source["track_type"].str.startswith("HEATMAP")].copy()
|
|
441
|
+
heatmap_source["group"] = np.select(
|
|
442
|
+
[
|
|
443
|
+
heatmap_source["track_type"].eq("HEATMAP MRNA_EXPRESSION Z-SCORE"),
|
|
444
|
+
heatmap_source["track_name"].eq("Dicipivirus"),
|
|
445
|
+
],
|
|
446
|
+
[_LUAD_HEATMAP_GROUPS[0], _LUAD_HEATMAP_GROUPS[2]],
|
|
447
|
+
default=_LUAD_HEATMAP_GROUPS[1],
|
|
448
|
+
)
|
|
449
|
+
heatmap_source["group_order"] = heatmap_source["group"].map(
|
|
450
|
+
_LUAD_HEATMAP_GROUP_ORDER
|
|
451
|
+
)
|
|
452
|
+
heatmap_source["track_order"] = heatmap_source.groupby(
|
|
453
|
+
"group", sort=False
|
|
454
|
+
).cumcount()
|
|
455
|
+
heatmap_rows = heatmap_source[
|
|
456
|
+
["group", "group_order", "track_name", "track_order"]
|
|
457
|
+
].rename(columns={"track_name": "track"})
|
|
458
|
+
heatmap_cells = (
|
|
459
|
+
heatmap_source.melt(
|
|
460
|
+
id_vars=["group", "group_order", "track_name", "track_order"],
|
|
461
|
+
value_vars=sample_columns,
|
|
462
|
+
var_name="sample",
|
|
463
|
+
value_name="value",
|
|
464
|
+
)
|
|
465
|
+
.loc[lambda frame: frame["sample"].isin(active_samples)]
|
|
466
|
+
.assign(value=lambda frame: pd.to_numeric(frame["value"], errors="coerce"))
|
|
467
|
+
.rename(columns={"track_name": "track"})
|
|
468
|
+
)
|
|
469
|
+
heatmap_cells["sample_order"] = heatmap_cells["sample"].map(sample_positions)
|
|
470
|
+
heatmap_cells = heatmap_cells[
|
|
471
|
+
[
|
|
472
|
+
"group",
|
|
473
|
+
"group_order",
|
|
474
|
+
"track",
|
|
475
|
+
"track_order",
|
|
476
|
+
"sample",
|
|
477
|
+
"sample_order",
|
|
478
|
+
"value",
|
|
479
|
+
]
|
|
480
|
+
]
|
|
481
|
+
|
|
482
|
+
gene_categories = pd.CategoricalDtype(gene_order, ordered=True)
|
|
483
|
+
for frame in (genes, events, grid, gene_counts):
|
|
484
|
+
frame["gene"] = frame["gene"].astype(gene_categories)
|
|
485
|
+
|
|
486
|
+
burden_limit = int(sample_burden.groupby("sample_order")["count"].sum().max())
|
|
487
|
+
spectrum_limit = int(mutation_spectrum.groupby("sample_order")["count"].sum().max())
|
|
488
|
+
count_limit = int(gene_counts.groupby("gene", observed=True)["count"].sum().max())
|
|
489
|
+
return {
|
|
490
|
+
"samples": samples,
|
|
491
|
+
"genes": genes,
|
|
492
|
+
"events": events,
|
|
493
|
+
"grid": grid,
|
|
494
|
+
"sample_burden": sample_burden,
|
|
495
|
+
"mutation_spectrum": mutation_spectrum,
|
|
496
|
+
"msi": msi,
|
|
497
|
+
"stage": stage,
|
|
498
|
+
"gene_counts": gene_counts,
|
|
499
|
+
"heatmap_rows": heatmap_rows,
|
|
500
|
+
"heatmap_cells": heatmap_cells,
|
|
501
|
+
"sample_domain": [-0.5, len(active_samples) - 0.5],
|
|
502
|
+
"gene_order": gene_order,
|
|
503
|
+
"burden_limit": burden_limit,
|
|
504
|
+
"spectrum_limit": spectrum_limit,
|
|
505
|
+
"count_limit": count_limit,
|
|
506
|
+
"msi_limit": float(msi["value"].max()),
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def _sample_gene_grid(
|
|
511
|
+
samples: list[str],
|
|
512
|
+
genes: list[str],
|
|
513
|
+
sample_positions: dict[str, int],
|
|
514
|
+
gene_positions: dict[str, int],
|
|
515
|
+
) -> pd.DataFrame:
|
|
516
|
+
import pandas as pd
|
|
517
|
+
|
|
518
|
+
grid = pd.MultiIndex.from_product(
|
|
519
|
+
[samples, genes], names=["sample", "gene"]
|
|
520
|
+
).to_frame(index=False)
|
|
521
|
+
grid["sample_order"] = grid["sample"].map(sample_positions)
|
|
522
|
+
grid["gene_order"] = grid["gene"].map(gene_positions)
|
|
523
|
+
return grid
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
id,dex,celltype,geo_id
|
|
2
|
+
SRR1039508,control,N61311,GSM1275862
|
|
3
|
+
SRR1039509,treated,N61311,GSM1275863
|
|
4
|
+
SRR1039512,control,N052611,GSM1275866
|
|
5
|
+
SRR1039513,treated,N052611,GSM1275867
|
|
6
|
+
SRR1039516,control,N080611,GSM1275870
|
|
7
|
+
SRR1039517,treated,N080611,GSM1275871
|
|
8
|
+
SRR1039520,control,N061011,GSM1275874
|
|
9
|
+
SRR1039521,treated,N061011,GSM1275875
|