tsam-xarray 0.6.1__tar.gz → 0.6.2__tar.gz
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.
- tsam_xarray-0.6.2/.release-please-manifest.json +3 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/CHANGELOG.md +12 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/PKG-INFO +1 -1
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/docs/examples/getting-started.ipynb +43 -9
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/src/tsam_xarray/_core.py +28 -23
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/src/tsam_xarray/_result.py +60 -2
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/src/tsam_xarray/_version.py +2 -2
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/test/test_aggregate.py +124 -0
- tsam_xarray-0.6.1/.release-please-manifest.json +0 -3
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/.github/dependabot.yml +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/.github/workflows/ci.yaml +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/.github/workflows/dependabot-auto-merge.yaml +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/.github/workflows/pr-title.yaml +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/.github/workflows/publish.yaml +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/.github/workflows/release.yaml +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/.gitignore +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/.pre-commit-config.yaml +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/.readthedocs.yaml +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/.release-please-config.json +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/CLAUDE.md +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/LICENSE +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/README.md +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/codecov.yml +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/docs/assets/multi-dim-input.png +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/docs/assets/multi-dim-metrics.png +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/docs/changelog.md +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/docs/data-model.md +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/docs/examples/clustering-io.ipynb +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/docs/examples/multi-dim.ipynb +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/docs/examples/segmentation.ipynb +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/docs/examples/tuning.ipynb +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/docs/gen_ref_pages.py +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/docs/index.md +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/docs/stylesheets/extra.css +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/mkdocs.yml +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/pyproject.toml +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/scripts/generate_readme_images.py +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/src/tsam_xarray/__init__.py +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/src/tsam_xarray/_clustering.py +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/src/tsam_xarray/_sample_data.py +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/src/tsam_xarray/_tuning.py +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/test/conftest.py +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/test/test_parametrized.py +0 -0
- {tsam_xarray-0.6.1 → tsam_xarray-0.6.2}/test/test_tuning.py +0 -0
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.2](https://github.com/FBumann/tsam_xarray/compare/v0.6.1...v0.6.2) (2026-07-21)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* first-class original-vs-reconstructed comparison on AggregationResult ([#93](https://github.com/FBumann/tsam_xarray/issues/93)) ([b3fd174](https://github.com/FBumann/tsam_xarray/commit/b3fd174cdbe379eb1de586aba5bfb75c0e4d21bb))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* align reconstructed coordinate order with input (+ comparison recipe) ([#97](https://github.com/FBumann/tsam_xarray/issues/97)) ([afd6818](https://github.com/FBumann/tsam_xarray/commit/afd6818715704822bacfad6edfa5c6595e9f8fc4))
|
|
14
|
+
|
|
3
15
|
## [0.6.1](https://github.com/FBumann/tsam_xarray/compare/v0.6.0...v0.6.1) (2026-07-09)
|
|
4
16
|
|
|
5
17
|
|
|
@@ -124,7 +124,13 @@
|
|
|
124
124
|
"cell_type": "markdown",
|
|
125
125
|
"metadata": {},
|
|
126
126
|
"source": [
|
|
127
|
-
"## Reconstructed vs original"
|
|
127
|
+
"## Reconstructed vs original\n",
|
|
128
|
+
"\n",
|
|
129
|
+
"`result.compare()` stacks `original` and `reconstructed` along a `variant`\n",
|
|
130
|
+
"dimension (on the original time axis), so the comparison plots directly with\n",
|
|
131
|
+
"`color=\"variant\"` — no manual `concat`/`melt`. Pass a coordinate selection\n",
|
|
132
|
+
"(e.g. `variable=\"solar\"`) to focus on one column. `result.original` and\n",
|
|
133
|
+
"`result.reconstructed` share the same dim order, so they always line up."
|
|
128
134
|
]
|
|
129
135
|
},
|
|
130
136
|
{
|
|
@@ -133,14 +139,8 @@
|
|
|
133
139
|
"metadata": {},
|
|
134
140
|
"outputs": [],
|
|
135
141
|
"source": [
|
|
136
|
-
"
|
|
137
|
-
"\n",
|
|
138
|
-
"comparison = xr.concat(\n",
|
|
139
|
-
" [da_simple.sel(variable=\"solar\"), result.reconstructed.sel(variable=\"solar\")],\n",
|
|
140
|
-
" dim=\"source\",\n",
|
|
141
|
-
").assign_coords(source=[\"original\", \"reconstructed\"])\n",
|
|
142
|
-
"comparison.plotly.line(\n",
|
|
143
|
-
" x=\"time\", color=\"source\", title=\"Original vs reconstructed (solar)\"\n",
|
|
142
|
+
"result.compare(variable=\"solar\").plotly.line(\n",
|
|
143
|
+
" x=\"time\", color=\"variant\", title=\"Original vs reconstructed (solar)\"\n",
|
|
144
144
|
")"
|
|
145
145
|
]
|
|
146
146
|
},
|
|
@@ -153,6 +153,38 @@
|
|
|
153
153
|
"result.residuals.plotly.line(x=\"time\", facet_col=\"variable\", title=\"Residuals\")"
|
|
154
154
|
]
|
|
155
155
|
},
|
|
156
|
+
{
|
|
157
|
+
"cell_type": "markdown",
|
|
158
|
+
"metadata": {},
|
|
159
|
+
"source": [
|
|
160
|
+
"Because `to_dataframe()` returns tidy long-form data, richer comparisons are a\n",
|
|
161
|
+
"few lines in the plotting library of your choice — no bespoke helper needed.\n",
|
|
162
|
+
"For example, load-duration curves (each series sorted descending, Original\n",
|
|
163
|
+
"dotted / Reconstructed solid):"
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"cell_type": "code",
|
|
168
|
+
"execution_count": null,
|
|
169
|
+
"metadata": {},
|
|
170
|
+
"outputs": [],
|
|
171
|
+
"source": [
|
|
172
|
+
"tidy = result.to_dataframe()\n",
|
|
173
|
+
"tidy[\"rank\"] = (\n",
|
|
174
|
+
" tidy.groupby([\"variant\", \"variable\"])[\"energy\"]\n",
|
|
175
|
+
" .rank(method=\"first\", ascending=False)\n",
|
|
176
|
+
" .astype(int)\n",
|
|
177
|
+
")\n",
|
|
178
|
+
"duration = tidy.set_index([\"rank\", \"variant\", \"variable\"])[\"energy\"].to_xarray()\n",
|
|
179
|
+
"duration.plotly.line(\n",
|
|
180
|
+
" x=\"rank\",\n",
|
|
181
|
+
" color=\"variable\",\n",
|
|
182
|
+
" line_dash=\"variant\",\n",
|
|
183
|
+
" line_dash_map={\"original\": \"dot\", \"reconstructed\": \"solid\"},\n",
|
|
184
|
+
" title=\"Load-duration curves: original vs reconstructed\",\n",
|
|
185
|
+
")"
|
|
186
|
+
]
|
|
187
|
+
},
|
|
156
188
|
{
|
|
157
189
|
"cell_type": "markdown",
|
|
158
190
|
"metadata": {},
|
|
@@ -195,6 +227,8 @@
|
|
|
195
227
|
"metadata": {},
|
|
196
228
|
"outputs": [],
|
|
197
229
|
"source": [
|
|
230
|
+
"import xarray as xr\n",
|
|
231
|
+
"\n",
|
|
198
232
|
"disaggregated = result.disaggregate(result.cluster_representatives)\n",
|
|
199
233
|
"\n",
|
|
200
234
|
"comparison = xr.concat(\n",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import itertools
|
|
6
|
+
import warnings
|
|
6
7
|
from collections.abc import Hashable, Sequence
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
@@ -280,8 +281,6 @@ def _validate_data(
|
|
|
280
281
|
|
|
281
282
|
# Dask arrays — compute before other checks
|
|
282
283
|
if hasattr(da.data, "dask"):
|
|
283
|
-
import warnings
|
|
284
|
-
|
|
285
284
|
warnings.warn(
|
|
286
285
|
"DataArray is backed by dask. Computing eagerly for tsam.",
|
|
287
286
|
stacklevel=3,
|
|
@@ -640,28 +639,33 @@ def _aggregate_single(
|
|
|
640
639
|
|
|
641
640
|
active_cols = _active_columns(df, active_coords, col_dims)
|
|
642
641
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
weights=tsam_weights, # type: ignore[arg-type]
|
|
649
|
-
**tsam_kwargs,
|
|
650
|
-
)
|
|
651
|
-
else:
|
|
652
|
-
# Cluster on the active subset, then apply that clustering back to the
|
|
653
|
-
# full frame so passive columns get representatives and reconstruction.
|
|
654
|
-
active_weights = (
|
|
655
|
-
None if tsam_weights is None else {c: tsam_weights[c] for c in active_cols}
|
|
642
|
+
with warnings.catch_warnings():
|
|
643
|
+
warnings.filterwarnings(
|
|
644
|
+
"ignore",
|
|
645
|
+
message=".*sorted alphabetically.*",
|
|
646
|
+
category=FutureWarning,
|
|
656
647
|
)
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
648
|
+
if len(active_cols) == len(df.columns):
|
|
649
|
+
tsam_result = tsam.aggregate(
|
|
650
|
+
df,
|
|
651
|
+
n_clusters,
|
|
652
|
+
weights=tsam_weights, # type: ignore[arg-type]
|
|
653
|
+
**tsam_kwargs,
|
|
654
|
+
)
|
|
655
|
+
else:
|
|
656
|
+
active_weights = (
|
|
657
|
+
None
|
|
658
|
+
if tsam_weights is None
|
|
659
|
+
else {c: tsam_weights[c] for c in active_cols}
|
|
660
|
+
)
|
|
661
|
+
clustering = tsam.aggregate(
|
|
662
|
+
df[active_cols],
|
|
663
|
+
n_clusters,
|
|
664
|
+
weights=active_weights, # type: ignore[arg-type]
|
|
665
|
+
**tsam_kwargs,
|
|
666
|
+
).clustering
|
|
667
|
+
apply_kwargs = {k: v for k, v in tsam_kwargs.items() if k in _APPLY_KWARGS}
|
|
668
|
+
tsam_result = clustering.apply(df, **apply_kwargs)
|
|
665
669
|
|
|
666
670
|
return _result_from_tsam(tsam_result, da, df, time_dim, col_dims)
|
|
667
671
|
|
|
@@ -676,6 +680,7 @@ def _result_from_tsam(
|
|
|
676
680
|
"""Build an AggregationResult from a tsam aggregation result."""
|
|
677
681
|
typical = _representatives_to_da(tsam_result.cluster_representatives, col_dims)
|
|
678
682
|
reconstructed = _reconstructed_to_da(tsam_result.reconstructed, time_dim, col_dims)
|
|
683
|
+
reconstructed = reconstructed.transpose(*da.dims).reindex_like(da)
|
|
679
684
|
|
|
680
685
|
cw = tsam_result.cluster_weights
|
|
681
686
|
cluster_ids = np.array(sorted(cw.keys()))
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
+
import pandas as pd
|
|
8
9
|
import xarray as xr
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
@@ -71,7 +72,7 @@ class AggregationResult:
|
|
|
71
72
|
*slice_dims)``.
|
|
72
73
|
accuracy: Per-column and weighted accuracy metrics.
|
|
73
74
|
reconstructed: Reconstructed time series
|
|
74
|
-
(same shape as
|
|
75
|
+
(same shape and dim order as ``original``).
|
|
75
76
|
original: The input data.
|
|
76
77
|
clustering: Reusable clustering metadata.
|
|
77
78
|
See `ClusteringResult`.
|
|
@@ -121,9 +122,66 @@ class AggregationResult:
|
|
|
121
122
|
|
|
122
123
|
@property
|
|
123
124
|
def residuals(self) -> xr.DataArray:
|
|
124
|
-
"""Difference between original and reconstructed data.
|
|
125
|
+
"""Difference between original and reconstructed data.
|
|
126
|
+
|
|
127
|
+
Shares the dim order of ``original`` and ``reconstructed``.
|
|
128
|
+
"""
|
|
125
129
|
return self.original - self.reconstructed
|
|
126
130
|
|
|
131
|
+
def compare(self, **sel: object) -> xr.DataArray:
|
|
132
|
+
"""Stack ``original`` and ``reconstructed`` along a ``variant`` dim.
|
|
133
|
+
|
|
134
|
+
Returns a single DataArray on the original time axis with a new
|
|
135
|
+
``variant`` coordinate ``["original", "reconstructed"]``, ready to
|
|
136
|
+
plot directly with a ``color=``/``hue="variant"`` grouping — no
|
|
137
|
+
``melt`` step. This is the canonical way to eyeball aggregation
|
|
138
|
+
quality per column and per slice dim.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
**sel: Optional label-based selection applied to both arrays
|
|
142
|
+
before stacking, e.g. ``compare(variable="solar")`` to
|
|
143
|
+
compare a single column.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
DataArray with dims ``("variant", *original.dims)``.
|
|
147
|
+
|
|
148
|
+
Examples:
|
|
149
|
+
>>> agg.compare(variable="solar").plotly.line(
|
|
150
|
+
... x="time", color="variant"
|
|
151
|
+
... )
|
|
152
|
+
"""
|
|
153
|
+
original = self.original
|
|
154
|
+
reconstructed = self.reconstructed
|
|
155
|
+
if sel:
|
|
156
|
+
original = original.sel(sel)
|
|
157
|
+
reconstructed = reconstructed.sel(sel)
|
|
158
|
+
variant = pd.Index(["original", "reconstructed"], name="variant")
|
|
159
|
+
combined = xr.concat([original, reconstructed], dim=variant)
|
|
160
|
+
combined.name = self.original.name
|
|
161
|
+
return combined
|
|
162
|
+
|
|
163
|
+
def to_dataframe(self, **sel: object) -> pd.DataFrame:
|
|
164
|
+
"""Tidy/long-form ``original`` vs ``reconstructed`` DataFrame.
|
|
165
|
+
|
|
166
|
+
A flat DataFrame with a ``variant`` column
|
|
167
|
+
(``"original"``/``"reconstructed"``), the ``time`` axis, every
|
|
168
|
+
cluster and slice dim, and a value column — ready to hand
|
|
169
|
+
straight to a plotting library.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
**sel: Optional label-based selection forwarded to
|
|
173
|
+
`compare` (e.g. ``variable="solar"``).
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Long-form DataFrame with a ``variant`` column and one value
|
|
177
|
+
column (named after the input DataArray, or ``"value"``).
|
|
178
|
+
"""
|
|
179
|
+
combined = self.compare(**sel)
|
|
180
|
+
name = combined.name
|
|
181
|
+
if name is None or str(name) == "variant":
|
|
182
|
+
name = "value"
|
|
183
|
+
return combined.to_dataframe(name=str(name)).reset_index()
|
|
184
|
+
|
|
127
185
|
def disaggregate(self, data: xr.DataArray) -> xr.DataArray:
|
|
128
186
|
"""Map data on ``(cluster, timestep)`` back to original time.
|
|
129
187
|
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.6.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 6,
|
|
21
|
+
__version__ = version = '0.6.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 6, 2)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -1529,3 +1529,127 @@ class TestSliceEdgeCases:
|
|
|
1529
1529
|
_validate_consistent_cluster_counts(
|
|
1530
1530
|
[r1, r2], [("low",), ("high",)]
|
|
1531
1531
|
) # should not raise
|
|
1532
|
+
|
|
1533
|
+
|
|
1534
|
+
def _make_time_last_da() -> xr.DataArray:
|
|
1535
|
+
"""DataArray with time as the LAST dim (the #92 papercut case)."""
|
|
1536
|
+
time = pd.date_range("2020-01-01", periods=10 * 24, freq="h")
|
|
1537
|
+
rng = np.random.default_rng(0)
|
|
1538
|
+
return xr.DataArray(
|
|
1539
|
+
rng.random((2, 2, len(time))),
|
|
1540
|
+
dims=["scenario", "variable", "time"],
|
|
1541
|
+
coords={
|
|
1542
|
+
"scenario": ["a", "b"],
|
|
1543
|
+
"variable": ["solar", "wind"],
|
|
1544
|
+
"time": time,
|
|
1545
|
+
},
|
|
1546
|
+
name="power",
|
|
1547
|
+
)
|
|
1548
|
+
|
|
1549
|
+
|
|
1550
|
+
class TestDimOrderConsistency:
|
|
1551
|
+
"""original / reconstructed / residuals must share dim order (#92)."""
|
|
1552
|
+
|
|
1553
|
+
def test_time_last_input_aligned(self):
|
|
1554
|
+
da = _make_time_last_da()
|
|
1555
|
+
result = tsam_xarray.aggregate(
|
|
1556
|
+
da, time_dim="time", cluster_dim="variable", n_clusters=4
|
|
1557
|
+
)
|
|
1558
|
+
assert result.original.dims == da.dims
|
|
1559
|
+
assert result.reconstructed.dims == result.original.dims
|
|
1560
|
+
assert result.residuals.dims == result.original.dims
|
|
1561
|
+
|
|
1562
|
+
def test_time_first_input_aligned(self):
|
|
1563
|
+
da = _make_da(scenarios=["low", "high"])
|
|
1564
|
+
result = tsam_xarray.aggregate(
|
|
1565
|
+
da,
|
|
1566
|
+
time_dim="time",
|
|
1567
|
+
cluster_dim=["variable", "region"],
|
|
1568
|
+
n_clusters=4,
|
|
1569
|
+
)
|
|
1570
|
+
assert result.reconstructed.dims == result.original.dims
|
|
1571
|
+
assert result.residuals.dims == result.original.dims
|
|
1572
|
+
|
|
1573
|
+
def test_single_dim_no_slice(self):
|
|
1574
|
+
da = _make_time_last_da().isel(scenario=0).drop_vars("scenario")
|
|
1575
|
+
result = tsam_xarray.aggregate(
|
|
1576
|
+
da, time_dim="time", cluster_dim="variable", n_clusters=4
|
|
1577
|
+
)
|
|
1578
|
+
assert result.reconstructed.dims == result.original.dims == da.dims
|
|
1579
|
+
|
|
1580
|
+
def test_non_alphabetical_coord_order_preserved(self):
|
|
1581
|
+
"""reconstructed keeps the input's coord order, not tsam's sorted one."""
|
|
1582
|
+
time = pd.date_range("2020-01-01", periods=5 * 24, freq="h")
|
|
1583
|
+
rng = np.random.default_rng(0)
|
|
1584
|
+
da = xr.DataArray(
|
|
1585
|
+
rng.random((len(time), 3)),
|
|
1586
|
+
dims=["time", "variable"],
|
|
1587
|
+
coords={"time": time, "variable": ["wind", "solar", "demand"]},
|
|
1588
|
+
name="p",
|
|
1589
|
+
)
|
|
1590
|
+
result = tsam_xarray.aggregate(
|
|
1591
|
+
da, time_dim="time", cluster_dim="variable", n_clusters=3
|
|
1592
|
+
)
|
|
1593
|
+
assert list(result.reconstructed["variable"].values) == [
|
|
1594
|
+
"wind",
|
|
1595
|
+
"solar",
|
|
1596
|
+
"demand",
|
|
1597
|
+
]
|
|
1598
|
+
assert not bool(result.reconstructed.isnull().any())
|
|
1599
|
+
|
|
1600
|
+
|
|
1601
|
+
class TestCompare:
|
|
1602
|
+
"""AggregationResult.compare() and .to_dataframe() (#92)."""
|
|
1603
|
+
|
|
1604
|
+
def test_compare_adds_variant_dim(self):
|
|
1605
|
+
da = _make_time_last_da()
|
|
1606
|
+
result = tsam_xarray.aggregate(
|
|
1607
|
+
da, time_dim="time", cluster_dim="variable", n_clusters=4
|
|
1608
|
+
)
|
|
1609
|
+
cmp = result.compare()
|
|
1610
|
+
assert cmp.dims == ("variant", *result.original.dims)
|
|
1611
|
+
assert list(cmp["variant"].values) == ["original", "reconstructed"]
|
|
1612
|
+
assert cmp.name == "power"
|
|
1613
|
+
xr.testing.assert_equal(cmp.sel(variant="original", drop=True), result.original)
|
|
1614
|
+
xr.testing.assert_equal(
|
|
1615
|
+
cmp.sel(variant="reconstructed", drop=True), result.reconstructed
|
|
1616
|
+
)
|
|
1617
|
+
|
|
1618
|
+
def test_compare_with_selection(self):
|
|
1619
|
+
da = _make_time_last_da()
|
|
1620
|
+
result = tsam_xarray.aggregate(
|
|
1621
|
+
da, time_dim="time", cluster_dim="variable", n_clusters=4
|
|
1622
|
+
)
|
|
1623
|
+
cmp = result.compare(variable="solar")
|
|
1624
|
+
assert "variable" not in cmp.dims
|
|
1625
|
+
assert cmp.sizes["variant"] == 2
|
|
1626
|
+
|
|
1627
|
+
def test_to_dataframe_is_long_form(self):
|
|
1628
|
+
da = _make_time_last_da()
|
|
1629
|
+
result = tsam_xarray.aggregate(
|
|
1630
|
+
da, time_dim="time", cluster_dim="variable", n_clusters=4
|
|
1631
|
+
)
|
|
1632
|
+
df = result.to_dataframe(variable="solar")
|
|
1633
|
+
assert "variant" in df.columns
|
|
1634
|
+
assert "power" in df.columns
|
|
1635
|
+
assert set(df["variant"].unique()) == {"original", "reconstructed"}
|
|
1636
|
+
n_time = da.sizes["time"]
|
|
1637
|
+
assert len(df) == 2 * da.sizes["scenario"] * n_time
|
|
1638
|
+
|
|
1639
|
+
def test_to_dataframe_unnamed_uses_value(self):
|
|
1640
|
+
da = _make_time_last_da().rename(None)
|
|
1641
|
+
result = tsam_xarray.aggregate(
|
|
1642
|
+
da, time_dim="time", cluster_dim="variable", n_clusters=4
|
|
1643
|
+
)
|
|
1644
|
+
df = result.to_dataframe()
|
|
1645
|
+
assert "value" in df.columns
|
|
1646
|
+
|
|
1647
|
+
def test_to_dataframe_name_collision_with_variant(self):
|
|
1648
|
+
"""An input named 'variant' falls back to 'value' (no insert clash)."""
|
|
1649
|
+
da = _make_time_last_da().rename("variant")
|
|
1650
|
+
result = tsam_xarray.aggregate(
|
|
1651
|
+
da, time_dim="time", cluster_dim="variable", n_clusters=4
|
|
1652
|
+
)
|
|
1653
|
+
df = result.to_dataframe(variable="solar")
|
|
1654
|
+
assert "value" in df.columns
|
|
1655
|
+
assert list(df.columns).count("variant") == 1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|