From d199094aeb85a2be00c307525f2a389d5503a121 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Thu, 14 Jul 2022 20:07:48 -0700 Subject: [PATCH 1/9] ENH/TST: Add Reduction tests for ArrowExtensionArray --- pandas/core/arrays/arrow/array.py | 48 +++++++++++++++ pandas/tests/extension/test_arrow.py | 88 ++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) diff --git a/pandas/core/arrays/arrow/array.py b/pandas/core/arrays/arrow/array.py index 92aedbb836b38..5458ebf44de9f 100644 --- a/pandas/core/arrays/arrow/array.py +++ b/pandas/core/arrays/arrow/array.py @@ -534,6 +534,54 @@ def _concat_same_type( arr = pa.chunked_array(chunks) return cls(arr) + def _reduce(self, name: str, *, skipna: bool = True, **kwargs): + """ + Return a scalar result of performing the reduction operation. + + Parameters + ---------- + name : str + Name of the function, supported values are: + { any, all, min, max, sum, mean, median, prod, + std, var, sem, kurt, skew }. + skipna : bool, default True + If True, skip NaN values. + **kwargs + Additional keyword arguments passed to the reduction function. + Currently, `ddof` is the only supported kwarg. + + Returns + ------- + scalar + + Raises + ------ + TypeError : subclass does not define reductions + """ + if name == "sem": + numerator = pc.stddev(self._data, skip_nulls=skipna, **kwargs) + denominator = pc.sqrt_checked( + pc.subtract_checked( + pc.count(self._data, skip_nulls=skipna), kwargs["ddof"] + ) + ) + result = pc.divide_checked(numerator, denominator) + else: + pyarrow_reduction_map = { + "median": "approximate_median", + "prod": "product", + "std": "stddev", + "var": "variance", + } + pyarrow_meth = getattr(pc, pyarrow_reduction_map.get(name, name), None) + if pyarrow_meth is None: + # Let ExtensionArray._reduce raise the TypeError + return super()._reduce(name, skipna=skipna, **kwargs) + result = pyarrow_meth(self._data, skip_nulls=skipna, **kwargs) + if pc.is_null(result, nan_is_null=True).as_py(): + return self.dtype.na_value + return result.as_py() + def __setitem__(self, key: int | slice | np.ndarray, value: Any) -> None: """Set one or more values inplace. diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index 7e0792a6010a7..74e335ca565bb 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -292,6 +292,94 @@ def test_loc_iloc_frame_single_dtype(self, request, using_array_manager, data): super().test_loc_iloc_frame_single_dtype(data) +class TestBaseNumericReduce(base.BaseNumericReduceTests): + def check_reduce(self, ser, op_name, skipna): + pa_dtype = ser.dtype.pyarrow_dtype + result = getattr(ser, op_name)(skipna=skipna) + if pa.types.is_boolean(pa_dtype): + # Can't convert if ser contains NA + ser = ser.fillna(False).astype("Float64") + elif pa.types.is_integer(pa_dtype) or pa.types.is_floating(pa_dtype): + ser = ser.astype("Float64") + if pa.types.is_boolean(pa_dtype): + # TODO: Validate reduction functions on pandas boolean w/skipna + if op_name in {"sum", "max", "min", "mean", "prod"} and skipna is False: + assert result is pd.NA + elif op_name == "mean" and skipna is True: + assert result == 0.5 + else: + expected = getattr(ser, op_name)(skipna=skipna) + tm.assert_almost_equal(result, expected) + + @pytest.mark.parametrize("skipna", [True, False]) + def test_reduce_series(self, data, all_numeric_reductions, skipna, request): + pa_dtype = data.dtype.pyarrow_dtype + if all_numeric_reductions in {"skew", "kurt"}: + request.node.add_marker( + pytest.mark.xfail( + raises=TypeError, + reason=( + f"{all_numeric_reductions} is not implemented in pyarrow " + f"for {pa_dtype}" + ), + ) + ) + elif not ( + pa.types.is_integer(pa_dtype) + or pa.types.is_floating(pa_dtype) + or pa.types.is_boolean(pa_dtype) + ) and not ( + all_numeric_reductions in {"min", "max"} + and (pa.types.is_temporal(pa_dtype) and not pa.types.is_duration(pa_dtype)) + ): + request.node.add_marker( + pytest.mark.xfail( + raises=pa.ArrowNotImplementedError, + reason=( + f"{all_numeric_reductions} is not implemented in pyarrow " + f"for {pa_dtype}" + ), + ) + ) + elif pa.types.is_boolean(pa_dtype) and all_numeric_reductions in { + "std", + "var", + "median", + }: + request.node.add_marker( + pytest.mark.xfail( + raises=pa.ArrowNotImplementedError, + reason=( + f"{all_numeric_reductions} is not implemented in pyarrow " + f"for {pa_dtype}" + ), + ) + ) + super().test_reduce_series(data, all_numeric_reductions, skipna) + + +class TestBaseBooleanReduce(base.BaseBooleanReduceTests): + @pytest.mark.parametrize("skipna", [True, False]) + def test_reduce_series( + self, data, all_boolean_reductions, skipna, na_value, request + ): + pa_dtype = data.dtype.pyarrow_dtype + if not pa.types.is_boolean(pa_dtype): + request.node.add_marker( + pytest.mark.xfail( + raises=pa.ArrowNotImplementedError, + reason=( + f"{all_boolean_reductions} is not implemented in pyarrow " + f"for {pa_dtype}" + ), + ) + ) + op_name = all_boolean_reductions + s = pd.Series(data) + result = getattr(s, op_name)(skipna=skipna) + assert result is (op_name == "any") + + class TestBaseGroupby(base.BaseGroupbyTests): def test_groupby_agg_extension(self, data_for_grouping, request): tz = getattr(data_for_grouping.dtype.pyarrow_dtype, "tz", None) From e3d74abfe83ecc8e862925adeaa25283ea401fe4 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Fri, 15 Jul 2022 11:07:37 -0700 Subject: [PATCH 2/9] remove nan_is_null --- pandas/core/arrays/arrow/array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/arrays/arrow/array.py b/pandas/core/arrays/arrow/array.py index 5458ebf44de9f..41a365b02f28b 100644 --- a/pandas/core/arrays/arrow/array.py +++ b/pandas/core/arrays/arrow/array.py @@ -578,7 +578,7 @@ def _reduce(self, name: str, *, skipna: bool = True, **kwargs): # Let ExtensionArray._reduce raise the TypeError return super()._reduce(name, skipna=skipna, **kwargs) result = pyarrow_meth(self._data, skip_nulls=skipna, **kwargs) - if pc.is_null(result, nan_is_null=True).as_py(): + if pc.is_null(result).as_py(): return self.dtype.na_value return result.as_py() From 0139529435e684f7b791884d7831e41a5998ed24 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Fri, 15 Jul 2022 15:13:42 -0700 Subject: [PATCH 3/9] Address some of the string reduction impacts --- pandas/tests/arrays/string_/test_string.py | 9 ++++--- pandas/tests/extension/test_string.py | 28 +++++++++++++++++++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/pandas/tests/arrays/string_/test_string.py b/pandas/tests/arrays/string_/test_string.py index b563f84207b22..cc73f99644a0c 100644 --- a/pandas/tests/arrays/string_/test_string.py +++ b/pandas/tests/arrays/string_/test_string.py @@ -5,7 +5,10 @@ import numpy as np import pytest -from pandas.compat import pa_version_under2p0 +from pandas.compat import ( + pa_version_under2p0, + pa_version_under6p0, +) from pandas.errors import PerformanceWarning import pandas.util._test_decorators as td @@ -375,7 +378,7 @@ def test_reduce_missing(skipna, dtype): @pytest.mark.parametrize("method", ["min", "max"]) @pytest.mark.parametrize("skipna", [True, False]) def test_min_max(method, skipna, dtype, request): - if dtype.storage == "pyarrow": + if dtype.storage == "pyarrow" and pa_version_under6p0: reason = "'ArrowStringArray' object has no attribute 'max'" mark = pytest.mark.xfail(raises=TypeError, reason=reason) request.node.add_marker(mark) @@ -392,7 +395,7 @@ def test_min_max(method, skipna, dtype, request): @pytest.mark.parametrize("method", ["min", "max"]) @pytest.mark.parametrize("box", [pd.Series, pd.array]) def test_min_max_numpy(method, box, dtype, request): - if dtype.storage == "pyarrow": + if dtype.storage == "pyarrow" and (pa_version_under6p0 or box is pd.array): if box is pd.array: reason = "'<=' not supported between instances of 'str' and 'NoneType'" else: diff --git a/pandas/tests/extension/test_string.py b/pandas/tests/extension/test_string.py index 6cea21b6672d8..91e06694e4494 100644 --- a/pandas/tests/extension/test_string.py +++ b/pandas/tests/extension/test_string.py @@ -154,6 +154,24 @@ def test_dropna_array(self, data_missing): class TestNoReduce(base.BaseNoReduceTests): + @pytest.mark.parametrize("skipna", [True, False]) + def test_reduce_series_boolean(self, data, all_boolean_reductions, skipna): + op_name = all_boolean_reductions + s = pd.Series(data) + + if s.dtype.storage == "pyarrow": + msg = "Function" + err = NotImplementedError + else: + err = TypeError + msg = ( + "[Cc]annot perform|Categorical is not ordered for operation|" + "does not support reduction|" + ) + + with pytest.raises(err, match=msg): + getattr(s, op_name)(skipna=skipna) + @pytest.mark.parametrize("skipna", [True, False]) def test_reduce_series_numeric(self, data, all_numeric_reductions, skipna): op_name = all_numeric_reductions @@ -161,8 +179,16 @@ def test_reduce_series_numeric(self, data, all_numeric_reductions, skipna): if op_name in ["min", "max"]: return None + if data.dtype.storage == "pyarrow" and all_numeric_reductions not in { + "skew", + "kurt", + }: + err = NotImplementedError + else: + err = TypeError + ser = pd.Series(data) - with pytest.raises(TypeError): + with pytest.raises(err): getattr(ser, op_name)(skipna=skipna) From 4a787e008d3a19343bbc92138d1858fd7a734b80 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Mon, 18 Jul 2022 16:08:35 -0700 Subject: [PATCH 4/9] Punt on comparison with pandas boolean with NA --- pandas/tests/extension/test_arrow.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index 852dafbacf296..f5451078151f0 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -309,18 +309,13 @@ def check_reduce(self, ser, op_name, skipna): result = getattr(ser, op_name)(skipna=skipna) if pa.types.is_boolean(pa_dtype): # Can't convert if ser contains NA - ser = ser.fillna(False).astype("Float64") + pytest.skip( + "pandas boolean data with NA does not fully support all reductions" + ) elif pa.types.is_integer(pa_dtype) or pa.types.is_floating(pa_dtype): ser = ser.astype("Float64") - if pa.types.is_boolean(pa_dtype): - # TODO: Validate reduction functions on pandas boolean w/skipna - if op_name in {"sum", "max", "min", "mean", "prod"} and skipna is False: - assert result is pd.NA - elif op_name == "mean" and skipna is True: - assert result == 0.5 - else: - expected = getattr(ser, op_name)(skipna=skipna) - tm.assert_almost_equal(result, expected) + expected = getattr(ser, op_name)(skipna=skipna) + tm.assert_almost_equal(result, expected) @pytest.mark.parametrize("skipna", [True, False]) def test_reduce_series(self, data, all_numeric_reductions, skipna, request): From 93d3a4a307550b7fc14d07e76881a3f2ce3e1c68 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Mon, 18 Jul 2022 19:27:28 -0700 Subject: [PATCH 5/9] Ensure _reduce always raises TypeError --- pandas/core/arrays/arrow/array.py | 24 +++++++++---- pandas/tests/extension/test_arrow.py | 49 +++++++++++---------------- pandas/tests/extension/test_string.py | 28 +-------------- 3 files changed, 38 insertions(+), 63 deletions(-) diff --git a/pandas/core/arrays/arrow/array.py b/pandas/core/arrays/arrow/array.py index cdc227efdcbeb..3ce174e23e7b9 100644 --- a/pandas/core/arrays/arrow/array.py +++ b/pandas/core/arrays/arrow/array.py @@ -653,13 +653,16 @@ def _reduce(self, name: str, *, skipna: bool = True, **kwargs): TypeError : subclass does not define reductions """ if name == "sem": - numerator = pc.stddev(self._data, skip_nulls=skipna, **kwargs) - denominator = pc.sqrt_checked( - pc.subtract_checked( - pc.count(self._data, skip_nulls=skipna), kwargs["ddof"] + + def pyarrow_meth(data, skipna, **kwargs): + numerator = pc.stddev(data, skip_nulls=skipna, **kwargs) + denominator = pc.sqrt_checked( + pc.subtract_checked( + pc.count(self._data, skip_nulls=skipna), kwargs["ddof"] + ) ) - ) - result = pc.divide_checked(numerator, denominator) + return pc.divide_checked(numerator, denominator) + else: pyarrow_reduction_map = { "median": "approximate_median", @@ -671,7 +674,16 @@ def _reduce(self, name: str, *, skipna: bool = True, **kwargs): if pyarrow_meth is None: # Let ExtensionArray._reduce raise the TypeError return super()._reduce(name, skipna=skipna, **kwargs) + try: result = pyarrow_meth(self._data, skip_nulls=skipna, **kwargs) + except (AttributeError, NotImplementedError, TypeError) as err: + msg = ( + f"'{type(self).__name__}' with dtype {self.dtype} " + f"does not support reduction '{name}' with pyarrow " + f"version {pa.__version__}. '{name}' may be supported by " + f"upgrading pyarrow." + ) + raise TypeError(msg) from err if pc.is_null(result).as_py(): return self.dtype.na_value return result.as_py() diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index f5451078151f0..66dfeac15a15f 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -24,6 +24,7 @@ from pandas.compat import ( pa_version_under2p0, pa_version_under3p0, + pa_version_under6p0, pa_version_under8p0, ) @@ -320,16 +321,20 @@ def check_reduce(self, ser, op_name, skipna): @pytest.mark.parametrize("skipna", [True, False]) def test_reduce_series(self, data, all_numeric_reductions, skipna, request): pa_dtype = data.dtype.pyarrow_dtype + xfail_mark = pytest.mark.xfail( + raises=TypeError, + reason=( + f"{all_numeric_reductions} is not implemented in " + f"pyarrow={pa.__version__} for {pa_dtype}" + ), + ) if all_numeric_reductions in {"skew", "kurt"}: - request.node.add_marker( - pytest.mark.xfail( - raises=TypeError, - reason=( - f"{all_numeric_reductions} is not implemented in pyarrow " - f"for {pa_dtype}" - ), - ) - ) + request.node.add_marker(xfail_mark) + elif ( + all_numeric_reductions in {"median", "var", "std", "prod"} + and pa_version_under6p0 + ): + request.node.add_marker(xfail_mark) elif not ( pa.types.is_integer(pa_dtype) or pa.types.is_floating(pa_dtype) @@ -338,29 +343,13 @@ def test_reduce_series(self, data, all_numeric_reductions, skipna, request): all_numeric_reductions in {"min", "max"} and (pa.types.is_temporal(pa_dtype) and not pa.types.is_duration(pa_dtype)) ): - request.node.add_marker( - pytest.mark.xfail( - raises=pa.ArrowNotImplementedError, - reason=( - f"{all_numeric_reductions} is not implemented in pyarrow " - f"for {pa_dtype}" - ), - ) - ) + request.node.add_marker(xfail_mark) elif pa.types.is_boolean(pa_dtype) and all_numeric_reductions in { "std", "var", "median", }: - request.node.add_marker( - pytest.mark.xfail( - raises=pa.ArrowNotImplementedError, - reason=( - f"{all_numeric_reductions} is not implemented in pyarrow " - f"for {pa_dtype}" - ), - ) - ) + request.node.add_marker(xfail_mark) super().test_reduce_series(data, all_numeric_reductions, skipna) @@ -373,10 +362,10 @@ def test_reduce_series( if not pa.types.is_boolean(pa_dtype): request.node.add_marker( pytest.mark.xfail( - raises=pa.ArrowNotImplementedError, + raises=TypeError, reason=( - f"{all_boolean_reductions} is not implemented in pyarrow " - f"for {pa_dtype}" + f"{all_boolean_reductions} is not implemented in " + f"pyarrow={pa.__version__} for {pa_dtype}" ), ) ) diff --git a/pandas/tests/extension/test_string.py b/pandas/tests/extension/test_string.py index 91e06694e4494..6cea21b6672d8 100644 --- a/pandas/tests/extension/test_string.py +++ b/pandas/tests/extension/test_string.py @@ -154,24 +154,6 @@ def test_dropna_array(self, data_missing): class TestNoReduce(base.BaseNoReduceTests): - @pytest.mark.parametrize("skipna", [True, False]) - def test_reduce_series_boolean(self, data, all_boolean_reductions, skipna): - op_name = all_boolean_reductions - s = pd.Series(data) - - if s.dtype.storage == "pyarrow": - msg = "Function" - err = NotImplementedError - else: - err = TypeError - msg = ( - "[Cc]annot perform|Categorical is not ordered for operation|" - "does not support reduction|" - ) - - with pytest.raises(err, match=msg): - getattr(s, op_name)(skipna=skipna) - @pytest.mark.parametrize("skipna", [True, False]) def test_reduce_series_numeric(self, data, all_numeric_reductions, skipna): op_name = all_numeric_reductions @@ -179,16 +161,8 @@ def test_reduce_series_numeric(self, data, all_numeric_reductions, skipna): if op_name in ["min", "max"]: return None - if data.dtype.storage == "pyarrow" and all_numeric_reductions not in { - "skew", - "kurt", - }: - err = NotImplementedError - else: - err = TypeError - ser = pd.Series(data) - with pytest.raises(err): + with pytest.raises(TypeError): getattr(ser, op_name)(skipna=skipna) From 2ded4639e2b140e31bf80de9b7c89ddde67ac9f0 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Mon, 18 Jul 2022 20:20:41 -0700 Subject: [PATCH 6/9] Add type ignore --- pandas/core/arrays/arrow/array.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pandas/core/arrays/arrow/array.py b/pandas/core/arrays/arrow/array.py index 3ce174e23e7b9..ab0e262caa6a9 100644 --- a/pandas/core/arrays/arrow/array.py +++ b/pandas/core/arrays/arrow/array.py @@ -664,13 +664,16 @@ def pyarrow_meth(data, skipna, **kwargs): return pc.divide_checked(numerator, denominator) else: - pyarrow_reduction_map = { + pyarrow_name = { "median": "approximate_median", "prod": "product", "std": "stddev", "var": "variance", - } - pyarrow_meth = getattr(pc, pyarrow_reduction_map.get(name, name), None) + }.get(name, name) + # error: Incompatible types in assignment + # (expression has type "Optional[Any]", variable has type + # "Callable[[Any, Any, KwArg(Any)], Any]") + pyarrow_meth = getattr(pc, pyarrow_name, None) # type: ignore[assignment] if pyarrow_meth is None: # Let ExtensionArray._reduce raise the TypeError return super()._reduce(name, skipna=skipna, **kwargs) From f633a895fda4410702da9ee3bdf081b14e4fdf20 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Tue, 19 Jul 2022 11:57:45 -0700 Subject: [PATCH 7/9] address more compat --- pandas/tests/extension/test_arrow.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index 66dfeac15a15f..d3b6ef882aeea 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -331,10 +331,24 @@ def test_reduce_series(self, data, all_numeric_reductions, skipna, request): if all_numeric_reductions in {"skew", "kurt"}: request.node.add_marker(xfail_mark) elif ( - all_numeric_reductions in {"median", "var", "std", "prod"} + all_numeric_reductions in {"median", "var", "std", "prod", "max", "min"} and pa_version_under6p0 ): request.node.add_marker(xfail_mark) + elif ( + all_numeric_reductions in {"sum", "mean"} + and skipna is False + and pa_version_under6p0 + ): + request.node.add_marker( + pytest.mark.xfail( + raises=AssertionError, + reason=( + f"{all_numeric_reductions} with skip_nulls={skipna} did not " + f"return NA for {pa_dtype} with pyarrow={pa.__version__}" + ), + ) + ) elif not ( pa.types.is_integer(pa_dtype) or pa.types.is_floating(pa_dtype) From c2a2f8364ab44b813a88e5980908a93af3d520e5 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Tue, 19 Jul 2022 11:59:10 -0700 Subject: [PATCH 8/9] Add dtype condiditon --- pandas/tests/extension/test_arrow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index d3b6ef882aeea..308296cc95db5 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -339,6 +339,7 @@ def test_reduce_series(self, data, all_numeric_reductions, skipna, request): all_numeric_reductions in {"sum", "mean"} and skipna is False and pa_version_under6p0 + and (pa.types.is_integer(pa_dtype) or pa.types.is_floating(pa_dtype)) ): request.node.add_marker( pytest.mark.xfail( From e244af918e466204e19b07649fde0a3f105beb26 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Tue, 19 Jul 2022 14:14:04 -0700 Subject: [PATCH 9/9] Sum mean not available in 1.0.1 --- pandas/tests/extension/test_arrow.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index 308296cc95db5..62f8a855ce263 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -335,6 +335,8 @@ def test_reduce_series(self, data, all_numeric_reductions, skipna, request): and pa_version_under6p0 ): request.node.add_marker(xfail_mark) + elif all_numeric_reductions in {"sum", "mean"} and pa_version_under2p0: + request.node.add_marker(xfail_mark) elif ( all_numeric_reductions in {"sum", "mean"} and skipna is False @@ -374,16 +376,17 @@ def test_reduce_series( self, data, all_boolean_reductions, skipna, na_value, request ): pa_dtype = data.dtype.pyarrow_dtype + xfail_mark = pytest.mark.xfail( + raises=TypeError, + reason=( + f"{all_boolean_reductions} is not implemented in " + f"pyarrow={pa.__version__} for {pa_dtype}" + ), + ) if not pa.types.is_boolean(pa_dtype): - request.node.add_marker( - pytest.mark.xfail( - raises=TypeError, - reason=( - f"{all_boolean_reductions} is not implemented in " - f"pyarrow={pa.__version__} for {pa_dtype}" - ), - ) - ) + request.node.add_marker(xfail_mark) + elif pa_version_under3p0: + request.node.add_marker(xfail_mark) op_name = all_boolean_reductions s = pd.Series(data) result = getattr(s, op_name)(skipna=skipna)