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
genome_spy/data.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Helpers for schema-backed GenomeSpy data sources."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from typing import Any, cast
|
|
7
|
+
|
|
8
|
+
from genome_spy.schema import Data, ExprRef, LazyDataParams, UrlTemplate
|
|
9
|
+
from genome_spy.schema.lazy import LazyDataMethodMixin
|
|
10
|
+
|
|
11
|
+
__all__ = ["Data", "LazyNamespace", "lazy"]
|
|
12
|
+
|
|
13
|
+
_LazyUrl = str | Sequence[str] | ExprRef | dict[str, Any] | UrlTemplate
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LazyNamespace(LazyDataMethodMixin):
|
|
17
|
+
"""Convenience builders for GenomeSpy lazy data sources.
|
|
18
|
+
|
|
19
|
+
Description:
|
|
20
|
+
These helpers build schema-backed :class:`genome_spy.schema.Data`
|
|
21
|
+
objects with a populated ``lazy`` block so callers can stay within the
|
|
22
|
+
handwritten chart API instead of assembling nested dictionaries by hand.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
>>> lazy.bigwig("https://example.test/signal.bw")
|
|
26
|
+
>>> lazy.gff3("https://example.test/genes.gff3.gz", windowSize=2_000_000)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def source(self, type: str, url: _LazyUrl, /, **kwargs: Any) -> Data:
|
|
30
|
+
"""Create a lazy data source of an arbitrary GenomeSpy type.
|
|
31
|
+
|
|
32
|
+
Description:
|
|
33
|
+
This is the generic escape hatch for lazy data sources. Named
|
|
34
|
+
helpers such as :meth:`bigwig` and :meth:`gff3` cover each
|
|
35
|
+
schema-defined URL-backed source type.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
type: GenomeSpy lazy source type such as ``"bigwig"`` or
|
|
39
|
+
``"gff3"``.
|
|
40
|
+
url: Remote data URL.
|
|
41
|
+
**kwargs: Additional lazy-source parameters supported by GenomeSpy.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
A schema-backed :class:`genome_spy.schema.Data` object.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
No exceptions are raised directly here.
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
>>> lazy.source("bigwig", "https://example.test/signal.bw")
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
return Data(lazy=LazyDataParams(type=cast(Any, type), url=url, **kwargs))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
lazy = LazyNamespace()
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""Settings for preparing chart data during serialization."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import Iterator
|
|
7
|
+
from contextlib import AbstractContextManager
|
|
8
|
+
from hashlib import sha256
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from genome_spy.schema.core import _ROOT_SCHEMA
|
|
12
|
+
from genome_spy.schemapi import SchemaBase, Undefined, UndefinedType
|
|
13
|
+
|
|
14
|
+
__all__ = ["DataTransformerSettings", "data_transformers"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _RestoreSettings(AbstractContextManager["DataTransformerSettings"]):
|
|
18
|
+
def __init__(self, settings: DataTransformerSettings, previous: bool) -> None:
|
|
19
|
+
self.settings = settings
|
|
20
|
+
self.previous = previous
|
|
21
|
+
|
|
22
|
+
def __enter__(self) -> DataTransformerSettings:
|
|
23
|
+
return self.settings
|
|
24
|
+
|
|
25
|
+
def __exit__(self, *args: Any) -> None:
|
|
26
|
+
self.settings.consolidate_datasets = self.previous
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DataTransformerSettings:
|
|
30
|
+
"""Control whether exported charts share repeated inline tables.
|
|
31
|
+
|
|
32
|
+
Description:
|
|
33
|
+
Consolidation is enabled by default. This settings object supports
|
|
34
|
+
dataset consolidation only, not a registry of transformer plugins.
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
>>> with data_transformers.enable(consolidate_datasets=False):
|
|
38
|
+
... spec = chart.to_dict()
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self) -> None:
|
|
42
|
+
self._consolidate_datasets = True
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def consolidate_datasets(self) -> bool:
|
|
46
|
+
"""Whether eligible inline tables become shared named datasets."""
|
|
47
|
+
return self._consolidate_datasets
|
|
48
|
+
|
|
49
|
+
@consolidate_datasets.setter
|
|
50
|
+
def consolidate_datasets(self, value: bool) -> None:
|
|
51
|
+
if not isinstance(value, bool):
|
|
52
|
+
raise TypeError("consolidate_datasets must be a boolean.")
|
|
53
|
+
self._consolidate_datasets = value
|
|
54
|
+
|
|
55
|
+
def enable(
|
|
56
|
+
self, *, consolidate_datasets: bool | UndefinedType = Undefined
|
|
57
|
+
) -> AbstractContextManager[DataTransformerSettings]:
|
|
58
|
+
"""Apply a setting, optionally restoring it after a ``with`` block.
|
|
59
|
+
|
|
60
|
+
Description:
|
|
61
|
+
The change takes effect immediately. When used as a context manager,
|
|
62
|
+
the previous value is restored even if the block raises an error.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
consolidate_datasets: Whether to share eligible inline tables.
|
|
66
|
+
Omit to keep the current setting.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
A context manager that restores the previous setting.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
TypeError: If the setting is not a boolean.
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
>>> with data_transformers.enable(consolidate_datasets=False):
|
|
76
|
+
... spec = chart.to_dict()
|
|
77
|
+
"""
|
|
78
|
+
previous = self.consolidate_datasets
|
|
79
|
+
if not isinstance(consolidate_datasets, UndefinedType):
|
|
80
|
+
self.consolidate_datasets = consolidate_datasets
|
|
81
|
+
return _RestoreSettings(self, previous)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
data_transformers = DataTransformerSettings()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _schema_parts(
|
|
88
|
+
schema: dict[str, Any], seen: set[int] | None = None
|
|
89
|
+
) -> Iterator[dict[str, Any]]:
|
|
90
|
+
"""Resolve schema alternatives without descending into instance fields."""
|
|
91
|
+
if seen is None:
|
|
92
|
+
seen = set()
|
|
93
|
+
if id(schema) in seen:
|
|
94
|
+
return
|
|
95
|
+
seen.add(id(schema))
|
|
96
|
+
yield schema
|
|
97
|
+
ref = schema.get("$ref")
|
|
98
|
+
if isinstance(ref, str):
|
|
99
|
+
name = ref.removeprefix("#/definitions/")
|
|
100
|
+
target = _ROOT_SCHEMA.get("definitions", {}).get(name, {})
|
|
101
|
+
yield from _schema_parts(target, seen)
|
|
102
|
+
for keyword in ("anyOf", "oneOf", "allOf"):
|
|
103
|
+
for alternative in schema.get(keyword, []):
|
|
104
|
+
yield from _schema_parts(alternative, seen)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _data_slots(
|
|
108
|
+
value: Any,
|
|
109
|
+
schema: dict[str, Any] = _ROOT_SCHEMA,
|
|
110
|
+
*,
|
|
111
|
+
owner: str | None = None,
|
|
112
|
+
scoped: bool = False,
|
|
113
|
+
root: bool = True,
|
|
114
|
+
include_templates: bool = False,
|
|
115
|
+
) -> Iterator[tuple[str, dict[str, Any], str | None, bool]]:
|
|
116
|
+
"""Visit only schema-declared data sources and dataset declarations.
|
|
117
|
+
|
|
118
|
+
Open mappings (including rows, metadata, and parameter values) are opaque.
|
|
119
|
+
Templates are separate serialization roots, not live view instances.
|
|
120
|
+
Schema references also discover sources inside new transform definitions.
|
|
121
|
+
"""
|
|
122
|
+
if isinstance(value, SchemaBase):
|
|
123
|
+
value = value._kwds
|
|
124
|
+
if not isinstance(value, (dict, list, tuple)):
|
|
125
|
+
return
|
|
126
|
+
parts = list(_schema_parts(schema))
|
|
127
|
+
# Stop at a data source: its records are user data, not chart grammar.
|
|
128
|
+
source_refs = {"#/definitions/InlineData", "#/definitions/NamedData"}
|
|
129
|
+
if (
|
|
130
|
+
isinstance(value, dict)
|
|
131
|
+
and value.keys() & {"values", "name", "url", "lazy", "sequence"}
|
|
132
|
+
and any(p.get("$ref") in source_refs for p in parts)
|
|
133
|
+
):
|
|
134
|
+
yield "data", value, owner, scoped
|
|
135
|
+
return
|
|
136
|
+
if isinstance(value, (list, tuple)):
|
|
137
|
+
items = [p["items"] for p in parts if isinstance(p.get("items"), dict)]
|
|
138
|
+
if items:
|
|
139
|
+
for item in value:
|
|
140
|
+
yield from _data_slots(
|
|
141
|
+
item,
|
|
142
|
+
{"anyOf": items},
|
|
143
|
+
owner=owner,
|
|
144
|
+
scoped=scoped,
|
|
145
|
+
root=False,
|
|
146
|
+
include_templates=include_templates,
|
|
147
|
+
)
|
|
148
|
+
return
|
|
149
|
+
# Merge property locations, not validation rules. The generated wrappers
|
|
150
|
+
# remain responsible for validating which union branch the value satisfies.
|
|
151
|
+
properties: dict[str, list[dict[str, Any]]] = {}
|
|
152
|
+
for part in parts:
|
|
153
|
+
for key, child_schema in part.get("properties", {}).items():
|
|
154
|
+
properties.setdefault(key, []).append(child_schema)
|
|
155
|
+
if not root and "datasets" in properties and "datasets" in value:
|
|
156
|
+
name = value.get("name")
|
|
157
|
+
owner = name if isinstance(name, str) and name else None
|
|
158
|
+
scoped = True
|
|
159
|
+
for key, child_schemas in properties.items():
|
|
160
|
+
if key not in value:
|
|
161
|
+
continue
|
|
162
|
+
if key == "templates" and isinstance(value[key], dict):
|
|
163
|
+
for template in value[key].values():
|
|
164
|
+
if isinstance(template, SchemaBase):
|
|
165
|
+
template = template._kwds
|
|
166
|
+
if isinstance(template, dict):
|
|
167
|
+
yield "template", template, None, True
|
|
168
|
+
if include_templates:
|
|
169
|
+
yield from _data_slots(template, include_templates=True)
|
|
170
|
+
elif key == "datasets":
|
|
171
|
+
if isinstance(value[key], dict):
|
|
172
|
+
yield "datasets", value[key], owner, scoped
|
|
173
|
+
else:
|
|
174
|
+
yield from _data_slots(
|
|
175
|
+
value[key],
|
|
176
|
+
{"anyOf": child_schemas},
|
|
177
|
+
owner=owner,
|
|
178
|
+
scoped=scoped,
|
|
179
|
+
root=False,
|
|
180
|
+
include_templates=include_templates,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Typed maps (Record[str, T]) contain grammar; arbitrary maps do not.
|
|
184
|
+
additional = [
|
|
185
|
+
part["additionalProperties"]
|
|
186
|
+
for part in parts
|
|
187
|
+
if isinstance(part.get("additionalProperties"), dict)
|
|
188
|
+
]
|
|
189
|
+
if additional:
|
|
190
|
+
for key in value.keys() - properties.keys():
|
|
191
|
+
yield from _data_slots(
|
|
192
|
+
value[key],
|
|
193
|
+
{"anyOf": additional},
|
|
194
|
+
owner=owner,
|
|
195
|
+
scoped=scoped,
|
|
196
|
+
root=False,
|
|
197
|
+
include_templates=include_templates,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _dataset_name(canonical: str) -> str:
|
|
202
|
+
"""Name normalized content consistently, independently of object identity."""
|
|
203
|
+
return "data-" + sha256(canonical.encode()).hexdigest()[:32]
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class _DatasetConsolidation:
|
|
207
|
+
"""Collect tables once per serialization, reserving authored names first."""
|
|
208
|
+
|
|
209
|
+
def __init__(self, authored: Any) -> None:
|
|
210
|
+
self.reserved: set[str] = set()
|
|
211
|
+
for kind, value, _, _ in _data_slots(authored, include_templates=True):
|
|
212
|
+
if kind == "datasets":
|
|
213
|
+
self.reserved.update(value)
|
|
214
|
+
elif kind == "data" and isinstance(value.get("name"), str):
|
|
215
|
+
self.reserved.add(value["name"])
|
|
216
|
+
self.names: dict[str, str] = {}
|
|
217
|
+
self.datasets: dict[str, Any] = {}
|
|
218
|
+
|
|
219
|
+
def source(self, source: Any) -> Any:
|
|
220
|
+
if not isinstance(source, dict) or set(source) - {"values", "description"}:
|
|
221
|
+
return source
|
|
222
|
+
rows = source.get("values")
|
|
223
|
+
# Non-array and format-bearing sources need their inline loader.
|
|
224
|
+
if not isinstance(rows, list) or not all(isinstance(row, dict) for row in rows):
|
|
225
|
+
return source
|
|
226
|
+
# Secondary inputs may arrive as raw schema values rather than through
|
|
227
|
+
# Chart's data normalizer. Hash the same JSON-safe rows in either path.
|
|
228
|
+
from genome_spy._chart_authoring import json_safe
|
|
229
|
+
|
|
230
|
+
rows = json_safe(rows)
|
|
231
|
+
canonical = json.dumps(
|
|
232
|
+
rows, sort_keys=True, separators=(",", ":"), allow_nan=False
|
|
233
|
+
)
|
|
234
|
+
name = self.names.get(canonical)
|
|
235
|
+
if name is None:
|
|
236
|
+
base = _dataset_name(canonical)
|
|
237
|
+
name = base
|
|
238
|
+
suffix = 1
|
|
239
|
+
while name in self.reserved:
|
|
240
|
+
name = f"{base}-{suffix}"
|
|
241
|
+
suffix += 1
|
|
242
|
+
self.reserved.add(name)
|
|
243
|
+
self.names[canonical] = name
|
|
244
|
+
self.datasets[name] = rows
|
|
245
|
+
return {key: value for key, value in source.items() if key != "values"} | {
|
|
246
|
+
"name": name
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
def finish(self, spec: dict[str, Any]) -> dict[str, Any]:
|
|
250
|
+
# Also cover raw/generated nested specs and secondary transform inputs.
|
|
251
|
+
for kind, source, _, _ in _data_slots(spec):
|
|
252
|
+
if kind == "template":
|
|
253
|
+
# Each imported instance owns these declarations at runtime.
|
|
254
|
+
_consolidate(source)
|
|
255
|
+
elif kind == "data":
|
|
256
|
+
replacement = self.source(source)
|
|
257
|
+
if replacement is not source:
|
|
258
|
+
source.clear()
|
|
259
|
+
source.update(replacement)
|
|
260
|
+
if self.datasets:
|
|
261
|
+
spec.setdefault("datasets", {}).update(self.datasets)
|
|
262
|
+
return spec
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _consolidate(spec: dict[str, Any]) -> dict[str, Any]:
|
|
266
|
+
"""Consolidate an owned serialized spec without changing authored names."""
|
|
267
|
+
return _DatasetConsolidation(spec).finish(spec)
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Dataset helpers for packaged GenomeSpy example data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from importlib.abc import Traversable
|
|
7
|
+
from importlib.resources import files
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Literal, cast, overload
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"DatasetNotFoundError",
|
|
13
|
+
"available_datasets",
|
|
14
|
+
"load_dataset",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
_DATA_DIR = files("genome_spy.datasets").joinpath("data")
|
|
18
|
+
_DATASETS = {
|
|
19
|
+
"airway_metadata": "airway_metadata.csv",
|
|
20
|
+
"airway_scaledcounts": "airway_scaledcounts.csv",
|
|
21
|
+
"brca_maf": "brca.maf.gz",
|
|
22
|
+
"hapmap_gwas": "hapmap_gwas.csv",
|
|
23
|
+
"mutation_impact_reference": "mutation_impact_reference.json",
|
|
24
|
+
"p53_sequence_comparison": "p53_sequence_comparison.json.gz",
|
|
25
|
+
"pik3ca_mutations": "pik3ca_mutations.json",
|
|
26
|
+
"pik3ca_tcga_brca_lollipop": "pik3ca_tcga_brca_lollipop.json",
|
|
27
|
+
"tal1_alphagenome_reference": "tal1_alphagenome_reference.json.gz",
|
|
28
|
+
"pyoncoprint_tcga": "tcga.tsv",
|
|
29
|
+
"refseq_gene_bodies": "refseq_gene_bodies.csv.gz",
|
|
30
|
+
"tcga_laml_annotations": "tcga_laml_annot.tsv",
|
|
31
|
+
"tcga_laml_combined_oncoplot": "tcga_laml_combined_oncoplot.json.gz",
|
|
32
|
+
"tcga_laml_maf": "tcga_laml.maf.gz",
|
|
33
|
+
"tcga_ov_gistic_lesions": "tcga_ov_gistic_lesions.tsv.gz",
|
|
34
|
+
"tcga_ov_gistic_scores": "tcga_ov_gistic_scores.tsv.gz",
|
|
35
|
+
"tcga_oncoprint": "oncoprint_dataset3.json",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DatasetNotFoundError(ValueError):
|
|
40
|
+
"""Dataset name lookup failed.
|
|
41
|
+
|
|
42
|
+
Description:
|
|
43
|
+
``load_dataset`` and related helpers use this exception to report an
|
|
44
|
+
unknown dataset name together with the valid packaged choices.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def available_datasets() -> tuple[str, ...]:
|
|
49
|
+
"""Packaged dataset names.
|
|
50
|
+
|
|
51
|
+
Description:
|
|
52
|
+
The returned names are the public identifiers accepted by
|
|
53
|
+
``load_dataset``.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Dataset names that ``load_dataset`` accepts.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
None.
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
>>> available_datasets()
|
|
63
|
+
('airway_metadata', 'airway_scaledcounts', 'hapmap_gwas', ...)
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
return tuple(sorted(_DATASETS))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _resource_for(name: str) -> Traversable:
|
|
70
|
+
try:
|
|
71
|
+
filename = _DATASETS[name]
|
|
72
|
+
except KeyError as exc:
|
|
73
|
+
known = ", ".join(available_datasets())
|
|
74
|
+
raise DatasetNotFoundError(
|
|
75
|
+
f"Unknown dataset {name!r}. Available datasets: {known}."
|
|
76
|
+
) from exc
|
|
77
|
+
return _DATA_DIR.joinpath(filename)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _load_table_bundle(name: str, tables: tuple[str, ...]) -> dict[str, Any]:
|
|
81
|
+
"""Load selected prepared JSON tables as DataFrames, preserving metadata."""
|
|
82
|
+
import pandas as pd
|
|
83
|
+
|
|
84
|
+
data = load_dataset(name, as_format="json")
|
|
85
|
+
if not isinstance(data, dict):
|
|
86
|
+
raise ValueError(f"Dataset {name!r} must contain a mapping of tables.")
|
|
87
|
+
for table in tables:
|
|
88
|
+
data[table] = pd.DataFrame.from_records(data[table])
|
|
89
|
+
return data
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _load_dataframe(name: str) -> Any:
|
|
93
|
+
try:
|
|
94
|
+
import pandas as pd
|
|
95
|
+
except ImportError as exc:
|
|
96
|
+
raise ImportError(
|
|
97
|
+
f"Loading dataset {name!r} as a DataFrame requires pandas. "
|
|
98
|
+
"Install pandas or use as_format='text' or as_format='json'."
|
|
99
|
+
) from exc
|
|
100
|
+
|
|
101
|
+
resource = _resource_for(name)
|
|
102
|
+
suffixes = Path(_DATASETS[name]).suffixes
|
|
103
|
+
if suffixes in ([".csv"], [".csv", ".gz"]):
|
|
104
|
+
return pd.read_csv(resource)
|
|
105
|
+
if suffixes in ([".tsv"], [".tsv", ".gz"]):
|
|
106
|
+
return pd.read_csv(resource, sep="\t")
|
|
107
|
+
if suffixes[-2:] == [".maf", ".gz"]:
|
|
108
|
+
return pd.read_csv(resource, sep="\t", compression="gzip")
|
|
109
|
+
raise ValueError(
|
|
110
|
+
f"Dataset {name!r} is not tabular and cannot be read as a DataFrame."
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@overload
|
|
115
|
+
def load_dataset(name: str, *, as_format: Literal["auto"] = "auto") -> Any: ...
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@overload
|
|
119
|
+
def load_dataset(name: str, *, as_format: Literal["dataframe"]) -> Any: ...
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@overload
|
|
123
|
+
def load_dataset(
|
|
124
|
+
name: str, *, as_format: Literal["json"]
|
|
125
|
+
) -> dict[str, Any] | list[Any]: ...
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@overload
|
|
129
|
+
def load_dataset(name: str, *, as_format: Literal["text"]) -> str: ...
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def load_dataset(
|
|
133
|
+
name: str,
|
|
134
|
+
*,
|
|
135
|
+
as_format: Literal["auto", "dataframe", "json", "text"] = "auto",
|
|
136
|
+
) -> Any:
|
|
137
|
+
"""Load a packaged example dataset by name.
|
|
138
|
+
|
|
139
|
+
Description:
|
|
140
|
+
The loader understands the small set of real datasets vendored with the
|
|
141
|
+
package for examples and tutorials. ``"auto"`` returns a pandas
|
|
142
|
+
``DataFrame`` for CSV, TSV, and compressed MAF files and parsed Python
|
|
143
|
+
objects for JSON files.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
name: Dataset name from ``available_datasets()``.
|
|
147
|
+
as_format: Output format to return.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
The loaded dataset in the requested format.
|
|
151
|
+
|
|
152
|
+
Raises:
|
|
153
|
+
DatasetNotFoundError: If the dataset name is unknown.
|
|
154
|
+
ImportError: If ``as_format="dataframe"`` is requested without pandas.
|
|
155
|
+
ValueError: If the requested format does not match the stored file type.
|
|
156
|
+
|
|
157
|
+
Example:
|
|
158
|
+
>>> load_dataset("hapmap_gwas").head()
|
|
159
|
+
>>> load_dataset("pik3ca_mutations", as_format="json")["domains"][0]
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
resource = _resource_for(name)
|
|
163
|
+
filename = _DATASETS[name]
|
|
164
|
+
suffix = Path(filename).suffix
|
|
165
|
+
suffixes = Path(filename).suffixes
|
|
166
|
+
|
|
167
|
+
if as_format == "text":
|
|
168
|
+
if suffix == ".gz":
|
|
169
|
+
import gzip
|
|
170
|
+
|
|
171
|
+
return gzip.decompress(resource.read_bytes()).decode("utf-8")
|
|
172
|
+
return resource.read_text(encoding="utf-8")
|
|
173
|
+
is_json = suffix == ".json" or suffixes[-2:] == [".json", ".gz"]
|
|
174
|
+
if as_format == "json":
|
|
175
|
+
if not is_json:
|
|
176
|
+
raise ValueError(f"Dataset {name!r} is stored as {suffix}, not JSON.")
|
|
177
|
+
text = load_dataset(name, as_format="text")
|
|
178
|
+
return cast(dict[str, Any] | list[Any], json.loads(text))
|
|
179
|
+
if as_format == "dataframe":
|
|
180
|
+
return _load_dataframe(name)
|
|
181
|
+
if is_json:
|
|
182
|
+
return json.loads(load_dataset(name, as_format="text"))
|
|
183
|
+
if suffix in {".csv", ".tsv"} or suffixes[-2:] in (
|
|
184
|
+
[".csv", ".gz"],
|
|
185
|
+
[".tsv", ".gz"],
|
|
186
|
+
[".maf", ".gz"],
|
|
187
|
+
):
|
|
188
|
+
return _load_dataframe(name)
|
|
189
|
+
raise ValueError(f"Unsupported dataset format for {name!r}: {suffix}.")
|