Skip to content
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
5 changes: 5 additions & 0 deletions doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ Bug Fixes
the array. Previously, e.g. ``da.cumulative("time").argmax()`` returned indices
shifted by the number of missing values in front of the array (:issue:`11336`).
By `Michael Niklas <https://github.com/headtr1ck>`_.
- :py:meth:`DataArray.str.replace` now respects ``case=False`` when ``regex=False``;
the option was silently ignored. :py:meth:`DataArray.str.contains` with
``case=False, regex=False`` no longer raises ``AttributeError`` on object-dtype
or ``StringDType`` arrays (:pull:`11747`).
By `Novain Shibu <https://github.com/novain-shibu>`_.
- Fix :py:func:`tutorial.open_dataset` and :py:func:`tutorial.open_datatree`
with ``cache=False`` failing on Windows, because the downloaded file was
removed while still open (:pull:`11703`).
Expand Down
27 changes: 19 additions & 8 deletions xarray/core/accessor_str.py
Original file line number Diff line number Diff line change
Expand Up @@ -1520,14 +1520,10 @@ def func(x, ipat):
pat = self._stringify(pat)
if case or case is None:
func = lambda x, ipat: ipat in x
elif self._obj.dtype.char == "U":
uppered = self.casefold()
uppat = StringAccessor(pat).casefold() # type: ignore[type-var] # hack?
return uppered.str.contains(uppat, regex=False) # type: ignore[return-value]
elif self._obj.dtype.kind == "S":
func = lambda x, ipat: ipat.upper() in x.upper()
else:
uppered = self.upper()
uppat = StringAccessor(pat).upper() # type: ignore[type-var] # hack?
return uppered.str.contains(uppat, regex=False) # type: ignore[return-value]
func = lambda x, ipat: ipat.casefold() in x.casefold()

return self._apply(func=func, func_args=(pat,), dtype=bool)

Expand Down Expand Up @@ -1962,7 +1958,22 @@ def replace(
)
else:
pat = self._stringify(pat)
func = lambda x, ipat, irepl, i_n: x.replace(ipat, irepl, i_n)
if case is False:
# Literal match, but case-insensitive: escape the pattern and
# return the replacement as-is so it is not parsed as a template.
func = lambda x, ipat, irepl, i_n: (
x
if i_n == 0
else re.sub(
re.escape(ipat),
lambda _: irepl,
x,
count=max(i_n, 0),
flags=re.IGNORECASE,
)
)
else:
func = lambda x, ipat, irepl, i_n: x.replace(ipat, irepl, i_n)
return self._apply(func=func, func_args=(pat, repl, n))

def extract(
Expand Down
46 changes: 46 additions & 0 deletions xarray/tests/test_accessor_str.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,52 @@ def test_replace_n_zero(dtype) -> None:
assert_equal(result, expected)


def test_replace_literal_case_insensitive(dtype) -> None:
values = xr.DataArray(["fooBAD.bad__barbAd"], dims=["x"]).astype(dtype)

result = values.str.replace("bad", "X", case=False, regex=False)
expected = xr.DataArray(["fooX.X__barX"], dims=["x"]).astype(dtype)
assert result.dtype == expected.dtype
assert_equal(result, expected)

# the pattern is literal, and the replacement is not a regex template
result = values.str.replace("D.B", r"", n=1, case=False, regex=False)
expected = xr.DataArray([r"fooBAad__barbAd"], dims=["x"]).astype(dtype)
assert result.dtype == expected.dtype
assert_equal(result, expected)

result = values.str.replace("bad", "X", n=2, case=False, regex=False)
expected = xr.DataArray(["fooX.X__barbAd"], dims=["x"]).astype(dtype)
assert_equal(result, expected)

result = values.str.replace("bad", "X", n=0, case=False, regex=False)
assert_equal(result, values)


@pytest.mark.parametrize(
"values",
[
pytest.param(np.array(["Foo", "xYz", "fOO.oo"], dtype=object), id="object"),
pytest.param(
np.array(["Foo", "xYz", "fOO.oo"], dtype=np.dtypes.StringDType()),
id="StringDType",
),
],
)
def test_case_insensitive_literal_non_unicode_dtype(values) -> None:
da = xr.DataArray(values, dims=["x"])

result = da.str.contains("o.", case=False, regex=False)
assert_equal(result, xr.DataArray([False, False, True], dims=["x"]))

result = da.str.replace("o", "_", case=False, regex=False)
expected = xr.DataArray(
np.array(["F__", "xYz", "f__.__"], dtype=values.dtype), dims=["x"]
)
assert result.dtype == expected.dtype
assert_equal(result, expected)


def test_replace_callable() -> None:
values = xr.DataArray(["fooBAD__barBAD"])

Expand Down
Loading