如何通过NetCDF应用xarray u_function并将2D数组(多个新变量)返回到DataSet

问题描述 投票:3回答:1

我正在尝试使用xarray apply_ufunc将给定的函数f应用于数据集中的所有坐标对(即像素)。

函数f返回2D数组(NxN矩阵)作为结果。因此,分析后所得的数据集将具有几个新变量:总共M个新变量。

f功能确实可以正常工作。因此,该错误似乎并非源于此。

[可能的问题可能是2D数组从f返回的结构。据我了解,xarray.apply_ufunc要求结果数组以元组结构化。因此,我什至尝试将2D数组转换为数组的元组,但到目前为止没有任何效果。

也可以在其他作品works的其他地方检查此情况。在此当前链接中,作者必须对原始数据集运行两次相同的线性回归拟合函数,以便从回归中检索所有参数(beta_0和alpha)。

因此,我想知道,xarray.apply_ufunc是否能够像上面的链接(或下面的代码片段中的代码)一样操作归约函数,它返回多个新变量。

下面,我将介绍涉及所讨论问题的可复制代码。请注意,函数f返回2D数组。第二维的深度为4。因此,在整个处理之后,我希望有一个包含4个新变量的结果数据集。

import numpy as np
import xarray as xr


x_size = 10
y_size = 10
time_size = 30

lon = np.arange(50, 50+x_size)
lat = np.arange(10, 10+y_size)
time = np.arange(10, 10+time_size)

array = np.random.randn(y_size, x_size, time_size)

ds = xr.DataArray(
    data=array, 
    coords = {'lon':lon, 'lat':lat, 'time':time}, 
    dims=('lon', 'lat', 'time')
)

def f (x):
    return (x, x**2, x**3, x**4)

def f_xarray(ds, dim=['time'], dask='allowed', new_dim_name=['predicted']):   
    filtered = xr.apply_ufunc(
        f,
        ds,
        dask=dask,
        vectorize=True,
        input_core_dims=[dim],
        #exclude_dims = dim, # This must not be setted.
        output_core_dims= [['x', 'x2', 'x3', 'x4']], #[new_dim_name],
        #kwargs=kwargs,
        #output_dtypes=[float],
        #dataset_join='outer',
        #dataset_fill_value=np.nan,
    ).compute()
    return filtered


ds2 = f_xarray(ds)

# Error message returned: 
# ValueError: wrong number of outputs from pyfunc: expected 1, got 4
python netcdf python-xarray
1个回答
0
投票

很难熟悉xarray.apply_ufunc,它提供了非常广泛的可能性,而且并不总是很清楚如何最大程度地利用它。在这种情况下,错误是由于input_core_dimsoutput_core_dims引起的。我将首先扩展他们的文档,重点介绍我认为引起混乱的原因,然后提供一些解决方案。他们的文档是:

input_core_dims

与args长度相同的列表,给出每个广播输入参数上不应广播的核心尺寸列表。默认情况下,我们假定任何输入参数上都没有核心尺寸。

例如,input_core_dims = [[],['time']]指示应广播第一个参数的所有尺寸以及第二个参数的“时间”以外的所有尺寸。

在应用func之前,核心尺寸会自动移动到输入变量的最后一个轴,这有助于使用NumPy样式的通用ufuncs [2]。

它负责计算的两个重要方面。首先,它定义了要广播的尺寸,这特别重要,因为假定输出的形状与这些广播的尺寸所定义的形状相同(如果不是这种情况,则必须使用output_core_dims)。其次,input_core_dims移至末尾。下面有两个示例:

我们可以在不对apply_ufunc附加任何参数的情况下应用不会修改形状的函数:

xr.apply_ufunc(lambda x: x**2, ds)
# Output
<xarray.DataArray (lon: 10, lat: 10, time: 30)>
array([[[6.20066642e+00, 1.68502086e+00, 9.77868899e-01, ...,
         ...,
         2.28979668e+00, 1.76491683e+00, 2.17085164e+00]]])
Coordinates:
  * lon      (lon) int64 50 51 52 53 54 55 56 57 58 59
  * lat      (lat) int64 10 11 12 13 14 15 16 17 18 19
  * time     (time) int64 10 11 12 13 14 15 16 17 18 ... 32 33 34 35 36 37 38 39

例如,要计算沿lon维的平均值,我们减小其中一个维,因此,输出将比输入小一维:我们必须将lon传递为input_core_dim

xr.apply_ufunc(lambda x: x.mean(axis=-1), ds, input_core_dims=[["lon"]])
# Output
<xarray.DataArray (lat: 10, time: 30)>
array([[ 7.72163214e-01,  3.98689228e-01,  9.36398702e-03,
         ...,
        -3.70034281e-01, -4.57979868e-01,  1.29770762e-01]])
Coordinates:
  * lat      (lat) int64 10 11 12 13 14 15 16 17 18 19
  * time     (time) int64 10 11 12 13 14 15 16 17 18 ... 32 33 34 35 36 37 38 39

请注意,即使axis=-1是第一个维度,我们仍在计算lon的均值,因为它是input_core_dims时将被移到最后。因此,我们可以使用lat计算沿input_core_dims=[["lon"]]暗淡的平均值。

还要注意input_core_dims的格式,必须是列表列表:与args长度相同的列表给出了核心尺寸列表。元组(或任何序列)的元组也是有效的,但是,请注意,对于元组,元素为1的情况是(("lon",),)而不是(("lon"))

output_core_dims

与func的输出参数数量相同长度的列表,给出每个输出中未在输入中广播的核心尺寸的列表。默认情况下,我们假定func输出的数组恰好是一个数组,其轴对应于每个广播维度。

假定核心尺寸按提供的顺序显示为每个输出的最后一个尺寸。

再次,output_core_dims是列表列表。当有多个输出时(即func返回一个元组),或者输出除了广播的尺寸外还具有其他尺寸时,必须使用它。显然,如果有多个带有额外暗淡的输出,则也必须使用它。我们将使用两种可能的解决方案作为示例。

解决方案1

使用问题中发布的功能。此函数返回一个元组,因此即使数组的形状未修改,我们也需要使用output_core_dims。由于实际上没有多余的暗淡,我们将为每个输出传递一个空列表:

xr.apply_ufunc(
    f,
    ds,
    output_core_dims= [[] for _ in range(4)], 
)

这将返回一个DataArrays元组,其输出将与f(ds)完全相同。

解决方案2

我们现在将修改函数以输出单个数组,将所有4个输出堆叠在元组中。注意,我们必须确保在数组末尾添加了这个新维度:

def f2(x):
    return np.stack((x, x**2, x**3, x**4), axis=-1)

xr.apply_ufunc(
    f2,
    ds,
    output_core_dims= [["predictions"]], 
)
# Output
<xarray.DataArray (lon: 10, lat: 10, time: 30, predictions: 4)>
array([[[[ 2.49011374e+00,  6.20066642e+00,  1.54403646e+01,
           ...,
           4.71259686e+00]]]])
Coordinates:
  * lon      (lon) int64 50 51 52 53 54 55 56 57 58 59
  * lat      (lat) int64 10 11 12 13 14 15 16 17 18 19
  * time     (time) int64 10 11 12 13 14 15 16 17 18 ... 32 33 34 35 36 37 38 39
Dimensions without coordinates: predictions

我们现在已将predictions作为输出核心昏暗传递,这使输出除原始3之外还具有predictions作为新尺寸。这里的输出不再等于f2(ds)(它返回一个numpy数组)。因为有了apply_ufunc的帮助,我们才能够执行一些功能和堆叠而不会丢失标签。


旁注:通常不建议将可变对象用作函数中的默认参数:例如,请参见"Least Astonishment" and the Mutable Default Argument

© www.soinside.com 2019 - 2024. All rights reserved.