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.
Files changed (64) hide show
  1. genome_spy/__init__.py +199 -0
  2. genome_spy/_chart_authoring.py +231 -0
  3. genome_spy/_conditions.py +72 -0
  4. genome_spy/_embed.py +87 -0
  5. genome_spy/_expressions.py +271 -0
  6. genome_spy/_parameters.py +267 -0
  7. genome_spy/_render.py +207 -0
  8. genome_spy/_utils.py +75 -0
  9. genome_spy/_widget.py +262 -0
  10. genome_spy/api.py +198 -0
  11. genome_spy/arrow.py +155 -0
  12. genome_spy/channels.py +193 -0
  13. genome_spy/chart.py +1240 -0
  14. genome_spy/data.py +56 -0
  15. genome_spy/data_transformers.py +267 -0
  16. genome_spy/datasets/__init__.py +189 -0
  17. genome_spy/datasets/_airway.py +219 -0
  18. genome_spy/datasets/_annotations.py +37 -0
  19. genome_spy/datasets/_gistic.py +43 -0
  20. genome_spy/datasets/_grammar.py +66 -0
  21. genome_spy/datasets/_hapmap.py +180 -0
  22. genome_spy/datasets/_mutation.py +289 -0
  23. genome_spy/datasets/_oncoprint.py +523 -0
  24. genome_spy/datasets/data/airway_metadata.csv +9 -0
  25. genome_spy/datasets/data/airway_scaledcounts.csv +38695 -0
  26. genome_spy/datasets/data/brca.maf.gz +0 -0
  27. genome_spy/datasets/data/hapmap_gwas.csv +14413 -0
  28. genome_spy/datasets/data/mutation_impact_reference.json +27 -0
  29. genome_spy/datasets/data/oncoprint_dataset3.json +266 -0
  30. genome_spy/datasets/data/p53_sequence_comparison.json.gz +0 -0
  31. genome_spy/datasets/data/pik3ca_mutations.json +1 -0
  32. genome_spy/datasets/data/pik3ca_tcga_brca_lollipop.json +38 -0
  33. genome_spy/datasets/data/refseq_gene_bodies.csv.gz +0 -0
  34. genome_spy/datasets/data/tal1_alphagenome_reference.json.gz +0 -0
  35. genome_spy/datasets/data/tcga.tsv +146 -0
  36. genome_spy/datasets/data/tcga_laml.maf.gz +0 -0
  37. genome_spy/datasets/data/tcga_laml_annot.tsv +201 -0
  38. genome_spy/datasets/data/tcga_laml_combined_oncoplot.json.gz +0 -0
  39. genome_spy/datasets/data/tcga_ov_gistic_lesions.tsv.gz +0 -0
  40. genome_spy/datasets/data/tcga_ov_gistic_scores.tsv.gz +0 -0
  41. genome_spy/helpers.py +185 -0
  42. genome_spy/jupyter.py +5 -0
  43. genome_spy/py.typed +0 -0
  44. genome_spy/schema/__init__.py +784 -0
  45. genome_spy/schema/_kwds.py +1394 -0
  46. genome_spy/schema/_typing.py +186 -0
  47. genome_spy/schema/capabilities.json +593 -0
  48. genome_spy/schema/channels.py +8943 -0
  49. genome_spy/schema/composition.py +1064 -0
  50. genome_spy/schema/core.py +51821 -0
  51. genome_spy/schema/ergonomics.py +2056 -0
  52. genome_spy/schema/expressions.py +476 -0
  53. genome_spy/schema/genome-spy-schema.json +33657 -0
  54. genome_spy/schema/lazy.py +326 -0
  55. genome_spy/schema/mixins.py +11684 -0
  56. genome_spy/schemapi.py +264 -0
  57. genome_spy/static/widget.js +345 -0
  58. genome_spy_python-0.1.0.dist-info/METADATA +185 -0
  59. genome_spy_python-0.1.0.dist-info/RECORD +64 -0
  60. genome_spy_python-0.1.0.dist-info/WHEEL +4 -0
  61. genome_spy_python-0.1.0.dist-info/licenses/LICENSE +21 -0
  62. genome_spy_python-0.1.0.dist-info/licenses/LICENSES/ALTAIR-BSD-3-Clause.txt +27 -0
  63. genome_spy_python-0.1.0.dist-info/licenses/LICENSES/GALLERY-DATA-MIT.txt +22 -0
  64. 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