aggfly 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.
- aggfly/__init__.py +32 -0
- aggfly/aggregate/__init__.py +5 -0
- aggfly/aggregate/aggregate.py +228 -0
- aggfly/aggregate/aggregate_utils.py +76 -0
- aggfly/aggregate/spatial.py +154 -0
- aggfly/aggregate/temporal.py +198 -0
- aggfly/aggregate/z_old/spatial-Copy1.py +102 -0
- aggfly/aggregate/z_old/temporal-Copy1.py +430 -0
- aggfly/cache/__init__.py +1 -0
- aggfly/cache/project_cache.py +161 -0
- aggfly/dataset/__init__.py +3 -0
- aggfly/dataset/dataset.py +546 -0
- aggfly/dataset/grid.py +196 -0
- aggfly/dataset/grid_utils.py +142 -0
- aggfly/regions/__init__.py +1 -0
- aggfly/regions/georegions.py +222 -0
- aggfly/regions/shp_utils.py +47 -0
- aggfly/tests/__init__.py +1 -0
- aggfly/tests/test_aggregate.py +155 -0
- aggfly/utils.py +51 -0
- aggfly/weights/__init__.py +4 -0
- aggfly/weights/crop_weights.py +138 -0
- aggfly/weights/crop_weights_utils.py +0 -0
- aggfly/weights/grid_weights.py +486 -0
- aggfly/weights/pop_weights.py +76 -0
- aggfly/weights/secondary_weights.py +119 -0
- aggfly-0.1.0.dist-info/LICENSE +201 -0
- aggfly-0.1.0.dist-info/METADATA +47 -0
- aggfly-0.1.0.dist-info/RECORD +30 -0
- aggfly-0.1.0.dist-info/WHEEL +4 -0
aggfly/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from .aggregate import (
|
|
2
|
+
TemporalAggregator,
|
|
3
|
+
SpatialAggregator,
|
|
4
|
+
aggregate_dataset,
|
|
5
|
+
aggregate_time,
|
|
6
|
+
aggregate_space,
|
|
7
|
+
distributed_client,
|
|
8
|
+
is_distributed,
|
|
9
|
+
start_dask_client,
|
|
10
|
+
shutdown_dask_client
|
|
11
|
+
)
|
|
12
|
+
from .dataset import Dataset, Grid, dataset_from_path
|
|
13
|
+
from .weights import (
|
|
14
|
+
CropWeights,
|
|
15
|
+
PopWeights,
|
|
16
|
+
GridWeights,
|
|
17
|
+
SecondaryWeights,
|
|
18
|
+
weights_from_objects,
|
|
19
|
+
pop_weights_from_path,
|
|
20
|
+
crop_weights_from_path,
|
|
21
|
+
secondary_weights_from_path
|
|
22
|
+
)
|
|
23
|
+
from .regions import GeoRegions, georegions_from_path, georegions_from_name
|
|
24
|
+
from .tests import (
|
|
25
|
+
georegion,
|
|
26
|
+
dataset_360,
|
|
27
|
+
secondary_weights,
|
|
28
|
+
weights,
|
|
29
|
+
test_weights,
|
|
30
|
+
test_aggregate_time,
|
|
31
|
+
test_aggregate
|
|
32
|
+
)
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains functions for aggregating datasets over time and space.
|
|
3
|
+
|
|
4
|
+
Functions:
|
|
5
|
+
- transform_dataset(dataset: Dataset, key: str, **kwargs: Union[int, List[int]]) -> Dict[str, Dataset]:
|
|
6
|
+
Transform a given dataset by raising it to a power or interacting with another dataset.
|
|
7
|
+
|
|
8
|
+
- aggregate_time(dataset: Dataset, aggregator_dict: Dict[str, Union[List[Tuple], TemporalAggregator]] = None, weights: GridWeights = None, **kwargs) -> Dict[str, Dataset]:
|
|
9
|
+
Aggregate a dataset over time using the specified temporal aggregators.
|
|
10
|
+
|
|
11
|
+
- aggregate_space(dataset_dict: Dict[str, Dataset], weights: GridWeights) -> pd.DataFrame:
|
|
12
|
+
Aggregate a dictionary of datasets over space.
|
|
13
|
+
|
|
14
|
+
- aggregate_dataset(dataset: Dataset, weights: GridWeights, aggregator_dict: Dict[str, Union[List[Tuple], TemporalAggregator]] = None, **kwargs) -> pd.DataFrame:
|
|
15
|
+
Aggregate a dataset over time and space.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import List, Dict, Union, Tuple
|
|
19
|
+
import pandas as pd
|
|
20
|
+
import dask
|
|
21
|
+
from dask.distributed import LocalCluster, Client, progress
|
|
22
|
+
|
|
23
|
+
from .temporal import TemporalAggregator
|
|
24
|
+
from .spatial import SpatialAggregator
|
|
25
|
+
from ..dataset import Dataset
|
|
26
|
+
from ..weights import GridWeights
|
|
27
|
+
from .aggregate_utils import distributed_client, is_distributed, start_dask_client, shutdown_dask_client
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
from dask.diagnostics import ProgressBar
|
|
31
|
+
ProgressBar().register()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def transform_dataset(
|
|
35
|
+
dataset: Dataset, key: str, **kwargs: Union[int, List[int]]
|
|
36
|
+
) -> Dict[str, Dataset]:
|
|
37
|
+
"""
|
|
38
|
+
Transform a given dataset by raising it to a power or interacting with another dataset.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
dataset (Dataset): The dataset to transform.
|
|
42
|
+
key (str): The name of the dataset.
|
|
43
|
+
**kwargs: Keyword arguments for the transformation.
|
|
44
|
+
If 'exp' is provided, raise the dataset to the power of the provided exponent(s).
|
|
45
|
+
If 'inter' is provided, interact the dataset with another dataset.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
dict: A dictionary containing the transformed dataset(s).
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
if "exp" in kwargs:
|
|
52
|
+
if not isinstance(kwargs["exp"], list):
|
|
53
|
+
kwargs["exp"] = [kwargs["exp"]]
|
|
54
|
+
dataset = [dataset.power(exp) for exp in kwargs["exp"][0]]
|
|
55
|
+
new_keys = [f"{key}_{exp}" for exp in kwargs["exp"][0]]
|
|
56
|
+
output_dict = dict(zip(new_keys, dataset))
|
|
57
|
+
elif "inter" in kwargs:
|
|
58
|
+
dataset = dataset.interact(kwargs["inter"])
|
|
59
|
+
output_dict = {key: dataset}
|
|
60
|
+
else:
|
|
61
|
+
raise ValueError("No valid transform argument provided.")
|
|
62
|
+
return output_dict.values(), output_dict.keys()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def aggregate_time(
|
|
66
|
+
dataset: Dataset,
|
|
67
|
+
weights: GridWeights = None,
|
|
68
|
+
aggregator_dict: Dict[str, Union[List[Tuple], TemporalAggregator]] = None,
|
|
69
|
+
**kwargs,
|
|
70
|
+
) -> Dict[str, Dataset]:
|
|
71
|
+
"""
|
|
72
|
+
Aggregate a dataset over time using the specified temporal aggregators.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
dataset (Dataset): The dataset to aggregate.
|
|
76
|
+
weights (GridWeights, optional): The weights to use for aggregation. Defaults to None.
|
|
77
|
+
aggregator_dict (Dict[str, Union[List[Tuple], TemporalAggregator]], optional): A dictionary of temporal aggregators to apply to the dataset. Defaults to None.
|
|
78
|
+
**kwargs: Additional keyword arguments to use if `aggregator_dict` is not provided.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Dict[str, Dataset]: A dictionary of aggregated datasets, with keys corresponding to the keys in `aggregator_dict`.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def aggregate_time(
|
|
86
|
+
dataset: Dataset,
|
|
87
|
+
weights: GridWeights = None,
|
|
88
|
+
aggregator_dict: Dict[str, Union[List[Tuple], TemporalAggregator]] = None,
|
|
89
|
+
**kwargs,
|
|
90
|
+
) -> Dict[str, Dataset]:
|
|
91
|
+
if aggregator_dict is None:
|
|
92
|
+
if kwargs is None:
|
|
93
|
+
raise ValueError("No arguments provided.")
|
|
94
|
+
else:
|
|
95
|
+
aggregator_dict = kwargs
|
|
96
|
+
out_dict = {}
|
|
97
|
+
for key, value in aggregator_dict.items():
|
|
98
|
+
keys = [key]
|
|
99
|
+
data = [dataset.deepcopy()]
|
|
100
|
+
for key2, value2 in value:
|
|
101
|
+
if key2 == "aggregate":
|
|
102
|
+
if not isinstance(value2, TemporalAggregator):
|
|
103
|
+
value2 = TemporalAggregator(**value2)
|
|
104
|
+
data = [value2.execute(x, weights) for x in data]
|
|
105
|
+
|
|
106
|
+
if value2.multi_dd:
|
|
107
|
+
if len(data) > 1:
|
|
108
|
+
raise ValueError(
|
|
109
|
+
"Cannot aggregate multiple datasets with multiple ddargs, e.g., multiple polynomials for multiple bins"
|
|
110
|
+
)
|
|
111
|
+
data, keys = multi_dd_to_dict(data[0], key, value2.ddargs)
|
|
112
|
+
|
|
113
|
+
elif key2 == "transform":
|
|
114
|
+
transformed_data, transformed_keys = [], []
|
|
115
|
+
for d, k in zip(data, keys):
|
|
116
|
+
d2, k2 = transform_dataset(d, k, **value2)
|
|
117
|
+
transformed_data.extend(d2)
|
|
118
|
+
transformed_keys.extend(k2)
|
|
119
|
+
data, keys = transformed_data, transformed_keys
|
|
120
|
+
|
|
121
|
+
data_dict = dict(zip(keys, data))
|
|
122
|
+
out_dict = out_dict | data_dict
|
|
123
|
+
return out_dict
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def aggregate_space(
|
|
127
|
+
dataset_dict: Dict[str, Dataset], weights: GridWeights,
|
|
128
|
+
npartitions=None, **kwargs
|
|
129
|
+
) -> pd.DataFrame:
|
|
130
|
+
"""
|
|
131
|
+
Aggregate a dictionary of datasets over space.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
dataset_dict (dict): A dictionary containing the datasets to aggregate, where
|
|
135
|
+
the keys are the names of the datasets and the values are the datasets themselves.
|
|
136
|
+
weights (GridWeights): The weights to use for aggregation.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
df: A dataframe containing the aggregated data.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
dataset_list = list(dataset_dict.values())
|
|
144
|
+
|
|
145
|
+
# client = distributed_client()
|
|
146
|
+
# if client is None and npartitions is None:
|
|
147
|
+
# npartitions=1
|
|
148
|
+
# else:
|
|
149
|
+
# npartitions=len(client.scheduler_info()["workers"])
|
|
150
|
+
# da_list = dask.persist([x.da for x in dataset_list])[0]
|
|
151
|
+
# progress(da_list)
|
|
152
|
+
# for i, dataset in enumerate(dataset_list):
|
|
153
|
+
# dataset.da = da_list[i]
|
|
154
|
+
df = SpatialAggregator(
|
|
155
|
+
dataset_list, weights, names=list(dataset_dict.keys()),
|
|
156
|
+
).compute(npartitions=npartitions)
|
|
157
|
+
return df
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def aggregate_dataset(
|
|
161
|
+
weights: GridWeights,
|
|
162
|
+
dataset: Dataset = None,
|
|
163
|
+
aggregator_dict: Dict[str, Union[List[Tuple], TemporalAggregator]] = None,
|
|
164
|
+
dataset_dict = None,
|
|
165
|
+
n_workers = 50,
|
|
166
|
+
threads_per_worker = 1,
|
|
167
|
+
processes = True,
|
|
168
|
+
memory_limit=None,
|
|
169
|
+
cluster_args = {},
|
|
170
|
+
**kwargs,
|
|
171
|
+
) -> pd.DataFrame:
|
|
172
|
+
"""
|
|
173
|
+
Aggregate a dataset over time and space.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
dataset (Dataset): The dataset to aggregate.
|
|
177
|
+
weights (GridWeights): The weights to use for aggregation.
|
|
178
|
+
agg_dict (dict): A dictionary containing the arguments for creating TemporalAggregator objects.
|
|
179
|
+
The keys of the dictionary are names, and the values are a list of either tuples or TemporalAggregator objects.
|
|
180
|
+
If the list contains tuples, use them as arguments to instantiate a temporal aggregator.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
df: A dataframe containing the aggregated data.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
if dataset is None:
|
|
187
|
+
raise ValueError("No dataset provided.")
|
|
188
|
+
|
|
189
|
+
if aggregator_dict is None and kwargs is not None:
|
|
190
|
+
aggregator_dict = kwargs
|
|
191
|
+
|
|
192
|
+
if aggregator_dict is not None:
|
|
193
|
+
dataset_dict = aggregate_time(dataset, weights, aggregator_dict)
|
|
194
|
+
elif dataset_dict is None:
|
|
195
|
+
dataset_dict = {"variable": dataset}
|
|
196
|
+
if dataset_dict is None and dataset is None:
|
|
197
|
+
raise ValueError("No aggregator dict or dataset dict provided.")
|
|
198
|
+
|
|
199
|
+
df = aggregate_space(dataset_dict, weights)
|
|
200
|
+
df = (
|
|
201
|
+
weights.georegions.shp[[weights.georegions.regionid]].merge(
|
|
202
|
+
df, left_index=True, right_on="region_id"
|
|
203
|
+
)
|
|
204
|
+
).drop(columns="region_id")
|
|
205
|
+
|
|
206
|
+
# client.shutdown()
|
|
207
|
+
|
|
208
|
+
# _ = shutdown_dask_client()
|
|
209
|
+
|
|
210
|
+
return df
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def multi_dd_to_dict(data, key, ddargs):
|
|
214
|
+
"""
|
|
215
|
+
Converts a multi-variable list of datasets to a dictionary with keys
|
|
216
|
+
generated from the given key and ddargs.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
data (list): The list of Datasets to convert to a dictionary.
|
|
220
|
+
key (str): The base key to use for generating the dictionary keys.
|
|
221
|
+
ddargs (list): A list of tuples representing the dimensions of the array.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
dict: A dictionary with keys generated from the given key and ddargs, and values from the list of datasets.
|
|
225
|
+
"""
|
|
226
|
+
keys = [f"{key}_{x[0]}_{x[1]}" for x in ddargs]
|
|
227
|
+
# data_dict = dict(zip(keys, data))
|
|
228
|
+
return data, keys
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import dask.distributed
|
|
2
|
+
from dask.distributed import Client
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
def is_distributed():
|
|
6
|
+
"""
|
|
7
|
+
Returns True if the code is running in a distributed environment, False otherwise.
|
|
8
|
+
|
|
9
|
+
This function checks if the code is running in a distributed environment by attempting to get the global Dask client.
|
|
10
|
+
If the client is not None, it means that the code is running in a distributed environment.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
bool: True if the code is running in a distributed environment, False otherwise.
|
|
14
|
+
"""
|
|
15
|
+
client = dask.distributed.client._get_global_client()
|
|
16
|
+
if client is not None:
|
|
17
|
+
return True
|
|
18
|
+
else:
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
def distributed_client():
|
|
22
|
+
"""
|
|
23
|
+
Returns the global Dask distributed client object.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
--------
|
|
27
|
+
client : dask.distributed.Client
|
|
28
|
+
The global Dask distributed client object.
|
|
29
|
+
"""
|
|
30
|
+
client = dask.distributed.client._get_global_client()
|
|
31
|
+
return client
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def start_dask_client(n_workers: int = 2, threads_per_worker: int = 2, **kwargs):
|
|
35
|
+
"""
|
|
36
|
+
Start a dask distributed cluster.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
n_workers (int, optional): The number of workers to use. Defaults to 2.
|
|
40
|
+
threads_per_worker (int, optional): The number of threads per worker. Defaults to 2.
|
|
41
|
+
**kwargs: Additional keyword arguments to pass to the dask Client constructor.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
client: A dask distributed client.
|
|
45
|
+
"""
|
|
46
|
+
client = Client(
|
|
47
|
+
n_workers=n_workers,
|
|
48
|
+
threads_per_worker=threads_per_worker,
|
|
49
|
+
**kwargs
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
arg_dict = {
|
|
53
|
+
"n_workers": n_workers,
|
|
54
|
+
"threads_per_worker": threads_per_worker,
|
|
55
|
+
}
|
|
56
|
+
all_dict = {**arg_dict, **kwargs}
|
|
57
|
+
for k in all_dict.keys():
|
|
58
|
+
client.set_metadata(['args', k], all_dict[k])
|
|
59
|
+
|
|
60
|
+
return client
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def shutdown_dask_client():
|
|
64
|
+
"""
|
|
65
|
+
Shutdown the global Dask distributed client object.
|
|
66
|
+
"""
|
|
67
|
+
client = dask.distributed.client._get_global_client()
|
|
68
|
+
if client is not None:
|
|
69
|
+
try:
|
|
70
|
+
args = client.get_metadata('args')
|
|
71
|
+
except:
|
|
72
|
+
raise ValueError("Please start Dask client with af.start_dask_client() or run weight calculation without Dask distributed client.")
|
|
73
|
+
client.shutdown()
|
|
74
|
+
return args
|
|
75
|
+
else:
|
|
76
|
+
return None
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SpatialAggregator class that aggregates climate data over regions using weights.
|
|
3
|
+
|
|
4
|
+
Attributes:
|
|
5
|
+
clim (list): List of xarray DataArrays containing climate data.
|
|
6
|
+
weights (xarray Dataset): Dataset containing region weights.
|
|
7
|
+
names (list): List of names for the climate data variables.
|
|
8
|
+
|
|
9
|
+
Methods:
|
|
10
|
+
compute(npartitions=30): Aggregates climate data over regions using weights.
|
|
11
|
+
weighted_average(ddf, names): Computes weighted average of climate data.
|
|
12
|
+
"""
|
|
13
|
+
import warnings
|
|
14
|
+
|
|
15
|
+
import xarray as xr
|
|
16
|
+
import dask
|
|
17
|
+
from dask.distributed import progress
|
|
18
|
+
|
|
19
|
+
from .aggregate_utils import distributed_client, is_distributed
|
|
20
|
+
from ..dataset import Dataset
|
|
21
|
+
from ..weights import GridWeights
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
from typing import List, Union
|
|
25
|
+
import dask.dataframe
|
|
26
|
+
import pandas as pd
|
|
27
|
+
import xarray as xr
|
|
28
|
+
|
|
29
|
+
class SpatialAggregator:
|
|
30
|
+
"""
|
|
31
|
+
A class for spatially aggregating climate data using weights.
|
|
32
|
+
|
|
33
|
+
Parameters:
|
|
34
|
+
-----------
|
|
35
|
+
clim : list or Dataset
|
|
36
|
+
A list of Dataset objects containing climate data to be aggregated.
|
|
37
|
+
weights : GridWeights
|
|
38
|
+
A GridWeights object containing the weights to be used for aggregation.
|
|
39
|
+
names : str or list of str, optional
|
|
40
|
+
The name(s) of the climate variable(s) to be aggregated. Default is "climate".
|
|
41
|
+
|
|
42
|
+
Methods:
|
|
43
|
+
--------
|
|
44
|
+
compute(npartitions: int = 30) -> pd.DataFrame:
|
|
45
|
+
Compute the spatial aggregation.
|
|
46
|
+
|
|
47
|
+
weighted_average(ddf: dask.dataframe.DataFrame, names: List[str]) -> pd.DataFrame:
|
|
48
|
+
Compute the weighted average of the climate data.
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
def __init__(self, dataset: Union[list, Dataset], weights: GridWeights, names: Union[str, List[str]] = "climate") -> None:
|
|
52
|
+
"""
|
|
53
|
+
Initialize a SpatialAggregator object.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
dataset : list or Dataset
|
|
58
|
+
A list of Dataset objects, each containing the climate data
|
|
59
|
+
for a different temporal aggregation. Alternatively, a single Dataset
|
|
60
|
+
object can be passed.
|
|
61
|
+
weights : GridWeights
|
|
62
|
+
A GridWeights object containing the spatial weights used to
|
|
63
|
+
aggregate the climate data.
|
|
64
|
+
names : str or list of str, optional
|
|
65
|
+
The name(s) of the climate variable(s) being aggregated. If a single
|
|
66
|
+
variable is being aggregated, a string can be passed. If multiple
|
|
67
|
+
variables are being aggregated, a list of strings can be passed.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
None
|
|
72
|
+
|
|
73
|
+
"""
|
|
74
|
+
if type(dataset) != list:
|
|
75
|
+
self.dataset = [dataset]
|
|
76
|
+
else:
|
|
77
|
+
self.dataset = dataset
|
|
78
|
+
_ = [x.rescale_longitude() for x in self.dataset if x.lon_is_360]
|
|
79
|
+
self.grid = weights.grid
|
|
80
|
+
self.weights = weights.weights
|
|
81
|
+
self.names = [names] if isinstance(names, str) else names
|
|
82
|
+
|
|
83
|
+
def compute(self, npartitions: int = 30) -> pd.DataFrame:
|
|
84
|
+
"""
|
|
85
|
+
Compute the weighted average of the climate data over the regions defined by the weights.
|
|
86
|
+
|
|
87
|
+
Parameters:
|
|
88
|
+
-----------
|
|
89
|
+
npartitions : int, optional
|
|
90
|
+
The number of partitions to use for the Dask DataFrame. Default is 30.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
--------
|
|
94
|
+
aggregated : pandas.DataFrame
|
|
95
|
+
A DataFrame containing the weighted average of the climate data over the regions and time periods.
|
|
96
|
+
"""
|
|
97
|
+
# with dask.config.set({"multiprocessing.context": "forkserver"}):
|
|
98
|
+
print("Computing...")
|
|
99
|
+
clim_ds = dask.compute([x.da for x in self.dataset])[0] #, scheduler='processes'
|
|
100
|
+
|
|
101
|
+
print("Combining datasets...")
|
|
102
|
+
clim_ds = xr.combine_by_coords(
|
|
103
|
+
[x.to_dataset(name=self.names[i]) for i, x in enumerate(clim_ds)]
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
print("Stacking...")
|
|
107
|
+
clim_df = (
|
|
108
|
+
clim_ds.stack({"cell_id": ["latitude", "longitude"]})
|
|
109
|
+
.drop_vars(["cell_id", "latitude", "longitude"])
|
|
110
|
+
.assign_coords(coords={"cell_id": ("cell_id", self.dataset[0].grid.cell_id)})
|
|
111
|
+
.to_dataframe()
|
|
112
|
+
.reset_index("time")
|
|
113
|
+
.dropna(subset=self.names)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
print("Merging...")
|
|
117
|
+
self.weights["region_id"] = self.weights.index_right
|
|
118
|
+
merged_df = clim_df.merge(self.weights, how="inner", on="cell_id")
|
|
119
|
+
merged_df = merged_df.dropna(subset=self.names)
|
|
120
|
+
|
|
121
|
+
print("Grouping...")
|
|
122
|
+
group_key = (
|
|
123
|
+
merged_df[["region_id", "time"]]
|
|
124
|
+
.drop_duplicates()
|
|
125
|
+
.reset_index(drop=True)
|
|
126
|
+
.reset_index()
|
|
127
|
+
.rename(columns={"index": "group_ID"})
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
print("Merging again...")
|
|
131
|
+
merged_df = merged_df.merge(group_key, on=["region_id", "time"]).set_index(
|
|
132
|
+
"group_ID"
|
|
133
|
+
)[["weight", *self.names]]
|
|
134
|
+
|
|
135
|
+
print("Creating Dask DataFrame...")
|
|
136
|
+
ddf = dask.dataframe.from_pandas(merged_df, npartitions=50)
|
|
137
|
+
|
|
138
|
+
print("Aggregating...")
|
|
139
|
+
out = self.weighted_average(ddf, self.names).compute()
|
|
140
|
+
aggregated = (
|
|
141
|
+
out.merge(group_key, how="right", left_index=True, right_on="group_ID")
|
|
142
|
+
.drop(columns="group_ID")[["region_id", "time"] + self.names]
|
|
143
|
+
.reset_index(drop=True)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return aggregated
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def weighted_average(ddf: dask.dataframe.DataFrame, names: List[str]) -> pd.DataFrame:
|
|
150
|
+
out = ddf[names].mul(ddf["weight"], axis=0)
|
|
151
|
+
out["weight"] = ddf["weight"]
|
|
152
|
+
out = out.groupby(out.index).sum()
|
|
153
|
+
out = out[names].div(out["weight"], axis=0)
|
|
154
|
+
return out
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
import os
|
|
3
|
+
os.environ['USE_PYGEOS'] = '0'
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import dask.array as da
|
|
7
|
+
from .aggregate_utils import *
|
|
8
|
+
from ..dataset import Dataset, array_lon_to_360
|
|
9
|
+
from ..weights import GridWeights
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from typing import List, Union
|
|
13
|
+
from copy import deepcopy
|
|
14
|
+
|
|
15
|
+
class TemporalAggregator:
|
|
16
|
+
"""
|
|
17
|
+
A class for aggregating temporal data.
|
|
18
|
+
|
|
19
|
+
Parameters:
|
|
20
|
+
-----------
|
|
21
|
+
calc: str
|
|
22
|
+
The type of calculation to perform. Can be one of "mean", "sum", "dd", "bins", "min", or "max".
|
|
23
|
+
groupby: str
|
|
24
|
+
The time frequency to group the data by.
|
|
25
|
+
ddargs: List[Union[int, float]], optional
|
|
26
|
+
A list of values to use for the "dd" calculation. Only used if calc is "dd" or "bins".
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
-----------
|
|
30
|
+
calc: str
|
|
31
|
+
The type of calculation to perform.
|
|
32
|
+
groupby: str
|
|
33
|
+
The time frequency to group the data by.
|
|
34
|
+
kwargs: dict
|
|
35
|
+
Additional keyword arguments to pass to the reduce function.
|
|
36
|
+
ddargs: List[Union[int, float]], optional
|
|
37
|
+
A list of values to use for the "dd" calculation.
|
|
38
|
+
multi_dd: bool
|
|
39
|
+
Whether or not multiple "dd" values were provided.
|
|
40
|
+
func: function
|
|
41
|
+
The function to use for the calculation.
|
|
42
|
+
|
|
43
|
+
Methods:
|
|
44
|
+
--------
|
|
45
|
+
assign_func(self) -> function:
|
|
46
|
+
Assigns the appropriate function based on the value of self.calc.
|
|
47
|
+
get_ddargs(self, ddargs: List[Union[int, float]]) -> List[Union[int, float]]:
|
|
48
|
+
Returns the ddargs list if it is not None, and sets self.multi_dd to True if there are multiple values.
|
|
49
|
+
execute(self, dataset: Dataset, weights: Dataset, update: bool, **kwargs) -> Dataset:
|
|
50
|
+
Executes the aggregation and returns the result.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self, calc: str, groupby: str, ddargs: List[Union[int, float]] = None):
|
|
54
|
+
self.calc = calc
|
|
55
|
+
self.groupby = translate_groupby(groupby)
|
|
56
|
+
self.kwargs = {}
|
|
57
|
+
self.ddargs = self.get_ddargs(ddargs)
|
|
58
|
+
self.func = self.assign_func()
|
|
59
|
+
|
|
60
|
+
def assign_func(self):
|
|
61
|
+
"""
|
|
62
|
+
Assigns the appropriate function based on the value of self.calc.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
--------
|
|
66
|
+
function:
|
|
67
|
+
The function to use for the calculation.
|
|
68
|
+
"""
|
|
69
|
+
if self.calc == "mean":
|
|
70
|
+
f = np.mean
|
|
71
|
+
if self.calc == "sum":
|
|
72
|
+
f = np.sum
|
|
73
|
+
elif self.calc == "dd":
|
|
74
|
+
if self.multi_dd:
|
|
75
|
+
f = _multi_dd
|
|
76
|
+
else:
|
|
77
|
+
f = _dd
|
|
78
|
+
self.kwargs = {"ddargs": self.ddargs}
|
|
79
|
+
elif self.calc == "bins":
|
|
80
|
+
if self.multi_dd:
|
|
81
|
+
f = _multi_bins
|
|
82
|
+
else:
|
|
83
|
+
f = _bins
|
|
84
|
+
self.kwargs = {"ddargs": self.ddargs}
|
|
85
|
+
if self.calc == "min":
|
|
86
|
+
f = np.min
|
|
87
|
+
if self.calc == "max":
|
|
88
|
+
f = np.max
|
|
89
|
+
return f
|
|
90
|
+
|
|
91
|
+
def get_ddargs(self, ddargs: List[Union[int, float]]) -> List[Union[int, float]]:
|
|
92
|
+
"""
|
|
93
|
+
Returns the ddargs list if it is not None, and sets self.multi_dd to True if there are multiple values.
|
|
94
|
+
|
|
95
|
+
Parameters:
|
|
96
|
+
-----------
|
|
97
|
+
ddargs: List[Union[int, float]]
|
|
98
|
+
A list of values to use for the "dd" calculation.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
--------
|
|
102
|
+
List[Union[int, float]]:
|
|
103
|
+
The ddargs list.
|
|
104
|
+
"""
|
|
105
|
+
if ddargs is None:
|
|
106
|
+
self.multi_dd = False
|
|
107
|
+
return None
|
|
108
|
+
else:
|
|
109
|
+
ddarr = np.array(ddargs)
|
|
110
|
+
if len(ddarr.shape) > 1:
|
|
111
|
+
self.multi_dd = True
|
|
112
|
+
else:
|
|
113
|
+
self.multi_dd = False
|
|
114
|
+
return ddargs
|
|
115
|
+
|
|
116
|
+
def execute(self, dataset: Dataset, weights: GridWeights = None, update: bool = False, **kwargs) -> Dataset:
|
|
117
|
+
"""
|
|
118
|
+
Executes the aggregation and returns the result.
|
|
119
|
+
|
|
120
|
+
Parameters:
|
|
121
|
+
-----------
|
|
122
|
+
dataset: Dataset
|
|
123
|
+
The data to aggregate.
|
|
124
|
+
weights: GridWeights, optional
|
|
125
|
+
The weights to use for the aggregation.
|
|
126
|
+
update: bool, optional
|
|
127
|
+
Whether or not to update the input data with the result.
|
|
128
|
+
**kwargs:
|
|
129
|
+
Additional keyword arguments to pass to the reduce function.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
--------
|
|
133
|
+
Dataset:
|
|
134
|
+
The aggregated data.
|
|
135
|
+
"""
|
|
136
|
+
ds = deepcopy(dataset.da)
|
|
137
|
+
|
|
138
|
+
# if weights is not None:
|
|
139
|
+
# if dataset.grid.lon_is_360:
|
|
140
|
+
# weights.nonzero_weight_mask = array_lon_to_360(weights.nonzero_weight_mask)
|
|
141
|
+
# ds = ds.where(weights.nonzero_weight_mask)
|
|
142
|
+
|
|
143
|
+
if self.multi_dd:
|
|
144
|
+
ds = ds.expand_dims("dd", axis=-1)
|
|
145
|
+
if not update:
|
|
146
|
+
dataset_list = [deepcopy(dataset) for x in np.arange(len(self.ddargs))]
|
|
147
|
+
else:
|
|
148
|
+
if not update:
|
|
149
|
+
dataset = deepcopy(dataset)
|
|
150
|
+
with dask.config.set(**{'array.slicing.split_large_chunks': False}):
|
|
151
|
+
out = ds.resample(time=self.groupby).reduce(self.func, **self.kwargs)
|
|
152
|
+
|
|
153
|
+
if self.multi_dd:
|
|
154
|
+
out = out.to_dataset(dim="dd")
|
|
155
|
+
out = [out[var_name] for var_name in out.variables]
|
|
156
|
+
|
|
157
|
+
# Update object and return result
|
|
158
|
+
if type(dataset) == Dataset:
|
|
159
|
+
[x.update(y) for x, y in zip(dataset_list, out)]
|
|
160
|
+
[x.history.append(self.groupby) for x in dataset_list]
|
|
161
|
+
if len(dataset_list) == 1:
|
|
162
|
+
return dataset_list[0]
|
|
163
|
+
else:
|
|
164
|
+
return dataset_list
|
|
165
|
+
else:
|
|
166
|
+
return out
|
|
167
|
+
else:
|
|
168
|
+
# Update object and return result
|
|
169
|
+
if type(dataset) == Dataset:
|
|
170
|
+
dataset.update(out)
|
|
171
|
+
dataset.history.append(self.groupby)
|
|
172
|
+
return dataset
|
|
173
|
+
else:
|
|
174
|
+
return out
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _dd(frame, axis, ddargs):
|
|
178
|
+
return (
|
|
179
|
+
(frame > ddargs[0])
|
|
180
|
+
* (frame < ddargs[1])
|
|
181
|
+
* np.absolute(frame - ddargs[ddargs[2]])
|
|
182
|
+
).sum(axis=axis)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _multi_dd(frame, axis, ddargs):
|
|
186
|
+
return da.concatenate([_dd(frame, axis, ddarg) for ddarg in ddargs], axis=-1)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _bins(frame, axis, ddargs):
|
|
190
|
+
return ((frame > ddargs[0]) * (frame < ddargs[1])).sum(axis=axis)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _multi_bins(frame, axis, ddargs):
|
|
194
|
+
return da.concatenate([_bins(frame, axis, ddarg) for ddarg in ddargs], axis=-1)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def translate_groupby(groupby):
|
|
198
|
+
return {"date": "1D", "month": "ME", "year": "YE"}[groupby]
|