from datetime import (
    date,
    datetime,
    timedelta,
)
from decimal import Decimal

import numpy as np
import pytest

import pandas as pd
import pandas._testing as tm
from pandas.core import nanops
from pandas.core.arrays.string_arrow import ArrowStringArray


def get_objs():
    indexes = [
        pd.Index([True, False] * 5, name="a"),
        pd.Index(np.arange(10), dtype=np.int64, name="a"),
        pd.Index(np.arange(10), dtype=np.float64, name="a"),
        pd.DatetimeIndex(pd.date_range("2020-01-01", periods=10, unit="ns"), name="a"),
        pd.DatetimeIndex(
            pd.date_range("2020-01-01", periods=10, unit="ns"), name="a"
        ).tz_localize(tz="US/Eastern"),
        pd.PeriodIndex(pd.period_range("2020-01-01", periods=10, freq="D"), name="a"),
        pd.Index([str(i) for i in range(10)], name="a"),
    ]

    arr = np.random.default_rng(2).standard_normal(10)
    series = [pd.Series(arr, index=idx, name="a") for idx in indexes]

    objs = indexes + series
    return objs


class TestReductions:
    @pytest.mark.parametrize("opname", ["max", "min"])
    @pytest.mark.parametrize("obj", get_objs())
    def test_ops(self, opname, obj):
        result = getattr(obj, opname)()
        if not isinstance(obj, pd.PeriodIndex):
            if isinstance(obj._values, ArrowStringArray):
                # max not on the interface
                expected = getattr(np.array(obj._values), opname)()
            else:
                expected = getattr(obj._values, opname)()
        else:
            expected = pd.Period(ordinal=getattr(obj.asi8, opname)(), freq=obj.freq)

        assert result == expected

    @pytest.mark.parametrize("opname", ["max", "min"])
    @pytest.mark.parametrize(
        "dtype, val",
        [
            ("object", 2.0),
            ("float64", 2.0),
            ("datetime64[ns]", datetime(2011, 11, 1)),
            ("Int64", 2),
            ("boolean", True),
        ],
    )
    def test_nanminmax(self, opname, dtype, val, index_or_series):
        # GH#7261
        klass = index_or_series

        def check_missing(res):
            if dtype == "datetime64[ns]":
                return res is pd.NaT
            elif dtype in ["Int64", "boolean"]:
                return res is pd.NA
            else:
                return pd.isna(res)

        obj = klass([None], dtype=dtype)
        assert check_missing(getattr(obj, opname)())
        assert check_missing(getattr(obj, opname)(skipna=False))

        obj = klass([], dtype=dtype)
        assert check_missing(getattr(obj, opname)())
        assert check_missing(getattr(obj, opname)(skipna=False))

        if dtype == "object":
            # generic test with object only works for empty / all NaN
            return

        obj = klass([None, val], dtype=dtype)
        assert getattr(obj, opname)() == val
        assert check_missing(getattr(obj, opname)(skipna=False))

        obj = klass([None, val, None], dtype=dtype)
        assert getattr(obj, opname)() == val
        assert check_missing(getattr(obj, opname)(skipna=False))

    def test_minmax_object_dtype_preserves_numpy_scalars(self, index_or_series):
        # GH#64266
        klass = index_or_series
        left, right = np.int8(1), np.int8(3)
        obj = klass([left, right], dtype=object)
        assert obj.min() is left  # monotonic fast path for Index
        assert obj.max() is right
        assert np.maximum.reduce(obj) is right

        val = np.longdouble("1.1") + np.longdouble("1e-19")
        idx = pd.Index([val, np.longdouble("0.5")], dtype=object)
        result = idx.max()
        assert result is val
        ser = pd.Series(["a", "b"], index=idx)
        # Ensure we can index using the result.
        assert ser.loc[result] == "a"

    def test_object_dtype_reductions(self, index_or_series, using_python_scalars):
        # GH#64266
        klass = index_or_series
        obj = klass([1, 2, 4], dtype=object)
        expected_bool = bool if using_python_scalars else np.bool_
        result = obj.any()
        assert type(result) is expected_bool
        assert result
        result = obj.all()
        assert type(result) is expected_bool
        assert result
        if klass is pd.Series:
            result = obj.mean()
            assert type(result) is np.float64
            assert result == 7 / 3
            # for corr/cov/autocorr, checking the exact float value would
            #  just re-derive the statistic; only the type is of interest
            other = pd.Series([2, 5, 7], dtype=object)
            assert type(obj.corr(other)) is np.float64
            assert type(obj.cov(other)) is np.float64
            assert type(obj.autocorr()) is np.float64

    @pytest.mark.parametrize("opname", ["max", "min"])
    def test_nanargminmax(self, opname, index_or_series):
        # GH#7261
        klass = index_or_series
        arg_op = "arg" + opname if klass is pd.Index else "idx" + opname

        obj = klass([pd.NaT, datetime(2011, 11, 1)])
        assert getattr(obj, arg_op)() == 1

        with pytest.raises(ValueError, match="Encountered an NA value"):
            getattr(obj, arg_op)(skipna=False)

        obj = klass([pd.NaT, datetime(2011, 11, 1), pd.NaT])
        # check DatetimeIndex non-monotonic path
        assert getattr(obj, arg_op)() == 1
        with pytest.raises(ValueError, match="Encountered an NA value"):
            getattr(obj, arg_op)(skipna=False)

    @pytest.mark.parametrize("opname", ["max", "min"])
    @pytest.mark.parametrize("dtype", ["M8[ns]", "datetime64[ns, UTC]"])
    def test_nanops_empty_object(self, opname, index_or_series, dtype):
        klass = index_or_series
        arg_op = "arg" + opname if klass is pd.Index else "idx" + opname

        obj = klass([], dtype=dtype)

        assert getattr(obj, opname)() is pd.NaT
        assert getattr(obj, opname)(skipna=False) is pd.NaT

        with pytest.raises(ValueError, match="empty sequence"):
            getattr(obj, arg_op)()
        with pytest.raises(ValueError, match="empty sequence"):
            getattr(obj, arg_op)(skipna=False)

    def test_argminmax(self):
        obj = pd.Index(np.arange(5, dtype="int64"))
        assert obj.argmin() == 0
        assert obj.argmax() == 4

        obj = pd.Index([np.nan, 1, np.nan, 2])
        assert obj.argmin() == 1
        assert obj.argmax() == 3
        with pytest.raises(ValueError, match="Encountered an NA value"):
            obj.argmin(skipna=False)
        with pytest.raises(ValueError, match="Encountered an NA value"):
            obj.argmax(skipna=False)

        obj = pd.Index([np.nan])
        with pytest.raises(ValueError, match="Encountered all NA values"):
            obj.argmin()
        with pytest.raises(ValueError, match="Encountered all NA values"):
            obj.argmax()
        with pytest.raises(ValueError, match="Encountered an NA value"):
            obj.argmin(skipna=False)
        with pytest.raises(ValueError, match="Encountered an NA value"):
            obj.argmax(skipna=False)

        obj = pd.Index([pd.NaT, datetime(2011, 11, 1), datetime(2011, 11, 2), pd.NaT])
        assert obj.argmin() == 1
        assert obj.argmax() == 2
        with pytest.raises(ValueError, match="Encountered an NA value"):
            obj.argmin(skipna=False)
        with pytest.raises(ValueError, match="Encountered an NA value"):
            obj.argmax(skipna=False)

        obj = pd.Index([pd.NaT])
        with pytest.raises(ValueError, match="Encountered all NA values"):
            obj.argmin()
        with pytest.raises(ValueError, match="Encountered all NA values"):
            obj.argmax()
        with pytest.raises(ValueError, match="Encountered an NA value"):
            obj.argmin(skipna=False)
        with pytest.raises(ValueError, match="Encountered an NA value"):
            obj.argmax(skipna=False)

    @pytest.mark.parametrize(
        "dtype", ["int64", "float64", "Int64", "category", "datetime64[ns]", "object"]
    )
    @pytest.mark.parametrize("op", ["argmin", "argmax"])
    def test_argminmax_python_scalars(
        self, index_or_series, dtype, op, using_python_scalars
    ):
        # GH#64266
        obj = index_or_series([1, 3, 2], dtype=dtype)
        result = getattr(obj, op)()
        assert result == (0 if op == "argmin" else 1)
        if using_python_scalars:
            assert type(result) is int
        else:
            assert isinstance(result, np.integer)

    @pytest.mark.parametrize(
        "dtype, expected_type", [("int64", int), ("float64", float), ("Int64", int)]
    )
    @pytest.mark.parametrize("op", ["idxmin", "idxmax"])
    def test_idxminmax_result_type(
        self, dtype, expected_type, op, using_python_scalars
    ):
        # GH#64266
        ser = pd.Series([1, 3, 2], index=pd.Index([10, 20, 30], dtype=dtype))
        result = getattr(ser, op)()
        assert result == (10 if op == "idxmin" else 20)
        if using_python_scalars:
            assert type(result) is expected_type
        else:
            assert isinstance(result, np.generic)

    @pytest.mark.parametrize("op, expected_col", [["max", "a"], ["min", "b"]])
    def test_same_tz_min_max_axis_1(self, op, expected_col):
        # GH 10390
        df = pd.DataFrame(
            pd.date_range("2016-01-01 00:00:00", periods=3, tz="UTC", unit="ns"),
            columns=["a"],
        )
        df["b"] = df.a.subtract(pd.Timedelta(seconds=3600))
        result = getattr(df, op)(axis=1)
        expected = df[expected_col].rename(None)
        tm.assert_series_equal(result, expected)

    @pytest.mark.parametrize("func", ["maximum", "minimum"])
    def test_numpy_reduction_with_tz_aware_dtype(self, tz_aware_fixture, func):
        # GH 15552
        tz = tz_aware_fixture
        arg = pd.to_datetime(["2019"]).tz_localize(tz)
        expected = pd.Series(arg)
        result = getattr(np, func)(expected, expected)
        tm.assert_series_equal(result, expected)

    def test_nan_int_timedelta_sum(self):
        # GH 27185
        df = pd.DataFrame(
            {
                "A": pd.Series([1, 2, pd.NaT], dtype="timedelta64[ns]"),
                "B": pd.Series([1, 2, pd.NA], dtype="Int64"),
            }
        )
        expected = pd.Series({"A": pd.Timedelta(3), "B": 3})
        result = df.sum()
        tm.assert_series_equal(result, expected)


class TestIndexReductions:
    # Note: the name TestIndexReductions indicates these tests
    #  were moved from an Index-specific test file, _not_ that these tests are
    #  intended long-term to be Index-specific

    @pytest.mark.parametrize(
        "start,stop,step",
        [
            (0, 400, 3),
            (500, 0, -6),
            (-(10**6), 10**6, 4),
            (10**6, -(10**6), -4),
            (0, 10, 20),
        ],
    )
    def test_max_min_range(self, start, stop, step):
        # GH#17607
        idx = pd.RangeIndex(start, stop, step)
        expected = idx._values.max()
        result = idx.max()
        assert result == expected

        # skipna should be irrelevant since RangeIndex should never have NAs
        result2 = idx.max(skipna=False)
        assert result2 == expected

        expected = idx._values.min()
        result = idx.min()
        assert result == expected

        # skipna should be irrelevant since RangeIndex should never have NAs
        result2 = idx.min(skipna=False)
        assert result2 == expected

        # empty
        idx = pd.RangeIndex(start, stop, -step)
        assert pd.isna(idx.max())
        assert pd.isna(idx.min())

    def test_minmax_timedelta64(self):
        # monotonic
        idx1 = pd.TimedeltaIndex(["1 days", "2 days", "3 days"])
        assert idx1.is_monotonic_increasing

        # non-monotonic
        idx2 = pd.TimedeltaIndex(["1 days", np.nan, "3 days", "NaT"])
        assert not idx2.is_monotonic_increasing

        for idx in [idx1, idx2]:
            assert idx.min() == pd.Timedelta("1 days")
            assert idx.max() == pd.Timedelta("3 days")
            assert idx.argmin() == 0
            assert idx.argmax() == 2

    @pytest.mark.parametrize("op", ["min", "max"])
    def test_minmax_timedelta_empty_or_na(self, op):
        # Return NaT
        obj = pd.TimedeltaIndex([])
        assert getattr(obj, op)() is pd.NaT

        obj = pd.TimedeltaIndex([pd.NaT])
        assert getattr(obj, op)() is pd.NaT

        obj = pd.TimedeltaIndex([pd.NaT, pd.NaT, pd.NaT])
        assert getattr(obj, op)() is pd.NaT

    def test_numpy_minmax_timedelta64(self):
        td = pd.timedelta_range("16815 days", "16820 days", freq="D")

        assert np.min(td) == pd.Timedelta("16815 days")
        assert np.max(td) == pd.Timedelta("16820 days")

        errmsg = "the 'out' parameter is not supported"
        with pytest.raises(ValueError, match=errmsg):
            np.min(td, out=0)
        with pytest.raises(ValueError, match=errmsg):
            np.max(td, out=0)

        assert np.argmin(td) == 0
        assert np.argmax(td) == 5

        errmsg = "the 'out' parameter is not supported"
        with pytest.raises(ValueError, match=errmsg):
            np.argmin(td, out=0)
        with pytest.raises(ValueError, match=errmsg):
            np.argmax(td, out=0)

    def test_timedelta_ops(self):
        # GH#4984
        # make sure ops return Timedelta
        s = pd.Series(
            [pd.Timestamp("20130101") + timedelta(seconds=i * i) for i in range(10)]
        )
        td = s.diff()

        result = td.mean()
        expected = pd.to_timedelta(timedelta(seconds=9))
        assert result == expected

        result = td.to_frame().mean()
        assert result[0] == expected

        result = td.quantile(0.1)
        expected = pd.Timedelta(np.timedelta64(2600, "ms"))
        assert result == expected

        result = td.median()
        expected = pd.to_timedelta("00:00:09")
        assert result == expected

        result = td.to_frame().median()
        assert result[0] == expected

        # GH#6462
        # consistency in returned values for sum
        result = td.sum()
        expected = pd.to_timedelta("00:01:21")
        assert result == expected

        result = td.to_frame().sum()
        assert result[0] == expected

        # std
        result = td.std()
        expected = pd.to_timedelta(pd.Series(td.dropna().values).std())
        assert result == expected

        result = td.to_frame().std()
        assert result[0] == expected

        # GH#10040
        # make sure NaT is properly handled by median()
        s = pd.Series([pd.Timestamp("2015-02-03"), pd.Timestamp("2015-02-07")])
        assert s.diff().median() == timedelta(days=4)

        s = pd.Series(
            [
                pd.Timestamp("2015-02-03"),
                pd.Timestamp("2015-02-07"),
                pd.Timestamp("2015-02-15"),
            ]
        )
        assert s.diff().median() == timedelta(days=6)

    @pytest.mark.parametrize("opname", ["skew", "kurt", "sem", "prod", "var"])
    def test_invalid_td64_reductions(self, opname):
        s = pd.Series(
            [pd.Timestamp("20130101") + timedelta(seconds=i * i) for i in range(10)]
        )
        td = s.diff()

        msg = "|".join(
            [
                f"reduction operation '{opname}' not allowed for this dtype",
                rf"cannot perform {opname} with type timedelta64\[ns\]",
                f"does not support operation '{opname}'",
            ]
        )

        with pytest.raises(TypeError, match=msg):
            getattr(td, opname)()

        with pytest.raises(TypeError, match=msg):
            getattr(td.to_frame(), opname)(numeric_only=False)

    def test_minmax_tz(self, tz_naive_fixture):
        tz = tz_naive_fixture
        # monotonic
        idx1 = pd.DatetimeIndex(["2011-01-01", "2011-01-02", "2011-01-03"], tz=tz)
        assert idx1.is_monotonic_increasing

        # non-monotonic
        idx2 = pd.DatetimeIndex(
            ["2011-01-01", pd.NaT, "2011-01-03", "2011-01-02", pd.NaT], tz=tz
        )
        assert not idx2.is_monotonic_increasing

        for idx in [idx1, idx2]:
            assert idx.min() == pd.Timestamp("2011-01-01", tz=tz)
            assert idx.max() == pd.Timestamp("2011-01-03", tz=tz)
            assert idx.argmin() == 0
            assert idx.argmax() == 2

    @pytest.mark.parametrize("op", ["min", "max"])
    def test_minmax_nat_datetime64(self, op):
        # Return NaT
        obj = pd.DatetimeIndex([])
        assert pd.isna(getattr(obj, op)())

        obj = pd.DatetimeIndex([pd.NaT])
        assert pd.isna(getattr(obj, op)())

        obj = pd.DatetimeIndex([pd.NaT, pd.NaT, pd.NaT])
        assert pd.isna(getattr(obj, op)())

    def test_numpy_minmax_integer(self):
        # GH#26125
        idx = pd.Index([1, 2, 3])

        expected = idx.values.max()
        result = np.max(idx)
        assert result == expected

        expected = idx.values.min()
        result = np.min(idx)
        assert result == expected

        errmsg = "the 'out' parameter is not supported"
        with pytest.raises(ValueError, match=errmsg):
            np.min(idx, out=0)
        with pytest.raises(ValueError, match=errmsg):
            np.max(idx, out=0)

        expected = idx.values.argmax()
        result = np.argmax(idx)
        assert result == expected

        expected = idx.values.argmin()
        result = np.argmin(idx)
        assert result == expected

        errmsg = "the 'out' parameter is not supported"
        with pytest.raises(ValueError, match=errmsg):
            np.argmin(idx, out=0)
        with pytest.raises(ValueError, match=errmsg):
            np.argmax(idx, out=0)

    def test_numpy_minmax_range(self):
        # GH#26125
        idx = pd.RangeIndex(0, 10, 3)

        result = np.max(idx)
        assert result == 9

        result = np.min(idx)
        assert result == 0

        errmsg = "the 'out' parameter is not supported"
        with pytest.raises(ValueError, match=errmsg):
            np.min(idx, out=0)
        with pytest.raises(ValueError, match=errmsg):
            np.max(idx, out=0)

        # No need to test again argmax/argmin compat since the implementation
        # is the same as basic integer index

    def test_numpy_minmax_datetime64(self):
        dr = pd.date_range(start="2016-01-15", end="2016-01-20")

        assert np.min(dr) == pd.Timestamp("2016-01-15 00:00:00")
        assert np.max(dr) == pd.Timestamp("2016-01-20 00:00:00")

        errmsg = "the 'out' parameter is not supported"
        with pytest.raises(ValueError, match=errmsg):
            np.min(dr, out=0)

        with pytest.raises(ValueError, match=errmsg):
            np.max(dr, out=0)

        assert np.argmin(dr) == 0
        assert np.argmax(dr) == 5

        errmsg = "the 'out' parameter is not supported"
        with pytest.raises(ValueError, match=errmsg):
            np.argmin(dr, out=0)

        with pytest.raises(ValueError, match=errmsg):
            np.argmax(dr, out=0)

    def test_minmax_period(self):
        # monotonic
        idx1 = pd.PeriodIndex(
            [pd.NaT, "2011-01-01", "2011-01-02", "2011-01-03"], freq="D"
        )
        assert not idx1.is_monotonic_increasing
        assert idx1[1:].is_monotonic_increasing

        # non-monotonic
        idx2 = pd.PeriodIndex(
            ["2011-01-01", pd.NaT, "2011-01-03", "2011-01-02", pd.NaT], freq="D"
        )
        assert not idx2.is_monotonic_increasing

        for idx in [idx1, idx2]:
            assert idx.min() == pd.Period("2011-01-01", freq="D")
            assert idx.max() == pd.Period("2011-01-03", freq="D")
        assert idx1.argmin() == 1
        assert idx2.argmin() == 0
        assert idx1.argmax() == 3
        assert idx2.argmax() == 2

    @pytest.mark.parametrize("op", ["min", "max"])
    @pytest.mark.parametrize("data", [[], [pd.NaT], [pd.NaT, pd.NaT, pd.NaT]])
    def test_minmax_period_empty_nat(self, op, data):
        # Return NaT
        obj = pd.PeriodIndex(data, freq="M")
        result = getattr(obj, op)()
        assert result is pd.NaT

    def test_numpy_minmax_period(self):
        pr = pd.period_range(start="2016-01-15", end="2016-01-20")

        assert np.min(pr) == pd.Period("2016-01-15", freq="D")
        assert np.max(pr) == pd.Period("2016-01-20", freq="D")

        errmsg = "the 'out' parameter is not supported"
        with pytest.raises(ValueError, match=errmsg):
            np.min(pr, out=0)
        with pytest.raises(ValueError, match=errmsg):
            np.max(pr, out=0)

        assert np.argmin(pr) == 0
        assert np.argmax(pr) == 5

        errmsg = "the 'out' parameter is not supported"
        with pytest.raises(ValueError, match=errmsg):
            np.argmin(pr, out=0)
        with pytest.raises(ValueError, match=errmsg):
            np.argmax(pr, out=0)

    def test_min_max_categorical(self):
        ci = pd.CategoricalIndex(list("aabbca"), categories=list("cab"), ordered=False)
        msg = (
            r"Categorical is not ordered for operation min\n"
            r"you can use .as_ordered\(\) to change the Categorical to an ordered one\n"
        )
        with pytest.raises(TypeError, match=msg):
            ci.min()
        msg = (
            r"Categorical is not ordered for operation max\n"
            r"you can use .as_ordered\(\) to change the Categorical to an ordered one\n"
        )
        with pytest.raises(TypeError, match=msg):
            ci.max()

        ci = pd.CategoricalIndex(list("aabbca"), categories=list("cab"), ordered=True)
        assert ci.min() == "c"
        assert ci.max() == "b"

    @pytest.mark.parametrize("op", ["min", "max"])
    @pytest.mark.parametrize("monotonic", [True, False])
    def test_min_max_multiindex_result_type(self, op, monotonic, using_python_scalars):
        # GH#64266
        tuples = [(1, 1.5), (2, 2.5), (3, 3.5)]
        if not monotonic:
            tuples = tuples[::-1]
        mi = pd.MultiIndex.from_tuples(tuples)
        result = getattr(mi, op)()
        assert result == ((1, 1.5) if op == "min" else (3, 3.5))
        if using_python_scalars or not monotonic:
            # the non-monotonic path returns Python scalars regardless of the option
            assert type(result[0]) is int
            assert type(result[1]) is float
        else:
            assert isinstance(result[0], np.integer)
            assert isinstance(result[1], np.floating)


class TestSeriesReductions:
    # Note: the name TestSeriesReductions indicates these tests
    #  were moved from a series-specific test file, _not_ that these tests are
    #  intended long-term to be series-specific

    def test_sum_inf(self):
        s = pd.Series(np.random.default_rng(2).standard_normal(10))
        s2 = s.copy()

        s[5:8] = np.inf
        s2[5:8] = np.nan

        assert np.isinf(s.sum())

        arr = np.random.default_rng(2).standard_normal((100, 100)).astype("f4")
        arr[:, 2] = np.inf

        res = nanops.nansum(arr, axis=1)
        assert np.isinf(res).all()

    @pytest.mark.parametrize(
        "dtype", ["float64", "Float32", "Int64", "boolean", "object"]
    )
    @pytest.mark.parametrize("use_bottleneck", [True, False])
    @pytest.mark.parametrize("method, unit", [("sum", 0.0), ("prod", 1.0)])
    def test_empty(self, method, unit, use_bottleneck, dtype):
        item = pd.NA if dtype in ["Float32", "Int64"] else np.nan
        with pd.option_context("use_bottleneck", use_bottleneck):
            # GH#9422 / GH#18921
            # Entirely empty
            s = pd.Series([], dtype=dtype)
            # NA by default
            result = getattr(s, method)()
            assert result == unit

            # Explicit
            result = getattr(s, method)(min_count=0)
            assert result == unit

            result = getattr(s, method)(min_count=1)
            assert pd.isna(result)

            # Skipna, default
            result = getattr(s, method)(skipna=True)
            result == unit

            # Skipna, explicit
            result = getattr(s, method)(skipna=True, min_count=0)
            assert result == unit

            result = getattr(s, method)(skipna=True, min_count=1)
            assert pd.isna(result)

            result = getattr(s, method)(skipna=False, min_count=0)
            assert result == unit

            result = getattr(s, method)(skipna=False, min_count=1)
            assert pd.isna(result)

            # All-NA
            s = pd.Series([item], dtype=dtype)
            # NA by default
            result = getattr(s, method)()
            assert result == unit

            # Explicit
            result = getattr(s, method)(min_count=0)
            assert result == unit

            result = getattr(s, method)(min_count=1)
            assert pd.isna(result)

            # Skipna, default
            result = getattr(s, method)(skipna=True)
            result == unit

            # skipna, explicit
            result = getattr(s, method)(skipna=True, min_count=0)
            assert result == unit

            result = getattr(s, method)(skipna=True, min_count=1)
            assert pd.isna(result)

            # Mix of valid, empty
            s = pd.Series([item, 1], dtype=dtype)
            # Default
            result = getattr(s, method)()
            assert result == 1.0

            # Explicit
            result = getattr(s, method)(min_count=0)
            assert result == 1.0

            result = getattr(s, method)(min_count=1)
            assert result == 1.0

            # Skipna
            result = getattr(s, method)(skipna=True)
            assert result == 1.0

            result = getattr(s, method)(skipna=True, min_count=0)
            assert result == 1.0

            # GH#844 (changed in GH#9422)
            df = pd.DataFrame(np.empty((10, 0)), dtype=dtype)
            assert (getattr(df, method)(axis=1) == unit).all()

            s = pd.Series([1], dtype=dtype)
            result = getattr(s, method)(min_count=2)
            assert pd.isna(result)

            result = getattr(s, method)(skipna=False, min_count=2)
            assert pd.isna(result)

            s = pd.Series([item], dtype=dtype)
            result = getattr(s, method)(min_count=2)
            assert pd.isna(result)

            s = pd.Series([item, 1], dtype=dtype)
            result = getattr(s, method)(min_count=2)
            assert pd.isna(result)

    @pytest.mark.parametrize("method", ["mean", "var"])
    @pytest.mark.parametrize("dtype", ["Float64", "Int64", "boolean"])
    def test_ops_consistency_on_empty_nullable(self, method, dtype):
        # GH#34814
        # consistency for nullable dtypes on empty or ALL-NA mean

        # empty series
        eser = pd.Series([], dtype=dtype)
        result = getattr(eser, method)()
        assert result is pd.NA

        # ALL-NA series
        nser = pd.Series([pd.NA], dtype=dtype)
        result = getattr(nser, method)()
        assert result is pd.NA

    @pytest.mark.parametrize("method", ["mean", "median", "std", "var"])
    def test_ops_consistency_on_empty(self, method):
        # GH#7869
        # consistency on empty

        # float
        result = getattr(pd.Series(dtype=float), method)()
        assert pd.isna(result)

        # timedelta64[ns]
        tdser = pd.Series([], dtype="m8[ns]")
        if method == "var":
            msg = "|".join(
                [
                    "operation 'var' not allowed",
                    "does not support operation 'var'",
                ]
            )
            with pytest.raises(TypeError, match=msg):
                getattr(tdser, method)()
        else:
            result = getattr(tdser, method)()
            assert result is pd.NaT

    def test_nansum_buglet(self):
        ser = pd.Series([1.0, np.nan], index=[0, 1])
        result = np.nansum(ser)
        tm.assert_almost_equal(result, 1)

    @pytest.mark.parametrize("use_bottleneck", [True, False])
    @pytest.mark.parametrize("dtype, size", [("int32", 70_000), ("int64", 100_000)])
    def test_sum_overflow_int(self, use_bottleneck, dtype, size):
        with pd.option_context("use_bottleneck", use_bottleneck):
            # GH#6915
            # overflowing on the smaller int dtypes
            v = np.arange(size, dtype=dtype)
            s = pd.Series(v)

            result = s.sum(skipna=False)
            assert int(result) == v.sum(dtype="int64")
            result = s.min(skipna=False)
            assert int(result) == 0
            result = s.max(skipna=False)
            assert int(result) == v[-1]

    @pytest.mark.parametrize("use_bottleneck", [True, False])
    @pytest.mark.parametrize("dtype", ["float32", "float64"])
    def test_sum_overflow_float(self, use_bottleneck, dtype):
        with pd.option_context("use_bottleneck", use_bottleneck):
            v = np.arange(5000000, dtype=dtype)
            s = pd.Series(v)

            result = s.sum(skipna=False)
            assert result == v.sum(dtype=dtype)
            result = s.min(skipna=False)
            assert np.allclose(float(result), 0.0)
            result = s.max(skipna=False)
            assert np.allclose(float(result), v[-1])

    def test_mean_masked_overflow(self):
        # GH#48378
        val = 100_000_000_000_000_000
        n_elements = 100
        na = np.array([val] * n_elements)
        ser = pd.Series([val] * n_elements, dtype="Int64")

        result_numpy = np.mean(na)
        result_masked = ser.mean()
        assert result_masked - result_numpy == 0
        assert result_masked == 1e17

    @pytest.mark.parametrize("ddof, exp", [(1, 2.5), (0, 2.0)])
    def test_var_masked_array(self, ddof, exp):
        # GH#48379
        ser = pd.Series([1, 2, 3, 4, 5], dtype="Int64")
        ser_numpy_dtype = pd.Series([1, 2, 3, 4, 5], dtype="int64")
        result = ser.var(ddof=ddof)
        result_numpy_dtype = ser_numpy_dtype.var(ddof=ddof)
        assert result == result_numpy_dtype
        assert result == exp

    @pytest.mark.parametrize(
        "values,expected", [([-1j, 0j, 1j], 1.0), ([1 + 2j, 2 + 3j, 3 + 4j], 2.0)]
    )
    def test_var_complex_array(self, values, expected):
        # GH 61645, 62421
        ser = pd.Series(values, dtype=complex)
        tm.assert_almost_equal(ser.var(ddof=1), expected)
        tm.assert_almost_equal(ser.std(ddof=1), np.sqrt(expected))
        tm.assert_almost_equal(ser.sem(ddof=1), np.sqrt(expected / len(values)))

    @pytest.mark.parametrize("dtype", ("m8[ns]", "M8[ns]", "M8[ns, UTC]"))
    def test_empty_timeseries_reductions_return_nat(self, dtype, skipna):
        # covers GH#11245
        assert pd.Series([], dtype=dtype).min(skipna=skipna) is pd.NaT
        assert pd.Series([], dtype=dtype).max(skipna=skipna) is pd.NaT

    def test_numpy_argmin(self):
        # See GH#16830
        data = np.arange(1, 11)

        s = pd.Series(data, index=data)
        result = np.argmin(s)

        expected = np.argmin(data)
        assert result == expected

        result = s.argmin()

        assert result == expected

        msg = "the 'out' parameter is not supported"
        with pytest.raises(ValueError, match=msg):
            np.argmin(s, out=data)

    def test_numpy_argmax(self):
        # See GH#16830
        data = np.arange(1, 11)

        ser = pd.Series(data, index=data)
        result = np.argmax(ser)
        expected = np.argmax(data)
        assert result == expected

        result = ser.argmax()

        assert result == expected

        msg = "the 'out' parameter is not supported"
        with pytest.raises(ValueError, match=msg):
            np.argmax(ser, out=data)

    def test_idxmin_dt64index(self, unit):
        # GH#43587 should have NaT instead of NaN
        dti = pd.DatetimeIndex(["NaT", "2015-02-08", "NaT"]).as_unit(unit)
        ser = pd.Series([1.0, 2.0, np.nan], index=dti)
        with pytest.raises(ValueError, match="Encountered an NA value"):
            ser.idxmin(skipna=False)
        with pytest.raises(ValueError, match="Encountered an NA value"):
            ser.idxmax(skipna=False)

        df = ser.to_frame()
        with pytest.raises(ValueError, match="Encountered an NA value"):
            df.idxmin(skipna=False)
        with pytest.raises(ValueError, match="Encountered an NA value"):
            df.idxmax(skipna=False)

    def test_idxmin(self):
        # test idxmin
        # _check_stat_op approach can not be used here because of isna check.
        string_series = pd.Series(range(20), dtype=np.float64, name="series")

        # add some NaNs
        string_series[5:15] = np.nan

        # skipna or no
        assert string_series[string_series.idxmin()] == string_series.min()
        with pytest.raises(ValueError, match="Encountered an NA value"):
            string_series.idxmin(skipna=False)

        # no NaNs
        nona = string_series.dropna()
        assert nona[nona.idxmin()] == nona.min()
        assert nona.index.values.tolist().index(nona.idxmin()) == nona.values.argmin()

        # all NaNs
        allna = string_series * np.nan
        msg = "Encountered all NA values"
        with pytest.raises(ValueError, match=msg):
            allna.idxmin()

        # datetime64[ns]
        s = pd.Series(pd.date_range("20130102", periods=6))
        result = s.idxmin()
        assert result == 0

        s[0] = np.nan
        result = s.idxmin()
        assert result == 1

    def test_idxmax(self):
        # test idxmax
        # _check_stat_op approach can not be used here because of isna check.
        string_series = pd.Series(range(20), dtype=np.float64, name="series")

        # add some NaNs
        string_series[5:15] = np.nan

        # skipna or no
        assert string_series[string_series.idxmax()] == string_series.max()
        with pytest.raises(ValueError, match="Encountered an NA value"):
            assert pd.isna(string_series.idxmax(skipna=False))

        # no NaNs
        nona = string_series.dropna()
        assert nona[nona.idxmax()] == nona.max()
        assert nona.index.values.tolist().index(nona.idxmax()) == nona.values.argmax()

        # all NaNs
        allna = string_series * np.nan
        msg = "Encountered all NA values"
        with pytest.raises(ValueError, match=msg):
            allna.idxmax()

        s = pd.Series(pd.date_range("20130102", periods=6))
        result = s.idxmax()
        assert result == 5

        s[5] = np.nan
        result = s.idxmax()
        assert result == 4

        # Index with float64 dtype
        # GH#5914
        s = pd.Series([1, 2, 3], [1.1, 2.1, 3.1])
        result = s.idxmax()
        assert result == 3.1
        result = s.idxmin()
        assert result == 1.1

        s = pd.Series(s.index, s.index)
        result = s.idxmax()
        assert result == 3.1
        result = s.idxmin()
        assert result == 1.1

    def test_all_any(self):
        ts = pd.Series(
            np.arange(10, dtype=np.float64),
            index=pd.date_range("2020-01-01", periods=10),
            name="ts",
        )
        bool_series = ts > 0
        assert not bool_series.all()
        assert bool_series.any()

        # Alternative types, with implicit 'object' dtype.
        s = pd.Series(["abc", True])
        assert s.any()

    def test_numpy_all_any(self, index_or_series):
        # GH#40180
        idx = index_or_series([0, 1, 2])
        assert not np.all(idx)
        assert np.any(idx)
        idx = pd.Index([1, 2, 3])
        assert np.all(idx)

    def test_all_any_skipna(self):
        # Check skipna, with implicit 'object' dtype.
        s1 = pd.Series([np.nan, True])
        s2 = pd.Series([np.nan, False])
        assert s1.all(skipna=False)  # nan && True => True
        assert s1.all(skipna=True)
        assert s2.any(skipna=False)
        assert not s2.any(skipna=True)

    def test_all_any_bool_only(self):
        s = pd.Series([False, False, True, True, False, True], index=[0, 0, 1, 1, 2, 2])

        # GH#47500 - test bool_only works
        assert s.any(bool_only=True)
        assert not s.all(bool_only=True)

    def test_any_all_object_dtype(self, all_boolean_reductions, skipna):
        # GH#12863
        ser = pd.Series(["a", "b", "c", "d", "e"], dtype=object)
        result = getattr(ser, all_boolean_reductions)(skipna=skipna)
        expected = True

        assert result == expected

    @pytest.mark.parametrize(
        "data", [[False, None], [None, False], [False, np.nan], [np.nan, False]]
    )
    def test_any_all_object_dtype_missing(self, data, all_boolean_reductions):
        # GH#27709
        ser = pd.Series(data)
        result = getattr(ser, all_boolean_reductions)(skipna=False)

        # None is treated is False, but np.nan is treated as True
        expected = all_boolean_reductions == "any" and None not in data
        assert result == expected

    @pytest.mark.parametrize("dtype", ["boolean", "Int64", "UInt64", "Float64"])
    @pytest.mark.parametrize(
        # expected_data indexed as [[skipna=False/any, skipna=False/all],
        #                           [skipna=True/any, skipna=True/all]]
        "data,expected_data",
        [
            ([0, 0, 0], [[False, False], [False, False]]),
            ([1, 1, 1], [[True, True], [True, True]]),
            ([pd.NA, pd.NA, pd.NA], [[pd.NA, pd.NA], [False, True]]),
            ([0, pd.NA, 0], [[pd.NA, False], [False, False]]),
            ([1, pd.NA, 1], [[True, pd.NA], [True, True]]),
            ([1, pd.NA, 0], [[True, False], [True, False]]),
        ],
    )
    def test_any_all_nullable_kleene_logic(
        self, all_boolean_reductions, skipna, data, dtype, expected_data
    ):
        # GH-37506, GH-41967
        ser = pd.Series(data, dtype=dtype)
        expected = expected_data[skipna][all_boolean_reductions == "all"]

        result = getattr(ser, all_boolean_reductions)(skipna=skipna)
        assert (result is pd.NA and expected is pd.NA) or result == expected

    def test_any_axis1_bool_only(self):
        # GH#32432
        df = pd.DataFrame({"A": [True, False], "B": [1, 2]})
        result = df.any(axis=1, bool_only=True)
        expected = pd.Series([True, False])
        tm.assert_series_equal(result, expected)

    def test_any_all_datetimelike(self):
        # GH#38723 these may not be the desired long-term behavior (GH#34479)
        #  but in the interim should be internally consistent
        dta = pd.date_range("1995-01-02", periods=3)._data
        ser = pd.Series(dta)
        df = pd.DataFrame(ser)

        # GH#34479
        msg = "'(any|all)' with datetime64 dtypes is not supported"
        with pytest.raises(TypeError, match=msg):
            dta.all()
        with pytest.raises(TypeError, match=msg):
            dta.any()

        with pytest.raises(TypeError, match=msg):
            ser.all()
        with pytest.raises(TypeError, match=msg):
            ser.any()

        with pytest.raises(TypeError, match=msg):
            df.any().all()
        with pytest.raises(TypeError, match=msg):
            df.all().all()

        dta = dta.tz_localize("UTC")
        ser = pd.Series(dta)
        df = pd.DataFrame(ser)
        # GH#34479
        with pytest.raises(TypeError, match=msg):
            dta.all()
        with pytest.raises(TypeError, match=msg):
            dta.any()

        with pytest.raises(TypeError, match=msg):
            ser.all()
        with pytest.raises(TypeError, match=msg):
            ser.any()

        with pytest.raises(TypeError, match=msg):
            df.any().all()
        with pytest.raises(TypeError, match=msg):
            df.all().all()

        tda = dta - dta[0]
        ser = pd.Series(tda)
        df = pd.DataFrame(ser)

        assert tda.any()
        assert not tda.all()

        assert ser.any()
        assert not ser.all()

        assert df.any().all()
        assert not df.all().any()

    def test_any_all_string_dtype(self, any_string_dtype):
        # GH#54591
        if (
            isinstance(any_string_dtype, pd.StringDtype)
            and any_string_dtype.na_value is pd.NA
        ):
            # the nullable string dtype currently still raise an error
            # https://github.com/pandas-dev/pandas/issues/51939
            ser = pd.Series(["a", "b"], dtype=any_string_dtype)
            with pytest.raises(TypeError):
                ser.any()
            with pytest.raises(TypeError):
                ser.all()
            return

        ser = pd.Series(["", "a"], dtype=any_string_dtype)
        assert ser.any()
        assert not ser.all()
        assert ser.any(skipna=False)
        assert not ser.all(skipna=False)

        ser = pd.Series([np.nan, "a"], dtype=any_string_dtype)
        assert ser.any()
        assert ser.all()
        assert ser.any(skipna=False)
        assert ser.all(skipna=False)  # NaN is considered truthy

        ser = pd.Series([np.nan, ""], dtype=any_string_dtype)
        assert not ser.any()
        assert not ser.all()
        assert ser.any(skipna=False)  # NaN is considered truthy
        assert not ser.all(skipna=False)

        ser = pd.Series(["a", "b"], dtype=any_string_dtype)
        assert ser.any()
        assert ser.all()
        assert ser.any(skipna=False)
        assert ser.all(skipna=False)

        ser = pd.Series([], dtype=any_string_dtype)
        assert not ser.any()
        assert ser.all()
        assert not ser.any(skipna=False)
        assert ser.all(skipna=False)

        ser = pd.Series([""], dtype=any_string_dtype)
        assert not ser.any()
        assert not ser.all()
        assert not ser.any(skipna=False)
        assert not ser.all(skipna=False)

        ser = pd.Series([np.nan], dtype=any_string_dtype)
        assert not ser.any()
        assert ser.all()
        assert ser.any(skipna=False)  # NaN is considered truthy
        assert ser.all(skipna=False)  # NaN is considered truthy

    def test_timedelta64_analytics(self):
        # index min/max
        dti = pd.date_range("2012-1-1", periods=3, freq="D")
        td = pd.Series(dti) - pd.Timestamp("20120101")

        result = td.idxmin()
        assert result == 0

        result = td.idxmax()
        assert result == 2

        # GH#2982
        # with NaT
        td[0] = np.nan

        result = td.idxmin()
        assert result == 1

        result = td.idxmax()
        assert result == 2

        # abs
        s1 = pd.Series(pd.date_range("20120101", periods=3))
        s2 = pd.Series(pd.date_range("20120102", periods=3))
        expected = pd.Series(s2 - s1)

        result = np.abs(s1 - s2)
        tm.assert_series_equal(result, expected)

        result = (s1 - s2).abs()
        tm.assert_series_equal(result, expected)

        # max/min
        result = td.max()
        expected = pd.Timedelta("2 days")
        assert result == expected

        result = td.min()
        expected = pd.Timedelta("1 days")
        assert result == expected

    def test_assert_idxminmax_empty_raises(self):
        """
        Cases where ``Series.argmax`` and related should raise an exception
        """
        test_input = pd.Series([], dtype="float64")
        msg = "attempt to get argmin of an empty sequence"
        with pytest.raises(ValueError, match=msg):
            test_input.idxmin()
        with pytest.raises(ValueError, match=msg):
            test_input.idxmin(skipna=False)
        msg = "attempt to get argmax of an empty sequence"
        with pytest.raises(ValueError, match=msg):
            test_input.idxmax()
        with pytest.raises(ValueError, match=msg):
            test_input.idxmax(skipna=False)

    @pytest.mark.parametrize(
        "data, exp_min, exp_max",
        [
            (["a", "b", np.nan], "a", "b"),
            (["a", "b", None], "a", "b"),
            # GH#4147
            (["alpha", np.nan, "charlie", "delta"], "alpha", "delta"),
            ([(1, 3), (2, 2), np.nan], (1, 3), (2, 2)),
            # GH#24109, including the NaT sentinel left behind by .dt.date
            (
                [date(2018, 1, 1), None, date(2018, 1, 3)],
                date(2018, 1, 1),
                date(2018, 1, 3),
            ),
            (
                [date(2018, 1, 1), pd.NaT, date(2018, 1, 3)],
                date(2018, 1, 1),
                date(2018, 1, 3),
            ),
        ],
    )
    def test_minmax_object_with_na(self, data, exp_min, exp_max):
        # GH#65500: NA entries in object dtype were filled with +/-inf,
        # which cannot be compared with strings/tuples
        ser = pd.Series(data, dtype=object)
        assert ser.min() == exp_min
        assert ser.max() == exp_max

    def test_minmax_object_timestamps_mixed_tz(self):
        # GH#65500
        ts1 = pd.Timestamp("2026-01-01 15:13:44", tz="Europe/Budapest")
        ts2 = pd.Timestamp("2026-01-01 15:13:44", tz="Europe/Moscow")
        # ts2 is the earlier of the two in UTC terms
        ser = pd.Series([ts1, ts2, pd.NaT], dtype=object)
        assert ser.max() == ts1
        assert ser.min() == ts2

    def test_minmax_object_tzaware_with_nat(self):
        # GH#58707: concatenating tz-aware with tz-naive NaT gives object dtype
        ts = pd.Timestamp("2024-05-13 12:00:00", tz="America/New_York")
        ser = pd.concat([pd.Series([ts]), pd.Series([pd.NaT])])
        assert ser.dtype == object
        assert ser.max() == ts
        assert ser.min() == ts

    def test_minmax_object_all_na(self):
        # GH#65500
        ser = pd.Series([np.nan, np.nan], dtype=object)
        assert pd.isna(ser.min())
        assert pd.isna(ser.max())

    @pytest.mark.parametrize(
        "data",
        [
            ["a", None, "d"],
            # pre-3.1 this silently returned 1.0/3.0 rather than propagating
            [1.0, np.nan, 3.0],
            [date(2018, 1, 1), None, date(2018, 1, 3)],
            [pd.Timestamp("2024-05-13", tz="America/New_York"), pd.NaT],
            [np.nan, np.nan],
        ],
    )
    def test_minmax_object_with_na_skipna_false(self, data):
        # GH#4147: object dtype used to compare NAs as-is under skipna=False,
        # raising or returning a wrong answer instead of propagating the NA
        ser = pd.Series(data, dtype=object)
        assert pd.isna(ser.min(skipna=False))
        assert pd.isna(ser.max(skipna=False))

    @pytest.mark.parametrize(
        "data, exp_argmin, exp_argmax",
        [
            (["a", None, "d"], 0, 2),
            ([(1, 3), np.nan, (2, 2)], 0, 2),
            ([date(2018, 1, 1), None, date(2018, 1, 3)], 0, 2),
            ([date(2018, 1, 1), pd.NaT, date(2018, 1, 3)], 0, 2),
            ([np.nan, pd.Timestamp("2024-05-13", tz="America/New_York")], 1, 1),
        ],
    )
    def test_idxminmax_object_with_na(self, data, exp_argmin, exp_argmax):
        # GH#4147: the +/-inf fill for NA entries is not comparable with
        # strings/tuples/dates, so idxmin/idxmax raised TypeError
        ser = pd.Series(data, dtype=object)
        assert ser.idxmin() == exp_argmin
        assert ser.idxmax() == exp_argmax

    @pytest.mark.parametrize("data", [["a", None, "b"], [None, "a", "b"]])
    def test_minmax_argminmax_object_index_with_na(self, data):
        # GH#4147: Index reductions go through the same nanops code, but
        # Index.min/max short-circuit on hasnans before reaching it
        idx = pd.Index(data, dtype=object)
        assert idx.min() == "a"
        assert idx.max() == "b"
        assert pd.isna(idx.min(skipna=False))
        assert pd.isna(idx.max(skipna=False))

        assert idx.argmin() == data.index("a")
        assert idx.argmax() == data.index("b")
        msg = "Encountered an NA value with skipna=False"
        with pytest.raises(ValueError, match=msg):
            idx.argmin(skipna=False)
        with pytest.raises(ValueError, match=msg):
            idx.argmax(skipna=False)

    def test_minmax_object_incomparable_skipna_false(self):
        # GH#4147: the slice is nulled out anyway, so values that cannot be
        # compared with each other must not be compared
        ser = pd.Series([1, "a", np.nan], dtype=object)
        assert pd.isna(ser.min(skipna=False))
        assert pd.isna(ser.max(skipna=False))

        # idxmin/idxmax raise on the NA rather than on the comparison
        msg = "Encountered an NA value with skipna=False"
        with pytest.raises(ValueError, match=msg):
            ser.idxmin(skipna=False)
        with pytest.raises(ValueError, match=msg):
            ser.idxmax(skipna=False)

        # without an NA there is nothing to null out, so this still raises
        msg = "not supported between instances of"
        with pytest.raises(TypeError, match=msg):
            pd.Series([1, "a"], dtype=object).min(skipna=False)
        with pytest.raises(TypeError, match=msg):
            pd.Series([1, "a"], dtype=object).idxmin(skipna=False)

    @pytest.mark.parametrize("how", ["sum", "prod"])
    @pytest.mark.parametrize(
        "na_value, expected",
        [
            (pd.NA, pd.NA),
            (Decimal("NaN"), Decimal("NaN")),
            (np.nan, np.nan),
        ],
    )
    def test_sum_prod_object_skipna_false_keeps_na_type(self, how, na_value, expected):
        # GH#4147: an object NA that supports the operation propagates on its
        # own and says more than a bare nan would, so it must not be replaced
        result = getattr(pd.Series([1, na_value, 3], dtype=object), how)(skipna=False)
        assert result is expected or (pd.isna(result) and pd.isna(expected))
        assert type(result) is type(expected)

    @pytest.mark.parametrize("how, exp_no_na", [("sum", 5), ("prod", 6)])
    def test_sum_prod_object_with_na_skipna_false(self, how, exp_no_na):
        # GH#4147: an object NA raises rather than propagating through the
        # arithmetic, so sum/prod raised TypeError instead of returning NA
        assert pd.isna(getattr(pd.Series([2, None], dtype=object), how)(skipna=False))

        # the slice is nulled out anyway, so values that do not support the
        # operation with each other must not be combined
        assert pd.isna(
            getattr(pd.Series([1, "a", None], dtype=object), how)(skipna=False)
        )

        # without an NA there is nothing to null out
        ser = pd.Series([2, 3], dtype=object)
        assert getattr(ser, how)(skipna=False) == exp_no_na
        # ... and an explicit larger min_count is still honoured
        assert pd.isna(getattr(ser, how)(skipna=False, min_count=5))

    def test_idxminmax_object_all_na(self):
        # GH#4147: matches the float64 message rather than raising TypeError
        ser = pd.Series([np.nan, None], dtype=object)
        with pytest.raises(ValueError, match="Encountered all NA values"):
            ser.idxmin()
        with pytest.raises(ValueError, match="Encountered all NA values"):
            ser.idxmax()

    @pytest.mark.parametrize("data", [["a", "d"], [(1, 3), (2, 2)]])
    def test_minmax_object_skipna_false_no_na(self, data):
        # GH#4147: without NAs, skipna=False still reduces normally
        ser = pd.Series(data, dtype=object)
        assert ser.min(skipna=False) == data[0]
        assert ser.max(skipna=False) == data[1]

    def test_idxminmax_object_dtype(self):
        # pre-2.1 object-dtype was disallowed for argmin/max
        ser = pd.Series(["foo", "bar", "baz"])
        assert ser.idxmax() == 0
        assert ser.idxmax(skipna=False) == 0
        assert ser.idxmin() == 1
        assert ser.idxmin(skipna=False) == 1

        ser2 = pd.Series([(1,), (2,)])
        assert ser2.idxmax() == 1
        assert ser2.idxmax(skipna=False) == 1
        assert ser2.idxmin() == 0
        assert ser2.idxmin(skipna=False) == 0

        # GH#4147: NA entries used to be compared against the string values and
        # raise TypeError; they are skipped now, and skipna=False raises the
        # same ValueError as it does for other dtypes
        ser3 = pd.Series(
            ["foo", "foo", "bar", "bar", None, np.nan, "baz"], dtype=object
        )
        assert ser3.idxmax() == 0
        assert ser3.idxmin() == 2
        msg = "Encountered an NA value with skipna=False"
        with pytest.raises(ValueError, match=msg):
            ser3.idxmax(skipna=False)
        with pytest.raises(ValueError, match=msg):
            ser3.idxmin(skipna=False)

    def test_idxminmax_object_frame(self):
        # GH#4279
        df = pd.DataFrame([["zimm", 2.5], ["biff", 1.0], ["bid", 12.0]])
        res = df.idxmax()
        exp = pd.Series([0, 2])
        tm.assert_series_equal(res, exp)

    def test_idxminmax_object_tuples(self):
        # GH#43697
        ser = pd.Series([(1, 3), (2, 2), (3, 1)])
        assert ser.idxmax() == 2
        assert ser.idxmin() == 0
        assert ser.idxmax(skipna=False) == 2
        assert ser.idxmin(skipna=False) == 0

    def test_idxminmax_object_decimals(self):
        # GH#40685
        df = pd.DataFrame(
            {
                "idx": [0, 1],
                "x": [Decimal("8.68"), Decimal("42.23")],
                "y": [Decimal("7.11"), Decimal("79.61")],
            }
        )
        res = df.idxmax()
        exp = pd.Series({"idx": 1, "x": 1, "y": 1})
        tm.assert_series_equal(res, exp)

        res2 = df.idxmin()
        exp2 = exp - 1
        tm.assert_series_equal(res2, exp2)

    def test_argminmax_object_ints(self):
        # GH#18021
        ser = pd.Series([0, 1], dtype="object")
        assert ser.argmax() == 1
        assert ser.argmin() == 0
        assert ser.argmax(skipna=False) == 1
        assert ser.argmin(skipna=False) == 0

    def test_idxminmax_with_inf(self):
        # For numeric data with NA and Inf (GH #13595)
        s = pd.Series([0, -np.inf, np.inf, np.nan])

        assert s.idxmin() == 1
        with pytest.raises(ValueError, match="Encountered an NA value"):
            s.idxmin(skipna=False)

        assert s.idxmax() == 2
        with pytest.raises(ValueError, match="Encountered an NA value"):
            s.idxmax(skipna=False)

    def test_sum_uint64(self):
        # GH 53401
        s = pd.Series([10000000000000000000], dtype="uint64")
        result = s.sum()
        expected = np.uint64(10000000000000000000)
        tm.assert_almost_equal(result, expected)

    def test_signedness_preserved_after_sum(self, using_python_scalars):
        # GH 37491
        ser = pd.Series([1, 2, 3, 4])

        result = ser.astype("uint8").sum()
        if using_python_scalars:
            assert isinstance(result, int)
        else:
            assert isinstance(result, np.uint64)


class TestDatetime64SeriesReductions:
    # Note: the name TestDatetime64SeriesReductions indicates these tests
    #  were moved from a series-specific test file, _not_ that these tests are
    #  intended long-term to be series-specific

    @pytest.mark.parametrize(
        "nat_ser",
        [
            pd.Series([pd.NaT, pd.NaT]),
            pd.Series([pd.NaT, pd.Timedelta("nat")]),
            pd.Series([pd.Timedelta("nat"), pd.Timedelta("nat")]),
        ],
    )
    def test_minmax_nat_series(self, nat_ser):
        # GH#23282
        assert nat_ser.min() is pd.NaT
        assert nat_ser.max() is pd.NaT
        assert nat_ser.min(skipna=False) is pd.NaT
        assert nat_ser.max(skipna=False) is pd.NaT

    @pytest.mark.parametrize(
        "nat_df",
        [
            [pd.NaT, pd.NaT],
            [pd.NaT, pd.Timedelta("nat")],
            [pd.Timedelta("nat"), pd.Timedelta("nat")],
        ],
    )
    def test_minmax_nat_dataframe(self, nat_df):
        # GH#23282
        nat_df = pd.DataFrame(nat_df)
        assert nat_df.min()[0] is pd.NaT
        assert nat_df.max()[0] is pd.NaT
        assert nat_df.min(skipna=False)[0] is pd.NaT
        assert nat_df.max(skipna=False)[0] is pd.NaT

    def test_min_max(self):
        rng = pd.date_range("1/1/2000", "12/31/2000")
        rng2 = rng.take(np.random.default_rng(2).permutation(len(rng)))

        the_min = rng2.min()
        the_max = rng2.max()
        assert isinstance(the_min, pd.Timestamp)
        assert isinstance(the_max, pd.Timestamp)
        assert the_min == rng[0]
        assert the_max == rng[-1]

        assert rng.min() == rng[0]
        assert rng.max() == rng[-1]

    def test_min_max_series(self):
        rng = pd.date_range("1/1/2000", periods=10, freq="4h")
        lvls = ["A", "A", "A", "B", "B", "B", "C", "C", "C", "C"]
        df = pd.DataFrame(
            {
                "TS": rng,
                "V": np.random.default_rng(2).standard_normal(len(rng)),
                "L": lvls,
            }
        )

        result = df.TS.max()
        exp = pd.Timestamp(df.TS.iat[-1])
        assert isinstance(result, pd.Timestamp)
        assert result == exp

        result = df.TS.min()
        exp = pd.Timestamp(df.TS.iat[0])
        assert isinstance(result, pd.Timestamp)
        assert result == exp


class TestCategoricalSeriesReductions:
    # Note: the name TestCategoricalSeriesReductions indicates these tests
    #  were moved from a series-specific test file, _not_ that these tests are
    #  intended long-term to be series-specific

    @pytest.mark.parametrize("function", ["min", "max"])
    def test_min_max_unordered_raises(self, function):
        # unordered cats have no min/max
        cat = pd.Series(pd.Categorical(["a", "b", "c", "d"], ordered=False))
        msg = f"Categorical is not ordered for operation {function}"
        with pytest.raises(TypeError, match=msg):
            getattr(cat, function)()

    @pytest.mark.parametrize(
        "values, categories",
        [
            (list("abc"), list("abc")),
            (list("abc"), list("cba")),
            ([*list("abc"), np.nan], list("cba")),
            ([1, 2, 3], [3, 2, 1]),
            ([1, 2, 3, np.nan], [3, 2, 1]),
        ],
    )
    @pytest.mark.parametrize("function", ["min", "max"])
    def test_min_max_ordered(self, values, categories, function):
        # GH 25303
        cat = pd.Series(pd.Categorical(values, categories=categories, ordered=True))
        result = getattr(cat, function)(skipna=True)
        expected = categories[0] if function == "min" else categories[2]
        assert result == expected

    @pytest.mark.parametrize("function", ["min", "max"])
    def test_min_max_ordered_with_nan_only(self, function, skipna):
        # https://github.com/pandas-dev/pandas/issues/33450
        cat = pd.Series(pd.Categorical([np.nan], categories=[1, 2], ordered=True))
        result = getattr(cat, function)(skipna=skipna)
        assert result is np.nan

    @pytest.mark.parametrize("function", ["min", "max"])
    def test_min_max_skipna(self, function, skipna):
        cat = pd.Series(
            pd.Categorical(["a", "b", np.nan, "a"], categories=["b", "a"], ordered=True)
        )
        result = getattr(cat, function)(skipna=skipna)

        if skipna is True:
            expected = "b" if function == "min" else "a"
            assert result == expected
        else:
            assert result is np.nan


class TestSeriesMode:
    # Note: the name TestSeriesMode indicates these tests
    #  were moved from a series-specific test file, _not_ that these tests are
    #  intended long-term to be series-specific

    def test_mode_empty(self, dropna):
        s = pd.Series([], dtype=np.float64)
        result = s.mode(dropna)
        tm.assert_series_equal(result, s)

    @pytest.mark.parametrize(
        "dropna, data, expected",
        [
            (True, [1, 1, 1, 2], [1]),
            (True, [1, 1, 1, 2, 3, 3, 3], [1, 3]),
            (False, [1, 1, 1, 2], [1]),
            (False, [1, 1, 1, 2, 3, 3, 3], [1, 3]),
        ],
    )
    def test_mode_numerical(self, dropna, data, expected, any_real_numpy_dtype):
        s = pd.Series(data, dtype=any_real_numpy_dtype)
        result = s.mode(dropna)
        expected = pd.Series(expected, dtype=any_real_numpy_dtype)
        tm.assert_series_equal(result, expected)

    @pytest.mark.parametrize("dropna, expected", [(True, [1.0]), (False, [1, np.nan])])
    def test_mode_numerical_nan(self, dropna, expected):
        s = pd.Series([1, 1, 2, np.nan, np.nan])
        result = s.mode(dropna)
        expected = pd.Series(expected)
        tm.assert_series_equal(result, expected)

    @pytest.mark.parametrize(
        "dropna, expected1, expected2",
        [
            (True, ["b"], ["bar"]),
            (False, ["b"], [np.nan]),
        ],
    )
    def test_mode_object(self, dropna, expected1, expected2):
        # Test string and object types.
        data = ["a"] * 2 + ["b"] * 3

        s = pd.Series(data, dtype="c")
        result = s.mode(dropna)
        expected1 = pd.Series(expected1, dtype="c")
        tm.assert_series_equal(result, expected1)

        data = ["foo", "bar", "bar", np.nan, np.nan, np.nan]

        s = pd.Series(data, dtype=object)
        result = s.mode(dropna)
        expected2 = pd.Series(expected2, dtype=object)
        tm.assert_series_equal(result, expected2)

    @pytest.mark.parametrize(
        "dropna, expected1, expected2",
        [
            (True, ["b"], ["bar"]),
            (False, ["b"], [np.nan]),
        ],
    )
    def test_mode_string(self, dropna, expected1, expected2, any_string_dtype):
        # Test string and object types.
        data = ["a"] * 2 + ["b"] * 3

        s = pd.Series(data, dtype=any_string_dtype)
        result = s.mode(dropna)
        expected1 = pd.Series(expected1, dtype=any_string_dtype)
        tm.assert_series_equal(result, expected1)

        data = ["foo", "bar", "bar", np.nan, np.nan, np.nan]

        s = pd.Series(data, dtype=any_string_dtype)
        result = s.mode(dropna)
        expected2 = pd.Series(expected2, dtype=any_string_dtype)
        tm.assert_series_equal(result, expected2)

    @pytest.mark.parametrize(
        "dropna, expected1, expected2",
        [(True, ["foo"], ["foo"]), (False, ["foo"], [np.nan])],
    )
    def test_mode_mixeddtype(self, dropna, expected1, expected2):
        s = pd.Series([1, "foo", "foo"])
        result = s.mode(dropna)
        expected = pd.Series(expected1, dtype=object)
        tm.assert_series_equal(result, expected)

        s = pd.Series([1, "foo", "foo", np.nan, np.nan, np.nan])
        result = s.mode(dropna)
        expected = pd.Series(expected2, dtype=object)
        tm.assert_series_equal(result, expected)

    @pytest.mark.parametrize(
        "dropna, expected1, expected2",
        [
            (
                True,
                ["1900-05-03", "2011-01-03", "2013-01-02"],
                ["2011-01-03", "2013-01-02"],
            ),
            (False, [np.nan], [np.nan, "2011-01-03", "2013-01-02"]),
        ],
    )
    def test_mode_datetime(self, dropna, expected1, expected2):
        s = pd.Series(
            ["2011-01-03", "2013-01-02", "1900-05-03", "nan", "nan"], dtype="M8[ns]"
        )
        result = s.mode(dropna)
        expected1 = pd.Series(expected1, dtype="M8[ns]")
        tm.assert_series_equal(result, expected1)

        s = pd.Series(
            [
                "2011-01-03",
                "2013-01-02",
                "1900-05-03",
                "2011-01-03",
                "2013-01-02",
                "nan",
                "nan",
            ],
            dtype="M8[ns]",
        )
        result = s.mode(dropna)
        expected2 = pd.Series(expected2, dtype="M8[ns]")
        tm.assert_series_equal(result, expected2)

    @pytest.mark.parametrize(
        "dropna, expected1, expected2",
        [
            (True, ["-1 days", "0 days", "1 days"], ["2 min", "1 day"]),
            (False, [np.nan], [np.nan, "2 min", "1 day"]),
        ],
    )
    def test_mode_timedelta(self, dropna, expected1, expected2):
        # gh-5986: Test timedelta types.

        s = pd.Series(
            ["1 days", "-1 days", "0 days", "nan", "nan"], dtype="timedelta64[ns]"
        )
        result = s.mode(dropna)
        expected1 = pd.Series(expected1, dtype="timedelta64[ns]")
        tm.assert_series_equal(result, expected1)

        s = pd.Series(
            [
                "1 day",
                "1 day",
                "-1 day",
                "-1 day 2 min",
                "2 min",
                "2 min",
                "nan",
                "nan",
            ],
            dtype="timedelta64[ns]",
        )
        result = s.mode(dropna)
        expected2 = pd.Series(expected2, dtype="timedelta64[ns]")
        tm.assert_series_equal(result, expected2)

    @pytest.mark.parametrize(
        "dropna, expected1, expected2, expected3",
        [
            (
                True,
                pd.Categorical([1, 2], categories=[1, 2]),
                pd.Categorical(["a"], categories=[1, "a"]),
                pd.Categorical([3, 1], categories=[3, 2, 1], ordered=True),
            ),
            (
                False,
                pd.Categorical([np.nan], categories=[1, 2]),
                pd.Categorical([np.nan, "a"], categories=[1, "a"]),
                pd.Categorical([np.nan, 3, 1], categories=[3, 2, 1], ordered=True),
            ),
        ],
    )
    def test_mode_category(self, dropna, expected1, expected2, expected3):
        s = pd.Series(pd.Categorical([1, 2, np.nan, np.nan]))
        result = s.mode(dropna)
        expected1 = pd.Series(expected1, dtype="category")
        tm.assert_series_equal(result, expected1)

        s = pd.Series(pd.Categorical([1, "a", "a", np.nan, np.nan]))
        result = s.mode(dropna)
        expected2 = pd.Series(expected2, dtype="category")
        tm.assert_series_equal(result, expected2)

        s = pd.Series(
            pd.Categorical(
                [1, 1, 2, 3, 3, np.nan, np.nan], categories=[3, 2, 1], ordered=True
            )
        )
        result = s.mode(dropna)
        expected3 = pd.Series(expected3, dtype="category")
        tm.assert_series_equal(result, expected3)

    @pytest.mark.parametrize(
        "dropna, expected1, expected2",
        [(True, [2**63], [1, 2**63]), (False, [2**63], [1, 2**63])],
    )
    def test_mode_intoverflow(self, dropna, expected1, expected2):
        # Test for uint64 overflow.
        s = pd.Series([1, 2**63, 2**63], dtype=np.uint64)
        result = s.mode(dropna)
        expected1 = pd.Series(expected1, dtype=np.uint64)
        tm.assert_series_equal(result, expected1)

        s = pd.Series([1, 2**63], dtype=np.uint64)
        result = s.mode(dropna)
        expected2 = pd.Series(expected2, dtype=np.uint64)
        tm.assert_series_equal(result, expected2)

    def test_mode_sort_with_na(self):
        s = pd.Series([1, "foo", "foo", np.nan, np.nan])
        expected = pd.Series(["foo", np.nan], dtype=object)
        result = s.mode(dropna=False)
        tm.assert_series_equal(result, expected)

    def test_mode_boolean_with_na(self):
        # GH#42107
        ser = pd.Series([True, False, True, pd.NA], dtype="boolean")
        result = ser.mode()
        expected = pd.Series([True], dtype="boolean")
        tm.assert_series_equal(result, expected)

    @pytest.mark.parametrize(
        "array,expected,dtype",
        [
            (
                [0, 1j, 1, 1, 1 + 1j, 1 + 2j],
                [1],
                np.complex128,
            ),
            (
                [0, 1j, 1, 1, 1 + 1j, 1 + 2j],
                [1],
                np.complex64,
            ),
            (
                [1 + 1j, 2j, 1 + 1j],
                [1 + 1j],
                np.complex128,
            ),
        ],
    )
    def test_single_mode_value_complex(self, array, expected, dtype):
        result = pd.Series(array, dtype=dtype).mode()
        expected = pd.Series(expected, dtype=dtype)
        tm.assert_series_equal(result, expected)

    @pytest.mark.parametrize(
        "array,expected,dtype",
        [
            (
                # no modes
                [0, 1j, 1, 1 + 1j, 1 + 2j],
                [0j, 1j, 1 + 0j, 1 + 1j, 1 + 2j],
                np.complex128,
            ),
            (
                [1 + 1j, 2j, 1 + 1j, 2j, 3],
                [2j, 1 + 1j],
                np.complex64,
            ),
        ],
    )
    def test_multimode_complex(self, array, expected, dtype):
        # GH 17927
        # mode tries to sort multimodal series.
        # Complex numbers are sorted by their magnitude
        result = pd.Series(array, dtype=dtype).mode()
        expected = pd.Series(expected, dtype=dtype)
        tm.assert_series_equal(result, expected)


@pytest.mark.parametrize("op_name", ["var", "std", "sem"])
@pytest.mark.parametrize("ddof", [0, 1, 2])
def test_ea_reduction_method_ddof(any_numeric_ea_and_arrow_dtype, op_name, ddof):
    # GH#68391 the sem method rejected ddof instead of forwarding it
    arr = pd.array([1, 2, None, 4], dtype=any_numeric_ea_and_arrow_dtype)
    expected = getattr(pd.Series([1.0, 2.0, 4.0]), op_name)(ddof=ddof)

    tm.assert_almost_equal(getattr(arr, op_name)(ddof=ddof), expected)
    assert pd.isna(getattr(arr, op_name)(skipna=False, ddof=ddof))
