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 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,5 @@
1
+ from .aggregate_utils import *
2
+ from .aggregate import aggregate_dataset, aggregate_time, aggregate_space
3
+ from .temporal import TemporalAggregator
4
+ from .spatial import SpatialAggregator
5
+
@@ -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]