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 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