From 43d1151de73d41f481a8867ec0e7a3bbbfb7cbb1 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sun, 19 Nov 2023 10:16:31 -0600 Subject: [PATCH 01/12] [ENH]: Expand types allowed in Series.struct.field This expands the set of types allowed by Series.struct.field to allow those allowed by pyarrow. Closes https://github.com/pandas-dev/pandas/issues/56065 --- doc/source/whatsnew/v2.2.0.rst | 7 ++ pandas/core/arrays/arrow/accessors.py | 111 ++++++++++++++++-- .../series/accessors/test_struct_accessor.py | 40 ++++++- 3 files changed, 144 insertions(+), 14 deletions(-) diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index 8893fe0ecd398..a149fdbe74907 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -64,6 +64,13 @@ DataFrame. (:issue:`54938`) ) series.struct.explode() +Use :meth:`Series.struct.field` to index into a (possible nested) +struct field. + +.. ipython:: python + + series.struct.field("project") + .. _whatsnew_220.enhancements.list_accessor: Series.list accessor for PyArrow list data diff --git a/pandas/core/arrays/arrow/accessors.py b/pandas/core/arrays/arrow/accessors.py index 7f88267943526..634b07711c4ca 100644 --- a/pandas/core/arrays/arrow/accessors.py +++ b/pandas/core/arrays/arrow/accessors.py @@ -13,6 +13,8 @@ pa_version_under11p0, ) +from pandas.core.dtypes.common import is_list_like + if not pa_version_under10p1: import pyarrow as pa import pyarrow.compute as pc @@ -267,7 +269,16 @@ def dtypes(self) -> Series: names = [struct.name for struct in pa_type] return Series(types, index=Index(names)) - def field(self, name_or_index: str | int) -> Series: + def field( + self, + name_or_index: list[str] + | list[bytes] + | list[int] + | pc.Expression + | bytes + | str + | int, + ) -> Series: """ Extract a child field of a struct as a Series. @@ -281,6 +292,17 @@ def field(self, name_or_index: str | int) -> Series: pandas.Series The data corresponding to the selected child field. + Notes + ----- + The name of the resulting Series will be set using the following + rules: + + - For string, bytes, or integer `name_or_index` (or a list of these, for + a nested selection), the Series name is set to the selected + field's name. + - For a :class:`pyarrow.compute.Expression`, this is set to + the string form of the expression. + See Also -------- Series.struct.explode : Return all child fields as a DataFrame. @@ -314,27 +336,90 @@ def field(self, name_or_index: str | int) -> Series: 1 2 2 1 Name: version, dtype: int64[pyarrow] + + Or an expression + + >>> import pyarrow.compute as pc + >>> s.struct.field(pc.field("project")) + 0 pandas + 1 pandas + 2 numpy + Name: project, dtype: string[pyarrow] + + For nested struct types, you can pass a list of values to index + multiple levels: + + >>> version_type = pa.struct([ + ... ("major", pa.int64()), + ... ("minor", pa.int64()), + ... ]) + >>> s = pd.Series( + ... [ + ... {"version": {"major": 1, "minor": 5}, "project": "pandas"}, + ... {"version": {"major": 2, "minor": 1}, "project": "pandas"}, + ... {"version": {"major": 1, "minor": 26}, "project": "numpy"}, + ... ], + ... dtype=pd.ArrowDtype(pa.struct( + ... [("version", version_type), ("project", pa.string())] + ... )) + ... ) + >>> s.struct.field(["version", "minor"]) + 0 5 + 1 1 + 2 26 + Name: minor, dtype: int64[pyarrow] + >>> s.struct.field([0, 0]) + 0 1 + 1 2 + 2 1 + Name: major, dtype: int64[pyarrow] """ from pandas import Series + def get_name( + level_name_or_index: list[str] + | list[bytes] + | list[int] + | pc.Expression + | bytes + | str + | int, + data: pa.ChunkedArray, + ): + if isinstance(level_name_or_index, int): + index = data.type.field(level_name_or_index).name + elif isinstance(level_name_or_index, (str, bytes)): + index = level_name_or_index + elif isinstance(level_name_or_index, pc.Expression): + index = str(level_name_or_index) + elif is_list_like(level_name_or_index): + # For nested input like [2, 1, 2] + # iteratively get the struct and field name. The last + # one is used for the name of the index. + level_name_or_index = list(reversed(level_name_or_index)) + selected = data + while level_name_or_index: + name_or_index = level_name_or_index.pop() + name = get_name(name_or_index, selected) + selected = selected.type.field(selected.type.get_field_index(name)) + index = selected.name + return index + else: + raise ValueError( + "name_or_index must be an int, str, bytes, " + "pyarrow.compute.Expression, or list of those" + ) + return index + pa_arr = self._data.array._pa_array - if isinstance(name_or_index, int): - index = name_or_index - elif isinstance(name_or_index, str): - index = pa_arr.type.get_field_index(name_or_index) - else: - raise ValueError( - "name_or_index must be an int or str, " - f"got {type(name_or_index).__name__}" - ) + name = get_name(name_or_index, pa_arr) + field_arr = pc.struct_field(pa_arr, name_or_index) - pa_field = pa_arr.type[index] - field_arr = pc.struct_field(pa_arr, [index]) return Series( field_arr, dtype=ArrowDtype(field_arr.type), index=self._data.index, - name=pa_field.name, + name=name, ) def explode(self) -> DataFrame: diff --git a/pandas/tests/series/accessors/test_struct_accessor.py b/pandas/tests/series/accessors/test_struct_accessor.py index 1ec5b3b726d17..e1acce86f0410 100644 --- a/pandas/tests/series/accessors/test_struct_accessor.py +++ b/pandas/tests/series/accessors/test_struct_accessor.py @@ -1,5 +1,6 @@ import re +import pyarrow.compute as pc import pytest from pandas import ( @@ -94,7 +95,7 @@ def test_struct_accessor_field(): def test_struct_accessor_field_with_invalid_name_or_index(): ser = Series([], dtype=ArrowDtype(pa.struct([("field", pa.int64())]))) - with pytest.raises(ValueError, match="name_or_index must be an int or str"): + with pytest.raises(ValueError, match="name_or_index must be an int, str,"): ser.struct.field(1.1) @@ -148,3 +149,40 @@ def test_struct_accessor_api_for_invalid(invalid): ), ): invalid.struct + + +@pytest.mark.parametrize( + ["indices", "name"], + [ + (0, "int_col"), + ([1, 2], "str_col"), + (pc.field("int_col"), "int_col"), + ("int_col", "int_col"), + (b"string_col", b"string_col"), + ([b"string_col"], "string_col"), + ], +) +def test_struct_accessor_field_expanded(indices, name): + arrow_type = pa.struct( + [ + ("int_col", pa.int64()), + ( + "struct_col", + pa.struct( + [ + ("int_col", pa.int64()), + ("float_col", pa.float64()), + ("str_col", pa.string()), + ] + ), + ), + (b"string_col", pa.string()), + ] + ) + + data = pa.array([], type=arrow_type) + ser = Series(data, dtype=ArrowDtype(arrow_type)) + expected = pc.struct_field(data, indices) + result = ser.struct.field(indices) + tm.assert_equal(result.array._pa_array.combine_chunks(), expected) + assert result.name == name From 0cfd8af74b99891cf5efa8e42b54ebe180362d73 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sun, 26 Nov 2023 14:31:38 -0600 Subject: [PATCH 02/12] fixed docstring order --- pandas/core/arrays/arrow/accessors.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/core/arrays/arrow/accessors.py b/pandas/core/arrays/arrow/accessors.py index 634b07711c4ca..42ca412a9d318 100644 --- a/pandas/core/arrays/arrow/accessors.py +++ b/pandas/core/arrays/arrow/accessors.py @@ -292,6 +292,10 @@ def field( pandas.Series The data corresponding to the selected child field. + See Also + -------- + Series.struct.explode : Return all child fields as a DataFrame. + Notes ----- The name of the resulting Series will be set using the following @@ -303,10 +307,6 @@ def field( - For a :class:`pyarrow.compute.Expression`, this is set to the string form of the expression. - See Also - -------- - Series.struct.explode : Return all child fields as a DataFrame. - Examples -------- >>> import pyarrow as pa From 57f4722c77ddaa36cf5b0074cdca284f42cebf50 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sun, 26 Nov 2023 14:36:40 -0600 Subject: [PATCH 03/12] Skip on older pyarrow --- pandas/tests/series/accessors/test_struct_accessor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pandas/tests/series/accessors/test_struct_accessor.py b/pandas/tests/series/accessors/test_struct_accessor.py index e1acce86f0410..f97efa3f7be33 100644 --- a/pandas/tests/series/accessors/test_struct_accessor.py +++ b/pandas/tests/series/accessors/test_struct_accessor.py @@ -3,6 +3,8 @@ import pyarrow.compute as pc import pytest +from pandas.compat.pyarrow import pa_version_under11p0 + from pandas import ( ArrowDtype, DataFrame, @@ -162,6 +164,7 @@ def test_struct_accessor_api_for_invalid(invalid): ([b"string_col"], "string_col"), ], ) +@pytest.mark.skipif(not pa_version_under11p0, reason="pyarrow>=11.0.0 required") def test_struct_accessor_field_expanded(indices, name): arrow_type = pa.struct( [ From 591dfb6e07eee18393673062fba0e8b60aa58047 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sun, 26 Nov 2023 15:01:45 -0600 Subject: [PATCH 04/12] skip if no pyarrow.compute --- pandas/tests/series/accessors/test_struct_accessor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/series/accessors/test_struct_accessor.py b/pandas/tests/series/accessors/test_struct_accessor.py index f97efa3f7be33..3d5f9bc001d5d 100644 --- a/pandas/tests/series/accessors/test_struct_accessor.py +++ b/pandas/tests/series/accessors/test_struct_accessor.py @@ -1,6 +1,5 @@ import re -import pyarrow.compute as pc import pytest from pandas.compat.pyarrow import pa_version_under11p0 @@ -14,6 +13,7 @@ import pandas._testing as tm pa = pytest.importorskip("pyarrow") +pc = pytest.importorskip("pyarrow.compute") def test_struct_accessor_dtypes(): From ec103c735e92126b237f3f12dba671c6f4aa7932 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sun, 26 Nov 2023 15:22:44 -0600 Subject: [PATCH 05/12] flip skip --- pandas/core/arrays/arrow/accessors.py | 13 ++++++------- .../tests/series/accessors/test_struct_accessor.py | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/pandas/core/arrays/arrow/accessors.py b/pandas/core/arrays/arrow/accessors.py index 42ca412a9d318..41e527b743627 100644 --- a/pandas/core/arrays/arrow/accessors.py +++ b/pandas/core/arrays/arrow/accessors.py @@ -387,11 +387,11 @@ def get_name( data: pa.ChunkedArray, ): if isinstance(level_name_or_index, int): - index = data.type.field(level_name_or_index).name + name = data.type.field(level_name_or_index).name elif isinstance(level_name_or_index, (str, bytes)): - index = level_name_or_index + name = level_name_or_index elif isinstance(level_name_or_index, pc.Expression): - index = str(level_name_or_index) + name = str(level_name_or_index) elif is_list_like(level_name_or_index): # For nested input like [2, 1, 2] # iteratively get the struct and field name. The last @@ -402,18 +402,17 @@ def get_name( name_or_index = level_name_or_index.pop() name = get_name(name_or_index, selected) selected = selected.type.field(selected.type.get_field_index(name)) - index = selected.name - return index + name = selected.name else: raise ValueError( "name_or_index must be an int, str, bytes, " "pyarrow.compute.Expression, or list of those" ) - return index + return name pa_arr = self._data.array._pa_array - name = get_name(name_or_index, pa_arr) field_arr = pc.struct_field(pa_arr, name_or_index) + name = get_name(name_or_index, pa_arr) return Series( field_arr, diff --git a/pandas/tests/series/accessors/test_struct_accessor.py b/pandas/tests/series/accessors/test_struct_accessor.py index 3d5f9bc001d5d..a66a4d301068e 100644 --- a/pandas/tests/series/accessors/test_struct_accessor.py +++ b/pandas/tests/series/accessors/test_struct_accessor.py @@ -164,7 +164,7 @@ def test_struct_accessor_api_for_invalid(invalid): ([b"string_col"], "string_col"), ], ) -@pytest.mark.skipif(not pa_version_under11p0, reason="pyarrow>=11.0.0 required") +@pytest.mark.skipif(pa_version_under11p0, reason="pyarrow>=11.0.0 required") def test_struct_accessor_field_expanded(indices, name): arrow_type = pa.struct( [ From 12793788f34da5ea7741640ced8250a4419d5890 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 22 Dec 2023 07:22:57 -0600 Subject: [PATCH 06/12] fixups --- pandas/core/arrays/arrow/accessors.py | 7 ++++++- pandas/tests/series/accessors/test_struct_accessor.py | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pandas/core/arrays/arrow/accessors.py b/pandas/core/arrays/arrow/accessors.py index 41e527b743627..d21ed5c55017f 100644 --- a/pandas/core/arrays/arrow/accessors.py +++ b/pandas/core/arrays/arrow/accessors.py @@ -284,9 +284,12 @@ def field( Parameters ---------- - name_or_index : str | int + name_or_index : str | bytes | int | expression | list Name or index of the child field to extract. + For list-like inputs, this will index into a nested + struct. + Returns ------- pandas.Series @@ -306,6 +309,8 @@ def field( field's name. - For a :class:`pyarrow.compute.Expression`, this is set to the string form of the expression. + - For list-like `name_or_index`, the name will be set to the + name of the final field selected. Examples -------- diff --git a/pandas/tests/series/accessors/test_struct_accessor.py b/pandas/tests/series/accessors/test_struct_accessor.py index a66a4d301068e..52fbc97d06905 100644 --- a/pandas/tests/series/accessors/test_struct_accessor.py +++ b/pandas/tests/series/accessors/test_struct_accessor.py @@ -2,7 +2,7 @@ import pytest -from pandas.compat.pyarrow import pa_version_under11p0 +from pandas.compat.pyarrow import pa_version_under15p0 from pandas import ( ArrowDtype, @@ -164,7 +164,7 @@ def test_struct_accessor_api_for_invalid(invalid): ([b"string_col"], "string_col"), ], ) -@pytest.mark.skipif(pa_version_under11p0, reason="pyarrow>=11.0.0 required") +@pytest.mark.skipif(pa_version_under15p0, reason="pyarrow>=11.0.0 required") def test_struct_accessor_field_expanded(indices, name): arrow_type = pa.struct( [ From 1f8c779052a77ebf1ca24274fee99bcf0a918857 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 22 Dec 2023 08:18:25 -0600 Subject: [PATCH 07/12] fixed versions --- pandas/core/arrays/arrow/accessors.py | 2 +- pandas/tests/series/accessors/test_struct_accessor.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/core/arrays/arrow/accessors.py b/pandas/core/arrays/arrow/accessors.py index d21ed5c55017f..d83ea08f151da 100644 --- a/pandas/core/arrays/arrow/accessors.py +++ b/pandas/core/arrays/arrow/accessors.py @@ -416,8 +416,8 @@ def get_name( return name pa_arr = self._data.array._pa_array - field_arr = pc.struct_field(pa_arr, name_or_index) name = get_name(name_or_index, pa_arr) + field_arr = pc.struct_field(pa_arr, name_or_index) return Series( field_arr, diff --git a/pandas/tests/series/accessors/test_struct_accessor.py b/pandas/tests/series/accessors/test_struct_accessor.py index 52fbc97d06905..6b0c9ddd3f325 100644 --- a/pandas/tests/series/accessors/test_struct_accessor.py +++ b/pandas/tests/series/accessors/test_struct_accessor.py @@ -2,7 +2,7 @@ import pytest -from pandas.compat.pyarrow import pa_version_under15p0 +from pandas.compat.pyarrow import pa_version_under13p0 from pandas import ( ArrowDtype, @@ -164,7 +164,7 @@ def test_struct_accessor_api_for_invalid(invalid): ([b"string_col"], "string_col"), ], ) -@pytest.mark.skipif(pa_version_under15p0, reason="pyarrow>=11.0.0 required") +@pytest.mark.skipif(pa_version_under13p0, reason="pyarrow>=13.0.0 required") def test_struct_accessor_field_expanded(indices, name): arrow_type = pa.struct( [ From d9435aff51b5419be97186d152790d06df0db324 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 22 Dec 2023 08:20:45 -0600 Subject: [PATCH 08/12] doc --- doc/source/whatsnew/v2.2.0.rst | 82 ---------------------------------- 1 file changed, 82 deletions(-) diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index 172a81ca001f9..d1481639ca5a0 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -97,88 +97,6 @@ This future dtype inference logic can be enabled with: Enhancements ~~~~~~~~~~~~ -.. _whatsnew_220.enhancements.calamine: - -Calamine engine for :func:`read_excel` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``calamine`` engine was added to :func:`read_excel`. -It uses ``python-calamine``, which provides Python bindings for the Rust library `calamine `__. -This engine supports Excel files (``.xlsx``, ``.xlsm``, ``.xls``, ``.xlsb``) and OpenDocument spreadsheets (``.ods``) (:issue:`50395`). - -There are two advantages of this engine: - -1. Calamine is often faster than other engines, some benchmarks show results up to 5x faster than 'openpyxl', 20x - 'odf', 4x - 'pyxlsb', and 1.5x - 'xlrd'. - But, 'openpyxl' and 'pyxlsb' are faster in reading a few rows from large files because of lazy iteration over rows. -2. Calamine supports the recognition of datetime in ``.xlsb`` files, unlike 'pyxlsb' which is the only other engine in pandas that can read ``.xlsb`` files. - -.. code-block:: python - - pd.read_excel("path_to_file.xlsb", engine="calamine") - - -For more, see :ref:`io.calamine` in the user guide on IO tools. - -.. _whatsnew_220.enhancements.struct_accessor: - -Series.struct accessor to with PyArrow structured data -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``Series.struct`` accessor provides attributes and methods for processing -data with ``struct[pyarrow]`` dtype Series. For example, -:meth:`Series.struct.explode` converts PyArrow structured data to a pandas -DataFrame. (:issue:`54938`) - -.. ipython:: python - - import pyarrow as pa - series = pd.Series( - [ - {"project": "pandas", "version": "2.2.0"}, - {"project": "numpy", "version": "1.25.2"}, - {"project": "pyarrow", "version": "13.0.0"}, - ], - dtype=pd.ArrowDtype( - pa.struct([ - ("project", pa.string()), - ("version", pa.string()), - ]) - ), - ) - series.struct.explode() - -Use :meth:`Series.struct.field` to index into a (possible nested) -struct field. - -.. ipython:: python - - series.struct.field("project") - -.. _whatsnew_220.enhancements.list_accessor: - -Series.list accessor for PyArrow list data -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``Series.list`` accessor provides attributes and methods for processing -data with ``list[pyarrow]`` dtype Series. For example, -:meth:`Series.list.__getitem__` allows indexing pyarrow lists in -a Series. (:issue:`55323`) - -.. ipython:: python - - import pyarrow as pa - series = pd.Series( - [ - [1, 2, 3], - [4, 5], - [6], - ], - dtype=pd.ArrowDtype( - pa.list_(pa.int64()) - ), - ) - series.list[0] - .. _whatsnew_220.enhancements.adbc_support: ADBC Driver support in to_sql and read_sql From bbc49685bf5b7dda3d213091e3adb96fb20bfe47 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 22 Dec 2023 08:22:36 -0600 Subject: [PATCH 09/12] fixup --- doc/source/whatsnew/v2.2.0.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index d1481639ca5a0..39361c3505e61 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -251,6 +251,14 @@ DataFrame. (:issue:`54938`) ) series.struct.explode() +Use :meth:`Series.struct.field` to index into a (possible nested) +struct field. + + +.. ipython:: python + + series.struct.field("project") + .. _whatsnew_220.enhancements.list_accessor: Series.list accessor for PyArrow list data From bbe76818fb5bf86972714be5c9f405383639fc2e Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 22 Dec 2023 09:14:48 -0600 Subject: [PATCH 10/12] fixup --- pandas/core/arrays/arrow/accessors.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pandas/core/arrays/arrow/accessors.py b/pandas/core/arrays/arrow/accessors.py index d83ea08f151da..124f8fb6ad8bc 100644 --- a/pandas/core/arrays/arrow/accessors.py +++ b/pandas/core/arrays/arrow/accessors.py @@ -6,7 +6,10 @@ ABCMeta, abstractmethod, ) -from typing import TYPE_CHECKING +from typing import ( + TYPE_CHECKING, + cast, +) from pandas.compat import ( pa_version_under10p1, @@ -404,6 +407,9 @@ def get_name( level_name_or_index = list(reversed(level_name_or_index)) selected = data while level_name_or_index: + # we need the cast, otherwise mypy complains about + # getting ints, bytes, or str here, which isn't possible. + level_name_or_index = cast(list, level_name_or_index) name_or_index = level_name_or_index.pop() name = get_name(name_or_index, selected) selected = selected.type.field(selected.type.get_field_index(name)) From 9cdd0c7c15558cecfdaa882123f6ad9a90c0bc97 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 22 Dec 2023 11:12:05 -0600 Subject: [PATCH 11/12] fixup --- pandas/tests/series/accessors/test_struct_accessor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/tests/series/accessors/test_struct_accessor.py b/pandas/tests/series/accessors/test_struct_accessor.py index 6b0c9ddd3f325..c1ef1b14ec3d0 100644 --- a/pandas/tests/series/accessors/test_struct_accessor.py +++ b/pandas/tests/series/accessors/test_struct_accessor.py @@ -56,6 +56,7 @@ def test_struct_accessor_dtypes(): tm.assert_series_equal(actual, expected) +@pytest.mark.skipif(pa_version_under13p0, reason="pyarrow>=13.0.0 required") def test_struct_accessor_field(): index = Index([-100, 42, 123]) ser = Series( From cce3fcc14e9f8aff2df2a5d77e69f35bcc48f554 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 22 Dec 2023 12:49:09 -0600 Subject: [PATCH 12/12] fixup --- pandas/tests/series/accessors/test_struct_accessor.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pandas/tests/series/accessors/test_struct_accessor.py b/pandas/tests/series/accessors/test_struct_accessor.py index c1ef1b14ec3d0..80aea75fda406 100644 --- a/pandas/tests/series/accessors/test_struct_accessor.py +++ b/pandas/tests/series/accessors/test_struct_accessor.py @@ -2,7 +2,10 @@ import pytest -from pandas.compat.pyarrow import pa_version_under13p0 +from pandas.compat.pyarrow import ( + pa_version_under11p0, + pa_version_under13p0, +) from pandas import ( ArrowDtype, @@ -102,6 +105,7 @@ def test_struct_accessor_field_with_invalid_name_or_index(): ser.struct.field(1.1) +@pytest.mark.skipif(pa_version_under11p0, reason="pyarrow>=11.0.0 required") def test_struct_accessor_explode(): index = Index([-100, 42, 123]) ser = Series(