Skip to content

CF encoding should preserve vlen dtype for empty arrays #7862

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 16, 2023

Conversation

tomwhite
Copy link
Contributor

@tomwhite tomwhite commented May 22, 2023

The idea is that it enables a workaround for #7328, where the dtype metadata can be set to a vlen string so that it doesn't get changed to float.

The line that is changed in the example in #7328 is:

ds = xr.Dataset({"a": np.array([], dtype=coding.strings.create_vlen_dtype(str))})

@kmuehlbauer
Copy link
Contributor

Thanks @tomwhite for the PR. I've only quickly checked the approach, which looks reasonable. But those changes have implications on several locations of the backend code, which we would have to sort out.

Considering this example:

import numpy as np
import xarray as xr
print(f"creating dataset with empty string array")
print("-----------------------------------------")
dtype = xr.coding.strings.create_vlen_dtype(str)
ds = xr.Dataset({"a": np.array([], dtype=dtype)})
print(f"dtype: {ds['a'].dtype}")
print(f"metadata: {ds['a'].dtype.metadata}")
ds.to_netcdf("a.nc", engine="netcdf4")

print("\nncdump")
print("-------")
!ncdump a.nc

engines = ["netcdf4", "h5netcdf"]
for engine in engines:
    with xr.open_dataset("a.nc", engine=engine) as ds:
        print(f"\nloading with {engine}")
        print("-------------------")
        print(f"dtype: {ds['a'].dtype}")
        print(f"metadata: {ds['a'].dtype.metadata}")
creating dataset with empty string array
-----------------------------------------
dtype: object
metadata: {'element_type': <class 'str'>}

ncdump
-------
netcdf a {
dimensions:
	a = UNLIMITED ; // (0 currently)
variables:
	string a(a) ;
data:
}

loading with netcdf4
-------------------
dtype: object
metadata: None

loading with h5netcdf
-------------------
dtype: object
metadata: {'vlen': <class 'str'>}

Engine netcdf4 does not roundtrip here, losing the dtype metadata information. There is special casing for h5netcdf backend, though.

The source is actually located in open_store_variable of netcdf4 backend, when the underlying data is converted to Variable (which does some object dtype twiddling).

Unfortunately I do not have an immediate solution here.

@tomwhite
Copy link
Contributor Author

Thanks for taking a look @kmuehlbauer and for the useful example code. I hadn't considered the netcdf cases, so thanks for pointing those out.

Engine netcdf4 does not roundtrip here, losing the dtype metadata information. There is special casing for h5netcdf backend, though.

Could netcdf4 do the same special-casing as h5netcdf?

@kmuehlbauer
Copy link
Contributor

kmuehlbauer commented May 24, 2023

@tomwhite Special casing on netcdf4 backend should be possible, too.

But it might need fixing at zarr backend, too:

ds = xr.Dataset({"a": np.array([], dtype=xr.coding.strings.create_vlen_dtype(str))})
print(f"dtype: {ds['a'].dtype}")
print(f"metadata: {ds['a'].dtype.metadata}")
ds.to_zarr("a.zarr")
print("\n### Loading ###")
with xr.open_dataset("a.zarr", engine="zarr") as ds:
    print(f"dtype: {ds['a'].dtype}")
    print(f"metadata: {ds['a'].dtype.metadata}")
dtype: object
metadata: {'element_type': <class 'str'>}

### Loading ###
dtype: object
metadata: None

Could you verify the above example, please? I'm relatively new to zarr 😬

@kmuehlbauer
Copy link
Contributor

@tomwhite I've put a commit with changes to zarr/netcdf4-backends which should preserve the dtype metadata here: https://github.com/kmuehlbauer/xarray/tree/preserve-vlen-string-dtype.

I'm not really sure if that is the right location, but as it was already present that location at netcdf4-backend I think it will do.

@tomwhite
Copy link
Contributor Author

Could you verify the above example, please?

The code looks fine, and I get the same result when I run it with this PR.

Your fix in https://github.com/kmuehlbauer/xarray/tree/preserve-vlen-string-dtype changes the metadata so it is correctly preserved as metadata: {'element_type': <class 'str'>}.

I feel less qualified to evaluate the impact of the netcdf4 fix.

@kmuehlbauer
Copy link
Contributor

Thanks for trying. I can't think of any downsides for the netcdf4-fix, as it just adds the needed metadata to the object-dtype. But you never know, so it would be good to get another set of eyes on it.

So it looks like the changes here with the fix in my branch will get your issue resolved @tomwhite, right?

I'm a bit worried, that this might break other users workflows, if they depend on the current conversion to floating point for some reason. Also other backends might rely on this feature. Especially because this has been there since the early days when xarray was known as xray.

@dcherian What would be the way to go here?

There is also a somehow contradicting issue in #7868.

@tomwhite
Copy link
Contributor Author

So it looks like the changes here with the fix in my branch will get your issue resolved @tomwhite, right?

Yes - thanks!

I'm a bit worried, that this might break other users workflows, if they depend on the current conversion to floating point for some reason.

The floating point default is preserved if you do e.g. xr.Dataset({"a": np.array([], dtype=object)}). The change here will only convert to string if there is extra metadata present that says it is a string.

@github-actions github-actions bot added io topic-backends topic-zarr Related to zarr storage library labels May 25, 2023
Copy link
Contributor

@kmuehlbauer kmuehlbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After discussion with @tomwhite this PR should now preserve the vlen dtype for empty arrays (on encoding) and preserve the vlen dtype metadata for zarr/netcdf4 backends.

Tests would have to be added for the latter and a zarr expert might want to have another look at that part.

@kmuehlbauer
Copy link
Contributor

@tomwhite I've added tests to check the backend code for vlen string dtype metadadata. Also had to add specific check for the h5py vlen string metadata. I think we've covered everything for the proposed change to allow empty vlen strings dtype metadata.

I'm looking at the mypy error and do not have the slightest clue what and where to change. Any help appreciated.

@tomwhite
Copy link
Contributor Author

tomwhite commented Jun 2, 2023

@kmuehlbauer thanks for adding tests! I'm not sure what the mypy error is either, I'm afraid...

@dcherian dcherian requested a review from jhamman June 2, 2023 14:35
@dcherian
Copy link
Contributor

dcherian commented Jun 2, 2023

xarray/tests/test_coding_strings.py:36: error: No overload variant of "dtype" matches argument types "str", "Dict[str, Type[str]]" [call-overload]

cc @Illviljan @headtr1ck

@headtr1ck
Copy link
Collaborator

headtr1ck commented Jun 2, 2023

This seems to be a numpy issue, mypy thinks that you cannot call np.dtype like you do.

Might be worth an issue over at numpy with the example from the test.

For now we can simply ignore this error.

@kmuehlbauer
Copy link
Contributor

Might be worth an issue over at numpy with the example from the test.

numpy/numpy#23886

@kmuehlbauer
Copy link
Contributor

Might be worth an issue over at numpy with the example from the test.

numpy/numpy#23886

The issue is already resolved over at numpy which is really great! It was also marked as backport. @headtr1ck How are these issues resolved currently or how do we track removing the ignore?

@headtr1ck
Copy link
Collaborator

If you want you can leave a comment
But the mypy CI should fail on unused ignores, so we will notice :)

@kmuehlbauer
Copy link
Contributor

But the mypy CI should fail on unused ignores, so we will notice :)

I've added an ignore for now.

Copy link
Member

@jhamman jhamman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, this looks approach seems fine to me. I left one comment that should be easy to address. Thanks @tomwhite and @kmuehlbauer for making this work.

@kmuehlbauer kmuehlbauer added plan to merge Final call for comments and removed needs discussion labels Jun 12, 2023
@dcherian dcherian enabled auto-merge (squash) June 16, 2023 03:20
@dcherian dcherian merged commit 0c876e4 into pydata:main Jun 16, 2023
@kmuehlbauer
Copy link
Contributor

Thanks @tomwhite and all others for getting this in.

@tomwhite
Copy link
Contributor Author

Thank you @kmuehlbauer for your work on this!

dstansby pushed a commit to dstansby/xarray that referenced this pull request Jun 28, 2023
* CF encoding should preserve vlen dtype for empty arrays

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* preserve vlen string dtype in netcdf4 and zarr backends

* check for h5py-variant ("vlen") in coding.strings.check_vlen_dtype

* add test to check preserving vlen dtype for empty vlen string arrays

* ignore call_overload error for np.dtype("O", metadata={"vlen": str})

* use filter.codec_id instead of private filter._meta as suggested in review

* update comment and add whats-new.rst entry

* fix whats-new.rst

* fix whats-new.rst (missing dot)

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Kai Mühlbauer <[email protected]>
Co-authored-by: Kai Mühlbauer <[email protected]>
Co-authored-by: Deepak Cherian <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
io plan to merge Final call for comments topic-backends topic-CF conventions topic-zarr Related to zarr storage library
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Zarr store array dtype changes for empty object string
6 participants