Numpy 如何使用xarray实现内存高效的多维groupby/stack?
我有一个大型的Numpy 如何使用xarray实现内存高效的多维groupby/stack?,numpy,time-series,dask,python-xarray,Numpy,Time Series,Dask,Python Xarray,我有一个大型的np.float64时间序列,频率为5分钟(大小约为2500000~=24年) 我使用Xarray在内存中表示它,时间维度名为'time' 我想按'time.hour'和'time.dayofyear'进行分组(反之亦然),并从时间序列中删除它们的平均值 为了有效地完成这项工作,我需要将时间序列重新排序为一个新的xr.DataArray,维度为['hour'、'dayofyear'、'rest'] 我编写了一个函数,它可以处理Xarray的GroupBy对象,并设法做到这一点,尽管
np.float64
时间序列,频率为5分钟(大小约为2500000~=24年)
我使用Xarray
在内存中表示它,时间维度名为'time'
我想按'time.hour'
和'time.dayofyear'
进行分组(反之亦然),并从时间序列中删除它们的平均值
为了有效地完成这项工作,我需要将时间序列重新排序为一个新的xr.DataArray
,维度为['hour'、'dayofyear'、'rest']
我编写了一个函数,它可以处理Xarray
的GroupBy
对象,并设法做到这一点,尽管这需要大量内存
我有一台32GB内存的机器,我仍然从numpy
获得MemoryError
我知道代码是有效的,因为我在原始时间序列的每小时重新采样版本上使用了它。下面是代码:
def time_series_stack(time_da, time_dim='time', grp1='hour', grp2='dayofyear'):
"""Takes a time-series xr.DataArray objects and reshapes it using
grp1 and grp2. outout is a xr.Dataset that includes the reshaped DataArray
, its datetime-series and the grps."""
import xarray as xr
import numpy as np
import pandas as pd
# try to infer the freq and put it into attrs for later reconstruction:
freq = pd.infer_freq(time_da[time_dim].values)
name = time_da.name
time_da.attrs['freq'] = freq
attrs = time_da.attrs
# drop all NaNs:
time_da = time_da.dropna(time_dim)
# group grp1 and concat:
grp_obj1 = time_da.groupby(time_dim + '.' + grp1)
s_list = []
for grp_name, grp_inds in grp_obj1.groups.items():
da = time_da.isel({time_dim: grp_inds})
s_list.append(da)
grps1 = [x for x in grp_obj1.groups.keys()]
stacked_da = xr.concat(s_list, dim=grp1)
stacked_da[grp1] = grps1
# group over the concatenated da and concat again:
grp_obj2 = stacked_da.groupby(time_dim + '.' + grp2)
s_list = []
for grp_name, grp_inds in grp_obj2.groups.items():
da = stacked_da.isel({time_dim: grp_inds})
s_list.append(da)
grps2 = [x for x in grp_obj2.groups.keys()]
stacked_da = xr.concat(s_list, dim=grp2)
stacked_da[grp2] = grps2
# numpy part:
# first, loop over both dims and drop NaNs, append values and datetimes:
vals = []
dts = []
for i, grp1_val in enumerate(stacked_da[grp1]):
da = stacked_da.sel({grp1: grp1_val})
for j, grp2_val in enumerate(da[grp2]):
val = da.sel({grp2: grp2_val}).dropna(time_dim)
vals.append(val.values)
dts.append(val[time_dim].values)
# second, we get the max of the vals after the second groupby:
max_size = max([len(x) for x in vals])
# we fill NaNs and NaT for the remainder of them:
concat_sizes = [max_size - len(x) for x in vals]
concat_arrys = [np.empty((x)) * np.nan for x in concat_sizes]
concat_vals = [np.concatenate(x) for x in list(zip(vals, concat_arrys))]
# 1970-01-01 is the NaT for this time-series:
concat_arrys = [np.zeros((x), dtype='datetime64[ns]')
for x in concat_sizes]
concat_dts = [np.concatenate(x) for x in list(zip(dts, concat_arrys))]
concat_vals = np.array(concat_vals)
concat_dts = np.array(concat_dts)
# finally , we reshape them:
concat_vals = concat_vals.reshape((stacked_da[grp1].shape[0],
stacked_da[grp2].shape[0],
max_size))
concat_dts = concat_dts.reshape((stacked_da[grp1].shape[0],
stacked_da[grp2].shape[0],
max_size))
# create a Dataset and DataArrays for them:
sda = xr.Dataset()
sda.attrs = attrs
sda[name] = xr.DataArray(concat_vals, dims=[grp1, grp2, 'rest'])
sda[time_dim] = xr.DataArray(concat_dts, dims=[grp1, grp2, 'rest'])
sda[grp1] = grps1
sda[grp2] = grps2
sda['rest'] = range(max_size)
return sda
因此,对于2500000项的时间序列,numpy
抛出了MemoryError
,所以我猜这一定是我的记忆瓶颈。我能做些什么来解决这个问题
Dask
能帮我吗?如果是这样的话,我该如何实现它呢?像你一样,我在输入一个小的时间序列(10000长)时没有问题地运行了它。然而,当输入一个100000长的时间序列时,xr.DataArray
for循环的grp_obj2
会跑掉并使用系统的所有内存
这就是我用来生成时间序列的内容xr.DataArray
:
n = 10**5
times = np.datetime64('2000-01-01') + np.arange(n) * np.timedelta64(5,'m')
data = np.random.randn(n)
time_da = xr.DataArray(data, name='rand_data', dims=('time'), coords={'time': times})
# time_da.to_netcdf('rand_time_series.nc')
正如你所指出的,Dask将是解决问题的一种方法,但我目前看不到一条明确的道路。。。
通常,Dask的问题类型为:
dask.delayed
或dask.futures
方法为整个代码体定义所有计算,直到写入输出。这就是允许Dask将一小段数据分块读取然后写入的原因time\u da
xr.DataArray
分解成块供Dask处理,但没有任何帮助。据我所知,行stacked_da=xr.concat(s_list,dim=grp1)
强制Dask在内存中创建stacked_da
的完整副本,以及更多内容。。。
解决此问题的一种方法是将stacked_da
写入磁盘,然后立即再次读取:
##For group1
xr.concat(s_list, dim=grp1).to_netcdf('stacked_da1.nc')
stacked_da = xr.load_dataset('stacked_da1.nc')
stacked_da[grp1] = grps1
##For group2
xr.concat(s_list, dim=grp2).to_netcdf('stacked_da2.nc')
stacked_da = xr.load_dataset('stacked_da2.nc')
stacked_da[grp2] = grps2
但是,stacked_da1.nc
的文件大小为19MB,stacked_da2.nc
的文件大小为6.5GB。这是用于包含100000个元素的time\u da
。。。所以很明显有点不对劲
最初,听起来好像你想从时间序列数据中减去各组的平均值。看起来Xarray docs有一个这样的例子 关键是分组一次,在组上循环,然后在每个组上再次分组并将其附加到列表中 接下来,我对这些组使用
pd.MultiIndex.from_product
无需内存问题和Dask
,只需几秒钟即可运行
以下是代码,请享受:
def time_series_stack(time_da, time_dim='time', grp1='hour', grp2='month',
plot=True):
"""Takes a time-series xr.DataArray objects and reshapes it using
grp1 and grp2. output is a xr.Dataset that includes the reshaped DataArray
, its datetime-series and the grps. plots the mean also"""
import xarray as xr
import pandas as pd
# try to infer the freq and put it into attrs for later reconstruction:
freq = pd.infer_freq(time_da[time_dim].values)
name = time_da.name
time_da.attrs['freq'] = freq
attrs = time_da.attrs
# drop all NaNs:
time_da = time_da.dropna(time_dim)
# first grouping:
grp_obj1 = time_da.groupby(time_dim + '.' + grp1)
da_list = []
t_list = []
for grp1_name, grp1_inds in grp_obj1.groups.items():
da = time_da.isel({time_dim: grp1_inds})
# second grouping:
grp_obj2 = da.groupby(time_dim + '.' + grp2)
for grp2_name, grp2_inds in grp_obj2.groups.items():
da2 = da.isel({time_dim: grp2_inds})
# extract datetimes and rewrite time coord to 'rest':
times = da2[time_dim]
times = times.rename({time_dim: 'rest'})
times.coords['rest'] = range(len(times))
t_list.append(times)
da2 = da2.rename({time_dim: 'rest'})
da2.coords['rest'] = range(len(da2))
da_list.append(da2)
# get group keys:
grps1 = [x for x in grp_obj1.groups.keys()]
grps2 = [x for x in grp_obj2.groups.keys()]
# concat and convert to dataset:
stacked_ds = xr.concat(da_list, dim='all').to_dataset(name=name)
stacked_ds[time_dim] = xr.concat(t_list, 'all')
# create a multiindex for the groups:
mindex = pd.MultiIndex.from_product([grps1, grps2], names=[grp1, grp2])
stacked_ds.coords['all'] = mindex
# unstack:
ds = stacked_ds.unstack('all')
ds.attrs = attrs
return ds
我没有提到它,但我希望时间序列重组的原因是1)我可以看到季节性与我选择的组。例如,如果我选择时间序列的2D pcolormesh图(例如,x:hour of the day,y:dayofyear),它是bc,我怀疑信号具有每日和每年的季节性。2) Xarray可以很容易地减去一个组的平均值,但到目前为止还不存在两个或多个坐标的分组(在Xarray生态系统中,我很高兴在这一点上是错误的)。现在我想起来了,这个解决方案是琐碎的、艰难的、无聊的。我可以将原始的时间序列划分成更小的块(我可以根据经验测试它的块大小是否正确,这样它就不会阻塞我的记忆),然后运行函数,然后对输出进行压缩。你可能不需要重新整形就可以实现这一点。可以这样做:1)从原始数据中按小时分组,减去每小时平均数,然后绘制图2)然后从原始数据中按天分组,减去每日平均数,然后绘制图I很难准确地描绘出你的最终结果应该是什么样的,所以我可能知道步骤的顺序,或者逻辑不正确。你也可以投票给我的答案吗?我花了大量时间研究这个……仅供参考,它根本没有效率(在组上循环),但它对我来说已经足够好了:-)