From 1995aba2ed50bdc4adc46948520a7c0919954111 Mon Sep 17 00:00:00 2001 From: dchaudhari7177 Date: Mon, 7 Sep 2026 16:31:29 +0530 Subject: [PATCH] Let concat promote a mix of numpy and pandas extension coord dtypes PandasIndex.concat promotes the coordinate dtypes of the indexes it joins with np.result_type, which cannot interpret a pandas extension dtype at all. Under pandas 3 a string index carries a StringDtype, so joining an object whose dimension coordinate came from a pd.Index with one whose came from a plain list raises TypeError: Cannot interpret '' as a data type _concat_indexes has already built the joined index at that point, and pandas promoted the dtypes to do it. Use the dtype it settled on when any input is an extension dtype, rather than reimplementing pandas' promotion rules. Two numpy dtypes still go through np.result_type unchanged. Fixes #11317. --- doc/whats-new.rst | 6 ++++++ xarray/core/indexes.py | 10 ++++++++++ xarray/tests/test_concat.py | 23 +++++++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 6d67d234020..0a8129a4952 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -117,6 +117,12 @@ Bug Fixes type error when applying reduction methods, due to the reduction methods being dynamically generated (:issue:`8136`). By `Andrew Scherer `_. +- :py:func:`concat` no longer raises ``TypeError: Cannot interpret + '' as a data type`` when the objects being joined carry + index coordinates with a mix of numpy and pandas extension dtypes, which + happens routinely under pandas 3 when one dimension coordinate came from a + :py:class:`pandas.Index` and another from a plain list (:issue:`11317`). + By `Dipak Chaudhari `_. .. _`pandas-dev/pandas#64793`: https://github.com/pandas-dev/pandas/pull/64793 diff --git a/xarray/core/indexes.py b/xarray/core/indexes.py index ab9b7962020..116f444d297 100644 --- a/xarray/core/indexes.py +++ b/xarray/core/indexes.py @@ -789,6 +789,16 @@ def concat( indexes_coord_dtypes = {idx.coord_dtype for idx in indexes} if len(indexes_coord_dtypes) == 1: coord_dtype = next(iter(indexes_coord_dtypes)) + elif any( + isinstance(dtype, pd.api.extensions.ExtensionDtype) + for dtype in indexes_coord_dtypes + ): + # np.result_type cannot interpret a pandas extension dtype, and + # mixing one with a numpy dtype is ordinary now that pandas + # gives string indexes a StringDtype. _concat_indexes above has + # already had pandas promote these, so use the dtype it settled + # on rather than reimplementing pandas' rules here. + coord_dtype = new_pd_index.dtype else: coord_dtype = np.result_type(*indexes_coord_dtypes) diff --git a/xarray/tests/test_concat.py b/xarray/tests/test_concat.py index bc98d72d50c..08f5b668412 100644 --- a/xarray/tests/test_concat.py +++ b/xarray/tests/test_concat.py @@ -1491,6 +1491,29 @@ def test_concat_index_not_same_dim() -> None: concat([ds1, ds2], dim="x") +def test_concat_mixed_numpy_and_extension_coord_dtype() -> None: + # GH11317. Passing a pd.Index as the concat dim gives that index a pandas + # extension dtype (StringDtype under pandas 3), while a coord built from a + # plain list keeps a numpy None: + # The extension-dtype branch must not swallow the ordinary case: two numpy + # string coords of different widths still promote to the wider numpy dtype. + da = DataArray([0], dims=["d"], coords={"d": ["aaa"]}) + db = DataArray([1], dims=["d"], coords={"d": ["b"]}) + + assert concat([da, db], dim="d").coords["d"].dtype == np.dtype(" None: ds1 = Dataset(data_vars={"a": ("y", [0.1]), "b": 0.1}, coords={"x": 0.1})