Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions cf_xarray/coding.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ def encode_multi_index_as_compress(ds, idxnames=None):
xarray.Dataset
Encoded Dataset with ``name`` as a integer coordinate with a ``"compress"`` attribute.

Raises
------
ValueError
If a MultiIndex level name is empty, is not a string, or contains
whitespace, which cannot be represented in the CF ``compress`` attribute.

References
----------
CF conventions on `compression by gathering <http://cfconventions.org/Data/cf-conventions/cf-conventions-1.8/cf-conventions.html#compression-by-gathering>`_
Expand All @@ -43,6 +49,14 @@ def encode_multi_index_as_compress(ds, idxnames=None):
if not idxnames:
raise ValueError("No MultiIndex-ed dimensions found in Dataset.")

for idxname in idxnames:
for name in ds.indexes[idxname].names:
if not isinstance(name, str) or not name or any(c.isspace() for c in name):
raise ValueError(
"MultiIndex level names must be nonempty strings without whitespace "
f"to be encoded in a CF 'compress' attribute. Got {name!r}."
)

encoded = ds.reset_index(idxnames)
for idxname in idxnames:
mindex = ds.indexes[idxname]
Expand Down
18 changes: 18 additions & 0 deletions cf_xarray/tests/test_coding.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@
import cf_xarray as cfxr


@pytest.mark.parametrize(
"name", ["lat name", " leading", "trailing ", "tab\tname", "line\nname", ""]
)
@pytest.mark.parametrize("level", [0, 1])
def test_compression_rejects_ambiguous_level_names(name, level):
names = ["lat", "lon"]
names[level] = name
index = pd.MultiIndex.from_product([[1, 2], [3, 4]], names=names)
dataset = xr.Dataset(
{"data": ("point", [1, 2, 3, 4])},
coords=xr.Coordinates.from_pandas_multiindex(index, "point"),
)
before = dataset.copy(deep=True)
with pytest.raises(ValueError, match="nonempty strings without whitespace"):
cfxr.encode_multi_index_as_compress(dataset)
xr.testing.assert_identical(dataset, before)


@pytest.mark.parametrize("with_data", [False, True])
def test_compression_preserves_unrelated_coordinates(with_data):
index = pd.MultiIndex.from_product([[1, 2], [3, 4]], names=("x", "y"))
Expand Down
Loading