pydata / pydata/xarray

Vectorizing behaviour of apply_ufunc with dask="allowed" and vectorize=True depends on whether dask array is loaded

Open
#7,452 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug topic-dask
Dominant language
Python
Stars
4.2k
Forks
1.4k
Avg merge
2d 15h
Merged PRs (30d)
14

Description

What happened?

I have an xr.DataArray which is loaded as a dask array with one big chunk along the "time" dimension and chunk sizes of 1 along all other (substantially smaller) dimensions.
The aim is to apply a function along the time dimension to basically process the data based on how far apart non-NaN data values are spaced in time. For this, the function uses the time coordinate and the values of the DataArray, and returns one data array. My function works over an 1d-array, so I use xr.apply_ufunc to apply it to each 1D time series, setting vectorize=True. See below for MCVE.

Now, whether this works as intended depends on whether I preload the array:

  • If I run this code with dask="allowed", vectorize=True and just using data as apply_ufunc arg, I get the error message pasted below. This is the same message I would get if I'd set vectorize=False. In this case, xr.apply_ufunc doesn't seem to pass 1D arrays to the function but rather passes the entire (4x4x400) array.
  • If I run this code with dask='allowed', vectorize=True and using data.load() (which we don't always want to need to do), I get the expected and desired behaviour: 1D (400,) arrays are passed to the function and the 1D results are merged into a (4x4x400) xr.DataArray.

Is this a bug? Do dask arrays need some other arguments set in xr.apply_ufunc to resolve this issue? I didn't find the solution to this issue in the documentation.

What did you expect to happen?

Same vectorizing behaviour whether the array is loaded or not.

Minimal Complete Verifiable Example
from cftime import Datetime360Day
import xarray as xr
import numpy as np


def cf360day_to_days(da_tcrd):
    year, month, day = np.array([[c.year, c.month, c.day] for c in da_tcrd]).T
    return (year - year[0]) * 360 + (month - 1) * 30 + day


def minimal_1d(dur_data, dur_time):
    print("dur_data shape:\n", dur_data.shape)
    dur_end_day = cf360day_to_days(dur_time)
    data_mask = np.isfinite(dur_data)
    dur_end_day = dur_end_day[data_mask]
    return dur_end_day


y0 = 1981
times = [Datetime360Day(y // 360 + y0, 
                        y % 360 // 30 + 1,
                        y % 30 + 1) 
         for y in np.arange(400)] # included as this datatype has caused problems before
data = xr.DataArray(
    data=np.random.randn(4,4,400),
    dims=["q", "structure", "time"],
    coords={
        "q": ['0.01', '0.05', '0.10', '0.20'],
        "structure": ['A', 'B', 'C', 'D'],
        "time": times}
).chunk({"time": -1,  "q": 1, "structure": 1})

res = xr.apply_ufunc(
    minimal_1d, 
    data, #.load(),
    data["time"],
    input_core_dims=[["time"], ["time"]],
    output_core_dims=(("time", ),), 
    dask="allowed", vectorize=False,
    output_dtypes=[float],
)
MVCE confirmation
  • Minimal example — the example is as focused as reasonably possible to demonstrate the underlying issue in xarray.
  • Complete example — the example is self-contained, including all data and the text of any traceback.
  • Verifiable example — the example copy & pastes into an IPython prompt or Binder notebook, returning the result.
  • New issue — a search of GitHub Issues suggests this is not a duplicate.
Relevant log output
dur_data shape:
 (4, 4, 400)

---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Input In [181], in <cell line: 15>()
      2 times = [Datetime360Day(y // 360 + 1981, 
      3                         y % 360 // 30 + 1,
      4                         y % 30 + 1) 
      5          for y in np.arange(400)]
      6 data = xr.DataArray(
      7     data=np.random.randn(4,4,400),
      8     dims=["q", "structure", "time"],
   (...)
     12         "time": times}
     13 ).chunk({"time": -1,  "q": 1, "structure": 1})
---> 15 res = xr.apply_ufunc(
     16     minimal_1d, 
     17     data, #.load(),
     18     data["time"],
     19     input_core_dims=[["time"], ["time"]],
     20     output_core_dims=(("time", ),), 
     21     dask="allowed", vectorize=False,
     22     output_dtypes=[float],
     23 )

File ~/.conda/envs/new-ada-env/lib/python3.8/site-packages/xarray/core/computation.py:1159, in apply_ufunc(func, input_core_dims, output_core_dims, exclude_dims, vectorize, join, dataset_join, dataset_fill_value, keep_attrs, kwargs, dask, output_dtypes, output_sizes, meta, dask_gufunc_kwargs, *args)
   1157 # feed DataArray apply_variable_ufunc through apply_dataarray_vfunc
   1158 elif any(isinstance(a, DataArray) for a in args):
-> 1159     return apply_dataarray_vfunc(
   1160         variables_vfunc,
   1161         *args,
   1162         signature=signature,
   1163         join=join,
   1164         exclude_dims=exclude_dims,
   1165         keep_attrs=keep_attrs,
   1166     )
   1167 # feed Variables directly through apply_variable_ufunc
   1168 elif any(isinstance(a, Variable) for a in args):

File ~/.conda/envs/new-ada-env/lib/python3.8/site-packages/xarray/core/computation.py:286, in apply_dataarray_vfunc(func, signature, join, exclude_dims, keep_attrs, *args)
    281 result_coords = build_output_coords(
    282     args, signature, exclude_dims, combine_attrs=keep_attrs
    283 )
    285 data_vars = [getattr(a, "variable", a) for a in args]
--> 286 result_var = func(*data_vars)
    288 if signature.num_outputs > 1:
    289     out = tuple(
    290         DataArray(variable, coords, name=name, fastpath=True)
    291         for variable, coords in zip(result_var, result_coords)
    292     )

File ~/.conda/envs/new-ada-env/lib/python3.8/site-packages/xarray/core/computation.py:727, in apply_variable_ufunc(func, signature, exclude_dims, dask, output_dtypes, vectorize, keep_attrs, dask_gufunc_kwargs, *args)
    722     if vectorize:
    723         func = _vectorize(
    724             func, signature, output_dtypes=output_dtypes, exclude_dims=exclude_dims
    725         )
--> 727 result_data = func(*input_data)
    729 if signature.num_outputs == 1:
    730     result_data = (result_data,)

Input In [174], in minimal_1d(dur_data, dur_time)
     65 dur_end_day = cf360day_to_days(dur_time)
     66 data_mask = np.isfinite(dur_data)
---> 67 dur_end_day = dur_end_day[data_mask]
     69 return dur_end_day

IndexError: too many indices for array: array is 1-dimensional, but 3 were indexed
Anything else we need to know?

No response

Environment

INSTALLED VERSIONS

commit: None
python: 3.8.15 | packaged by conda-forge | (default, Nov 22 2022, 08:49:35)
[GCC 10.4.0]
python-bits: 64
OS: Linux
OS-release: 3.10.0-1160.45.1.el7.x86_64
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_GB.UTF-8
LOCALE: ('en_GB', 'UTF-8')
libhdf5: 1.12.2
libnetcdf: 4.8.1

xarray: 2022.3.0
pandas: 1.2.3
numpy: 1.23.5
scipy: 1.9.3
netCDF4: 1.6.2
pydap: None
h5netcdf: None
h5py: None
Nio: None
zarr: None
cftime: 1.6.0
nc_time_axis: 1.4.1
PseudoNetCDF: None
rasterio: 1.3.4
cfgrib: None
iris: 3.1.0
bottleneck: 1.3.2
dask: 2022.05.0
distributed: 2022.5.0
matplotlib: 3.6.2
cartopy: 0.21.1
seaborn: 0.11.1
numbagg: None
fsspec: 2022.11.0
cupy: None
pint: None
sparse: None
setuptools: 59.8.0
pip: 22.3.1
conda: None
pytest: None
IPython: 8.4.0
sphinx: None

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the MCVE with dask="allowed" using both the chunked and loaded DataArray. Start in xarray/core/computation.py, especially apply_ufunc, apply_variable_ufunc, and _vectorize; done means vectorize=True passes 1D inputs consistently for loaded and dask-backed arrays, with coverage for the reported failure.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
data
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.