Skip to content

Commit 44cd694

Browse files
Add aggregate_time support to convert_and_aggregate (#491)
* Add aggregate_time support to convert_and_aggregate * Address PR review: add 'legacy' default, drop False, extract _aggregate_time helper, consistent keep_attrs, use fixtures in tests * update release notes * Replace capacity_factor with aggregate_time in example notebooks
1 parent 85136ab commit 44cd694

5 files changed

Lines changed: 257 additions & 33 deletions

File tree

RELEASE_NOTES.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ Upcoming Release
1818
``pip install git+https://github.com/pypsa/atlite``.
1919

2020

21+
* Add ``aggregate_time={"sum", "mean", None}`` to ``convert_and_aggregate`` for temporal
22+
aggregation with and without spatial aggregation, and deprecate ``capacity_factor``/``capacity_factor_timeseries``
23+
in favor of it
24+
2125
`v0.5.0 <https://github.com/PyPSA/atlite/releases/tag/v0.5.0>`__ (13th March 2026)
2226
=======================================================================================
2327

atlite/convert.py

Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99

1010
import datetime as dt
1111
import logging
12+
import warnings
1213
from collections import namedtuple
1314
from operator import itemgetter
1415
from pathlib import Path
15-
from typing import TYPE_CHECKING
16+
from typing import TYPE_CHECKING, Literal
1617

1718
import geopandas as gpd
1819
import numpy as np
@@ -43,11 +44,17 @@
4344
logger = logging.getLogger(__name__)
4445

4546
if TYPE_CHECKING:
46-
from typing import Literal
47-
4847
from atlite.resource import TurbineConfig
4948

5049

50+
def _aggregate_time(da: xr.DataArray, method: str | None) -> xr.DataArray:
51+
if method == "sum":
52+
return da.sum("time", keep_attrs=True)
53+
elif method == "mean":
54+
return da.mean("time", keep_attrs=True)
55+
return da
56+
57+
5158
def convert_and_aggregate(
5259
cutout,
5360
convert_func,
@@ -58,6 +65,7 @@ def convert_and_aggregate(
5865
shapes_crs=4326,
5966
per_unit=False,
6067
return_capacity=False,
68+
aggregate_time: Literal["sum", "mean", "legacy"] | None = "legacy",
6169
capacity_factor=False,
6270
capacity_factor_timeseries=False,
6371
show_progress=False,
@@ -93,12 +101,18 @@ def convert_and_aggregate(
93101
return_capacity : boolean
94102
Additionally returns the installed capacity at each bus corresponding
95103
to ``layout`` (defaults to False).
104+
aggregate_time : "sum", "mean", "legacy", or None
105+
Controls temporal aggregation of results. ``"sum"`` sums over time,
106+
``"mean"`` averages over time, ``None`` returns full timeseries.
107+
``"legacy"`` (default) preserves historical behavior: time-summed
108+
without spatial aggregation and full timeseries with spatial
109+
aggregation; this option is deprecated and will be removed in a
110+
future release. Replaces the deprecated ``capacity_factor`` and
111+
``capacity_factor_timeseries`` parameters.
96112
capacity_factor : boolean
97-
If True, the static capacity factor of the chosen resource for each
98-
grid cell is computed.
113+
Deprecated. Use ``aggregate_time="mean"`` instead.
99114
capacity_factor_timeseries : boolean
100-
If True, the capacity factor time series of the chosen resource for
101-
each grid cell is computed.
115+
Deprecated. Use ``aggregate_time=None`` instead (which is the default).
102116
show_progress : boolean, default False
103117
Whether to show a progress bar.
104118
dask_kwargs : dict, default {}
@@ -116,17 +130,21 @@ def convert_and_aggregate(
116130
117131
**With aggregation** (``matrix``, ``shapes``, or ``layout`` given):
118132
Time-series of renewable generation aggregated to buses, with
119-
dimensions ``(bus, time)``.
133+
dimensions ``(bus, time)``. If ``aggregate_time`` is set, the time
134+
dimension is reduced accordingly.
120135
121136
**Without aggregation** (none of the above given):
122137
123-
- ``capacity_factor_timeseries=True``: per-cell capacity factor
124-
time series with dimensions ``(time, y, x)`` in p.u. Individual
125-
locations can be extracted with
126-
``result.sel(x=lon, y=lat, method="nearest")``.
127-
- ``capacity_factor=True``: time-averaged capacity factor per cell
128-
with dimensions ``(y, x)`` in p.u.
129-
- Otherwise: total energy sum per cell with dimensions ``(y, x)``.
138+
- ``aggregate_time=None``: per-cell timeseries ``(time, y, x)``.
139+
- ``aggregate_time="mean"``: time-averaged per cell ``(y, x)``.
140+
- ``aggregate_time="sum"``: time-summed per cell ``(y, x)``.
141+
142+
Legacy behavior (deprecated):
143+
144+
- ``aggregate_time="legacy"``: historical context-dependent default.
145+
- ``capacity_factor_timeseries=True``: equivalent to
146+
``aggregate_time=None``.
147+
- ``capacity_factor=True``: equivalent to ``aggregate_time="mean"``.
130148
131149
units : xr.DataArray (optional)
132150
The installed units per bus in MW corresponding to ``layout``
@@ -138,6 +156,42 @@ def convert_and_aggregate(
138156
pv : Generate solar PV generation time-series.
139157
140158
"""
159+
if aggregate_time not in ("sum", "mean", "legacy", None):
160+
raise ValueError(
161+
f"aggregate_time must be 'sum', 'mean', 'legacy', or None, "
162+
f"got {aggregate_time!r}"
163+
)
164+
165+
if aggregate_time == "legacy":
166+
warnings.warn(
167+
"aggregate_time='legacy' is deprecated and will be removed in a "
168+
"future release. Pass 'sum', 'mean', or None explicitly.",
169+
FutureWarning,
170+
stacklevel=2,
171+
)
172+
173+
if capacity_factor or capacity_factor_timeseries:
174+
if aggregate_time != "legacy":
175+
raise ValueError(
176+
"Cannot use 'aggregate_time' together with deprecated "
177+
"'capacity_factor' or 'capacity_factor_timeseries'."
178+
)
179+
if capacity_factor:
180+
warnings.warn(
181+
"capacity_factor is deprecated. Use aggregate_time='mean' instead.",
182+
FutureWarning,
183+
stacklevel=2,
184+
)
185+
aggregate_time = "mean"
186+
if capacity_factor_timeseries:
187+
warnings.warn(
188+
"capacity_factor_timeseries is deprecated. "
189+
"Use aggregate_time=None instead.",
190+
FutureWarning,
191+
stacklevel=2,
192+
)
193+
aggregate_time = None
194+
141195
func_name = convert_func.__name__.replace("convert_", "")
142196
logger.info(f"Convert and aggregate '{func_name}'.")
143197
da = convert_func(cutout.data, **convert_kwds)
@@ -150,16 +204,10 @@ def convert_and_aggregate(
150204
"One of `matrix`, `shapes` and `layout` must be "
151205
"given for `per_unit` or `return_capacity`"
152206
)
153-
if capacity_factor or capacity_factor_timeseries:
154-
if capacity_factor_timeseries:
155-
res = da.rename("capacity factor")
156-
else:
157-
res = da.mean("time").rename("capacity factor")
158-
res.attrs["units"] = "p.u."
159-
return maybe_progressbar(res, show_progress, **dask_kwargs)
160-
else:
161-
res = da.sum("time", keep_attrs=True)
162-
return maybe_progressbar(res, show_progress, **dask_kwargs)
207+
208+
agg = "sum" if aggregate_time == "legacy" else aggregate_time
209+
res = _aggregate_time(da, agg)
210+
return maybe_progressbar(res, show_progress, **dask_kwargs)
163211

164212
if matrix is not None:
165213
if shapes is not None:
@@ -216,6 +264,9 @@ def convert_and_aggregate(
216264
else:
217265
results.attrs["units"] = "MW"
218266

267+
if aggregate_time != "legacy":
268+
results = _aggregate_time(results, aggregate_time)
269+
219270
if return_capacity:
220271
return maybe_progressbar(results, show_progress, **dask_kwargs), capacity
221272
else:
@@ -666,7 +717,7 @@ def wind(
666717
Get per-cell capacity factor time series (no aggregation):
667718
668719
>>> cf = cutout.wind(turbine="Vestas_V112_3MW",
669-
... capacity_factor_timeseries=True)
720+
... aggregate_time=None)
670721
>>> cf.dims
671722
('time', 'y', 'x')
672723
>>> location_cf = cf.sel(x=6.9, y=53.1, method="nearest")
@@ -850,7 +901,7 @@ def pv(cutout, panel, orientation, tracking=None, clearsky_model=None, **params)
850901
Get per-cell capacity factor time series (no aggregation):
851902
852903
>>> cf = cutout.pv(panel="CSi", orientation="latitude_optimal",
853-
... capacity_factor_timeseries=True)
904+
... aggregate_time=None)
854905
>>> location_cf = cf.sel(x=6.9, y=53.1, method="nearest")
855906
856907
References

examples/plotting_with_atlite.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@
331331
}
332332
],
333333
"source": [
334-
"cap_factors = cutout.wind(turbine=\"Vestas_V112_3MW\", capacity_factor=True)\n",
334+
"cap_factors = cutout.wind(turbine=\"Vestas_V112_3MW\", aggregate_time=\"mean\")\n",
335335
"\n",
336336
"fig, ax = plt.subplots(subplot_kw={\"projection\": projection}, figsize=(9, 7))\n",
337337
"cap_factors.name = \"Capacity Factor\"\n",

examples/working-with-csp.ipynb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@
337337
}
338338
],
339339
"source": [
340-
"cf = cutout.csp(installation=\"SAM_solar_tower\", capacity_factor=True)"
340+
"cf = cutout.csp(installation=\"SAM_solar_tower\", aggregate_time=\"mean\")"
341341
]
342342
},
343343
{
@@ -382,8 +382,8 @@
382382
"id": "123230d8-beaf-42e8-9fda-74f03cbfbe9c",
383383
"metadata": {},
384384
"source": [
385-
"Alternatively the specific generation can be calculated by setting `capacity_factor=False` (default).\n",
386-
"An comparison between the `SAM_solar_tower` installation and `lossless_installation` shows the difference due to the solar position dependend efficiency:"
385+
"Alternatively the specific generation can be calculated by omitting `aggregate_time` (or using `aggregate_time=\\\"sum\\\"`).\n",
386+
"A comparison between the `SAM_solar_tower` installation and `lossless_installation` shows the difference due to the solar position dependent efficiency:"
387387
]
388388
},
389389
{
@@ -417,7 +417,7 @@
417417
"# Calculation of Capacity Factor and Specific Generation for: SAM_solar_tower installation\n",
418418
"st = {\n",
419419
" \"capacity factor\": cutout.csp(\n",
420-
" installation=\"SAM_solar_tower\", capacity_factor=True\n",
420+
" installation=\"SAM_solar_tower\", aggregate_time=\"mean\"\n",
421421
" ).rename(\"SAM_solar_tower CF\"),\n",
422422
" \"specific generation\": cutout.csp(installation=\"SAM_solar_tower\").rename(\n",
423423
" \"SAM_solar_tower SG\"\n",
@@ -429,7 +429,7 @@
429429
" \"capacity factor\": cutout.csp(\n",
430430
" installation=\"lossless_installation\",\n",
431431
" technology=\"solar tower\",\n",
432-
" capacity_factor=True,\n",
432+
" aggregate_time=\"mean\",\n",
433433
" ).rename(\"lossless_installation CF\"),\n",
434434
" \"specific generation\": cutout.csp(\n",
435435
" installation=\"lossless_installation\",\n",

0 commit comments

Comments
 (0)