anytrellis 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.
- anytrellis/__init__.py +13 -0
- anytrellis/codec.py +71 -0
- anytrellis/data.py +251 -0
- anytrellis/datasets/SOURCES.md +17 -0
- anytrellis/datasets/__init__.py +55 -0
- anytrellis/datasets/anscombe.csv +45 -0
- anytrellis/datasets/barley.csv +121 -0
- anytrellis/datasets/cars.csv +407 -0
- anytrellis/datasets/gapminder.csv +694 -0
- anytrellis/datasets/penguins.csv +345 -0
- anytrellis/datasets/seattle_weather.csv +1462 -0
- anytrellis/facets.py +284 -0
- anytrellis/layers.py +345 -0
- anytrellis/linking.py +210 -0
- anytrellis/plot.py +1629 -0
- anytrellis/py.typed +0 -0
- anytrellis/scales.py +115 -0
- anytrellis/selection.py +121 -0
- anytrellis/static/standalone.js +159 -0
- anytrellis/static/widget.css +1 -0
- anytrellis/static/widget.js +159 -0
- anytrellis/stats.py +733 -0
- anytrellis/styling.py +107 -0
- anytrellis/synthetic.py +77 -0
- anytrellis/widget.py +113 -0
- anytrellis-0.1.0.dist-info/METADATA +308 -0
- anytrellis-0.1.0.dist-info/RECORD +30 -0
- anytrellis-0.1.0.dist-info/WHEEL +4 -0
- anytrellis-0.1.0.dist-info/licenses/LICENSE +21 -0
- anytrellis-0.1.0.dist-info/licenses/src/anytrellis/datasets/SOURCES.md +17 -0
anytrellis/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""anytrellis: GPU-accelerated trellis, facet and small-multiple plots for notebooks."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError
|
|
4
|
+
from importlib.metadata import version as _version
|
|
5
|
+
|
|
6
|
+
from anytrellis.linking import Link, link
|
|
7
|
+
from anytrellis.plot import Trellis
|
|
8
|
+
|
|
9
|
+
__all__ = ["Link", "Trellis", "link"]
|
|
10
|
+
try:
|
|
11
|
+
__version__ = _version("anytrellis")
|
|
12
|
+
except PackageNotFoundError: # run from a source tree that is not installed
|
|
13
|
+
__version__ = "0.0.0"
|
anytrellis/codec.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Binary encodings for arrays sent to the browser.
|
|
2
|
+
|
|
3
|
+
Arrays travel as raw little-endian typed buffers described by a small JSON descriptor, so
|
|
4
|
+
the browser can view them without parsing or copying. The descriptor's ``id`` is a hash of
|
|
5
|
+
the bytes and the dtype: identical data always has the same id and is sent once.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
DTYPES = {
|
|
17
|
+
"float64": "<f8",
|
|
18
|
+
"float32": "<f4",
|
|
19
|
+
"int32": "<i4",
|
|
20
|
+
"uint32": "<u4",
|
|
21
|
+
"uint16": "<u2",
|
|
22
|
+
"uint8": "u1",
|
|
23
|
+
}
|
|
24
|
+
"""The dtypes the browser reads, and their NumPy spelling."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class Encoded:
|
|
29
|
+
"""An encoded array: a JSON-able descriptor plus the raw bytes."""
|
|
30
|
+
|
|
31
|
+
descriptor: dict[str, Any]
|
|
32
|
+
data: bytes
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def id(self) -> str:
|
|
36
|
+
return self.descriptor["id"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def encode(values: Any, dtype: str, *, components: int = 1) -> Encoded:
|
|
40
|
+
"""Encode an array of shape ``(n,)`` or ``(n, components)`` as ``dtype``."""
|
|
41
|
+
if dtype not in DTYPES:
|
|
42
|
+
raise ValueError(f"unknown dtype {dtype!r}; known: {list(DTYPES)}")
|
|
43
|
+
arr = np.asarray(values)
|
|
44
|
+
if arr.ndim == 2 and arr.shape[1] != components:
|
|
45
|
+
raise ValueError(f"expected {components} components, got shape {arr.shape}")
|
|
46
|
+
if arr.ndim == 1 and components > 1:
|
|
47
|
+
raise ValueError("pass a (n, components) array for interleaved data")
|
|
48
|
+
data = np.ascontiguousarray(arr.astype(DTYPES[dtype], copy=False)).tobytes()
|
|
49
|
+
count = int(arr.shape[0]) if arr.ndim else 1
|
|
50
|
+
digest = hashlib.blake2b(data, digest_size=12).hexdigest()
|
|
51
|
+
descriptor = {
|
|
52
|
+
"id": f"{digest}-{dtype}-{components}",
|
|
53
|
+
"dtype": dtype,
|
|
54
|
+
"count": count,
|
|
55
|
+
"components": components,
|
|
56
|
+
"byteLength": len(data),
|
|
57
|
+
}
|
|
58
|
+
return Encoded(descriptor, data)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Buffers:
|
|
62
|
+
"""The buffers of a plot's spec, by id: what the browser may ask for."""
|
|
63
|
+
|
|
64
|
+
def __init__(self) -> None:
|
|
65
|
+
self.data: dict[str, bytes] = {}
|
|
66
|
+
|
|
67
|
+
def add(self, values: Any, dtype: str, *, components: int = 1) -> dict[str, Any]:
|
|
68
|
+
"""Encode ``values``, keep the bytes, and return the descriptor for the spec."""
|
|
69
|
+
enc = encode(values, dtype, components=components)
|
|
70
|
+
self.data[enc.id] = enc.data
|
|
71
|
+
return enc.descriptor
|
anytrellis/data.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Reading columns from tables: Polars, PyArrow, pandas, DuckDB (via narwhals) or a dict of
|
|
2
|
+
arrays. Every column is read as one of three kinds: numbers, times or categories."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Literal
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
Kind = Literal["number", "time", "category"]
|
|
13
|
+
|
|
14
|
+
MISSING_LABEL = "(missing)"
|
|
15
|
+
"""The category of rows without a value."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NotFoundError(KeyError):
|
|
19
|
+
"""A missing column: a ``KeyError`` whose message reads as written."""
|
|
20
|
+
|
|
21
|
+
def __str__(self) -> str:
|
|
22
|
+
return str(self.args[0]) if self.args else ""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class Column:
|
|
27
|
+
"""A column read for plotting.
|
|
28
|
+
|
|
29
|
+
``values`` are float64 for numbers (NaN where missing), float64 milliseconds since
|
|
30
|
+
1970 for times (wall-clock time in the column's zone; NaN where missing), and str for
|
|
31
|
+
categories (``MISSING_LABEL`` where missing). ``levels`` is the order of a category
|
|
32
|
+
column: declared (a Polars Enum, a pandas Categorical) or sorted.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
name: str
|
|
36
|
+
kind: Kind
|
|
37
|
+
values: np.ndarray
|
|
38
|
+
levels: tuple[str, ...] = ()
|
|
39
|
+
codes: np.ndarray | None = None
|
|
40
|
+
"""A category column's index of each row's value in ``levels`` (computed if not given)."""
|
|
41
|
+
log: bool = False
|
|
42
|
+
"""Numbers in log10 units, for a log axis (the data's own values: ``10 ** values``)."""
|
|
43
|
+
|
|
44
|
+
def level_codes(self) -> np.ndarray:
|
|
45
|
+
"""Each row's index in ``levels`` (−1: not one of them)."""
|
|
46
|
+
if self.codes is not None:
|
|
47
|
+
return self.codes
|
|
48
|
+
distinct, codes = _labels(self.values)
|
|
49
|
+
position = {lv: i for i, lv in enumerate(self.levels)}
|
|
50
|
+
level_of = np.array([position.get(t, -1) for t in distinct], dtype=np.int64)
|
|
51
|
+
out = level_of[codes] if len(codes) else codes
|
|
52
|
+
object.__setattr__(self, "codes", out)
|
|
53
|
+
return out
|
|
54
|
+
|
|
55
|
+
def __len__(self) -> int:
|
|
56
|
+
return len(self.values)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Table:
|
|
60
|
+
"""A table whose columns are read once each."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, data: Any) -> None:
|
|
63
|
+
if isinstance(data, Table):
|
|
64
|
+
data = data.native
|
|
65
|
+
self.native = data
|
|
66
|
+
self._frame = _frame(data)
|
|
67
|
+
self._cache: dict[str, Column] = {}
|
|
68
|
+
self.names = _column_names(self._frame)
|
|
69
|
+
self.n = _row_count(self._frame, self.names)
|
|
70
|
+
|
|
71
|
+
def __contains__(self, name: object) -> bool:
|
|
72
|
+
return name in self.names
|
|
73
|
+
|
|
74
|
+
def column(self, name: str) -> Column:
|
|
75
|
+
if name not in self._cache:
|
|
76
|
+
if name not in self.names:
|
|
77
|
+
raise NotFoundError(f"column {name!r} not found; available: {self.names}")
|
|
78
|
+
self._cache[name] = _read(self._frame, name)
|
|
79
|
+
return self._cache[name]
|
|
80
|
+
|
|
81
|
+
def log_scaled(self, names: set[str]) -> Table:
|
|
82
|
+
"""This table with the columns ``names`` in log10 units, for a log axis: values of 0
|
|
83
|
+
or less are missing. Raw values (tooltips) and rows (``take``) are as they were."""
|
|
84
|
+
import copy
|
|
85
|
+
|
|
86
|
+
out = copy.copy(self)
|
|
87
|
+
out._cache = dict(self._cache)
|
|
88
|
+
for name in sorted(names):
|
|
89
|
+
col = self.column(name)
|
|
90
|
+
if col.kind != "number":
|
|
91
|
+
raise ValueError(f"a log scale needs numbers; {name!r} holds {col.kind}s")
|
|
92
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
93
|
+
values = np.where(col.values > 0, np.log10(col.values), np.nan)
|
|
94
|
+
out._cache[name] = Column(name, "number", values, log=True)
|
|
95
|
+
return out
|
|
96
|
+
|
|
97
|
+
def take(self, rows: np.ndarray) -> Any:
|
|
98
|
+
"""These rows (indices), as the same kind of table as the data given."""
|
|
99
|
+
if isinstance(self._frame, Mapping):
|
|
100
|
+
return {k: np.asarray(v)[rows] for k, v in self._frame.items()}
|
|
101
|
+
import narwhals as nw
|
|
102
|
+
|
|
103
|
+
return nw.to_native(self._frame[rows])
|
|
104
|
+
|
|
105
|
+
def raw(self, name: str) -> np.ndarray:
|
|
106
|
+
"""A column's values as they are (for tooltips)."""
|
|
107
|
+
if isinstance(self._frame, Mapping):
|
|
108
|
+
return np.asarray(self._frame[name]).ravel()
|
|
109
|
+
return self._frame[name].to_numpy()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _frame(data: Any) -> Any:
|
|
113
|
+
if isinstance(data, Mapping):
|
|
114
|
+
return data
|
|
115
|
+
import narwhals as nw
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
out = nw.from_native(data)
|
|
119
|
+
except TypeError:
|
|
120
|
+
raise TypeError(
|
|
121
|
+
f"unsupported data of type {type(data).__name__}; pass a Polars, PyArrow, pandas "
|
|
122
|
+
"or DuckDB table, or a dict of arrays"
|
|
123
|
+
) from None
|
|
124
|
+
if isinstance(out, nw.LazyFrame):
|
|
125
|
+
out = out.collect()
|
|
126
|
+
return out
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _column_names(frame: Any) -> list[str]:
|
|
130
|
+
if isinstance(frame, Mapping):
|
|
131
|
+
return [str(k) for k in frame]
|
|
132
|
+
return list(frame.columns)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _row_count(frame: Any, names: list[str]) -> int:
|
|
136
|
+
if isinstance(frame, Mapping):
|
|
137
|
+
lengths = {len(np.asarray(frame[n]).ravel()) for n in names}
|
|
138
|
+
if len(lengths) > 1:
|
|
139
|
+
raise ValueError(f"the columns have different lengths: {sorted(lengths)}")
|
|
140
|
+
return lengths.pop() if lengths else 0
|
|
141
|
+
return len(frame)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _read(frame: Any, name: str) -> Column:
|
|
145
|
+
if isinstance(frame, Mapping):
|
|
146
|
+
return _from_array(name, frame[name])
|
|
147
|
+
import narwhals as nw
|
|
148
|
+
|
|
149
|
+
series = frame[name]
|
|
150
|
+
dtype = series.dtype
|
|
151
|
+
if isinstance(dtype, nw.Datetime):
|
|
152
|
+
if dtype.time_zone is not None:
|
|
153
|
+
series = series.dt.replace_time_zone(None) # wall-clock time in its zone
|
|
154
|
+
ms = series.dt.timestamp("ms").cast(nw.Float64)
|
|
155
|
+
missing = series.is_null().to_numpy()
|
|
156
|
+
values = np.array(ms.to_numpy(), dtype=np.float64) # a copy: it may be read-only
|
|
157
|
+
values[np.asarray(missing, dtype=bool)] = np.nan
|
|
158
|
+
return Column(name, "time", values)
|
|
159
|
+
if isinstance(dtype, nw.Date):
|
|
160
|
+
return _read(frame.with_columns(nw.col(name).cast(nw.Datetime("ms"))), name)
|
|
161
|
+
if dtype.is_numeric() or dtype == nw.Boolean:
|
|
162
|
+
values = series.cast(nw.Float64).to_numpy()
|
|
163
|
+
return Column(name, "number", np.asarray(values, dtype=np.float64))
|
|
164
|
+
levels: list[str] | None = None
|
|
165
|
+
if isinstance(dtype, nw.Enum):
|
|
166
|
+
levels = [str(c) for c in dtype.categories]
|
|
167
|
+
elif dtype == nw.Categorical:
|
|
168
|
+
native = nw.to_native(frame)
|
|
169
|
+
if hasattr(native, "iloc"): # pandas keeps its declared order
|
|
170
|
+
levels = [str(c) for c in native[name].cat.categories]
|
|
171
|
+
return _categories(name, series.to_numpy(), levels)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _from_array(name: str, values: Any) -> Column:
|
|
175
|
+
cat = getattr(values, "cat", None)
|
|
176
|
+
declared = getattr(cat, "categories", None) if cat is not None else None
|
|
177
|
+
if declared is None and hasattr(values, "categories") and hasattr(values, "codes"):
|
|
178
|
+
declared = values.categories
|
|
179
|
+
arr = np.asarray(values).ravel()
|
|
180
|
+
if declared is not None:
|
|
181
|
+
return _categories(name, arr, [str(c) for c in declared])
|
|
182
|
+
if arr.dtype.kind == "M":
|
|
183
|
+
ms = arr.astype("datetime64[ms]")
|
|
184
|
+
out = ms.astype(np.int64).astype(np.float64)
|
|
185
|
+
out[np.isnat(ms)] = np.nan
|
|
186
|
+
return Column(name, "time", out)
|
|
187
|
+
if arr.dtype.kind in "biuf":
|
|
188
|
+
return Column(name, "number", arr.astype(np.float64))
|
|
189
|
+
if arr.dtype.kind == "O":
|
|
190
|
+
present = [v for v in arr if v is not None]
|
|
191
|
+
import datetime as dt
|
|
192
|
+
|
|
193
|
+
if present and all(isinstance(v, dt.datetime | dt.date) for v in present):
|
|
194
|
+
return _from_array(name, np.array(arr, dtype="datetime64[ms]"))
|
|
195
|
+
if present and all(isinstance(v, int | float | np.number) for v in present):
|
|
196
|
+
return Column(name, "number", np.array(arr, dtype=np.float64))
|
|
197
|
+
return _categories(name, arr, None)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _categories(name: str, values: np.ndarray, levels: list[str] | None) -> Column:
|
|
201
|
+
distinct, codes = _labels(values)
|
|
202
|
+
seen = set(distinct[np.unique(codes)]) if len(codes) else set()
|
|
203
|
+
if levels is None:
|
|
204
|
+
present = sorted((t for t in seen if t != MISSING_LABEL), key=_natural)
|
|
205
|
+
else:
|
|
206
|
+
present = [lv for lv in levels if lv in seen]
|
|
207
|
+
if MISSING_LABEL in seen:
|
|
208
|
+
present.append(MISSING_LABEL)
|
|
209
|
+
position = {lv: i for i, lv in enumerate(present)}
|
|
210
|
+
level_of = np.array([position.get(t, -1) for t in distinct], dtype=np.int64)
|
|
211
|
+
return Column(name, "category", distinct[codes], tuple(present), level_of[codes])
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _labels(values: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
|
215
|
+
"""The distinct labels, and each value's index among them; each distinct value is
|
|
216
|
+
labelled once, so a million rows of a few categories cost a lookup each."""
|
|
217
|
+
arr = np.asarray(values).ravel()
|
|
218
|
+
try:
|
|
219
|
+
if arr.dtype.kind in "US":
|
|
220
|
+
uniq, inverse = np.unique(arr, return_inverse=True)
|
|
221
|
+
codes = inverse.ravel().astype(np.int64)
|
|
222
|
+
else:
|
|
223
|
+
index: dict[Any, int] = {}
|
|
224
|
+
codes = np.fromiter((index.setdefault(v, len(index)) for v in arr),
|
|
225
|
+
dtype=np.int64, count=arr.size) # fmt: skip
|
|
226
|
+
uniq = list(index)
|
|
227
|
+
except TypeError: # unhashable values: one by one
|
|
228
|
+
uniq, codes = [_label(v) for v in arr], np.arange(arr.size, dtype=np.int64)
|
|
229
|
+
labels = [_label(u) for u in uniq]
|
|
230
|
+
# values with one label (1 and "1", or NaN objects): one index
|
|
231
|
+
first: dict[str, int] = {}
|
|
232
|
+
remap = np.array([first.setdefault(t, len(first)) for t in labels], dtype=np.int64)
|
|
233
|
+
distinct = np.array(list(first) or [""], dtype=object)
|
|
234
|
+
return distinct, remap[codes] if len(remap) else codes
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _label(value: Any) -> str:
|
|
238
|
+
if value is None or (isinstance(value, float) and value != value):
|
|
239
|
+
return MISSING_LABEL
|
|
240
|
+
if isinstance(value, bytes):
|
|
241
|
+
return value.decode("utf-8", "replace")
|
|
242
|
+
if isinstance(value, np.generic):
|
|
243
|
+
value = value.item()
|
|
244
|
+
return str(value)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _natural(text: str) -> tuple[Any, ...]:
|
|
248
|
+
"""Sort key: numbers in text by value ("site 2" before "site 10")."""
|
|
249
|
+
import re
|
|
250
|
+
|
|
251
|
+
return tuple(int(p) if p.isdigit() else p.lower() for p in re.split(r"(\d+)", text))
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Sources of the bundled datasets
|
|
2
|
+
|
|
3
|
+
The datasets in this folder (`anytrellis.datasets.load`) are not covered by anytrellis's MIT
|
|
4
|
+
licence: each keeps the terms of its source.
|
|
5
|
+
|
|
6
|
+
| dataset | source | licence |
|
|
7
|
+
|---|---|---|
|
|
8
|
+
| anscombe | F. J. Anscombe (1973), *Graphs in Statistical Analysis*, The American Statistician 27(1):17-21; as in [vega-datasets](https://github.com/vega/vega-datasets) 2.8.0 | published numbers, also in R's `datasets` package; no licence stated |
|
|
9
|
+
| barley | Minnesota Agricultural Experiment Station; Immer, Hayes and Powers (1934); republished in Wiebe et al. (1940), *Yields of Barley Varieties in the United States and Canada, 1932-36*, USDA Technical Bulletin 735; as in vega-datasets 2.8.0 | a U.S. Government publication; also in R's `lattice` package |
|
|
10
|
+
| cars | Quinlan, R. (1993). *Auto MPG* [Dataset]. UCI Machine Learning Repository. <https://doi.org/10.24432/C5859H> | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) |
|
|
11
|
+
| gapminder | Gapminder Foundation: life expectancy, population and fertility, 1955-2005; as in vega-datasets 2.8.0 | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/): based on free material from [GAPMINDER.ORG](https://www.gapminder.org/), CC-BY LICENSE |
|
|
12
|
+
| penguins | Gorman, Williams and Fraser (2014), Palmer Station Antarctica LTER; [palmerpenguins](https://github.com/allisonhorst/palmerpenguins) by Horst, Hill and Gorman; as in vega-datasets 2.8.0 | [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/) |
|
|
13
|
+
| seattle_weather | NOAA National Climatic Data Center, daily records for Seattle, 2012-2015; as in vega-datasets 2.8.0 | a U.S. Government work: public domain |
|
|
14
|
+
|
|
15
|
+
Changes made here: column names in snake_case; for `gapminder`, readable region names; for
|
|
16
|
+
`cars`, the 406 cars of UCI's `auto-mpg.data-original`, with the model year as 1970-1982,
|
|
17
|
+
the origin as USA, Europe or Japan, and missing values left empty.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Small, well-known datasets for the examples: ``load("barley")``.
|
|
2
|
+
|
|
3
|
+
================= ===== ==============================================================
|
|
4
|
+
name rows columns
|
|
5
|
+
================= ===== ==============================================================
|
|
6
|
+
anscombe 44 dataset, x, y (Anscombe's quartet)
|
|
7
|
+
barley 120 site, variety, year, yield (Immer et al. 1934; Cleveland 1993)
|
|
8
|
+
cars 406 name, mpg, cylinders, displacement, horsepower, weight,
|
|
9
|
+
acceleration, year, origin (the classic auto-mpg data)
|
|
10
|
+
gapminder 693 country, region, year, pop, life_expect, fertility (1955-2005)
|
|
11
|
+
penguins 344 species, island, bill_length_mm, bill_depth_mm,
|
|
12
|
+
flipper_length_mm, body_mass_g, sex (Palmer penguins)
|
|
13
|
+
seattle_weather 1461 date, precipitation, temp_max, temp_min, wind, weather
|
|
14
|
+
================= ===== ==============================================================
|
|
15
|
+
|
|
16
|
+
``cars`` is the UCI Machine Learning Repository's Auto MPG (CC BY 4.0); the others are
|
|
17
|
+
taken from vega-datasets 2.8.0, with snake_case column names and readable categories
|
|
18
|
+
(``gapminder.region``). Each keeps its source's licence: see ``SOURCES.md`` here. The
|
|
19
|
+
gapminder data is based on free material from GAPMINDER.ORG, CC-BY LICENSE.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from importlib import resources
|
|
25
|
+
from typing import Any, Literal
|
|
26
|
+
|
|
27
|
+
NAMES = ("anscombe", "barley", "cars", "gapminder", "penguins", "seattle_weather")
|
|
28
|
+
_DATES = {"seattle_weather": "date"}
|
|
29
|
+
|
|
30
|
+
Backend = Literal["polars", "pandas", "pyarrow"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def load(name: str, backend: Backend | None = None) -> Any:
|
|
34
|
+
"""Dataset ``name`` as a table of ``backend`` (default: Polars, else pandas, else
|
|
35
|
+
PyArrow, whichever is installed)."""
|
|
36
|
+
import narwhals as nw
|
|
37
|
+
|
|
38
|
+
if name not in NAMES:
|
|
39
|
+
raise ValueError(f"no dataset {name!r}; there are {', '.join(NAMES)}")
|
|
40
|
+
path = resources.files(__name__) / f"{name}.csv"
|
|
41
|
+
with resources.as_file(path) as file:
|
|
42
|
+
frame = nw.read_csv(str(file), backend=backend or _backend())
|
|
43
|
+
column = _DATES.get(name)
|
|
44
|
+
if column is not None and frame.schema[column] == nw.String: # PyArrow parses dates itself
|
|
45
|
+
frame = frame.with_columns(nw.col(column).str.to_datetime("%Y-%m-%d").cast(nw.Date))
|
|
46
|
+
return frame.to_native()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _backend() -> Backend:
|
|
50
|
+
import importlib.util
|
|
51
|
+
|
|
52
|
+
for backend in ("polars", "pandas", "pyarrow"):
|
|
53
|
+
if importlib.util.find_spec(backend) is not None:
|
|
54
|
+
return backend
|
|
55
|
+
raise ModuleNotFoundError("load() needs Polars, pandas or PyArrow: pip install polars")
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
dataset,x,y
|
|
2
|
+
I,10.0,8.04
|
|
3
|
+
I,8.0,6.95
|
|
4
|
+
I,13.0,7.58
|
|
5
|
+
I,9.0,8.81
|
|
6
|
+
I,11.0,8.33
|
|
7
|
+
I,14.0,9.96
|
|
8
|
+
I,6.0,7.24
|
|
9
|
+
I,4.0,4.26
|
|
10
|
+
I,12.0,10.84
|
|
11
|
+
I,7.0,4.81
|
|
12
|
+
I,5.0,5.68
|
|
13
|
+
II,10.0,9.14
|
|
14
|
+
II,8.0,8.14
|
|
15
|
+
II,13.0,8.74
|
|
16
|
+
II,9.0,8.77
|
|
17
|
+
II,11.0,9.26
|
|
18
|
+
II,14.0,8.1
|
|
19
|
+
II,6.0,6.13
|
|
20
|
+
II,4.0,3.1
|
|
21
|
+
II,12.0,9.13
|
|
22
|
+
II,7.0,7.26
|
|
23
|
+
II,5.0,4.74
|
|
24
|
+
III,10.0,7.46
|
|
25
|
+
III,8.0,6.77
|
|
26
|
+
III,13.0,12.74
|
|
27
|
+
III,9.0,7.11
|
|
28
|
+
III,11.0,7.81
|
|
29
|
+
III,14.0,8.84
|
|
30
|
+
III,6.0,6.08
|
|
31
|
+
III,4.0,5.39
|
|
32
|
+
III,12.0,8.15
|
|
33
|
+
III,7.0,6.42
|
|
34
|
+
III,5.0,5.73
|
|
35
|
+
IV,8.0,6.58
|
|
36
|
+
IV,8.0,5.76
|
|
37
|
+
IV,8.0,7.71
|
|
38
|
+
IV,8.0,8.84
|
|
39
|
+
IV,8.0,8.47
|
|
40
|
+
IV,8.0,7.04
|
|
41
|
+
IV,8.0,5.25
|
|
42
|
+
IV,19.0,12.5
|
|
43
|
+
IV,8.0,5.56
|
|
44
|
+
IV,8.0,7.91
|
|
45
|
+
IV,8.0,6.89
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
site,variety,year,yield
|
|
2
|
+
University Farm,Manchuria,1931,27.0
|
|
3
|
+
Waseca,Manchuria,1931,48.86667
|
|
4
|
+
Morris,Manchuria,1931,27.43334
|
|
5
|
+
Crookston,Manchuria,1931,39.93333
|
|
6
|
+
Grand Rapids,Manchuria,1931,32.96667
|
|
7
|
+
Duluth,Manchuria,1931,28.96667
|
|
8
|
+
University Farm,Glabron,1931,43.06666
|
|
9
|
+
Waseca,Glabron,1931,55.2
|
|
10
|
+
Morris,Glabron,1931,28.76667
|
|
11
|
+
Crookston,Glabron,1931,38.13333
|
|
12
|
+
Grand Rapids,Glabron,1931,29.13333
|
|
13
|
+
Duluth,Glabron,1931,29.66667
|
|
14
|
+
University Farm,Svansota,1931,35.13333
|
|
15
|
+
Waseca,Svansota,1931,47.33333
|
|
16
|
+
Morris,Svansota,1931,25.76667
|
|
17
|
+
Crookston,Svansota,1931,40.46667
|
|
18
|
+
Grand Rapids,Svansota,1931,29.66667
|
|
19
|
+
Duluth,Svansota,1931,25.7
|
|
20
|
+
University Farm,Velvet,1931,39.9
|
|
21
|
+
Waseca,Velvet,1931,50.23333
|
|
22
|
+
Morris,Velvet,1931,26.13333
|
|
23
|
+
Crookston,Velvet,1931,41.33333
|
|
24
|
+
Grand Rapids,Velvet,1931,23.03333
|
|
25
|
+
Duluth,Velvet,1931,26.3
|
|
26
|
+
University Farm,Trebi,1931,36.56666
|
|
27
|
+
Waseca,Trebi,1931,63.8333
|
|
28
|
+
Morris,Trebi,1931,43.76667
|
|
29
|
+
Crookston,Trebi,1931,46.93333
|
|
30
|
+
Grand Rapids,Trebi,1931,29.76667
|
|
31
|
+
Duluth,Trebi,1931,33.93333
|
|
32
|
+
University Farm,No. 457,1931,43.26667
|
|
33
|
+
Waseca,No. 457,1931,58.1
|
|
34
|
+
Morris,No. 457,1931,28.7
|
|
35
|
+
Crookston,No. 457,1931,45.66667
|
|
36
|
+
Grand Rapids,No. 457,1931,32.16667
|
|
37
|
+
Duluth,No. 457,1931,33.6
|
|
38
|
+
University Farm,No. 462,1931,36.6
|
|
39
|
+
Waseca,No. 462,1931,65.7667
|
|
40
|
+
Morris,No. 462,1931,30.36667
|
|
41
|
+
Crookston,No. 462,1931,48.56666
|
|
42
|
+
Grand Rapids,No. 462,1931,24.93334
|
|
43
|
+
Duluth,No. 462,1931,28.1
|
|
44
|
+
University Farm,Peatland,1931,32.76667
|
|
45
|
+
Waseca,Peatland,1931,48.56666
|
|
46
|
+
Morris,Peatland,1931,29.86667
|
|
47
|
+
Crookston,Peatland,1931,41.6
|
|
48
|
+
Grand Rapids,Peatland,1931,34.7
|
|
49
|
+
Duluth,Peatland,1931,32.0
|
|
50
|
+
University Farm,No. 475,1931,24.66667
|
|
51
|
+
Waseca,No. 475,1931,46.76667
|
|
52
|
+
Morris,No. 475,1931,22.6
|
|
53
|
+
Crookston,No. 475,1931,44.1
|
|
54
|
+
Grand Rapids,No. 475,1931,19.7
|
|
55
|
+
Duluth,No. 475,1931,33.06666
|
|
56
|
+
University Farm,Wisconsin No. 38,1931,39.3
|
|
57
|
+
Waseca,Wisconsin No. 38,1931,58.8
|
|
58
|
+
Morris,Wisconsin No. 38,1931,29.46667
|
|
59
|
+
Crookston,Wisconsin No. 38,1931,49.86667
|
|
60
|
+
Grand Rapids,Wisconsin No. 38,1931,34.46667
|
|
61
|
+
Duluth,Wisconsin No. 38,1931,31.6
|
|
62
|
+
University Farm,Manchuria,1932,26.9
|
|
63
|
+
Waseca,Manchuria,1932,33.46667
|
|
64
|
+
Morris,Manchuria,1932,34.36666
|
|
65
|
+
Crookston,Manchuria,1932,32.96667
|
|
66
|
+
Grand Rapids,Manchuria,1932,22.13333
|
|
67
|
+
Duluth,Manchuria,1932,22.56667
|
|
68
|
+
University Farm,Glabron,1932,36.8
|
|
69
|
+
Waseca,Glabron,1932,37.73333
|
|
70
|
+
Morris,Glabron,1932,35.13333
|
|
71
|
+
Crookston,Glabron,1932,26.16667
|
|
72
|
+
Grand Rapids,Glabron,1932,14.43333
|
|
73
|
+
Duluth,Glabron,1932,25.86667
|
|
74
|
+
University Farm,Svansota,1932,27.43334
|
|
75
|
+
Waseca,Svansota,1932,38.5
|
|
76
|
+
Morris,Svansota,1932,35.03333
|
|
77
|
+
Crookston,Svansota,1932,20.63333
|
|
78
|
+
Grand Rapids,Svansota,1932,16.63333
|
|
79
|
+
Duluth,Svansota,1932,22.23333
|
|
80
|
+
University Farm,Velvet,1932,26.8
|
|
81
|
+
Waseca,Velvet,1932,37.4
|
|
82
|
+
Morris,Velvet,1932,38.83333
|
|
83
|
+
Crookston,Velvet,1932,32.06666
|
|
84
|
+
Grand Rapids,Velvet,1932,32.23333
|
|
85
|
+
Duluth,Velvet,1932,22.46667
|
|
86
|
+
University Farm,Trebi,1932,29.06667
|
|
87
|
+
Waseca,Trebi,1932,49.2333
|
|
88
|
+
Morris,Trebi,1932,46.63333
|
|
89
|
+
Crookston,Trebi,1932,41.83333
|
|
90
|
+
Grand Rapids,Trebi,1932,20.63333
|
|
91
|
+
Duluth,Trebi,1932,30.6
|
|
92
|
+
University Farm,No. 457,1932,26.43334
|
|
93
|
+
Waseca,No. 457,1932,42.2
|
|
94
|
+
Morris,No. 457,1932,43.53334
|
|
95
|
+
Crookston,No. 457,1932,34.33333
|
|
96
|
+
Grand Rapids,No. 457,1932,19.46667
|
|
97
|
+
Duluth,No. 457,1932,22.7
|
|
98
|
+
University Farm,No. 462,1932,25.56667
|
|
99
|
+
Waseca,No. 462,1932,44.7
|
|
100
|
+
Morris,No. 462,1932,47.0
|
|
101
|
+
Crookston,No. 462,1932,30.53333
|
|
102
|
+
Grand Rapids,No. 462,1932,19.9
|
|
103
|
+
Duluth,No. 462,1932,22.5
|
|
104
|
+
University Farm,Peatland,1932,28.06667
|
|
105
|
+
Waseca,Peatland,1932,36.03333
|
|
106
|
+
Morris,Peatland,1932,43.2
|
|
107
|
+
Crookston,Peatland,1932,25.23333
|
|
108
|
+
Grand Rapids,Peatland,1932,26.76667
|
|
109
|
+
Duluth,Peatland,1932,31.36667
|
|
110
|
+
University Farm,No. 475,1932,30.0
|
|
111
|
+
Waseca,No. 475,1932,41.26667
|
|
112
|
+
Morris,No. 475,1932,44.23333
|
|
113
|
+
Crookston,No. 475,1932,32.13333
|
|
114
|
+
Grand Rapids,No. 475,1932,15.23333
|
|
115
|
+
Duluth,No. 475,1932,27.36667
|
|
116
|
+
University Farm,Wisconsin No. 38,1932,38.0
|
|
117
|
+
Waseca,Wisconsin No. 38,1932,58.16667
|
|
118
|
+
Morris,Wisconsin No. 38,1932,47.16667
|
|
119
|
+
Crookston,Wisconsin No. 38,1932,35.9
|
|
120
|
+
Grand Rapids,Wisconsin No. 38,1932,20.66667
|
|
121
|
+
Duluth,Wisconsin No. 38,1932,29.33333
|