Skip to content

Improve rename_like #222

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 8 commits into from
May 27, 2021
Merged
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
81 changes: 55 additions & 26 deletions cf_xarray/accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1317,7 +1317,7 @@ def rename_like(
Variables will be renamed to match variable names in this xarray object
skip: str, Iterable[str], optional
Limit the renaming excluding
("axes", "cell_measures", "coordinates", "standard_names")
("axes", "bounds", cell_measures", "coordinates", "standard_names")
or a subset thereof.

Returns
Expand All @@ -1332,24 +1332,48 @@ def rename_like(
good_keys = ourkeys & theirkeys
keydict = {}
for key in good_keys:
ours = set(_get_all(self._obj, key))
theirs = set(_get_all(other, key))
ours = set(apply_mapper(_get_all, self._obj, key))
theirs = set(apply_mapper(_get_all, other, key))
for attr in skip:
ours -= set(getattr(self, attr).get(key, []))
theirs -= set(getattr(other.cf, attr).get(key, []))
ours.difference_update(getattr(self, attr).get(key, []))
theirs.difference_update(getattr(other.cf, attr).get(key, []))
if ours and theirs:
keydict[key] = dict(ours=list(ours), theirs=list(theirs))

conflicts = {}
for k0, v0 in keydict.items():
if len(v0["ours"]) > 1 or len(v0["theirs"]) > 1:
conflicts[k0] = v0
continue
for v1 in keydict.values():
# Conflicts have same ours but different theirs or vice versa
if (v0["ours"] == v1["ours"]) != (v0["theirs"] == v1["theirs"]):
def get_renamer_and_conflicts(keydict):
conflicts = {}
for k0, v0 in keydict.items():
if len(v0["ours"]) > 1 or len(v0["theirs"]) > 1:
conflicts[k0] = v0
break
continue
for v1 in keydict.values():
# Conflicts have same ours but different theirs or vice versa
if (v0["ours"] == v1["ours"]) != (v0["theirs"] == v1["theirs"]):
conflicts[k0] = v0
break

renamer = {
v["ours"][0]: v["theirs"][0]
for k, v in keydict.items()
if k not in conflicts
}

return renamer, conflicts

# Run get_renamer_and_conflicts twice.
# The second time add the bounds associated with variables to rename
renamer, conflicts = get_renamer_and_conflicts(keydict)
if "bounds" not in skip:
for k, v in renamer.items():
ours = set(getattr(self, "bounds", {}).get(k, []))
theirs = set(getattr(other.cf, "bounds", {}).get(v, []))
if ours and theirs:
ours.update(keydict.get(k, {}).get("ours", []))
theirs.update(keydict.get(k, {}).get("theirs", []))
keydict[k] = dict(ours=list(ours), theirs=list(theirs))
renamer, conflicts = get_renamer_and_conflicts(keydict)

# Rename and warn
if conflicts:
warnings.warn(
"Conflicting variables skipped:\n"
Expand All @@ -1363,23 +1387,28 @@ def rename_like(
),
UserWarning,
)

renamer = {
v["ours"][0]: v["theirs"][0]
for k, v in keydict.items()
if k not in conflicts
}
newobj = self._obj.rename(renamer)

# rename variable names in the coordinates attribute
# rename variable names in the attributes
# if present
ds = self._maybe_to_dataset(newobj)
for _, variable in ds.variables.items():
coordinates = variable.attrs.get("coordinates", None)
if coordinates:
for k, v in renamer.items():
coordinates = coordinates.replace(k, v)
variable.attrs["coordinates"] = coordinates
for attr in ("bounds", "coordinates", "cell_measures"):
if attr == "cell_measures":
varlist = [
f"{k}: {renamer.get(v, v)}"
for k, v in parse_cell_methods_attr(
variable.attrs.get(attr, "")
).items()
]
else:
varlist = [
renamer.get(var, var)
for var in variable.attrs.get(attr, "").split()
]

if varlist:
variable.attrs[attr] = " ".join(varlist)
return self._maybe_to_dataarray(ds)

def guess_coord_axis(self, verbose: bool = False) -> Union[DataArray, Dataset]:
Expand Down
16 changes: 15 additions & 1 deletion cf_xarray/tests/test_accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ def test_rename_like():
for k in ["TLONG", "TLAT"]:
assert k not in renamed.coords
assert k in original.coords
assert original.TEMP.attrs["coordinates"] == "TLONG TLAT"
assert original.TEMP.attrs["coordinates"] == "TLONG TLAT"

assert "lon" in renamed.coords
assert "lat" in renamed.coords
Expand Down Expand Up @@ -271,6 +271,20 @@ def test_rename_like():
actual = da.cf.rename_like(airds, skip="axes").cf.coordinates
assert expected == actual

# rename bounds
original = airds.cf[["air"]].cf.add_bounds("lon")
other = popds.cf[["TEMP"]].cf.add_bounds("nlon")
renamed = original.cf.rename_like(other, skip="coordinates")
assert renamed.cf.bounds["nlon"] == ["nlon_bounds"]

# rename cell measures
other = airds.cf["air"].cf.rename(area="CELL_AREA")
other.attrs["cell_measures"] = other.attrs["cell_measures"].replace(
"cell_area", "CELL_AREA"
)
renamed = airds.cf["air"].cf.rename_like(other)
assert renamed.cf.cell_measures["area"] == ["CELL_AREA"]


@pytest.mark.parametrize("obj", objects)
@pytest.mark.parametrize(
Expand Down
1 change: 1 addition & 0 deletions doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ v0.5.3 (unreleased)
===================
- Begin adding support for units with a unit registry for pint arrays. :pr:`197`.
By `Jon Thielen`_ and `Justus Magin`_.
- :py:meth:`Dataset.cf.rename_like` also updates the ``bounds`` and ``cell_measures`` attributes. By `Mattia Almansi`_.

v0.5.2 (May 11, 2021)
=====================
Expand Down