Skip to content

Commit db68db6

Browse files
keewisdcherianIllviljan
authored
keep_attrs for pad (#7267)
* make use of `keep_attrs` in `Variable.pad` * propagate `keep_attrs` * resolve the default `keep_attrs` in `Dataset.pad` * add tests for `Variable.pad` * add tests for `Dataset.pad` and keep the `Dataset` attrs * actually pad the variable * also check that untouched variables are not modified * add tests for `DataArray.pad` * update whats-new.rst * Set True by default * move the whats-new.rst entry to the dev section Co-authored-by: Deepak Cherian <[email protected]> Co-authored-by: Illviljan <[email protected]>
1 parent b610956 commit db68db6

File tree

7 files changed

+122
-2
lines changed

7 files changed

+122
-2
lines changed

doc/whats-new.rst

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ Bug fixes
3636
~~~~~~~~~
3737
- Allow numpy-only objects in :py:func:`where` when ``keep_attrs=True`` (:issue:`7362`, :pull:`7364`).
3838
By `Sam Levang <https://github.com/slevang>`_.
39+
- add a ``keep_attrs`` parameter to :py:meth:`Dataset.pad`, :py:meth:`DataArray.pad`,
40+
and :py:meth:`Variable.pad` (:pull:`7267`).
41+
By `Justus Magin <https://github.com/keewis>`_.
3942

4043
Documentation
4144
~~~~~~~~~~~~~

xarray/core/dataarray.py

+6
Original file line numberDiff line numberDiff line change
@@ -5270,6 +5270,7 @@ def pad(
52705270
| None = None,
52715271
end_values: int | tuple[int, int] | Mapping[Any, tuple[int, int]] | None = None,
52725272
reflect_type: PadReflectOptions = None,
5273+
keep_attrs: bool | None = None,
52735274
**pad_width_kwargs: Any,
52745275
) -> T_DataArray:
52755276
"""Pad this array along one or more dimensions.
@@ -5347,6 +5348,10 @@ def pad(
53475348
default with an unaltered reflection around the edge value. For
53485349
the "odd" style, the extended part of the array is created by
53495350
subtracting the reflected values from two times the edge value.
5351+
keep_attrs : bool or None, optional
5352+
If True, the attributes (``attrs``) will be copied from the
5353+
original object to the new one. If False, the new object
5354+
will be returned without attributes.
53505355
**pad_width_kwargs
53515356
The keyword arguments form of ``pad_width``.
53525357
One of ``pad_width`` or ``pad_width_kwargs`` must be provided.
@@ -5414,6 +5419,7 @@ def pad(
54145419
constant_values=constant_values,
54155420
end_values=end_values,
54165421
reflect_type=reflect_type,
5422+
keep_attrs=keep_attrs,
54175423
**pad_width_kwargs,
54185424
)
54195425
return self._from_temp_dataset(ds)

xarray/core/dataset.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -7939,6 +7939,7 @@ def pad(
79397939
) = None,
79407940
end_values: int | tuple[int, int] | Mapping[Any, tuple[int, int]] | None = None,
79417941
reflect_type: PadReflectOptions = None,
7942+
keep_attrs: bool | None = None,
79427943
**pad_width_kwargs: Any,
79437944
) -> T_Dataset:
79447945
"""Pad this dataset along one or more dimensions.
@@ -8016,6 +8017,10 @@ def pad(
80168017
default with an unaltered reflection around the edge value. For
80178018
the "odd" style, the extended part of the array is created by
80188019
subtracting the reflected values from two times the edge value.
8020+
keep_attrs : bool or None, optional
8021+
If True, the attributes (``attrs``) will be copied from the
8022+
original object to the new one. If False, the new object
8023+
will be returned without attributes.
80198024
**pad_width_kwargs
80208025
The keyword arguments form of ``pad_width``.
80218026
One of ``pad_width`` or ``pad_width_kwargs`` must be provided.
@@ -8062,6 +8067,9 @@ def pad(
80628067
coord_pad_mode = "constant"
80638068
coord_pad_options = {}
80648069

8070+
if keep_attrs is None:
8071+
keep_attrs = _get_keep_attrs(default=True)
8072+
80658073
variables = {}
80668074

80678075
# keep indexes that won't be affected by pad and drop all other indexes
@@ -8084,11 +8092,13 @@ def pad(
80848092
constant_values=constant_values,
80858093
end_values=end_values,
80868094
reflect_type=reflect_type,
8095+
keep_attrs=keep_attrs,
80878096
)
80888097
else:
80898098
variables[name] = var.pad(
80908099
pad_width=var_pad_width,
80918100
mode=coord_pad_mode,
8101+
keep_attrs=keep_attrs,
80928102
**coord_pad_options, # type: ignore[arg-type]
80938103
)
80948104
# reset default index of dimension coordinates
@@ -8099,7 +8109,8 @@ def pad(
80998109
indexes[name] = index
81008110
variables[name] = index_vars[name]
81018111

8102-
return self._replace_with_new_dims(variables, indexes=indexes)
8112+
attrs = self._attrs if keep_attrs else None
8113+
return self._replace_with_new_dims(variables, indexes=indexes, attrs=attrs)
81038114

81048115
def idxmin(
81058116
self: T_Dataset,

xarray/core/variable.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -1432,6 +1432,7 @@ def pad(
14321432
| None = None,
14331433
end_values: int | tuple[int, int] | Mapping[Any, tuple[int, int]] | None = None,
14341434
reflect_type: PadReflectOptions = None,
1435+
keep_attrs: bool | None = None,
14351436
**pad_width_kwargs: Any,
14361437
):
14371438
"""
@@ -1459,6 +1460,10 @@ def pad(
14591460
default with an unaltered reflection around the edge value. For
14601461
the "odd" style, the extended part of the array is created by
14611462
subtracting the reflected values from two times the edge value.
1463+
keep_attrs : bool, optional
1464+
If True, the variable's attributes (`attrs`) will be copied from
1465+
the original object to the new one. If False (default), the new
1466+
object will be returned without attributes.
14621467
**pad_width_kwargs
14631468
One of pad_width or pad_width_kwargs must be provided.
14641469
@@ -1515,7 +1520,11 @@ def pad(
15151520
**pad_option_kwargs,
15161521
)
15171522

1518-
return type(self)(self.dims, array)
1523+
if keep_attrs is None:
1524+
keep_attrs = _get_keep_attrs(default=True)
1525+
attrs = self._attrs if keep_attrs else None
1526+
1527+
return type(self)(self.dims, array, attrs=attrs)
15191528

15201529
def _roll_one_dim(self, dim, count):
15211530
axis = self.get_axis_num(dim)

xarray/tests/test_dataarray.py

+30
Original file line numberDiff line numberDiff line change
@@ -4166,6 +4166,36 @@ def test_pad_reflect(self, mode, reflect_type) -> None:
41664166
assert actual.shape == (7, 4, 9)
41674167
assert_identical(actual, expected)
41684168

4169+
@pytest.mark.parametrize(
4170+
["keep_attrs", "attrs", "expected"],
4171+
[
4172+
pytest.param(None, {"a": 1, "b": 2}, {"a": 1, "b": 2}, id="default"),
4173+
pytest.param(False, {"a": 1, "b": 2}, {}, id="False"),
4174+
pytest.param(True, {"a": 1, "b": 2}, {"a": 1, "b": 2}, id="True"),
4175+
],
4176+
)
4177+
def test_pad_keep_attrs(self, keep_attrs, attrs, expected) -> None:
4178+
arr = xr.DataArray(
4179+
[1, 2], dims="x", coords={"c": ("x", [-1, 1], attrs)}, attrs=attrs
4180+
)
4181+
expected = xr.DataArray(
4182+
[0, 1, 2, 0],
4183+
dims="x",
4184+
coords={"c": ("x", [np.nan, -1, 1, np.nan], expected)},
4185+
attrs=expected,
4186+
)
4187+
4188+
keep_attrs_ = "default" if keep_attrs is None else keep_attrs
4189+
4190+
with set_options(keep_attrs=keep_attrs_):
4191+
actual = arr.pad({"x": (1, 1)}, mode="constant", constant_values=0)
4192+
xr.testing.assert_identical(actual, expected)
4193+
4194+
actual = arr.pad(
4195+
{"x": (1, 1)}, mode="constant", constant_values=0, keep_attrs=keep_attrs
4196+
)
4197+
xr.testing.assert_identical(actual, expected)
4198+
41694199
@pytest.mark.parametrize("parser", ["pandas", "python"])
41704200
@pytest.mark.parametrize(
41714201
"engine", ["python", None, pytest.param("numexpr", marks=[requires_numexpr])]

xarray/tests/test_dataset.py

+34
Original file line numberDiff line numberDiff line change
@@ -6115,6 +6115,40 @@ def test_pad(self) -> None:
61156115
np.testing.assert_equal(padded["var1"].isel(dim2=[0, -1]).data, 42)
61166116
np.testing.assert_equal(padded["dim2"][[0, -1]].data, np.nan)
61176117

6118+
@pytest.mark.parametrize(
6119+
["keep_attrs", "attrs", "expected"],
6120+
[
6121+
pytest.param(None, {"a": 1, "b": 2}, {"a": 1, "b": 2}, id="default"),
6122+
pytest.param(False, {"a": 1, "b": 2}, {}, id="False"),
6123+
pytest.param(True, {"a": 1, "b": 2}, {"a": 1, "b": 2}, id="True"),
6124+
],
6125+
)
6126+
def test_pad_keep_attrs(self, keep_attrs, attrs, expected) -> None:
6127+
ds = xr.Dataset(
6128+
{"a": ("x", [1, 2], attrs), "b": ("y", [1, 2], attrs)},
6129+
coords={"c": ("x", [-1, 1], attrs), "d": ("y", [-1, 1], attrs)},
6130+
attrs=attrs,
6131+
)
6132+
expected = xr.Dataset(
6133+
{"a": ("x", [0, 1, 2, 0], expected), "b": ("y", [1, 2], attrs)},
6134+
coords={
6135+
"c": ("x", [np.nan, -1, 1, np.nan], expected),
6136+
"d": ("y", [-1, 1], attrs),
6137+
},
6138+
attrs=expected,
6139+
)
6140+
6141+
keep_attrs_ = "default" if keep_attrs is None else keep_attrs
6142+
6143+
with set_options(keep_attrs=keep_attrs_):
6144+
actual = ds.pad({"x": (1, 1)}, mode="constant", constant_values=0)
6145+
xr.testing.assert_identical(actual, expected)
6146+
6147+
actual = ds.pad(
6148+
{"x": (1, 1)}, mode="constant", constant_values=0, keep_attrs=keep_attrs
6149+
)
6150+
xr.testing.assert_identical(actual, expected)
6151+
61186152
def test_astype_attrs(self) -> None:
61196153
data = create_test_data(seed=123)
61206154
data.attrs["foo"] = "bar"

xarray/tests/test_variable.py

+27
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,33 @@ def test_pad_constant_values(self, xr_arg, np_arg):
910910
)
911911
assert_array_equal(actual, expected)
912912

913+
@pytest.mark.parametrize(
914+
["keep_attrs", "attrs", "expected"],
915+
[
916+
pytest.param(None, {"a": 1, "b": 2}, {"a": 1, "b": 2}, id="default"),
917+
pytest.param(False, {"a": 1, "b": 2}, {}, id="False"),
918+
pytest.param(True, {"a": 1, "b": 2}, {"a": 1, "b": 2}, id="True"),
919+
],
920+
)
921+
def test_pad_keep_attrs(self, keep_attrs, attrs, expected):
922+
data = np.arange(10, dtype=float)
923+
v = self.cls(["x"], data, attrs)
924+
925+
keep_attrs_ = "default" if keep_attrs is None else keep_attrs
926+
927+
with set_options(keep_attrs=keep_attrs_):
928+
actual = v.pad({"x": (1, 1)}, mode="constant", constant_values=np.nan)
929+
930+
assert actual.attrs == expected
931+
932+
actual = v.pad(
933+
{"x": (1, 1)},
934+
mode="constant",
935+
constant_values=np.nan,
936+
keep_attrs=keep_attrs,
937+
)
938+
assert actual.attrs == expected
939+
913940
@pytest.mark.parametrize("d, w", (("x", 3), ("y", 5)))
914941
def test_rolling_window(self, d, w):
915942
# Just a working test. See test_nputils for the algorithm validation

0 commit comments

Comments
 (0)