tsam-xarray 0.6.2__tar.gz → 0.6.3__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.
Files changed (44) hide show
  1. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/.github/workflows/ci.yaml +3 -3
  2. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/.github/workflows/publish.yaml +1 -1
  3. tsam_xarray-0.6.3/.release-please-manifest.json +3 -0
  4. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/CHANGELOG.md +7 -0
  5. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/PKG-INFO +1 -1
  6. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/src/tsam_xarray/_clustering.py +38 -0
  7. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/src/tsam_xarray/_version.py +2 -2
  8. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/test/test_aggregate.py +59 -0
  9. tsam_xarray-0.6.2/.release-please-manifest.json +0 -3
  10. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/.github/dependabot.yml +0 -0
  11. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/.github/workflows/dependabot-auto-merge.yaml +0 -0
  12. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/.github/workflows/pr-title.yaml +0 -0
  13. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/.github/workflows/release.yaml +0 -0
  14. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/.gitignore +0 -0
  15. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/.pre-commit-config.yaml +0 -0
  16. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/.readthedocs.yaml +0 -0
  17. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/.release-please-config.json +0 -0
  18. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/CLAUDE.md +0 -0
  19. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/LICENSE +0 -0
  20. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/README.md +0 -0
  21. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/codecov.yml +0 -0
  22. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/docs/assets/multi-dim-input.png +0 -0
  23. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/docs/assets/multi-dim-metrics.png +0 -0
  24. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/docs/changelog.md +0 -0
  25. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/docs/data-model.md +0 -0
  26. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/docs/examples/clustering-io.ipynb +0 -0
  27. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/docs/examples/getting-started.ipynb +0 -0
  28. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/docs/examples/multi-dim.ipynb +0 -0
  29. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/docs/examples/segmentation.ipynb +0 -0
  30. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/docs/examples/tuning.ipynb +0 -0
  31. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/docs/gen_ref_pages.py +0 -0
  32. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/docs/index.md +0 -0
  33. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/docs/stylesheets/extra.css +0 -0
  34. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/mkdocs.yml +0 -0
  35. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/pyproject.toml +0 -0
  36. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/scripts/generate_readme_images.py +0 -0
  37. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/src/tsam_xarray/__init__.py +0 -0
  38. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/src/tsam_xarray/_core.py +0 -0
  39. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/src/tsam_xarray/_result.py +0 -0
  40. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/src/tsam_xarray/_sample_data.py +0 -0
  41. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/src/tsam_xarray/_tuning.py +0 -0
  42. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/test/conftest.py +0 -0
  43. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/test/test_parametrized.py +0 -0
  44. {tsam_xarray-0.6.2 → tsam_xarray-0.6.3}/test/test_tuning.py +0 -0
@@ -30,7 +30,7 @@ jobs:
30
30
  with:
31
31
  enable-cache: true
32
32
 
33
- - uses: actions/setup-python@v6
33
+ - uses: actions/setup-python@v7
34
34
  with:
35
35
  python-version: "3.12"
36
36
 
@@ -56,7 +56,7 @@ jobs:
56
56
  with:
57
57
  enable-cache: true
58
58
 
59
- - uses: actions/setup-python@v6
59
+ - uses: actions/setup-python@v7
60
60
  with:
61
61
  python-version: ${{ matrix.python-version }}
62
62
 
@@ -83,7 +83,7 @@ jobs:
83
83
  with:
84
84
  enable-cache: true
85
85
 
86
- - uses: actions/setup-python@v6
86
+ - uses: actions/setup-python@v7
87
87
  with:
88
88
  python-version: "3.12"
89
89
 
@@ -31,7 +31,7 @@ jobs:
31
31
  with:
32
32
  enable-cache: true
33
33
 
34
- - uses: actions/setup-python@v6
34
+ - uses: actions/setup-python@v7
35
35
  with:
36
36
  python-version: "3.12"
37
37
 
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.6.3"
3
+ }
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.3](https://github.com/FBumann/tsam_xarray/compare/v0.6.2...v0.6.3) (2026-07-22)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * drop stored weights on apply() when columns are absent ([#100](https://github.com/FBumann/tsam_xarray/issues/100)) ([5616d1c](https://github.com/FBumann/tsam_xarray/commit/5616d1c14923c00e0bd795e7ea75193d457d6860))
9
+
3
10
  ## [0.6.2](https://github.com/FBumann/tsam_xarray/compare/v0.6.1...v0.6.2) (2026-07-21)
4
11
 
5
12
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tsam_xarray
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: Lightweight xarray wrapper for tsam time series aggregation
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -560,6 +560,43 @@ def _validate_apply(
560
560
  raise ValueError(msg)
561
561
 
562
562
 
563
+ def _drop_missing_weights(
564
+ cr: tsam.ClusteringResult, df: pd.DataFrame
565
+ ) -> tsam.ClusteringResult:
566
+ """Strip stored clustering weights when they reference absent columns.
567
+
568
+ tsam's ``ClusteringResult.apply()`` hard-fails when a stored weight column
569
+ is missing from the new data, which blocks transferring a clustering to a
570
+ differently-composed dataset. At apply time the cluster assignments and
571
+ centers are already fixed, so weights cannot change which periods are
572
+ selected — their only remaining effect is on the weighted accuracy
573
+ metrics. When a weighted column is absent we drop all weights and warn.
574
+
575
+ Tracking upstream fix: https://github.com/FZJ-IEK3-VSA/tsam/issues/396.
576
+ Once that lands, ``test_upstream_still_rejects_missing_weight_columns``
577
+ fails and this workaround can be removed.
578
+ """
579
+ import dataclasses
580
+ import warnings
581
+
582
+ weights = getattr(cr, "weights", None)
583
+ if not weights:
584
+ return cr
585
+ missing = set(weights) - set(df.columns)
586
+ if not missing:
587
+ return cr
588
+
589
+ warnings.warn(
590
+ f"Stored clustering weights reference columns absent from the new "
591
+ f"data ({sorted(map(str, missing))}); dropping all weights. "
592
+ f"Representatives and reconstruction are unaffected — only the "
593
+ f"weighted accuracy metrics change.",
594
+ UserWarning,
595
+ stacklevel=3,
596
+ )
597
+ return dataclasses.replace(cr, weights=None)
598
+
599
+
563
600
  def _apply_single(
564
601
  da: xr.DataArray,
565
602
  cr: tsam.ClusteringResult,
@@ -580,6 +617,7 @@ def _apply_single(
580
617
  from tsam_xarray._result import AccuracyMetrics, AggregationResult
581
618
 
582
619
  df = _to_dataframe(da, time_dim, col_dims)
620
+ cr = _drop_missing_weights(cr, df)
583
621
  tsam_result = cr.apply(df, **tsam_kwargs)
584
622
 
585
623
  typical = _representatives_to_da(tsam_result.cluster_representatives, col_dims)
@@ -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.2'
22
- __version_tuple__ = version_tuple = (0, 6, 2)
21
+ __version__ = version = '0.6.3'
22
+ __version_tuple__ = version_tuple = (0, 6, 3)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -2,6 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import warnings
6
+
5
7
  import numpy as np
6
8
  import pandas as pd
7
9
  import pytest
@@ -1373,6 +1375,63 @@ class TestClusteringIO:
1373
1375
  xr.testing.assert_allclose(dis, new_result.reconstructed)
1374
1376
 
1375
1377
 
1378
+ class TestApplyWeightTransfer:
1379
+ """Transferring a weighted clustering to differently-composed data.
1380
+
1381
+ Works around tsam#396: ``ClusteringResult.apply()`` hard-fails when a
1382
+ stored weight column is missing from the new data. We drop the weights
1383
+ and warn instead, since at apply time weights cannot change selection.
1384
+ """
1385
+
1386
+ def _weighted_clustering(self):
1387
+ da = _make_da()
1388
+ da_flat = da.isel(region=0).drop_vars("region")
1389
+ result = tsam_xarray.aggregate(
1390
+ da_flat,
1391
+ time_dim="time",
1392
+ cluster_dim="variable",
1393
+ n_clusters=4,
1394
+ weights={"solar": 5.0, "wind": 1.0},
1395
+ )
1396
+ return result.clustering, da_flat
1397
+
1398
+ def test_apply_warns_and_drops_missing_weight_columns(self):
1399
+ """Applying to data without a weighted column warns, not raises."""
1400
+ clustering, da_flat = self._weighted_clustering()
1401
+ new_da = da_flat.sel(variable=["wind"]) # 'solar' (weighted) absent
1402
+ with pytest.warns(UserWarning, match="absent from the new data"):
1403
+ new_result = clustering.apply(new_da)
1404
+ assert "solar" not in new_result.cluster_representatives["variable"].values
1405
+ assert new_result.reconstructed.notnull().all()
1406
+
1407
+ def test_apply_no_warning_when_all_weight_columns_present(self):
1408
+ """Same composition keeps weights and emits no warning."""
1409
+ clustering, da_flat = self._weighted_clustering()
1410
+ with warnings.catch_warnings():
1411
+ warnings.simplefilter("error")
1412
+ new_result = clustering.apply(da_flat)
1413
+ assert new_result.n_clusters == 4
1414
+
1415
+ def test_upstream_still_rejects_missing_weight_columns(self):
1416
+ """Canary: when raw tsam stops raising, tsam#396 is fixed.
1417
+
1418
+ Remove ``_drop_missing_weights`` and this test once that lands.
1419
+ """
1420
+ import tsam
1421
+
1422
+ idx = pd.date_range("2020-01-01", periods=24 * 20, freq="h")
1423
+ rng = np.random.default_rng(0)
1424
+ df = pd.DataFrame(
1425
+ {"a": rng.random(len(idx)), "b": rng.random(len(idx))},
1426
+ index=idx,
1427
+ )
1428
+ cr = tsam.aggregate(
1429
+ df, 4, period_duration=24, weights={"a": 5.0, "b": 0.1}
1430
+ ).clustering
1431
+ with pytest.raises(ValueError, match="Weight columns not found"):
1432
+ cr.apply(df.drop(columns=["b"]))
1433
+
1434
+
1376
1435
  class TestClusteringDisaggregate:
1377
1436
  """Tests for ClusteringInfo.disaggregate()."""
1378
1437
 
@@ -1,3 +0,0 @@
1
- {
2
- ".": "0.6.2"
3
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes