From fe4bd10a0c137a508d4fbdb6af1883aef4ca7b2f Mon Sep 17 00:00:00 2001 From: ilan-gold Date: Fri, 9 Aug 2024 15:32:33 -0400 Subject: [PATCH 1/9] (fix): disallow using dataframes with multi index columns --- src/anndata/_core/aligned_mapping.py | 26 ++++++++++++++++---------- src/anndata/_core/anndata.py | 18 ++++++++++++++++++ src/anndata/_core/storage.py | 5 +++++ src/anndata/tests/helpers.py | 11 +++++++++++ tests/test_annot.py | 8 ++++++++ tests/test_base.py | 20 +++++++++++++++++++- tests/test_obsmvarm.py | 7 +++++++ 7 files changed, 84 insertions(+), 11 deletions(-) diff --git a/src/anndata/_core/aligned_mapping.py b/src/anndata/_core/aligned_mapping.py index 26c89d9f2..aafbdb0ca 100644 --- a/src/anndata/_core/aligned_mapping.py +++ b/src/anndata/_core/aligned_mapping.py @@ -258,16 +258,22 @@ def to_df(self) -> pd.DataFrame: return df def _validate_value(self, val: Value, key: str) -> Value: - if isinstance(val, pd.DataFrame) and not val.index.equals(self.dim_names): - # Could probably also re-order index if it’s contained - try: - pd.testing.assert_index_equal(val.index, self.dim_names) - except AssertionError as e: - msg = f"value.index does not match parent’s {self.dim} names:\n{e}" - raise ValueError(msg) from None - else: - msg = "Index.equals and pd.testing.assert_index_equal disagree" - raise AssertionError(msg) + if isinstance(val, pd.DataFrame): + if isinstance(val.columns, pd.MultiIndex): + raise ValueError( + "MultiIndex columns are not supported in AnnData. " + "Please use a single-level index." + ) + if not val.index.equals(self.dim_names): + # Could probably also re-order index if it’s contained + try: + pd.testing.assert_index_equal(val.index, self.dim_names) + except AssertionError as e: + msg = f"value.index does not match parent’s {self.dim} names:\n{e}" + raise ValueError(msg) from None + else: + msg = "Index.equals and pd.testing.assert_index_equal disagree" + raise AssertionError(msg) return super()._validate_value(val, key) @property diff --git a/src/anndata/_core/anndata.py b/src/anndata/_core/anndata.py index 7da29e4a4..7bcd1df76 100644 --- a/src/anndata/_core/anndata.py +++ b/src/anndata/_core/anndata.py @@ -234,6 +234,19 @@ def __init__( oidx: Index1D = None, vidx: Index1D = None, ): + # check for any multi-indices + df_elems = [obs, var, X] + for xxxm in [obsm, varm]: + if xxxm is not None and not isinstance(xxxm, np.ndarray): + df_elems += [v for v in xxxm.values() if isinstance(v, pd.DataFrame)] + for attr in df_elems: + if isinstance(attr, pd.DataFrame) and isinstance( + attr.columns, pd.MultiIndex + ): + raise ValueError( + "MultiIndex columns are not supported in AnnData. " + "Please use a single-level index." + ) if asview: if not isinstance(X, AnnData): raise ValueError("`X` has to be an AnnData object.") @@ -736,6 +749,11 @@ def n_vars(self) -> int: def _set_dim_df(self, value: pd.DataFrame, attr: str): if not isinstance(value, pd.DataFrame): raise ValueError(f"Can only assign pd.DataFrame to {attr}.") + if isinstance(value.columns, pd.MultiIndex): + raise ValueError( + "MultiIndex columns are not supported in AnnData. " + "Please use a single-level index." + ) value_idx = self._prep_dim_index(value.index, attr) if self.is_view: self._init_as_actual(self.copy()) diff --git a/src/anndata/_core/storage.py b/src/anndata/_core/storage.py index bc6a091d3..40e7e78f6 100644 --- a/src/anndata/_core/storage.py +++ b/src/anndata/_core/storage.py @@ -84,6 +84,11 @@ def coerce_array( value = value.A return value if isinstance(value, pd.DataFrame): + if isinstance(value.columns, pd.MultiIndex) and allow_df: + raise ValueError( + "MultiIndex columns are not supported in AnnData. " + "Please use a single-level index." + ) return value if allow_df else ensure_df_homogeneous(value, name) # if value is an array-like object, try to convert it e = None diff --git a/src/anndata/tests/helpers.py b/src/anndata/tests/helpers.py index 6a30f4c32..185808b8d 100644 --- a/src/anndata/tests/helpers.py +++ b/src/anndata/tests/helpers.py @@ -1,5 +1,6 @@ from __future__ import annotations +import itertools import random import re import warnings @@ -1000,3 +1001,13 @@ def __init__(self, *_args, **_kwargs) -> None: raise ImportError( "zarr must be imported to create an `AccessTrackingStore` instance." ) + + +def get_multiindex_columns_df(shape): + return pd.DataFrame( + np.random.rand(shape[0], shape[1]), + columns=pd.MultiIndex.from_tuples( + list(itertools.product(["a"], range(shape[1] - (shape[1] // 2)))) + + list(itertools.product(["b"], range(shape[1] // 2))) + ), + ) diff --git a/tests/test_annot.py b/tests/test_annot.py index 7f4c2697e..8287add0d 100644 --- a/tests/test_annot.py +++ b/tests/test_annot.py @@ -8,6 +8,7 @@ from natsort import natsorted import anndata as ad +from anndata.tests.helpers import get_multiindex_columns_df @pytest.mark.parametrize("dtype", [object, "string"]) @@ -63,3 +64,10 @@ def test_non_str_to_not_categorical(): result_non_transformed = adata.obs.drop(columns=["str_with_nan"]) pd.testing.assert_frame_equal(expected_non_transformed, result_non_transformed) + + +def test_error_multiindex(): + adata = ad.AnnData(np.random.rand(100, 10)) + df = get_multiindex_columns_df((adata.shape[0], 20)) + with pytest.raises(ValueError, match=r"MultiIndex columns are not supported"): + adata.obs = df diff --git a/tests/test_base.py b/tests/test_base.py index 02e4eabe1..2c8367002 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -14,7 +14,7 @@ from anndata import AnnData, ImplicitModificationWarning from anndata._settings import settings from anndata.compat import CAN_USE_SPARSE_ARRAY -from anndata.tests.helpers import assert_equal, gen_adata +from anndata.tests.helpers import assert_equal, gen_adata, get_multiindex_columns_df # some test objects that we use below adata_dense = AnnData(np.array([[1, 2], [3, 4]])) @@ -117,6 +117,24 @@ def test_create_from_df(): assert df.index.tolist() == ad.obs_names.tolist() +def test_error_create_from_multiindex_df(): + df = get_multiindex_columns_df((100, 20)) + with pytest.raises(ValueError, match=r"MultiIndex columns are not supported"): + AnnData(df) + + +def test_error_with_obs_multiindex_df(): + df = get_multiindex_columns_df((100, 20)) + with pytest.raises(ValueError, match=r"MultiIndex columns are not supported"): + AnnData(X=np.random.rand(100, 10), obs=df) + + +def test_error_with_obsm_multiindex_df(): + df = get_multiindex_columns_df((100, 20)) + with pytest.raises(ValueError, match=r"MultiIndex columns are not supported"): + AnnData(X=np.random.rand(100, 10), obsm={"df": df}) + + def test_create_from_sparse_df(): s = sp.random(20, 30, density=0.2) obs_names = [f"obs{i}" for i in range(20)] diff --git a/tests/test_obsmvarm.py b/tests/test_obsmvarm.py index 92f5b8fa0..4ef9187f7 100644 --- a/tests/test_obsmvarm.py +++ b/tests/test_obsmvarm.py @@ -7,6 +7,7 @@ from scipy import sparse from anndata import AnnData +from anndata.tests.helpers import get_multiindex_columns_df M, N = (100, 100) @@ -137,3 +138,9 @@ def test_shape_error(adata: AnnData): ), ): adata.obsm["b"] = np.zeros((adata.shape[0] + 1, adata.shape[0])) + + +def test_error_set_multiindex_df(adata: AnnData): + df = get_multiindex_columns_df((adata.shape[0], 20)) + with pytest.raises(ValueError, match=r"MultiIndex columns are not supported"): + adata.obsm["df"] = df From 2c361667cec874c8f78cb8384db8e9e7eba6cadb Mon Sep 17 00:00:00 2001 From: ilan-gold Date: Fri, 9 Aug 2024 15:34:58 -0400 Subject: [PATCH 2/9] (chore): release note --- docs/release-notes/0.10.9.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/0.10.9.md b/docs/release-notes/0.10.9.md index f1a3dec9c..9604c72ac 100644 --- a/docs/release-notes/0.10.9.md +++ b/docs/release-notes/0.10.9.md @@ -9,6 +9,7 @@ * Upper bound {mod}`numpy` for `gpu` installation on account of https://github.com/cupy/cupy/issues/8391 {pr}`1540` {user}`ilan-gold` * Fix writing large number of columns for `h5` files {pr}`1147` {user}`ilan-gold` {user}`selmanozleyen` * Upper bound dask on account of https://github.com/scverse/anndata/issues/1579 {pr}`1580` {user}`ilan-gold` +* Disallow using {class}`~pandas.DataFrame`s with mutli-index columns {pr}`1589` {use}`ilan-gold` #### Documentation From bf4a9314ced9453108fdcf0ebe81578dfc769d4a Mon Sep 17 00:00:00 2001 From: ilan-gold Date: Sun, 11 Aug 2024 23:45:07 -0400 Subject: [PATCH 3/9] (refactor): make `util`. --- src/anndata/_core/aligned_mapping.py | 14 ++++++++------ src/anndata/_core/anndata.py | 22 +++++++++------------- src/anndata/_core/storage.py | 13 +++++++------ src/anndata/utils.py | 8 ++++++++ 4 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/anndata/_core/aligned_mapping.py b/src/anndata/_core/aligned_mapping.py index aafbdb0ca..7727fa39c 100644 --- a/src/anndata/_core/aligned_mapping.py +++ b/src/anndata/_core/aligned_mapping.py @@ -13,7 +13,13 @@ from .._warnings import ExperimentalFeatureWarning, ImplicitModificationWarning from ..compat import AwkArray -from ..utils import axis_len, convert_to_dict, deprecated, warn_once +from ..utils import ( + axis_len, + convert_to_dict, + deprecated, + raise_value_error_if_multiindex_columns, + warn_once, +) from .access import ElementRef from .index import _subset from .storage import coerce_array @@ -259,11 +265,7 @@ def to_df(self) -> pd.DataFrame: def _validate_value(self, val: Value, key: str) -> Value: if isinstance(val, pd.DataFrame): - if isinstance(val.columns, pd.MultiIndex): - raise ValueError( - "MultiIndex columns are not supported in AnnData. " - "Please use a single-level index." - ) + raise_value_error_if_multiindex_columns(val) if not val.index.equals(self.dim_names): # Could probably also re-order index if it’s contained try: diff --git a/src/anndata/_core/anndata.py b/src/anndata/_core/anndata.py index 7bcd1df76..2c0874854 100644 --- a/src/anndata/_core/anndata.py +++ b/src/anndata/_core/anndata.py @@ -26,7 +26,12 @@ from .._settings import settings from ..compat import DaskArray, SpArray, ZarrArray, _move_adj_mtx from ..logging import anndata_logger as logger -from ..utils import axis_len, deprecated, ensure_df_homogeneous +from ..utils import ( + axis_len, + deprecated, + ensure_df_homogeneous, + raise_value_error_if_multiindex_columns, +) from .access import ElementRef from .aligned_df import _gen_dataframe from .aligned_mapping import AlignedMappingProperty, AxisArrays, Layers, PairwiseArrays @@ -240,13 +245,8 @@ def __init__( if xxxm is not None and not isinstance(xxxm, np.ndarray): df_elems += [v for v in xxxm.values() if isinstance(v, pd.DataFrame)] for attr in df_elems: - if isinstance(attr, pd.DataFrame) and isinstance( - attr.columns, pd.MultiIndex - ): - raise ValueError( - "MultiIndex columns are not supported in AnnData. " - "Please use a single-level index." - ) + if isinstance(attr, pd.DataFrame): + raise_value_error_if_multiindex_columns(attr) if asview: if not isinstance(X, AnnData): raise ValueError("`X` has to be an AnnData object.") @@ -749,11 +749,7 @@ def n_vars(self) -> int: def _set_dim_df(self, value: pd.DataFrame, attr: str): if not isinstance(value, pd.DataFrame): raise ValueError(f"Can only assign pd.DataFrame to {attr}.") - if isinstance(value.columns, pd.MultiIndex): - raise ValueError( - "MultiIndex columns are not supported in AnnData. " - "Please use a single-level index." - ) + raise_value_error_if_multiindex_columns(value) value_idx = self._prep_dim_index(value.index, attr) if self.is_view: self._init_as_actual(self.copy()) diff --git a/src/anndata/_core/storage.py b/src/anndata/_core/storage.py index 40e7e78f6..eeccdd513 100644 --- a/src/anndata/_core/storage.py +++ b/src/anndata/_core/storage.py @@ -20,7 +20,11 @@ ZappyArray, ZarrArray, ) -from ..utils import ensure_df_homogeneous, join_english +from ..utils import ( + ensure_df_homogeneous, + join_english, + raise_value_error_if_multiindex_columns, +) from .sparse_dataset import BaseCompressedSparseDataset if TYPE_CHECKING: @@ -84,11 +88,8 @@ def coerce_array( value = value.A return value if isinstance(value, pd.DataFrame): - if isinstance(value.columns, pd.MultiIndex) and allow_df: - raise ValueError( - "MultiIndex columns are not supported in AnnData. " - "Please use a single-level index." - ) + if allow_df: + raise_value_error_if_multiindex_columns(value) return value if allow_df else ensure_df_homogeneous(value, name) # if value is an array-like object, try to convert it e = None diff --git a/src/anndata/utils.py b/src/anndata/utils.py index 177115d26..e8b923b22 100644 --- a/src/anndata/utils.py +++ b/src/anndata/utils.py @@ -400,3 +400,11 @@ def is_hidden(attr) -> bool: for item in type.__dir__(cls) if not is_hidden(getattr(cls, item, None)) ] + + +def raise_value_error_if_multiindex_columns(df: pd.DataFrame): + if isinstance(df.columns, pd.MultiIndex): + raise ValueError( + "MultiIndex columns are not supported in AnnData. " + "Please use a single-level index." + ) From 9b8a864b00cfd71f1af686e12cfb7bef14c1bdaf Mon Sep 17 00:00:00 2001 From: ilan-gold Date: Mon, 12 Aug 2024 00:18:13 -0400 Subject: [PATCH 4/9] (fix): `user` --- docs/release-notes/0.10.9.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/0.10.9.md b/docs/release-notes/0.10.9.md index 9604c72ac..f7ba67041 100644 --- a/docs/release-notes/0.10.9.md +++ b/docs/release-notes/0.10.9.md @@ -9,7 +9,7 @@ * Upper bound {mod}`numpy` for `gpu` installation on account of https://github.com/cupy/cupy/issues/8391 {pr}`1540` {user}`ilan-gold` * Fix writing large number of columns for `h5` files {pr}`1147` {user}`ilan-gold` {user}`selmanozleyen` * Upper bound dask on account of https://github.com/scverse/anndata/issues/1579 {pr}`1580` {user}`ilan-gold` -* Disallow using {class}`~pandas.DataFrame`s with mutli-index columns {pr}`1589` {use}`ilan-gold` +* Disallow using {class}`~pandas.DataFrame`s with mutli-index columns {pr}`1589` {user}`ilan-gold` #### Documentation From 41168a80d579cc317aea3e9aaf5c3e950133aa25 Mon Sep 17 00:00:00 2001 From: "Philipp A." Date: Mon, 12 Aug 2024 08:55:26 +0200 Subject: [PATCH 5/9] Simplify --- src/anndata/_core/anndata.py | 12 ++++-------- src/anndata/utils.py | 3 ++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/anndata/_core/anndata.py b/src/anndata/_core/anndata.py index 2c0874854..2bdd7bbdd 100644 --- a/src/anndata/_core/anndata.py +++ b/src/anndata/_core/anndata.py @@ -236,15 +236,11 @@ def __init__( *, obsp: np.ndarray | Mapping[str, Sequence[Any]] | None = None, varp: np.ndarray | Mapping[str, Sequence[Any]] | None = None, - oidx: Index1D = None, - vidx: Index1D = None, + oidx: Index1D | None = None, + vidx: Index1D | None = None, ): - # check for any multi-indices - df_elems = [obs, var, X] - for xxxm in [obsm, varm]: - if xxxm is not None and not isinstance(xxxm, np.ndarray): - df_elems += [v for v in xxxm.values() if isinstance(v, pd.DataFrame)] - for attr in df_elems: + # check for any multi-indices that aren’t later checked in coerce_array + for attr in [obs, var, X]: if isinstance(attr, pd.DataFrame): raise_value_error_if_multiindex_columns(attr) if asview: diff --git a/src/anndata/utils.py b/src/anndata/utils.py index e8b923b22..9467b72c2 100644 --- a/src/anndata/utils.py +++ b/src/anndata/utils.py @@ -404,7 +404,8 @@ def is_hidden(attr) -> bool: def raise_value_error_if_multiindex_columns(df: pd.DataFrame): if isinstance(df.columns, pd.MultiIndex): - raise ValueError( + msg = ( "MultiIndex columns are not supported in AnnData. " "Please use a single-level index." ) + raise ValueError(msg) From 31b333bea23215529ac6803674693c55ec202a1d Mon Sep 17 00:00:00 2001 From: "Philipp A." Date: Mon, 12 Aug 2024 08:56:00 +0200 Subject: [PATCH 6/9] typo --- docs/release-notes/0.10.9.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/0.10.9.md b/docs/release-notes/0.10.9.md index f7ba67041..0c57f830c 100644 --- a/docs/release-notes/0.10.9.md +++ b/docs/release-notes/0.10.9.md @@ -9,7 +9,7 @@ * Upper bound {mod}`numpy` for `gpu` installation on account of https://github.com/cupy/cupy/issues/8391 {pr}`1540` {user}`ilan-gold` * Fix writing large number of columns for `h5` files {pr}`1147` {user}`ilan-gold` {user}`selmanozleyen` * Upper bound dask on account of https://github.com/scverse/anndata/issues/1579 {pr}`1580` {user}`ilan-gold` -* Disallow using {class}`~pandas.DataFrame`s with mutli-index columns {pr}`1589` {user}`ilan-gold` +* Disallow using {class}`~pandas.DataFrame`s with multi-index columns {pr}`1589` {user}`ilan-gold` #### Documentation From 7bfb920f41f0caec7b38f15e631048580846e2b0 Mon Sep 17 00:00:00 2001 From: "Philipp A." Date: Mon, 12 Aug 2024 09:08:55 +0200 Subject: [PATCH 7/9] parametrize --- tests/test_base.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/tests/test_base.py b/tests/test_base.py index 2c8367002..277e8c8ab 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -117,22 +117,12 @@ def test_create_from_df(): assert df.index.tolist() == ad.obs_names.tolist() -def test_error_create_from_multiindex_df(): +@pytest.mark.parametrize("attr", ["X", "obs", "obsm"]) +def test_error_create_from_multiindex_df(attr): df = get_multiindex_columns_df((100, 20)) + val = df if attr != "obsm" else {"df": df} with pytest.raises(ValueError, match=r"MultiIndex columns are not supported"): - AnnData(df) - - -def test_error_with_obs_multiindex_df(): - df = get_multiindex_columns_df((100, 20)) - with pytest.raises(ValueError, match=r"MultiIndex columns are not supported"): - AnnData(X=np.random.rand(100, 10), obs=df) - - -def test_error_with_obsm_multiindex_df(): - df = get_multiindex_columns_df((100, 20)) - with pytest.raises(ValueError, match=r"MultiIndex columns are not supported"): - AnnData(X=np.random.rand(100, 10), obsm={"df": df}) + AnnData(**{attr: val}, shape=(100, 10)) def test_create_from_sparse_df(): From 3416668710dc5c8654d8e8b8329bc45faa8dac5c Mon Sep 17 00:00:00 2001 From: ilan-gold Date: Mon, 12 Aug 2024 12:21:38 -0400 Subject: [PATCH 8/9] (chore): add attr to message --- src/anndata/_core/aligned_mapping.py | 2 +- src/anndata/_core/anndata.py | 8 ++++---- src/anndata/_core/storage.py | 2 +- src/anndata/utils.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/anndata/_core/aligned_mapping.py b/src/anndata/_core/aligned_mapping.py index 7727fa39c..4bb8ca831 100644 --- a/src/anndata/_core/aligned_mapping.py +++ b/src/anndata/_core/aligned_mapping.py @@ -265,7 +265,7 @@ def to_df(self) -> pd.DataFrame: def _validate_value(self, val: Value, key: str) -> Value: if isinstance(val, pd.DataFrame): - raise_value_error_if_multiindex_columns(val) + raise_value_error_if_multiindex_columns(val, f"{self.attrname}.{key}") if not val.index.equals(self.dim_names): # Could probably also re-order index if it’s contained try: diff --git a/src/anndata/_core/anndata.py b/src/anndata/_core/anndata.py index 2bdd7bbdd..49d47665d 100644 --- a/src/anndata/_core/anndata.py +++ b/src/anndata/_core/anndata.py @@ -240,9 +240,9 @@ def __init__( vidx: Index1D | None = None, ): # check for any multi-indices that aren’t later checked in coerce_array - for attr in [obs, var, X]: + for attr, key in [(obs, "obs"), (var, "var"), (X, "X")]: if isinstance(attr, pd.DataFrame): - raise_value_error_if_multiindex_columns(attr) + raise_value_error_if_multiindex_columns(attr, key) if asview: if not isinstance(X, AnnData): raise ValueError("`X` has to be an AnnData object.") @@ -742,10 +742,10 @@ def n_vars(self) -> int: """Number of variables/features.""" return len(self.var_names) - def _set_dim_df(self, value: pd.DataFrame, attr: str): + def _set_dim_df(self, value: pd.DataFrame, attr: Literal["obs", "var"]): if not isinstance(value, pd.DataFrame): raise ValueError(f"Can only assign pd.DataFrame to {attr}.") - raise_value_error_if_multiindex_columns(value) + raise_value_error_if_multiindex_columns(value, attr) value_idx = self._prep_dim_index(value.index, attr) if self.is_view: self._init_as_actual(self.copy()) diff --git a/src/anndata/_core/storage.py b/src/anndata/_core/storage.py index eeccdd513..15630122f 100644 --- a/src/anndata/_core/storage.py +++ b/src/anndata/_core/storage.py @@ -89,7 +89,7 @@ def coerce_array( return value if isinstance(value, pd.DataFrame): if allow_df: - raise_value_error_if_multiindex_columns(value) + raise_value_error_if_multiindex_columns(value, name) return value if allow_df else ensure_df_homogeneous(value, name) # if value is an array-like object, try to convert it e = None diff --git a/src/anndata/utils.py b/src/anndata/utils.py index 9467b72c2..3ff844054 100644 --- a/src/anndata/utils.py +++ b/src/anndata/utils.py @@ -402,10 +402,10 @@ def is_hidden(attr) -> bool: ] -def raise_value_error_if_multiindex_columns(df: pd.DataFrame): +def raise_value_error_if_multiindex_columns(df: pd.DataFrame, attr: str): if isinstance(df.columns, pd.MultiIndex): msg = ( "MultiIndex columns are not supported in AnnData. " - "Please use a single-level index." + f"Please use a single-level index for {attr}." ) raise ValueError(msg) From d062a7e714668dc40bdbf163aacf9bc6752f6dc3 Mon Sep 17 00:00:00 2001 From: Ilan Gold Date: Mon, 26 Aug 2024 11:50:20 +0200 Subject: [PATCH 9/9] Update src/anndata/_core/aligned_mapping.py Co-authored-by: Philipp A. --- src/anndata/_core/aligned_mapping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/anndata/_core/aligned_mapping.py b/src/anndata/_core/aligned_mapping.py index 4bb8ca831..e2f6e4352 100644 --- a/src/anndata/_core/aligned_mapping.py +++ b/src/anndata/_core/aligned_mapping.py @@ -265,7 +265,7 @@ def to_df(self) -> pd.DataFrame: def _validate_value(self, val: Value, key: str) -> Value: if isinstance(val, pd.DataFrame): - raise_value_error_if_multiindex_columns(val, f"{self.attrname}.{key}") + raise_value_error_if_multiindex_columns(val, f"{self.attrname}[{key!r}]") if not val.index.equals(self.dim_names): # Could probably also re-order index if it’s contained try: