From 08d10668035fd888ea494876411ee4df1c3bc2f1 Mon Sep 17 00:00:00 2001 From: Jeff <138259519+jdotc1@users.noreply.github.com> Date: Fri, 23 Oct 2026 01:46:40 -0400 Subject: [PATCH 1/2] BUG: support variable-width strings in str.extract --- doc/whats-new.rst | 4 ++++ xarray/core/accessor_str.py | 4 ++-- xarray/tests/test_accessor_str.py | 20 ++++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 21f3671188a..3662d5cb00e 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -165,6 +165,10 @@ Deprecations Bug Fixes ~~~~~~~~~ +- ``DataArray.str.extract`` with a named output dimension now supports NumPy + variable-width strings, including unmatched values and optional capture groups + (:issue:`11785`). + By `jdotc1 `_. - Results of the string accessor methods ``strip``, ``pad``, ``center``, ``ljust``, ``rjust``, ``zfill`` and ``replace`` on dask arrays are no longer truncated to a single character, and ``repeat`` no longer fails on dask arrays diff --git a/xarray/core/accessor_str.py b/xarray/core/accessor_str.py index a51c3822bc2..5be9bff2dcb 100644 --- a/xarray/core/accessor_str.py +++ b/xarray/core/accessor_str.py @@ -2154,10 +2154,10 @@ def _get_res_single(val, pat): def _get_res_multi(val, pat): match = pat.search(val) if match is None: - return np.array([""], val.dtype) + return np.array([""], dtype=np.object_) match = match.groups() match = [grp if grp is not None else "" for grp in match] - return np.array(match, val.dtype) + return np.array(match, dtype=np.object_) if dim is None: return self._apply(func=_get_res_single, func_args=(pat,)) diff --git a/xarray/tests/test_accessor_str.py b/xarray/tests/test_accessor_str.py index d49f8bca2c9..a34329d88da 100644 --- a/xarray/tests/test_accessor_str.py +++ b/xarray/tests/test_accessor_str.py @@ -852,6 +852,26 @@ def test_extract_single_nocase(dtype) -> None: assert_equal(res_re_dim, targ_dim) +@pytest.mark.parametrize("string_dtype", ["T", "U", "S"]) +@pytest.mark.parametrize("compiled", [False, True]) +def test_extract_multi_string_dtypes(string_dtype, compiled) -> None: + value = xr.DataArray( + np.array(["alpha_1234", "beta", "!"], dtype=string_dtype), dims="X" + ) + pattern = r"([a-z]+)(?:_(\d+))?" + if compiled: + pattern = re.compile(pattern.encode() if string_dtype == "S" else pattern) + expected = xr.DataArray( + np.array([["alpha", "1234"], ["beta", ""], ["", ""]], dtype=string_dtype), + dims=["X", "group"], + ) + + result = value.str.extract(pattern, dim="group") + + assert result.dtype == expected.dtype + assert_equal(result, expected) + + def test_extract_multi_case(dtype) -> None: pat_str = r"(\w+)_Xy_(\d*)" pat_re: str | bytes = ( From 49cfacb21b2ba142e289cafd7eb7c6d68fd9e0f0 Mon Sep 17 00:00:00 2001 From: Jeff <138259519+jdotc1@users.noreply.github.com> Date: Fri, 23 Oct 2026 02:10:24 -0400 Subject: [PATCH 2/2] TST: fix inferred regex pattern type in string extraction test --- xarray/tests/test_accessor_str.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/xarray/tests/test_accessor_str.py b/xarray/tests/test_accessor_str.py index a34329d88da..e89db57794c 100644 --- a/xarray/tests/test_accessor_str.py +++ b/xarray/tests/test_accessor_str.py @@ -858,9 +858,12 @@ def test_extract_multi_string_dtypes(string_dtype, compiled) -> None: value = xr.DataArray( np.array(["alpha_1234", "beta", "!"], dtype=string_dtype), dims="X" ) - pattern = r"([a-z]+)(?:_(\d+))?" - if compiled: - pattern = re.compile(pattern.encode() if string_dtype == "S" else pattern) + pattern_str = r"([a-z]+)(?:_(\d+))?" + pattern = ( + re.compile(pattern_str.encode() if string_dtype == "S" else pattern_str) + if compiled + else pattern_str + ) expected = xr.DataArray( np.array([["alpha", "1234"], ["beta", ""], ["", ""]], dtype=string_dtype), dims=["X", "group"],