pydata / pydata/xarray

to_zarr writes unexpected NaNs with chunks=-1

Open
#7,672 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

What happened?

I'm running into some unexpected behavior with ds.to_zarr when my encoding includes chunks=-1 and ds is a dataset that I created by operating on zarr files opened from disk. When I run the following example code, many of my values are NaN. When I run the same code, but with ds.load() before ds.to_zarr(), the correct, non-NaN values are saved.

What did you expect to happen?

My data would be written the same regardless of whether I explicitly loaded the dataset.
The documentation for xarray.Dataset.load includes the following:

Normally, it should not be necessary to call this method in user code, because all xarray functions should either work on deferred data or load data automatically. However, this method can be necessary when working with many file objects on disk.

I encountered this situation when operating on datasets that had been loaded from disk (.sel, then concat), so this seems like a situation that the second sentence addresses, but I did not expect it to silently fail to write the correct data in the way that it did.

Minimal Complete Verifiable Example
import pandas as pd
import xarray as xr
import numpy as np

def create_dataset(time, site):
  temperature = 15 + 8 * np.random.randn(1, 3)
  precipitation = 10 * np.random.rand(1, 3)

  ds = xr.Dataset(
      data_vars=dict(
          temperature=(["site", "time"], temperature),
          precipitation=(["site", "time"], precipitation),
      ),

      coords=dict(
          site=site,
          time=time,
      ),
      attrs=dict(description="Weather related data."),
  )
  return ds

time_1 = pd.date_range("2014-09-06", periods=3)
time_2 = pd.date_range("2014-09-09", periods=3)

# create and save the first dataset as a zarr
ds_a = create_dataset(time_1, ["site_1"])
fname_a = '/tmp/ds_a.zarr'
ds_a.to_zarr(fname_a, mode='w')
ds_a_from_disk = xr.open_dataset(fname_a, engine='zarr', chunks={})

# create and save the second dataset as a zarr
ds_b = create_dataset(time_2, ["site_1"])
fname_b = '/tmp/ds_b.zarr'
ds_b.to_zarr(fname_b, mode='w')
ds_b_from_disk = xr.open_dataset(fname_b, engine='zarr', chunks={})

# concatenate the datasets
ds = xr.concat([ds_a_from_disk.sel(site="site_1"), ds_b_from_disk.sel(site="site_1")], dim='time')

# save all data in one chunk
encoding = {var: {'chunks': -1} for var in list(ds) + list(ds.coords)}
fname = '/tmp/concated.zarr'

# Uncomment the following line to fix this issue
# ds.load()

# save the dataset
ds.to_zarr(fname, mode='w', encoding=encoding)

ds_from_disk = xr.open_dataset(fname, engine='zarr')
print(ds_from_disk.to_dataframe())
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

No response

Anything else we need to know?

Example output without ds.load():

            precipitation    site  temperature
time                                          
2014-09-06            NaN  site_1     9.805297
2014-09-07            NaN  site_1    16.119194
2014-09-08            NaN  site_1     4.226150
2014-09-09       7.275470  site_1          NaN
2014-09-10       2.899134  site_1          NaN
2014-09-11       5.777094  site_1          NaN

Example output with ds.load():

            precipitation    site  temperature
time                                          
2014-09-06       3.445305  site_1    18.144503
2014-09-07       7.708728  site_1    20.289742
2014-09-08       7.358939  site_1    19.996060
2014-09-09       6.211692  site_1     9.748291
2014-09-10       4.981796  site_1    -7.676436
2014-09-11       8.667885  site_1    31.934328

My hunch is that this has to do with a mismatch between Dask chunks in the unloaded dataset and the chunks specified in to_zarr, but if they are incompatible I would expect to see an error surfaced.

Environment

INSTALLED VERSIONS

commit: None
python: 3.9.16 (main, Dec 7 2022, 01:11:51)
[GCC 9.4.0]
python-bits: 64
OS: Linux
OS-release: 5.19.0-35-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: None
LOCALE: ('en_US', 'UTF-8')
libhdf5: 1.12.0
libnetcdf: None

xarray: 2023.3.0
pandas: 1.4.0
numpy: 1.22.4
scipy: 1.8.0
netCDF4: None
pydap: None
h5netcdf: None
...
pytest: 6.2.2
mypy: None
IPython: 8.3.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

Start by running the provided MVCE and compare ds.to_zarr with and without ds.load(). Inspect the deferred data from xr.open_dataset(..., engine='zarr', chunks={}), xr.concat, and the encoding that sets chunks=-1. Done means reopening the written dataset preserves all original values without requiring ds.load(); the issue names no source files or tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
numpy, pandas, 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.