diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/period/test_asfreq.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/period/test_asfreq.py new file mode 100644 index 0000000000000000000000000000000000000000..6f6e47c138e7cbad4ef9f17a90c21c1e36a2d1df --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/period/test_asfreq.py @@ -0,0 +1,815 @@ +import pytest + +from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG + +from pandas import ( + Period, + Timestamp, + offsets, +) +import pandas._testing as tm + +bday_msg = "Period with BDay freq is deprecated" + + +class TestFreqConversion: + """Test frequency conversion of date objects""" + + @pytest.mark.filterwarnings("ignore:Period with BDay:FutureWarning") + @pytest.mark.parametrize("freq", ["Y", "Q", "M", "W", "B", "D"]) + def test_asfreq_near_zero(self, freq): + # GH#19643, GH#19650 + per = Period("0001-01-01", freq=freq) + tup1 = (per.year, per.hour, per.day) + + prev = per - 1 + assert prev.ordinal == per.ordinal - 1 + tup2 = (prev.year, prev.month, prev.day) + assert tup2 < tup1 + + def test_asfreq_near_zero_weekly(self): + # GH#19834 + per1 = Period("0001-01-01", "D") + 6 + per2 = Period("0001-01-01", "D") - 6 + week1 = per1.asfreq("W") + week2 = per2.asfreq("W") + assert week1 != week2 + assert week1.asfreq("D", "E") >= per1 + assert week2.asfreq("D", "S") <= per2 + + def test_to_timestamp_out_of_bounds(self): + # GH#19643, used to incorrectly give Timestamp in 1754 + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + per = Period("0001-01-01", freq="B") + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + per.to_timestamp() + + def test_asfreq_corner(self): + val = Period(freq="Y", year=2007) + result1 = val.asfreq("5min") + result2 = val.asfreq("min") + expected = Period("2007-12-31 23:59", freq="min") + assert result1.ordinal == expected.ordinal + assert result1.freqstr == "5min" + assert result2.ordinal == expected.ordinal + assert result2.freqstr == "min" + + def test_conv_annual(self): + # frequency conversion tests: from Annual Frequency + msg = INVALID_FREQ_ERR_MSG + + ival_A = Period(freq="Y", year=2007) + + ival_AJAN = Period(freq="Y-JAN", year=2007) + ival_AJUN = Period(freq="Y-JUN", year=2007) + ival_ANOV = Period(freq="Y-NOV", year=2007) + + ival_A_to_Q_start = Period(freq="Q", year=2007, quarter=1) + ival_A_to_Q_end = Period(freq="Q", year=2007, quarter=4) + ival_A_to_M_start = Period(freq="M", year=2007, month=1) + ival_A_to_M_end = Period(freq="M", year=2007, month=12) + ival_A_to_W_start = Period(freq="W", year=2007, month=1, day=1) + ival_A_to_W_end = Period(freq="W", year=2007, month=12, day=31) + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + ival_A_to_B_start = Period(freq="B", year=2007, month=1, day=1) + ival_A_to_B_end = Period(freq="B", year=2007, month=12, day=31) + ival_A_to_D_start = Period(freq="D", year=2007, month=1, day=1) + ival_A_to_D_end = Period(freq="D", year=2007, month=12, day=31) + ival_A_to_H_start = Period(freq="h", year=2007, month=1, day=1, hour=0) + ival_A_to_H_end = Period(freq="h", year=2007, month=12, day=31, hour=23) + ival_A_to_T_start = Period( + freq="Min", year=2007, month=1, day=1, hour=0, minute=0 + ) + ival_A_to_T_end = Period( + freq="Min", year=2007, month=12, day=31, hour=23, minute=59 + ) + ival_A_to_S_start = Period( + freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0 + ) + ival_A_to_S_end = Period( + freq="s", year=2007, month=12, day=31, hour=23, minute=59, second=59 + ) + + ival_AJAN_to_D_end = Period(freq="D", year=2007, month=1, day=31) + ival_AJAN_to_D_start = Period(freq="D", year=2006, month=2, day=1) + ival_AJUN_to_D_end = Period(freq="D", year=2007, month=6, day=30) + ival_AJUN_to_D_start = Period(freq="D", year=2006, month=7, day=1) + ival_ANOV_to_D_end = Period(freq="D", year=2007, month=11, day=30) + ival_ANOV_to_D_start = Period(freq="D", year=2006, month=12, day=1) + + assert ival_A.asfreq("Q", "s") == ival_A_to_Q_start + assert ival_A.asfreq("Q", "e") == ival_A_to_Q_end + assert ival_A.asfreq("M", "s") == ival_A_to_M_start + assert ival_A.asfreq("M", "E") == ival_A_to_M_end + assert ival_A.asfreq("W", "s") == ival_A_to_W_start + assert ival_A.asfreq("W", "E") == ival_A_to_W_end + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + assert ival_A.asfreq("B", "s") == ival_A_to_B_start + assert ival_A.asfreq("B", "E") == ival_A_to_B_end + assert ival_A.asfreq("D", "s") == ival_A_to_D_start + assert ival_A.asfreq("D", "E") == ival_A_to_D_end + with pytest.raises(ValueError, match=msg): + assert ival_A.asfreq("H", "s") == ival_A_to_H_start + assert ival_A.asfreq("H", "E") == ival_A_to_H_end + assert ival_A.asfreq("min", "s") == ival_A_to_T_start + assert ival_A.asfreq("min", "E") == ival_A_to_T_end + with pytest.raises(ValueError, match=msg): + assert ival_A.asfreq("T", "s") == ival_A_to_T_start + assert ival_A.asfreq("T", "E") == ival_A_to_T_end + assert ival_A.asfreq("S", "S") == ival_A_to_S_start + assert ival_A.asfreq("S", "E") == ival_A_to_S_end + + assert ival_AJAN.asfreq("D", "s") == ival_AJAN_to_D_start + assert ival_AJAN.asfreq("D", "E") == ival_AJAN_to_D_end + + assert ival_AJUN.asfreq("D", "s") == ival_AJUN_to_D_start + assert ival_AJUN.asfreq("D", "E") == ival_AJUN_to_D_end + + assert ival_ANOV.asfreq("D", "s") == ival_ANOV_to_D_start + assert ival_ANOV.asfreq("D", "E") == ival_ANOV_to_D_end + + assert ival_A.asfreq("Y") == ival_A + + def test_conv_quarterly(self): + # frequency conversion tests: from Quarterly Frequency + + ival_Q = Period(freq="Q", year=2007, quarter=1) + ival_Q_end_of_year = Period(freq="Q", year=2007, quarter=4) + + ival_QEJAN = Period(freq="Q-JAN", year=2007, quarter=1) + ival_QEJUN = Period(freq="Q-JUN", year=2007, quarter=1) + + ival_Q_to_A = Period(freq="Y", year=2007) + ival_Q_to_M_start = Period(freq="M", year=2007, month=1) + ival_Q_to_M_end = Period(freq="M", year=2007, month=3) + ival_Q_to_W_start = Period(freq="W", year=2007, month=1, day=1) + ival_Q_to_W_end = Period(freq="W", year=2007, month=3, day=31) + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + ival_Q_to_B_start = Period(freq="B", year=2007, month=1, day=1) + ival_Q_to_B_end = Period(freq="B", year=2007, month=3, day=30) + ival_Q_to_D_start = Period(freq="D", year=2007, month=1, day=1) + ival_Q_to_D_end = Period(freq="D", year=2007, month=3, day=31) + ival_Q_to_H_start = Period(freq="h", year=2007, month=1, day=1, hour=0) + ival_Q_to_H_end = Period(freq="h", year=2007, month=3, day=31, hour=23) + ival_Q_to_T_start = Period( + freq="Min", year=2007, month=1, day=1, hour=0, minute=0 + ) + ival_Q_to_T_end = Period( + freq="Min", year=2007, month=3, day=31, hour=23, minute=59 + ) + ival_Q_to_S_start = Period( + freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0 + ) + ival_Q_to_S_end = Period( + freq="s", year=2007, month=3, day=31, hour=23, minute=59, second=59 + ) + + ival_QEJAN_to_D_start = Period(freq="D", year=2006, month=2, day=1) + ival_QEJAN_to_D_end = Period(freq="D", year=2006, month=4, day=30) + + ival_QEJUN_to_D_start = Period(freq="D", year=2006, month=7, day=1) + ival_QEJUN_to_D_end = Period(freq="D", year=2006, month=9, day=30) + + assert ival_Q.asfreq("Y") == ival_Q_to_A + assert ival_Q_end_of_year.asfreq("Y") == ival_Q_to_A + + assert ival_Q.asfreq("M", "s") == ival_Q_to_M_start + assert ival_Q.asfreq("M", "E") == ival_Q_to_M_end + assert ival_Q.asfreq("W", "s") == ival_Q_to_W_start + assert ival_Q.asfreq("W", "E") == ival_Q_to_W_end + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + assert ival_Q.asfreq("B", "s") == ival_Q_to_B_start + assert ival_Q.asfreq("B", "E") == ival_Q_to_B_end + assert ival_Q.asfreq("D", "s") == ival_Q_to_D_start + assert ival_Q.asfreq("D", "E") == ival_Q_to_D_end + assert ival_Q.asfreq("h", "s") == ival_Q_to_H_start + assert ival_Q.asfreq("h", "E") == ival_Q_to_H_end + assert ival_Q.asfreq("Min", "s") == ival_Q_to_T_start + assert ival_Q.asfreq("Min", "E") == ival_Q_to_T_end + assert ival_Q.asfreq("s", "s") == ival_Q_to_S_start + assert ival_Q.asfreq("s", "E") == ival_Q_to_S_end + + assert ival_QEJAN.asfreq("D", "s") == ival_QEJAN_to_D_start + assert ival_QEJAN.asfreq("D", "E") == ival_QEJAN_to_D_end + assert ival_QEJUN.asfreq("D", "s") == ival_QEJUN_to_D_start + assert ival_QEJUN.asfreq("D", "E") == ival_QEJUN_to_D_end + + assert ival_Q.asfreq("Q") == ival_Q + + def test_conv_monthly(self): + # frequency conversion tests: from Monthly Frequency + + ival_M = Period(freq="M", year=2007, month=1) + ival_M_end_of_year = Period(freq="M", year=2007, month=12) + ival_M_end_of_quarter = Period(freq="M", year=2007, month=3) + ival_M_to_A = Period(freq="Y", year=2007) + ival_M_to_Q = Period(freq="Q", year=2007, quarter=1) + ival_M_to_W_start = Period(freq="W", year=2007, month=1, day=1) + ival_M_to_W_end = Period(freq="W", year=2007, month=1, day=31) + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + ival_M_to_B_start = Period(freq="B", year=2007, month=1, day=1) + ival_M_to_B_end = Period(freq="B", year=2007, month=1, day=31) + ival_M_to_D_start = Period(freq="D", year=2007, month=1, day=1) + ival_M_to_D_end = Period(freq="D", year=2007, month=1, day=31) + ival_M_to_H_start = Period(freq="h", year=2007, month=1, day=1, hour=0) + ival_M_to_H_end = Period(freq="h", year=2007, month=1, day=31, hour=23) + ival_M_to_T_start = Period( + freq="Min", year=2007, month=1, day=1, hour=0, minute=0 + ) + ival_M_to_T_end = Period( + freq="Min", year=2007, month=1, day=31, hour=23, minute=59 + ) + ival_M_to_S_start = Period( + freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0 + ) + ival_M_to_S_end = Period( + freq="s", year=2007, month=1, day=31, hour=23, minute=59, second=59 + ) + + assert ival_M.asfreq("Y") == ival_M_to_A + assert ival_M_end_of_year.asfreq("Y") == ival_M_to_A + assert ival_M.asfreq("Q") == ival_M_to_Q + assert ival_M_end_of_quarter.asfreq("Q") == ival_M_to_Q + + assert ival_M.asfreq("W", "s") == ival_M_to_W_start + assert ival_M.asfreq("W", "E") == ival_M_to_W_end + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + assert ival_M.asfreq("B", "s") == ival_M_to_B_start + assert ival_M.asfreq("B", "E") == ival_M_to_B_end + assert ival_M.asfreq("D", "s") == ival_M_to_D_start + assert ival_M.asfreq("D", "E") == ival_M_to_D_end + assert ival_M.asfreq("h", "s") == ival_M_to_H_start + assert ival_M.asfreq("h", "E") == ival_M_to_H_end + assert ival_M.asfreq("Min", "s") == ival_M_to_T_start + assert ival_M.asfreq("Min", "E") == ival_M_to_T_end + assert ival_M.asfreq("s", "s") == ival_M_to_S_start + assert ival_M.asfreq("s", "E") == ival_M_to_S_end + + assert ival_M.asfreq("M") == ival_M + + def test_conv_weekly(self): + # frequency conversion tests: from Weekly Frequency + ival_W = Period(freq="W", year=2007, month=1, day=1) + + ival_WSUN = Period(freq="W", year=2007, month=1, day=7) + ival_WSAT = Period(freq="W-SAT", year=2007, month=1, day=6) + ival_WFRI = Period(freq="W-FRI", year=2007, month=1, day=5) + ival_WTHU = Period(freq="W-THU", year=2007, month=1, day=4) + ival_WWED = Period(freq="W-WED", year=2007, month=1, day=3) + ival_WTUE = Period(freq="W-TUE", year=2007, month=1, day=2) + ival_WMON = Period(freq="W-MON", year=2007, month=1, day=1) + + ival_WSUN_to_D_start = Period(freq="D", year=2007, month=1, day=1) + ival_WSUN_to_D_end = Period(freq="D", year=2007, month=1, day=7) + ival_WSAT_to_D_start = Period(freq="D", year=2006, month=12, day=31) + ival_WSAT_to_D_end = Period(freq="D", year=2007, month=1, day=6) + ival_WFRI_to_D_start = Period(freq="D", year=2006, month=12, day=30) + ival_WFRI_to_D_end = Period(freq="D", year=2007, month=1, day=5) + ival_WTHU_to_D_start = Period(freq="D", year=2006, month=12, day=29) + ival_WTHU_to_D_end = Period(freq="D", year=2007, month=1, day=4) + ival_WWED_to_D_start = Period(freq="D", year=2006, month=12, day=28) + ival_WWED_to_D_end = Period(freq="D", year=2007, month=1, day=3) + ival_WTUE_to_D_start = Period(freq="D", year=2006, month=12, day=27) + ival_WTUE_to_D_end = Period(freq="D", year=2007, month=1, day=2) + ival_WMON_to_D_start = Period(freq="D", year=2006, month=12, day=26) + ival_WMON_to_D_end = Period(freq="D", year=2007, month=1, day=1) + + ival_W_end_of_year = Period(freq="W", year=2007, month=12, day=31) + ival_W_end_of_quarter = Period(freq="W", year=2007, month=3, day=31) + ival_W_end_of_month = Period(freq="W", year=2007, month=1, day=31) + ival_W_to_A = Period(freq="Y", year=2007) + ival_W_to_Q = Period(freq="Q", year=2007, quarter=1) + ival_W_to_M = Period(freq="M", year=2007, month=1) + + if Period(freq="D", year=2007, month=12, day=31).weekday == 6: + ival_W_to_A_end_of_year = Period(freq="Y", year=2007) + else: + ival_W_to_A_end_of_year = Period(freq="Y", year=2008) + + if Period(freq="D", year=2007, month=3, day=31).weekday == 6: + ival_W_to_Q_end_of_quarter = Period(freq="Q", year=2007, quarter=1) + else: + ival_W_to_Q_end_of_quarter = Period(freq="Q", year=2007, quarter=2) + + if Period(freq="D", year=2007, month=1, day=31).weekday == 6: + ival_W_to_M_end_of_month = Period(freq="M", year=2007, month=1) + else: + ival_W_to_M_end_of_month = Period(freq="M", year=2007, month=2) + + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + ival_W_to_B_start = Period(freq="B", year=2007, month=1, day=1) + ival_W_to_B_end = Period(freq="B", year=2007, month=1, day=5) + ival_W_to_D_start = Period(freq="D", year=2007, month=1, day=1) + ival_W_to_D_end = Period(freq="D", year=2007, month=1, day=7) + ival_W_to_H_start = Period(freq="h", year=2007, month=1, day=1, hour=0) + ival_W_to_H_end = Period(freq="h", year=2007, month=1, day=7, hour=23) + ival_W_to_T_start = Period( + freq="Min", year=2007, month=1, day=1, hour=0, minute=0 + ) + ival_W_to_T_end = Period( + freq="Min", year=2007, month=1, day=7, hour=23, minute=59 + ) + ival_W_to_S_start = Period( + freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0 + ) + ival_W_to_S_end = Period( + freq="s", year=2007, month=1, day=7, hour=23, minute=59, second=59 + ) + + assert ival_W.asfreq("Y") == ival_W_to_A + assert ival_W_end_of_year.asfreq("Y") == ival_W_to_A_end_of_year + + assert ival_W.asfreq("Q") == ival_W_to_Q + assert ival_W_end_of_quarter.asfreq("Q") == ival_W_to_Q_end_of_quarter + + assert ival_W.asfreq("M") == ival_W_to_M + assert ival_W_end_of_month.asfreq("M") == ival_W_to_M_end_of_month + + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + assert ival_W.asfreq("B", "s") == ival_W_to_B_start + assert ival_W.asfreq("B", "E") == ival_W_to_B_end + + assert ival_W.asfreq("D", "s") == ival_W_to_D_start + assert ival_W.asfreq("D", "E") == ival_W_to_D_end + + assert ival_WSUN.asfreq("D", "s") == ival_WSUN_to_D_start + assert ival_WSUN.asfreq("D", "E") == ival_WSUN_to_D_end + assert ival_WSAT.asfreq("D", "s") == ival_WSAT_to_D_start + assert ival_WSAT.asfreq("D", "E") == ival_WSAT_to_D_end + assert ival_WFRI.asfreq("D", "s") == ival_WFRI_to_D_start + assert ival_WFRI.asfreq("D", "E") == ival_WFRI_to_D_end + assert ival_WTHU.asfreq("D", "s") == ival_WTHU_to_D_start + assert ival_WTHU.asfreq("D", "E") == ival_WTHU_to_D_end + assert ival_WWED.asfreq("D", "s") == ival_WWED_to_D_start + assert ival_WWED.asfreq("D", "E") == ival_WWED_to_D_end + assert ival_WTUE.asfreq("D", "s") == ival_WTUE_to_D_start + assert ival_WTUE.asfreq("D", "E") == ival_WTUE_to_D_end + assert ival_WMON.asfreq("D", "s") == ival_WMON_to_D_start + assert ival_WMON.asfreq("D", "E") == ival_WMON_to_D_end + + assert ival_W.asfreq("h", "s") == ival_W_to_H_start + assert ival_W.asfreq("h", "E") == ival_W_to_H_end + assert ival_W.asfreq("Min", "s") == ival_W_to_T_start + assert ival_W.asfreq("Min", "E") == ival_W_to_T_end + assert ival_W.asfreq("s", "s") == ival_W_to_S_start + assert ival_W.asfreq("s", "E") == ival_W_to_S_end + + assert ival_W.asfreq("W") == ival_W + + msg = INVALID_FREQ_ERR_MSG + with pytest.raises(ValueError, match=msg): + ival_W.asfreq("WK") + + def test_conv_weekly_legacy(self): + # frequency conversion tests: from Weekly Frequency + msg = INVALID_FREQ_ERR_MSG + with pytest.raises(ValueError, match=msg): + Period(freq="WK", year=2007, month=1, day=1) + + with pytest.raises(ValueError, match=msg): + Period(freq="WK-SAT", year=2007, month=1, day=6) + with pytest.raises(ValueError, match=msg): + Period(freq="WK-FRI", year=2007, month=1, day=5) + with pytest.raises(ValueError, match=msg): + Period(freq="WK-THU", year=2007, month=1, day=4) + with pytest.raises(ValueError, match=msg): + Period(freq="WK-WED", year=2007, month=1, day=3) + with pytest.raises(ValueError, match=msg): + Period(freq="WK-TUE", year=2007, month=1, day=2) + with pytest.raises(ValueError, match=msg): + Period(freq="WK-MON", year=2007, month=1, day=1) + + def test_conv_business(self): + # frequency conversion tests: from Business Frequency" + + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + ival_B = Period(freq="B", year=2007, month=1, day=1) + ival_B_end_of_year = Period(freq="B", year=2007, month=12, day=31) + ival_B_end_of_quarter = Period(freq="B", year=2007, month=3, day=30) + ival_B_end_of_month = Period(freq="B", year=2007, month=1, day=31) + ival_B_end_of_week = Period(freq="B", year=2007, month=1, day=5) + + ival_B_to_A = Period(freq="Y", year=2007) + ival_B_to_Q = Period(freq="Q", year=2007, quarter=1) + ival_B_to_M = Period(freq="M", year=2007, month=1) + ival_B_to_W = Period(freq="W", year=2007, month=1, day=7) + ival_B_to_D = Period(freq="D", year=2007, month=1, day=1) + ival_B_to_H_start = Period(freq="h", year=2007, month=1, day=1, hour=0) + ival_B_to_H_end = Period(freq="h", year=2007, month=1, day=1, hour=23) + ival_B_to_T_start = Period( + freq="Min", year=2007, month=1, day=1, hour=0, minute=0 + ) + ival_B_to_T_end = Period( + freq="Min", year=2007, month=1, day=1, hour=23, minute=59 + ) + ival_B_to_S_start = Period( + freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0 + ) + ival_B_to_S_end = Period( + freq="s", year=2007, month=1, day=1, hour=23, minute=59, second=59 + ) + + assert ival_B.asfreq("Y") == ival_B_to_A + assert ival_B_end_of_year.asfreq("Y") == ival_B_to_A + assert ival_B.asfreq("Q") == ival_B_to_Q + assert ival_B_end_of_quarter.asfreq("Q") == ival_B_to_Q + assert ival_B.asfreq("M") == ival_B_to_M + assert ival_B_end_of_month.asfreq("M") == ival_B_to_M + assert ival_B.asfreq("W") == ival_B_to_W + assert ival_B_end_of_week.asfreq("W") == ival_B_to_W + + assert ival_B.asfreq("D") == ival_B_to_D + + assert ival_B.asfreq("h", "s") == ival_B_to_H_start + assert ival_B.asfreq("h", "E") == ival_B_to_H_end + assert ival_B.asfreq("Min", "s") == ival_B_to_T_start + assert ival_B.asfreq("Min", "E") == ival_B_to_T_end + assert ival_B.asfreq("s", "s") == ival_B_to_S_start + assert ival_B.asfreq("s", "E") == ival_B_to_S_end + + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + assert ival_B.asfreq("B") == ival_B + + def test_conv_daily(self): + # frequency conversion tests: from Business Frequency" + + ival_D = Period(freq="D", year=2007, month=1, day=1) + ival_D_end_of_year = Period(freq="D", year=2007, month=12, day=31) + ival_D_end_of_quarter = Period(freq="D", year=2007, month=3, day=31) + ival_D_end_of_month = Period(freq="D", year=2007, month=1, day=31) + ival_D_end_of_week = Period(freq="D", year=2007, month=1, day=7) + + ival_D_friday = Period(freq="D", year=2007, month=1, day=5) + ival_D_saturday = Period(freq="D", year=2007, month=1, day=6) + ival_D_sunday = Period(freq="D", year=2007, month=1, day=7) + + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + ival_B_friday = Period(freq="B", year=2007, month=1, day=5) + ival_B_monday = Period(freq="B", year=2007, month=1, day=8) + + ival_D_to_A = Period(freq="Y", year=2007) + + ival_Deoq_to_AJAN = Period(freq="Y-JAN", year=2008) + ival_Deoq_to_AJUN = Period(freq="Y-JUN", year=2007) + ival_Deoq_to_ADEC = Period(freq="Y-DEC", year=2007) + + ival_D_to_QEJAN = Period(freq="Q-JAN", year=2007, quarter=4) + ival_D_to_QEJUN = Period(freq="Q-JUN", year=2007, quarter=3) + ival_D_to_QEDEC = Period(freq="Q-DEC", year=2007, quarter=1) + + ival_D_to_M = Period(freq="M", year=2007, month=1) + ival_D_to_W = Period(freq="W", year=2007, month=1, day=7) + + ival_D_to_H_start = Period(freq="h", year=2007, month=1, day=1, hour=0) + ival_D_to_H_end = Period(freq="h", year=2007, month=1, day=1, hour=23) + ival_D_to_T_start = Period( + freq="Min", year=2007, month=1, day=1, hour=0, minute=0 + ) + ival_D_to_T_end = Period( + freq="Min", year=2007, month=1, day=1, hour=23, minute=59 + ) + ival_D_to_S_start = Period( + freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0 + ) + ival_D_to_S_end = Period( + freq="s", year=2007, month=1, day=1, hour=23, minute=59, second=59 + ) + + assert ival_D.asfreq("Y") == ival_D_to_A + + assert ival_D_end_of_quarter.asfreq("Y-JAN") == ival_Deoq_to_AJAN + assert ival_D_end_of_quarter.asfreq("Y-JUN") == ival_Deoq_to_AJUN + assert ival_D_end_of_quarter.asfreq("Y-DEC") == ival_Deoq_to_ADEC + + assert ival_D_end_of_year.asfreq("Y") == ival_D_to_A + assert ival_D_end_of_quarter.asfreq("Q") == ival_D_to_QEDEC + assert ival_D.asfreq("Q-JAN") == ival_D_to_QEJAN + assert ival_D.asfreq("Q-JUN") == ival_D_to_QEJUN + assert ival_D.asfreq("Q-DEC") == ival_D_to_QEDEC + assert ival_D.asfreq("M") == ival_D_to_M + assert ival_D_end_of_month.asfreq("M") == ival_D_to_M + assert ival_D.asfreq("W") == ival_D_to_W + assert ival_D_end_of_week.asfreq("W") == ival_D_to_W + + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + assert ival_D_friday.asfreq("B") == ival_B_friday + assert ival_D_saturday.asfreq("B", "s") == ival_B_friday + assert ival_D_saturday.asfreq("B", "E") == ival_B_monday + assert ival_D_sunday.asfreq("B", "s") == ival_B_friday + assert ival_D_sunday.asfreq("B", "E") == ival_B_monday + + assert ival_D.asfreq("h", "s") == ival_D_to_H_start + assert ival_D.asfreq("h", "E") == ival_D_to_H_end + assert ival_D.asfreq("Min", "s") == ival_D_to_T_start + assert ival_D.asfreq("Min", "E") == ival_D_to_T_end + assert ival_D.asfreq("s", "s") == ival_D_to_S_start + assert ival_D.asfreq("s", "E") == ival_D_to_S_end + + assert ival_D.asfreq("D") == ival_D + + def test_conv_hourly(self): + # frequency conversion tests: from Hourly Frequency" + + ival_H = Period(freq="h", year=2007, month=1, day=1, hour=0) + ival_H_end_of_year = Period(freq="h", year=2007, month=12, day=31, hour=23) + ival_H_end_of_quarter = Period(freq="h", year=2007, month=3, day=31, hour=23) + ival_H_end_of_month = Period(freq="h", year=2007, month=1, day=31, hour=23) + ival_H_end_of_week = Period(freq="h", year=2007, month=1, day=7, hour=23) + ival_H_end_of_day = Period(freq="h", year=2007, month=1, day=1, hour=23) + ival_H_end_of_bus = Period(freq="h", year=2007, month=1, day=1, hour=23) + + ival_H_to_A = Period(freq="Y", year=2007) + ival_H_to_Q = Period(freq="Q", year=2007, quarter=1) + ival_H_to_M = Period(freq="M", year=2007, month=1) + ival_H_to_W = Period(freq="W", year=2007, month=1, day=7) + ival_H_to_D = Period(freq="D", year=2007, month=1, day=1) + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + ival_H_to_B = Period(freq="B", year=2007, month=1, day=1) + + ival_H_to_T_start = Period( + freq="Min", year=2007, month=1, day=1, hour=0, minute=0 + ) + ival_H_to_T_end = Period( + freq="Min", year=2007, month=1, day=1, hour=0, minute=59 + ) + ival_H_to_S_start = Period( + freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0 + ) + ival_H_to_S_end = Period( + freq="s", year=2007, month=1, day=1, hour=0, minute=59, second=59 + ) + + assert ival_H.asfreq("Y") == ival_H_to_A + assert ival_H_end_of_year.asfreq("Y") == ival_H_to_A + assert ival_H.asfreq("Q") == ival_H_to_Q + assert ival_H_end_of_quarter.asfreq("Q") == ival_H_to_Q + assert ival_H.asfreq("M") == ival_H_to_M + assert ival_H_end_of_month.asfreq("M") == ival_H_to_M + assert ival_H.asfreq("W") == ival_H_to_W + assert ival_H_end_of_week.asfreq("W") == ival_H_to_W + assert ival_H.asfreq("D") == ival_H_to_D + assert ival_H_end_of_day.asfreq("D") == ival_H_to_D + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + assert ival_H.asfreq("B") == ival_H_to_B + assert ival_H_end_of_bus.asfreq("B") == ival_H_to_B + + assert ival_H.asfreq("Min", "s") == ival_H_to_T_start + assert ival_H.asfreq("Min", "E") == ival_H_to_T_end + assert ival_H.asfreq("s", "s") == ival_H_to_S_start + assert ival_H.asfreq("s", "E") == ival_H_to_S_end + + assert ival_H.asfreq("h") == ival_H + + def test_conv_minutely(self): + # frequency conversion tests: from Minutely Frequency" + + ival_T = Period(freq="Min", year=2007, month=1, day=1, hour=0, minute=0) + ival_T_end_of_year = Period( + freq="Min", year=2007, month=12, day=31, hour=23, minute=59 + ) + ival_T_end_of_quarter = Period( + freq="Min", year=2007, month=3, day=31, hour=23, minute=59 + ) + ival_T_end_of_month = Period( + freq="Min", year=2007, month=1, day=31, hour=23, minute=59 + ) + ival_T_end_of_week = Period( + freq="Min", year=2007, month=1, day=7, hour=23, minute=59 + ) + ival_T_end_of_day = Period( + freq="Min", year=2007, month=1, day=1, hour=23, minute=59 + ) + ival_T_end_of_bus = Period( + freq="Min", year=2007, month=1, day=1, hour=23, minute=59 + ) + ival_T_end_of_hour = Period( + freq="Min", year=2007, month=1, day=1, hour=0, minute=59 + ) + + ival_T_to_A = Period(freq="Y", year=2007) + ival_T_to_Q = Period(freq="Q", year=2007, quarter=1) + ival_T_to_M = Period(freq="M", year=2007, month=1) + ival_T_to_W = Period(freq="W", year=2007, month=1, day=7) + ival_T_to_D = Period(freq="D", year=2007, month=1, day=1) + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + ival_T_to_B = Period(freq="B", year=2007, month=1, day=1) + ival_T_to_H = Period(freq="h", year=2007, month=1, day=1, hour=0) + + ival_T_to_S_start = Period( + freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0 + ) + ival_T_to_S_end = Period( + freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=59 + ) + + assert ival_T.asfreq("Y") == ival_T_to_A + assert ival_T_end_of_year.asfreq("Y") == ival_T_to_A + assert ival_T.asfreq("Q") == ival_T_to_Q + assert ival_T_end_of_quarter.asfreq("Q") == ival_T_to_Q + assert ival_T.asfreq("M") == ival_T_to_M + assert ival_T_end_of_month.asfreq("M") == ival_T_to_M + assert ival_T.asfreq("W") == ival_T_to_W + assert ival_T_end_of_week.asfreq("W") == ival_T_to_W + assert ival_T.asfreq("D") == ival_T_to_D + assert ival_T_end_of_day.asfreq("D") == ival_T_to_D + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + assert ival_T.asfreq("B") == ival_T_to_B + assert ival_T_end_of_bus.asfreq("B") == ival_T_to_B + assert ival_T.asfreq("h") == ival_T_to_H + assert ival_T_end_of_hour.asfreq("h") == ival_T_to_H + + assert ival_T.asfreq("s", "s") == ival_T_to_S_start + assert ival_T.asfreq("s", "E") == ival_T_to_S_end + + assert ival_T.asfreq("Min") == ival_T + + def test_conv_secondly(self): + # frequency conversion tests: from Secondly Frequency" + + ival_S = Period(freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=0) + ival_S_end_of_year = Period( + freq="s", year=2007, month=12, day=31, hour=23, minute=59, second=59 + ) + ival_S_end_of_quarter = Period( + freq="s", year=2007, month=3, day=31, hour=23, minute=59, second=59 + ) + ival_S_end_of_month = Period( + freq="s", year=2007, month=1, day=31, hour=23, minute=59, second=59 + ) + ival_S_end_of_week = Period( + freq="s", year=2007, month=1, day=7, hour=23, minute=59, second=59 + ) + ival_S_end_of_day = Period( + freq="s", year=2007, month=1, day=1, hour=23, minute=59, second=59 + ) + ival_S_end_of_bus = Period( + freq="s", year=2007, month=1, day=1, hour=23, minute=59, second=59 + ) + ival_S_end_of_hour = Period( + freq="s", year=2007, month=1, day=1, hour=0, minute=59, second=59 + ) + ival_S_end_of_minute = Period( + freq="s", year=2007, month=1, day=1, hour=0, minute=0, second=59 + ) + + ival_S_to_A = Period(freq="Y", year=2007) + ival_S_to_Q = Period(freq="Q", year=2007, quarter=1) + ival_S_to_M = Period(freq="M", year=2007, month=1) + ival_S_to_W = Period(freq="W", year=2007, month=1, day=7) + ival_S_to_D = Period(freq="D", year=2007, month=1, day=1) + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + ival_S_to_B = Period(freq="B", year=2007, month=1, day=1) + ival_S_to_H = Period(freq="h", year=2007, month=1, day=1, hour=0) + ival_S_to_T = Period(freq="Min", year=2007, month=1, day=1, hour=0, minute=0) + + assert ival_S.asfreq("Y") == ival_S_to_A + assert ival_S_end_of_year.asfreq("Y") == ival_S_to_A + assert ival_S.asfreq("Q") == ival_S_to_Q + assert ival_S_end_of_quarter.asfreq("Q") == ival_S_to_Q + assert ival_S.asfreq("M") == ival_S_to_M + assert ival_S_end_of_month.asfreq("M") == ival_S_to_M + assert ival_S.asfreq("W") == ival_S_to_W + assert ival_S_end_of_week.asfreq("W") == ival_S_to_W + assert ival_S.asfreq("D") == ival_S_to_D + assert ival_S_end_of_day.asfreq("D") == ival_S_to_D + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + assert ival_S.asfreq("B") == ival_S_to_B + assert ival_S_end_of_bus.asfreq("B") == ival_S_to_B + assert ival_S.asfreq("h") == ival_S_to_H + assert ival_S_end_of_hour.asfreq("h") == ival_S_to_H + assert ival_S.asfreq("Min") == ival_S_to_T + assert ival_S_end_of_minute.asfreq("Min") == ival_S_to_T + + assert ival_S.asfreq("s") == ival_S + + def test_conv_microsecond(self): + # GH#31475 Avoid floating point errors dropping the start_time to + # before the beginning of the Period + per = Period("2020-01-30 15:57:27.576166", freq="us") + assert per.ordinal == 1580399847576166 + + start = per.start_time + expected = Timestamp("2020-01-30 15:57:27.576166") + assert start == expected + assert start._value == per.ordinal + + def test_asfreq_mult(self): + # normal freq to mult freq + p = Period(freq="Y", year=2007) + # ordinal will not change + for freq in ["3Y", offsets.YearEnd(3)]: + result = p.asfreq(freq) + expected = Period("2007", freq="3Y") + + assert result == expected + assert result.ordinal == expected.ordinal + assert result.freq == expected.freq + # ordinal will not change + for freq in ["3Y", offsets.YearEnd(3)]: + result = p.asfreq(freq, how="S") + expected = Period("2007", freq="3Y") + + assert result == expected + assert result.ordinal == expected.ordinal + assert result.freq == expected.freq + + # mult freq to normal freq + p = Period(freq="3Y", year=2007) + # ordinal will change because how=E is the default + for freq in ["Y", offsets.YearEnd()]: + result = p.asfreq(freq) + expected = Period("2009", freq="Y") + + assert result == expected + assert result.ordinal == expected.ordinal + assert result.freq == expected.freq + # ordinal will not change + for freq in ["Y", offsets.YearEnd()]: + result = p.asfreq(freq, how="s") + expected = Period("2007", freq="Y") + + assert result == expected + assert result.ordinal == expected.ordinal + assert result.freq == expected.freq + + p = Period(freq="Y", year=2007) + for freq in ["2M", offsets.MonthEnd(2)]: + result = p.asfreq(freq) + expected = Period("2007-12", freq="2M") + + assert result == expected + assert result.ordinal == expected.ordinal + assert result.freq == expected.freq + for freq in ["2M", offsets.MonthEnd(2)]: + result = p.asfreq(freq, how="s") + expected = Period("2007-01", freq="2M") + + assert result == expected + assert result.ordinal == expected.ordinal + assert result.freq == expected.freq + + p = Period(freq="3Y", year=2007) + for freq in ["2M", offsets.MonthEnd(2)]: + result = p.asfreq(freq) + expected = Period("2009-12", freq="2M") + + assert result == expected + assert result.ordinal == expected.ordinal + assert result.freq == expected.freq + for freq in ["2M", offsets.MonthEnd(2)]: + result = p.asfreq(freq, how="s") + expected = Period("2007-01", freq="2M") + + assert result == expected + assert result.ordinal == expected.ordinal + assert result.freq == expected.freq + + def test_asfreq_combined(self): + # normal freq to combined freq + p = Period("2007", freq="h") + + # ordinal will not change + expected = Period("2007", freq="25h") + for freq, how in zip(["1D1h", "1h1D"], ["E", "S"]): + result = p.asfreq(freq, how=how) + assert result == expected + assert result.ordinal == expected.ordinal + assert result.freq == expected.freq + + # combined freq to normal freq + p1 = Period(freq="1D1h", year=2007) + p2 = Period(freq="1h1D", year=2007) + + # ordinal will change because how=E is the default + result1 = p1.asfreq("h") + result2 = p2.asfreq("h") + expected = Period("2007-01-02", freq="h") + assert result1 == expected + assert result1.ordinal == expected.ordinal + assert result1.freq == expected.freq + assert result2 == expected + assert result2.ordinal == expected.ordinal + assert result2.freq == expected.freq + + # ordinal will not change + result1 = p1.asfreq("h", how="S") + result2 = p2.asfreq("h", how="S") + expected = Period("2007-01-01", freq="h") + assert result1 == expected + assert result1.ordinal == expected.ordinal + assert result1.freq == expected.freq + assert result2 == expected + assert result2.ordinal == expected.ordinal + assert result2.freq == expected.freq + + def test_asfreq_MS(self): + initial = Period("2013") + + assert initial.asfreq(freq="M", how="S") == Period("2013-01", "M") + + msg = INVALID_FREQ_ERR_MSG + with pytest.raises(ValueError, match=msg): + initial.asfreq(freq="MS", how="S") + + with pytest.raises(ValueError, match=msg): + Period("2013-01", "MS") diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/period/test_period.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/period/test_period.py new file mode 100644 index 0000000000000000000000000000000000000000..e5cb93b1dc8832681f36fd51d1ea0c004aea8566 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/period/test_period.py @@ -0,0 +1,1208 @@ +from datetime import ( + date, + datetime, + timedelta, +) +import re + +import numpy as np +import pytest + +from pandas._libs.tslibs import ( + iNaT, + to_offset, +) +from pandas._libs.tslibs.ccalendar import ( + DAYS, + MONTHS, +) +from pandas._libs.tslibs.parsing import DateParseError +from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG +from pandas.compat import PY314 +from pandas.errors import ( + OutOfBoundsDatetime, + Pandas4Warning, +) + +from pandas import ( + NaT, + Period, + Timedelta, + Timestamp, + offsets, +) +import pandas._testing as tm + +bday_msg = "Period with BDay freq is deprecated" + + +class TestPeriodDisallowedFreqs: + @pytest.mark.parametrize( + "freq, freq_msg", + [ + (offsets.BYearBegin(), "BYearBegin"), + (offsets.YearBegin(2), "YearBegin"), + (offsets.QuarterBegin(startingMonth=12), "QuarterBegin"), + (offsets.BusinessMonthEnd(2), "BusinessMonthEnd"), + ], + ) + def test_offsets_not_supported(self, freq, freq_msg): + # GH#55785 + msg = re.escape(f"{freq} is not supported as period frequency") + with pytest.raises(ValueError, match=msg): + Period(year=2014, freq=freq) + + def test_custom_business_day_freq_raises(self): + # GH#52534 + msg = "C is not supported as period frequency" + with pytest.raises(ValueError, match=msg): + Period("2023-04-10", freq="C") + msg = f"{offsets.CustomBusinessDay().base} is not supported as period frequency" + with pytest.raises(ValueError, match=msg): + Period("2023-04-10", freq=offsets.CustomBusinessDay()) + + def test_invalid_frequency_error_message(self): + msg = "WOM-1MON is not supported as period frequency" + with pytest.raises(ValueError, match=msg): + Period("2012-01-02", freq="WOM-1MON") + + def test_invalid_frequency_period_error_message(self): + msg = "Invalid frequency: ME" + with pytest.raises(ValueError, match=msg): + Period("2012-01-02", freq="ME") + + +class TestPeriodConstruction: + def test_from_td64nat_raises(self): + # GH#44507 + td = NaT.to_numpy("m8[ns]") + + msg = "Value must be Period, string, integer, or datetime" + with pytest.raises(ValueError, match=msg): + Period(td) + + with pytest.raises(ValueError, match=msg): + Period(td, freq="D") + + def test_construction(self): + i1 = Period("1/1/2005", freq="M") + i2 = Period("Jan 2005") + + assert i1 == i2 + + # GH#54105 - Period can be confusingly instantiated with lowercase freq + # TODO: raise in the future an error when passing lowercase freq + i1 = Period("2005", freq="Y") + i2 = Period("2005") + + assert i1 == i2 + + i4 = Period("2005", freq="M") + assert i1 != i4 + + i1 = Period.now(freq="Q") + i2 = Period(datetime.now(), freq="Q") + + assert i1 == i2 + + # Pass in freq as a keyword argument sometimes as a test for + # https://github.com/pandas-dev/pandas/issues/53369 + i1 = Period.now(freq="D") + i2 = Period(datetime.now(), freq="D") + i3 = Period.now(offsets.Day()) + + assert i1 == i2 + assert i1 == i3 + + i1 = Period("1982", freq="min") + msg = "'MIN' is deprecated and will be removed in a future version." + with tm.assert_produces_warning(Pandas4Warning, match=msg): + i2 = Period("1982", freq="MIN") + assert i1 == i2 + + i1 = Period(year=2005, month=3, day=1, freq="D") + i2 = Period("3/1/2005", freq="D") + assert i1 == i2 + + msg = "'d' is deprecated and will be removed in a future version." + with tm.assert_produces_warning(Pandas4Warning, match=msg): + i3 = Period(year=2005, month=3, day=1, freq="d") + assert i1 == i3 + + i1 = Period("2007-01-01 09:00:00.001") + expected = Period(datetime(2007, 1, 1, 9, 0, 0, 1000), freq="ms") + assert i1 == expected + + expected = Period("2007-01-01 09:00:00.001", freq="ms") + assert i1 == expected + + i1 = Period("2007-01-01 09:00:00.00101") + expected = Period(datetime(2007, 1, 1, 9, 0, 0, 1010), freq="us") + assert i1 == expected + + expected = Period("2007-01-01 09:00:00.00101", freq="us") + assert i1 == expected + + msg = "Must supply freq for ordinal value" + with pytest.raises(ValueError, match=msg): + Period(ordinal=200701) + + msg = "Invalid frequency: X" + with pytest.raises(ValueError, match=msg): + Period("2007-1-1", freq="X") + + def test_tuple_freq_disallowed(self): + # GH#34703 tuple freq disallowed + with pytest.raises(TypeError, match="pass as a string instead"): + Period("1982", freq=("Min", 1)) + + with pytest.raises(TypeError, match="pass as a string instead"): + Period("2006-12-31", ("w", 1)) + + def test_construction_from_timestamp_nanos(self): + # GH#46811 don't drop nanos from Timestamp + ts = Timestamp("2022-04-20 09:23:24.123456789") + per = Period(ts, freq="ns") + + # should losslessly round-trip, not lose the 789 + rt = per.to_timestamp() + assert rt == ts + + # same thing but from a datetime64 object + dt64 = ts.asm8 + per2 = Period(dt64, freq="ns") + rt2 = per2.to_timestamp() + assert rt2.asm8 == dt64 + + @pytest.mark.parametrize("freq", ["ms", "us", "ns"]) + def test_construction_from_min_timestamp(self, freq): + # GH-63278 + ts = Timestamp(Timestamp.min.value, unit=freq) + per = Period(ts, freq=freq) + + # pandas.errors.OutOfBoundsDatetime: Out of bounds nanosecond timestamp: + # -290308-12-21 19:59:05 + # rt = per.to_timestamp() + + assert per.year == ts.year + assert per.month == ts.month + assert per.day == ts.day + assert per.hour == ts.hour + assert per.minute == ts.minute + assert per.second == ts.second + + def test_construction_bday(self): + # Biz day construction, roll forward if non-weekday + with tm.assert_produces_warning( + Pandas4Warning, match="'b' is deprecated", raise_on_extra_warnings=False + ): + i1 = Period("3/10/12", freq="B") + i2 = Period("3/10/12", freq="D") + assert i1 == i2.asfreq("B") + i2 = Period("3/11/12", freq="D") + assert i1 == i2.asfreq("B") + i2 = Period("3/12/12", freq="D") + assert i1 == i2.asfreq("B") + + i3 = Period("3/10/12", freq="b") + assert i1 == i3 + + i1 = Period(year=2012, month=3, day=10, freq="B") + i2 = Period("3/12/12", freq="B") + assert i1 == i2 + + def test_construction_quarter(self): + i1 = Period(year=2005, quarter=1, freq="Q") + i2 = Period("1/1/2005", freq="Q") + assert i1 == i2 + + i1 = Period(year=2005, quarter=3, freq="Q") + i2 = Period("9/1/2005", freq="Q") + assert i1 == i2 + + i1 = Period("2005Q1") + i2 = Period(year=2005, quarter=1, freq="Q") + i3 = Period("2005q1") + assert i1 == i2 + assert i1 == i3 + + i1 = Period("05Q1") + assert i1 == i2 + lower = Period("05q1") + assert i1 == lower + + i1 = Period("1Q2005") + assert i1 == i2 + lower = Period("1q2005") + assert i1 == lower + + i1 = Period("1Q05") + assert i1 == i2 + lower = Period("1q05") + assert i1 == lower + + i1 = Period("4Q1984") + assert i1.year == 1984 + lower = Period("4q1984") + assert i1 == lower + + def test_construction_month(self): + expected = Period("2007-01", freq="M") + i1 = Period("200701", freq="M") + assert i1 == expected + + i1 = Period("200701", freq="M") + assert i1 == expected + + i1 = Period(200701, freq="M") + assert i1 == expected + + i1 = Period(ordinal=200701, freq="M") + assert i1.year == 18695 + + i1 = Period(datetime(2007, 1, 1), freq="M") + i2 = Period("200701", freq="M") + assert i1 == i2 + + i1 = Period(date(2007, 1, 1), freq="M") + i2 = Period(datetime(2007, 1, 1), freq="M") + i3 = Period(np.datetime64("2007-01-01"), freq="M") + i4 = Period("2007-01-01 00:00:00", freq="M") + i5 = Period("2007-01-01 00:00:00.000", freq="M") + assert i1 == i2 + assert i1 == i3 + assert i1 == i4 + assert i1 == i5 + + def test_period_constructor_offsets(self): + assert Period("1/1/2005", freq=offsets.MonthEnd()) == Period( + "1/1/2005", freq="M" + ) + assert Period("2005", freq=offsets.YearEnd()) == Period("2005", freq="Y") + assert Period("2005", freq=offsets.MonthEnd()) == Period("2005", freq="M") + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + assert Period("3/10/12", freq=offsets.BusinessDay()) == Period( + "3/10/12", freq="B" + ) + assert Period("3/10/12", freq=offsets.Day()) == Period("3/10/12", freq="D") + + assert Period( + year=2005, quarter=1, freq=offsets.QuarterEnd(startingMonth=12) + ) == Period(year=2005, quarter=1, freq="Q") + assert Period( + year=2005, quarter=2, freq=offsets.QuarterEnd(startingMonth=12) + ) == Period(year=2005, quarter=2, freq="Q") + + assert Period(year=2005, month=3, day=1, freq=offsets.Day()) == Period( + year=2005, month=3, day=1, freq="D" + ) + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + assert Period(year=2012, month=3, day=10, freq=offsets.BDay()) == Period( + year=2012, month=3, day=10, freq="B" + ) + + expected = Period("2005-03-01", freq="3D") + assert Period(year=2005, month=3, day=1, freq=offsets.Day(3)) == expected + assert Period(year=2005, month=3, day=1, freq="3D") == expected + + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + assert Period(year=2012, month=3, day=10, freq=offsets.BDay(3)) == Period( + year=2012, month=3, day=10, freq="3B" + ) + + assert Period(200701, freq=offsets.MonthEnd()) == Period(200701, freq="M") + + i1 = Period(ordinal=200701, freq=offsets.MonthEnd()) + i2 = Period(ordinal=200701, freq="M") + assert i1 == i2 + assert i1.year == 18695 + assert i2.year == 18695 + + i1 = Period(datetime(2007, 1, 1), freq="M") + i2 = Period("200701", freq="M") + assert i1 == i2 + + i1 = Period(date(2007, 1, 1), freq="M") + i2 = Period(datetime(2007, 1, 1), freq="M") + i3 = Period(np.datetime64("2007-01-01"), freq="M") + i4 = Period("2007-01-01 00:00:00", freq="M") + i5 = Period("2007-01-01 00:00:00.000", freq="M") + assert i1 == i2 + assert i1 == i3 + assert i1 == i4 + assert i1 == i5 + + i1 = Period("2007-01-01 09:00:00.001") + expected = Period(datetime(2007, 1, 1, 9, 0, 0, 1000), freq="ms") + assert i1 == expected + + expected = Period("2007-01-01 09:00:00.001", freq="ms") + assert i1 == expected + + i1 = Period("2007-01-01 09:00:00.00101") + expected = Period(datetime(2007, 1, 1, 9, 0, 0, 1010), freq="us") + assert i1 == expected + + expected = Period("2007-01-01 09:00:00.00101", freq="us") + assert i1 == expected + + def test_invalid_arguments(self): + msg = "Must supply freq for datetime value" + with pytest.raises(ValueError, match=msg): + Period(datetime.now()) + with pytest.raises(ValueError, match=msg): + Period(datetime.now().date()) + + msg = "Value must be Period, string, integer, or datetime" + with pytest.raises(ValueError, match=msg): + Period(1.6, freq="D") + msg = "Ordinal must be an integer" + with pytest.raises(ValueError, match=msg): + Period(ordinal=1.6, freq="D") + msg = "Only value or ordinal but not both should be given but not both" + with pytest.raises(ValueError, match=msg): + Period(ordinal=2, value=1, freq="D") + + msg = "If value is None, freq cannot be None" + with pytest.raises(ValueError, match=msg): + Period(month=1) + + msg = '^Given date string "-2000" not likely a datetime$' + with pytest.raises(ValueError, match=msg): + Period("-2000", "Y") + if PY314: + msg = "day 0 must be in range 1..31 for month 1 in year 1: 0" + else: + msg = "day is out of range for month" + with pytest.raises(DateParseError, match=msg): + Period("0", "Y") + msg = "Unknown datetime string format, unable to parse" + with pytest.raises(DateParseError, match=msg): + Period("1/1/-2000", "Y") + + def test_constructor_corner(self): + expected = Period("2007-01", freq="2M") + assert Period(year=2007, month=1, freq="2M") == expected + + assert Period(None) is NaT + + p = Period("2007-01-01", freq="D") + + result = Period(p, freq="Y") + exp = Period("2007", freq="Y") + assert result == exp + + def test_constructor_infer_freq(self): + p = Period("2007-01-01") + assert p.freq == "D" + + p = Period("2007-01-01 07") + assert p.freq == "h" + + p = Period("2007-01-01 07:10") + assert p.freq == "min" + + p = Period("2007-01-01 07:10:15") + assert p.freq == "s" + + p = Period("2007-01-01 07:10:15.123") + assert p.freq == "ms" + + # We see that there are 6 digits after the decimal, so get microsecond + # even though they are all zeros. + p = Period("2007-01-01 07:10:15.123000") + assert p.freq == "us" + + p = Period("2007-01-01 07:10:15.123400") + assert p.freq == "us" + + def test_multiples(self): + result1 = Period("1989", freq="2Y") + result2 = Period("1989", freq="Y") + assert result1.ordinal == result2.ordinal + assert result1.freqstr == "2Y-DEC" + assert result2.freqstr == "Y-DEC" + assert result1.freq == offsets.YearEnd(2) + assert result2.freq == offsets.YearEnd() + + assert (result1 + 1).ordinal == result1.ordinal + 2 + assert (1 + result1).ordinal == result1.ordinal + 2 + assert (result1 - 1).ordinal == result2.ordinal - 2 + assert (-1 + result1).ordinal == result2.ordinal - 2 + + @pytest.mark.parametrize("month", MONTHS) + def test_period_cons_quarterly(self, month): + # bugs in scikits.timeseries + freq = f"Q-{month}" + exp = Period("1989Q3", freq=freq) + assert "1989Q3" in str(exp) + stamp = exp.to_timestamp("D", how="end") + p = Period(stamp, freq=freq) + assert p == exp + + stamp = exp.to_timestamp("3D", how="end") + p = Period(stamp, freq=freq) + assert p == exp + + @pytest.mark.parametrize("month", MONTHS) + def test_period_cons_annual(self, month): + # bugs in scikits.timeseries + freq = f"Y-{month}" + exp = Period("1989", freq=freq) + stamp = exp.to_timestamp("D", how="end") + timedelta(days=30) + p = Period(stamp, freq=freq) + + assert p == exp + 1 + assert isinstance(p, Period) + + @pytest.mark.parametrize("day", DAYS) + @pytest.mark.parametrize("num", range(10, 17)) + def test_period_cons_weekly(self, num, day): + daystr = f"2011-02-{num}" + freq = f"W-{day}" + + result = Period(daystr, freq=freq) + expected = Period(daystr, freq="D").asfreq(freq) + assert result == expected + assert isinstance(result, Period) + + def test_parse_week_str_roundstrip(self): + # GH#50803 + per = Period("2017-01-23/2017-01-29") + assert per.freq.freqstr == "W-SUN" + + per = Period("2017-01-24/2017-01-30") + assert per.freq.freqstr == "W-MON" + + msg = "Could not parse as weekly-freq Period" + with pytest.raises(ValueError, match=msg): + # not 6 days apart + Period("2016-01-23/2017-01-29") + + def test_period_from_ordinal(self): + p = Period("2011-01", freq="M") + res = Period._from_ordinal(p.ordinal, freq=p.freq) + assert p == res + assert isinstance(res, Period) + + @pytest.mark.parametrize("freq", ["Y", "M", "D", "h"]) + def test_construct_from_nat_string_and_freq(self, freq): + per = Period("NaT", freq=freq) + assert per is NaT + + per = Period("NaT", freq="2" + freq) + assert per is NaT + + per = Period("NaT", freq="3" + freq) + assert per is NaT + + def test_period_cons_nat(self): + p = Period("nat", freq="W-SUN") + assert p is NaT + + p = Period(iNaT, freq="D") + assert p is NaT + + p = Period(iNaT, freq="3D") + assert p is NaT + + p = Period(iNaT, freq="1D1h") + assert p is NaT + + p = Period("NaT") + assert p is NaT + + p = Period(iNaT) + assert p is NaT + + def test_period_cons_mult(self): + p1 = Period("2011-01", freq="3M") + p2 = Period("2011-01", freq="M") + assert p1.ordinal == p2.ordinal + + assert p1.freq == offsets.MonthEnd(3) + assert p1.freqstr == "3M" + + assert p2.freq == offsets.MonthEnd() + assert p2.freqstr == "M" + + result = p1 + 1 + assert result.ordinal == (p2 + 3).ordinal + + assert result.freq == p1.freq + assert result.freqstr == "3M" + + result = p1 - 1 + assert result.ordinal == (p2 - 3).ordinal + assert result.freq == p1.freq + assert result.freqstr == "3M" + + msg = "Frequency must be positive, because it represents span: -3M" + with pytest.raises(ValueError, match=msg): + Period("2011-01", freq="-3M") + + msg = "Frequency must be positive, because it represents span: 0M" + with pytest.raises(ValueError, match=msg): + Period("2011-01", freq="0M") + + def test_period_cons_combined(self): + p = [ + ( + Period("2011-01", freq="1D1h"), + Period("2011-01", freq="1h1D"), + Period("2011-01", freq="h"), + ), + ( + Period(ordinal=1, freq="1D1h"), + Period(ordinal=1, freq="1h1D"), + Period(ordinal=1, freq="h"), + ), + ] + + for p1, p2, p3 in p: + assert p1.ordinal == p3.ordinal + assert p2.ordinal == p3.ordinal + + assert p1.freq == offsets.Hour(25) + assert p1.freqstr == "25h" + + assert p2.freq == offsets.Hour(25) + assert p2.freqstr == "25h" + + assert p3.freq == offsets.Hour() + assert p3.freqstr == "h" + + result = p1 + 1 + assert result.ordinal == (p3 + 25).ordinal + assert result.freq == p1.freq + assert result.freqstr == "25h" + + result = p2 + 1 + assert result.ordinal == (p3 + 25).ordinal + assert result.freq == p2.freq + assert result.freqstr == "25h" + + result = p1 - 1 + assert result.ordinal == (p3 - 25).ordinal + assert result.freq == p1.freq + assert result.freqstr == "25h" + + result = p2 - 1 + assert result.ordinal == (p3 - 25).ordinal + assert result.freq == p2.freq + assert result.freqstr == "25h" + + msg = "Frequency must be positive, because it represents span: -25h" + with pytest.raises(ValueError, match=msg): + Period("2011-01", freq="-1D1h") + with pytest.raises(ValueError, match=msg): + Period("2011-01", freq="-1h1D") + with pytest.raises(ValueError, match=msg): + Period(ordinal=1, freq="-1D1h") + with pytest.raises(ValueError, match=msg): + Period(ordinal=1, freq="-1h1D") + + msg = "Frequency must be positive, because it represents span: 0D" + with pytest.raises(ValueError, match=msg): + Period("2011-01", freq="0D0h") + with pytest.raises(ValueError, match=msg): + Period(ordinal=1, freq="0D0h") + + # You can only combine together day and intraday offsets + msg = "Invalid frequency: 1W1D" + with pytest.raises(ValueError, match=msg): + Period("2011-01", freq="1W1D") + msg = "Invalid frequency: 1D1W" + with pytest.raises(ValueError, match=msg): + Period("2011-01", freq="1D1W") + + @pytest.mark.parametrize("day", ["1970/01/01 ", "2020-12-31 ", "1981/09/13 "]) + @pytest.mark.parametrize("hour", ["00:00:00", "00:00:01", "23:59:59", "12:00:59"]) + @pytest.mark.parametrize( + "sec_float, expected", + [ + (".000000001", 1), + (".000000999", 999), + (".123456789", 789), + (".999999999", 999), + (".999999000", 0), + # Test femtoseconds, attoseconds, picoseconds are dropped like Timestamp + (".999999001123", 1), + (".999999001123456", 1), + (".999999001123456789", 1), + ], + ) + def test_period_constructor_nanosecond(self, day, hour, sec_float, expected): + # GH 34621 + + assert Period(day + hour + sec_float).start_time.nanosecond == expected + + @pytest.mark.parametrize("hour", range(24)) + def test_period_large_ordinal(self, hour): + # Issue #36430 + # Integer overflow for Period over the maximum timestamp + p = Period(ordinal=2562048 + hour, freq="1h") + assert p.hour == hour + + @pytest.mark.filterwarnings( + "ignore:Period with BDay freq is deprecated:FutureWarning" + ) + @pytest.mark.parametrize( + "freq,freq_depr", + [("2W", "2w"), ("2W-FRI", "2w-fri"), ("2D", "2d"), ("2B", "2b")], + ) + def test_period_deprecated_lowercase_freq(self, freq, freq_depr): + # GH#58998 + msg = ( + f"'{freq_depr[1:]}' is deprecated and will be removed in a future version." + ) + + with tm.assert_produces_warning( + Pandas4Warning, match=msg, raise_on_extra_warnings=False + ): + result = Period("2016-03-01 09:00", freq=freq_depr) + + expected = Period("2016-03-01 09:00", freq=freq) + assert result == expected + + +class TestPeriodMethods: + def test_round_trip(self, temp_file): + p = Period("2000Q1") + new_p = tm.round_trip_pickle(p, temp_file) + assert new_p == p + + def test_hash(self): + assert hash(Period("2011-01", freq="M")) == hash(Period("2011-01", freq="M")) + + assert hash(Period("2011-01-01", freq="D")) != hash(Period("2011-01", freq="M")) + + assert hash(Period("2011-01", freq="3M")) != hash(Period("2011-01", freq="2M")) + + assert hash(Period("2011-01", freq="M")) != hash(Period("2011-02", freq="M")) + + # -------------------------------------------------------------- + # to_timestamp + + def test_to_timestamp_mult(self): + p = Period("2011-01", freq="M") + assert p.to_timestamp(how="S") == Timestamp("2011-01-01") + expected = Timestamp("2011-02-01") - Timedelta(1, unit="us") + assert p.to_timestamp(how="E") == expected + + p = Period("2011-01", freq="3M") + assert p.to_timestamp(how="S") == Timestamp("2011-01-01") + expected = Timestamp("2011-04-01") - Timedelta(1, unit="us") + assert p.to_timestamp(how="E") == expected + + @pytest.mark.filterwarnings( + "ignore:Period with BDay freq is deprecated:FutureWarning" + ) + def test_to_timestamp(self): + p = Period("1982", freq="Y") + start_ts = p.to_timestamp(how="S") + aliases = ["s", "StarT", "BEGIn"] + for a in aliases: + assert start_ts == p.to_timestamp("D", how=a) + # freq with mult should not affect to the result + assert start_ts == p.to_timestamp("3D", how=a) + + end_ts = p.to_timestamp(how="E") + aliases = ["e", "end", "FINIsH"] + for a in aliases: + assert end_ts == p.to_timestamp("D", how=a) + assert end_ts == p.to_timestamp("3D", how=a) + + from_lst = ["Y", "Q", "M", "W", "B", "D", "h", "Min", "s"] + + def _ex(p): + if p.freq == "B": + return p.start_time + Timedelta(days=1) - Timedelta(microseconds=1) + return Timestamp((p + p.freq).start_time._value - 1, unit="us") + + for fcode in from_lst: + p = Period("1982", freq=fcode) + result = p.to_timestamp().to_period(fcode) + assert result == p + + assert p.start_time == p.to_timestamp(how="S") + + assert p.end_time == _ex(p) + + # Frequency other than daily + + p = Period("1985", freq="Y") + + result = p.to_timestamp("h", how="end") + expected = Timestamp(1986, 1, 1) - Timedelta(1, unit="us") + assert result == expected + result = p.to_timestamp("3h", how="end") + assert result == expected + + result = p.to_timestamp("min", how="end") + expected = Timestamp(1986, 1, 1) - Timedelta(1, unit="us") + assert result == expected + result = p.to_timestamp("2min", how="end") + assert result == expected + + result = p.to_timestamp(how="end") + expected = Timestamp(1986, 1, 1) - Timedelta(1, unit="us") + assert result == expected + + expected = datetime(1985, 1, 1) + result = p.to_timestamp("h", how="start") + assert result == expected + result = p.to_timestamp("min", how="start") + assert result == expected + result = p.to_timestamp("s", how="start") + assert result == expected + result = p.to_timestamp("3h", how="start") + assert result == expected + result = p.to_timestamp("5s", how="start") + assert result == expected + + def test_to_timestamp_business_end(self): + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + per = Period("1990-01-05", "B") # Friday + result = per.to_timestamp("B", how="E") + + expected = Timestamp("1990-01-06") - Timedelta(1, unit="us") + assert result == expected + + @pytest.mark.parametrize( + "ts, expected", + [ + ("1970-01-01 00:00:00", 0), + ("1970-01-01 00:00:00.000001", 1), + ("1970-01-01 00:00:00.00001", 10), + ("1970-01-01 00:00:00.499", 499000), + ("1999-12-31 23:59:59.999", 999000), + ("1999-12-31 23:59:59.999999", 999999), + ("2050-12-31 23:59:59.5", 500000), + ("2050-12-31 23:59:59.500001", 500001), + ("2050-12-31 23:59:59.123456", 123456), + ], + ) + @pytest.mark.parametrize("freq", [None, "us", "ns"]) + def test_to_timestamp_microsecond(self, ts, expected, freq): + # GH 24444 + result = Period(ts).to_timestamp(freq=freq).microsecond + assert result == expected + + # -------------------------------------------------------------- + # Rendering: __repr__, strftime, etc + + @pytest.mark.parametrize( + "str_ts,freq,str_res,str_freq", + ( + ("Jan-2000", None, "2000-01", "M"), + ("2000-12-15", None, "2000-12-15", "D"), + ( + "2000-12-15 13:45:26.123456789", + "ns", + "2000-12-15 13:45:26.123456789", + "ns", + ), + ("2000-12-15 13:45:26.123456789", "us", "2000-12-15 13:45:26.123456", "us"), + ("2000-12-15 13:45:26.123456", None, "2000-12-15 13:45:26.123456", "us"), + ("2000-12-15 13:45:26.123456789", "ms", "2000-12-15 13:45:26.123", "ms"), + ("2000-12-15 13:45:26.123", None, "2000-12-15 13:45:26.123", "ms"), + ("2000-12-15 13:45:26", "s", "2000-12-15 13:45:26", "s"), + ("2000-12-15 13:45:26", "min", "2000-12-15 13:45", "min"), + ("2000-12-15 13:45:26", "h", "2000-12-15 13:00", "h"), + ("2000-12-15", "Y", "2000", "Y-DEC"), + ("2000-12-15", "Q", "2000Q4", "Q-DEC"), + ("2000-12-15", "M", "2000-12", "M"), + ("2000-12-15", "W", "2000-12-11/2000-12-17", "W-SUN"), + ("2000-12-15", "D", "2000-12-15", "D"), + ("2000-12-15", "B", "2000-12-15", "B"), + ), + ) + @pytest.mark.filterwarnings( + "ignore:Period with BDay freq is deprecated:FutureWarning" + ) + def test_repr(self, str_ts, freq, str_res, str_freq): + p = Period(str_ts, freq=freq) + assert str(p) == str_res + assert repr(p) == f"Period('{str_res}', '{str_freq}')" + + def test_repr_nat(self): + p = Period("nat", freq="M") + assert repr(NaT) in repr(p) + + def test_strftime(self): + # GH#3363 + p = Period("2000-1-1 12:34:12", freq="s") + res = p.strftime("%Y-%m-%d %H:%M:%S") + assert res == "2000-01-01 12:34:12" + assert isinstance(res, str) + + +class TestPeriodProperties: + """Test properties such as year, month, weekday, etc....""" + + @pytest.mark.parametrize("freq", ["Y", "M", "D", "h"]) + def test_is_leap_year(self, freq): + # GH 13727 + p = Period("2000-01-01 00:00:00", freq=freq) + assert p.is_leap_year + assert isinstance(p.is_leap_year, bool) + + p = Period("1999-01-01 00:00:00", freq=freq) + assert not p.is_leap_year + + p = Period("2004-01-01 00:00:00", freq=freq) + assert p.is_leap_year + + p = Period("2100-01-01 00:00:00", freq=freq) + assert not p.is_leap_year + + def test_quarterly_negative_ordinals(self): + p = Period(ordinal=-1, freq="Q-DEC") + assert p.year == 1969 + assert p.quarter == 4 + assert isinstance(p, Period) + + p = Period(ordinal=-2, freq="Q-DEC") + assert p.year == 1969 + assert p.quarter == 3 + assert isinstance(p, Period) + + p = Period(ordinal=-2, freq="M") + assert p.year == 1969 + assert p.month == 11 + assert isinstance(p, Period) + + def test_freq_str(self): + i1 = Period("1982", freq="Min") + assert i1.freq == offsets.Minute() + assert i1.freqstr == "min" + + @pytest.mark.filterwarnings( + "ignore:Period with BDay freq is deprecated:FutureWarning" + ) + def test_period_deprecated_freq(self): + cases = { + "M": ["MTH", "MONTH", "MONTHLY", "Mth", "month", "monthly"], + "B": ["BUS", "BUSINESS", "BUSINESSLY", "WEEKDAY", "bus"], + "D": ["DAY", "DLY", "DAILY", "Day", "Dly", "Daily"], + "h": ["HR", "HOUR", "HRLY", "HOURLY", "hr", "Hour", "HRly"], + "min": ["minute", "MINUTE", "MINUTELY", "minutely"], + "s": ["sec", "SEC", "SECOND", "SECONDLY", "second"], + "ms": ["MILLISECOND", "MILLISECONDLY", "millisecond"], + "us": ["MICROSECOND", "MICROSECONDLY", "microsecond"], + "ns": ["NANOSECOND", "NANOSECONDLY", "nanosecond"], + } + + msg = INVALID_FREQ_ERR_MSG + for exp, freqs in cases.items(): + for freq in freqs: + with pytest.raises(ValueError, match=msg): + Period("2016-03-01 09:00", freq=freq) + with pytest.raises(ValueError, match=msg): + Period(ordinal=1, freq=freq) + + # check supported freq-aliases still works + p1 = Period("2016-03-01 09:00", freq=exp) + p2 = Period(ordinal=1, freq=exp) + assert isinstance(p1, Period) + assert isinstance(p2, Period) + + @staticmethod + def _period_constructor(bound, offset): + return Period( + year=bound.year, + month=bound.month, + day=bound.day, + hour=bound.hour, + minute=bound.minute, + second=bound.second + offset, + freq="us", + ) + + @pytest.mark.parametrize("bound, offset", [(Timestamp.min, -1), (Timestamp.max, 1)]) + @pytest.mark.parametrize("period_property", ["start_time", "end_time"]) + def test_outer_bounds_start_and_end_time(self, bound, offset, period_property): + # GH #13346 + period = TestPeriodProperties._period_constructor(bound, offset) + # post-GH#63760 this no longer raises OutOfBoundsDatetime + getattr(period, period_property) + + per = Period._from_ordinal(bound.value, freq=to_offset("ms")) + + if period_property == "end_time" and offset == 1: + err = OverflowError + msg = "value too large" + else: + err = OutOfBoundsDatetime + msg = "Out of bounds microsecond" + # But if we do the same thing with a much-more-remote date, we still + # raise OutOfBoundsDatetime (as opposed to silently giving a wrong result) + with pytest.raises(err, match=msg): + getattr(per, period_property) + + @pytest.mark.parametrize("bound, offset", [(Timestamp.min, -1), (Timestamp.max, 1)]) + @pytest.mark.parametrize("period_property", ["start_time", "end_time"]) + def test_inner_bounds_start_and_end_time(self, bound, offset, period_property): + # GH #13346 + period = TestPeriodProperties._period_constructor(bound, -offset) + expected = period.to_timestamp().round(freq="s") + assert getattr(period, period_property).round(freq="s") == expected + expected = (bound - offset * Timedelta(1, unit="s")).floor("s") + assert getattr(period, period_property).floor("s") == expected + + def test_start_time(self): + freq_lst = ["Y", "Q", "M", "D", "h", "min", "s"] + xp = datetime(2012, 1, 1) + for f in freq_lst: + p = Period("2012", freq=f) + assert p.start_time == xp + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + assert Period("2012", freq="B").start_time == datetime(2012, 1, 2) + assert Period("2012", freq="W").start_time == datetime(2011, 12, 26) + + def test_end_time(self): + p = Period("2012", freq="Y") + + exp = Timestamp(2012, 12, 31, 23, 59, 59, 999999) + assert p.end_time == exp + + p = Period("2012", freq="Q") + exp = Timestamp(2012, 3, 31, 23, 59, 59, 999999) + assert p.end_time == exp + + p = Period("2012", freq="M") + exp = Timestamp(2012, 1, 31, 23, 59, 59, 999999) + assert p.end_time == exp + + p = Period("2012", freq="D") + exp = Timestamp(2012, 1, 1, 23, 59, 59, 999999) + assert p.end_time == exp + + p = Period("2012", freq="h") + exp = Timestamp(2012, 1, 1, 0, 59, 59, 999999) + assert p.end_time == exp + + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + p = Period("2012", freq="B") + exp = Timestamp(2012, 1, 2, 23, 59, 59, 999999) + assert p.end_time == exp + + p = Period("2012", freq="W") + exp = Timestamp(2012, 1, 1, 23, 59, 59, 999999) + assert p.end_time == exp + + # Test for GH 11738 + p = Period("2012", freq="15D") + exp = Timestamp(2012, 1, 15, 23, 59, 59, 999999) + assert p.end_time == exp + + p = Period("2012", freq="1D1h") + exp = Timestamp(2012, 1, 2, 0, 59, 59, 999999) + assert p.end_time == exp + + p = Period("2012", freq="1h1D") + exp = Timestamp(2012, 1, 2, 0, 59, 59, 999999) + assert p.end_time == exp + + def test_end_time_business_friday(self): + # GH#34449 + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + per = Period("1990-01-05", "B") + result = per.end_time + + expected = Timestamp("1990-01-06") - Timedelta(1, unit="us") + assert result == expected + + def test_anchor_week_end_time(self): + p = Period("2013-1-1", "W-SAT") + exp = Timestamp(2013, 1, 5, 23, 59, 59, 999999) + assert p.end_time == exp + + def test_properties_annually(self): + # Test properties on Periods with annually frequency. + a_date = Period(freq="Y", year=2007) + assert a_date.year == 2007 + + def test_properties_quarterly(self): + # Test properties on Periods with daily frequency. + qedec_date = Period(freq="Q-DEC", year=2007, quarter=1) + qejan_date = Period(freq="Q-JAN", year=2007, quarter=1) + qejun_date = Period(freq="Q-JUN", year=2007, quarter=1) + for x in range(3): + for qd in (qedec_date, qejan_date, qejun_date): + assert (qd + x).qyear == 2007 + assert (qd + x).quarter == x + 1 + + def test_properties_monthly(self): + # Test properties on Periods with daily frequency. + m_date = Period(freq="M", year=2007, month=1) + for x in range(11): + m_ival_x = m_date + x + assert m_ival_x.year == 2007 + if 1 <= x + 1 <= 3: + assert m_ival_x.quarter == 1 + elif 4 <= x + 1 <= 6: + assert m_ival_x.quarter == 2 + elif 7 <= x + 1 <= 9: + assert m_ival_x.quarter == 3 + elif 10 <= x + 1 <= 12: + assert m_ival_x.quarter == 4 + assert m_ival_x.month == x + 1 + + def test_properties_weekly(self): + # Test properties on Periods with daily frequency. + w_date = Period(freq="W", year=2007, month=1, day=7) + assert w_date.year == 2007 + assert w_date.quarter == 1 + assert w_date.month == 1 + assert w_date.week == 1 + assert (w_date - 1).week == 52 + assert w_date.days_in_month == 31 + assert Period(freq="W", year=2012, month=2, day=1).days_in_month == 29 + + def test_properties_weekly_legacy(self): + # Test properties on Periods with daily frequency. + w_date = Period(freq="W", year=2007, month=1, day=7) + assert w_date.year == 2007 + assert w_date.quarter == 1 + assert w_date.month == 1 + assert w_date.week == 1 + assert (w_date - 1).week == 52 + assert w_date.days_in_month == 31 + + exp = Period(freq="W", year=2012, month=2, day=1) + assert exp.days_in_month == 29 + + msg = INVALID_FREQ_ERR_MSG + with pytest.raises(ValueError, match=msg): + Period(freq="WK", year=2007, month=1, day=7) + + def test_properties_daily(self): + # Test properties on Periods with daily frequency. + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + b_date = Period(freq="B", year=2007, month=1, day=1) + assert b_date.year == 2007 + assert b_date.quarter == 1 + assert b_date.month == 1 + assert b_date.day == 1 + assert b_date.weekday == 0 + assert b_date.dayofyear == 1 + assert b_date.days_in_month == 31 + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + assert Period(freq="B", year=2012, month=2, day=1).days_in_month == 29 + + d_date = Period(freq="D", year=2007, month=1, day=1) + + assert d_date.year == 2007 + assert d_date.quarter == 1 + assert d_date.month == 1 + assert d_date.day == 1 + assert d_date.weekday == 0 + assert d_date.dayofyear == 1 + assert d_date.days_in_month == 31 + assert Period(freq="D", year=2012, month=2, day=1).days_in_month == 29 + + def test_properties_hourly(self): + # Test properties on Periods with hourly frequency. + h_date1 = Period(freq="h", year=2007, month=1, day=1, hour=0) + h_date2 = Period(freq="2h", year=2007, month=1, day=1, hour=0) + + for h_date in [h_date1, h_date2]: + assert h_date.year == 2007 + assert h_date.quarter == 1 + assert h_date.month == 1 + assert h_date.day == 1 + assert h_date.weekday == 0 + assert h_date.dayofyear == 1 + assert h_date.hour == 0 + assert h_date.days_in_month == 31 + assert ( + Period(freq="h", year=2012, month=2, day=1, hour=0).days_in_month == 29 + ) + + def test_properties_minutely(self): + # Test properties on Periods with minutely frequency. + t_date = Period(freq="Min", year=2007, month=1, day=1, hour=0, minute=0) + assert t_date.quarter == 1 + assert t_date.month == 1 + assert t_date.day == 1 + assert t_date.weekday == 0 + assert t_date.dayofyear == 1 + assert t_date.hour == 0 + assert t_date.minute == 0 + assert t_date.days_in_month == 31 + assert ( + Period(freq="D", year=2012, month=2, day=1, hour=0, minute=0).days_in_month + == 29 + ) + + def test_properties_secondly(self): + # Test properties on Periods with secondly frequency. + s_date = Period( + freq="Min", year=2007, month=1, day=1, hour=0, minute=0, second=0 + ) + assert s_date.year == 2007 + assert s_date.quarter == 1 + assert s_date.month == 1 + assert s_date.day == 1 + assert s_date.weekday == 0 + assert s_date.dayofyear == 1 + assert s_date.hour == 0 + assert s_date.minute == 0 + assert s_date.second == 0 + assert s_date.days_in_month == 31 + assert ( + Period( + freq="Min", year=2012, month=2, day=1, hour=0, minute=0, second=0 + ).days_in_month + == 29 + ) + + +class TestPeriodComparisons: + def test_sort_periods(self): + jan = Period("2000-01", "M") + feb = Period("2000-02", "M") + mar = Period("2000-03", "M") + periods = [mar, jan, feb] + correctPeriods = [jan, feb, mar] + assert sorted(periods) == correctPeriods + + +def test_period_immutable(): + # see gh-17116 + msg = "not writable" + + per = Period("2014Q1") + with pytest.raises(AttributeError, match=msg): + per.ordinal = 14 + + freq = per.freq + with pytest.raises(AttributeError, match=msg): + per.freq = 2 * freq + + +def test_small_year_parsing(): + per1 = Period("0001-01-07", "D") + assert per1.year == 1 + assert per1.day == 7 + + +def test_negone_ordinals(): + freqs = ["Y", "M", "Q", "D", "h", "min", "s"] + + period = Period(ordinal=-1, freq="D") + for freq in freqs: + repr(period.asfreq(freq)) + + for freq in freqs: + period = Period(ordinal=-1, freq=freq) + repr(period) + assert period.year == 1969 + + with tm.assert_produces_warning(FutureWarning, match=bday_msg): + period = Period(ordinal=-1, freq="B") + repr(period) + period = Period(ordinal=-1, freq="W") + repr(period) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/test_na_scalar.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/test_na_scalar.py new file mode 100644 index 0000000000000000000000000000000000000000..9d4b3c57616b4d2c820e586104a7685f0ed9bf54 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/test_na_scalar.py @@ -0,0 +1,334 @@ +from datetime import ( + date, + time, + timedelta, +) +import pickle + +import numpy as np +import pytest + +from pandas._libs.missing import NA + +from pandas.core.dtypes.common import is_scalar + +import pandas as pd +import pandas._testing as tm + + +def test_singleton(): + assert NA is NA + new_NA = type(NA)() + assert new_NA is NA + + +def test_repr(): + assert repr(NA) == "" + assert str(NA) == "" + + +def test_format(): + # GH-34740 + assert format(NA) == "" + assert format(NA, ">10") == " " + assert format(NA, "xxx") == "" # NA is flexible, accept any format spec + + assert f"{NA}" == "" + assert f"{NA:>10}" == " " + assert f"{NA:xxx}" == "" + + +def test_truthiness(): + msg = "boolean value of NA is ambiguous" + + with pytest.raises(TypeError, match=msg): + bool(NA) + + with pytest.raises(TypeError, match=msg): + not NA + + +def test_hashable(): + assert hash(NA) == hash(NA) + d = {NA: "test"} + assert d[NA] == "test" + + +@pytest.mark.parametrize( + "other", [NA, 1, 1.0, "a", b"a", np.int64(1), np.nan], ids=repr +) +def test_arithmetic_ops(all_arithmetic_functions, other): + op = all_arithmetic_functions + + if op.__name__ in ("pow", "rpow", "rmod") and isinstance(other, (str, bytes)): + pytest.skip(reason=f"{op.__name__} with NA and {other} not defined.") + if op.__name__ in ("divmod", "rdivmod"): + assert op(NA, other) is (NA, NA) + else: + if op.__name__ == "rpow": + # avoid special case + other += 1 + assert op(NA, other) is NA + + +@pytest.mark.parametrize( + "other", + [ + NA, + 1, + 1.0, + "a", + b"a", + np.int64(1), + np.nan, + np.bool_(True), + time(0), + date(1, 2, 3), + timedelta(1), + pd.NaT, + ], +) +def test_comparison_ops(comparison_op, other): + assert comparison_op(NA, other) is NA + assert comparison_op(other, NA) is NA + + +@pytest.mark.parametrize( + "value", + [ + 0, + 0.0, + -0, + -0.0, + False, + np.bool_(False), + np.int_(0), + np.float64(0), + np.int_(-0), + np.float64(-0), + ], +) +@pytest.mark.parametrize("asarray", [True, False]) +def test_pow_special(value, asarray): + if asarray: + value = np.array([value]) + result = NA**value + + if asarray: + result = result[0] + else: + # this assertion isn't possible for ndarray. + assert isinstance(result, type(value)) + assert result == 1 + + +@pytest.mark.parametrize( + "value", [1, 1.0, True, np.bool_(True), np.int_(1), np.float64(1)] +) +@pytest.mark.parametrize("asarray", [True, False]) +def test_rpow_special(value, asarray): + if asarray: + value = np.array([value]) + result = value**NA + + if asarray: + result = result[0] + elif not isinstance(value, (np.float64, np.bool_, np.int_)): + # this assertion isn't possible with asarray=True + assert isinstance(result, type(value)) + + assert result == value + + +@pytest.mark.parametrize("value", [-1, -1.0, np.int_(-1), np.float64(-1)]) +@pytest.mark.parametrize("asarray", [True, False]) +def test_rpow_minus_one(value, asarray): + if asarray: + value = np.array([value]) + result = value**NA + + if asarray: + result = result[0] + + assert pd.isna(result) + + +def test_unary_ops(): + assert +NA is NA + assert -NA is NA + assert abs(NA) is NA + assert ~NA is NA + + +def test_logical_and(): + assert NA & True is NA + assert True & NA is NA + assert NA & False is False + assert False & NA is False + assert NA & NA is NA + + # GH#58427 + assert NA & np.bool_(True) is NA + assert np.bool_(True) & NA is NA + assert NA & np.bool_(False) is False + assert np.bool_(False) & NA is False + + msg = "unsupported operand type" + with pytest.raises(TypeError, match=msg): + NA & 5 + + +def test_logical_or(): + assert NA | True is True + assert True | NA is True + assert NA | False is NA + assert False | NA is NA + assert NA | NA is NA + + # GH#58427 + assert NA | np.bool_(True) is True + assert np.bool_(True) | NA is True + assert NA | np.bool_(False) is NA + assert np.bool_(False) | NA is NA + + msg = "unsupported operand type" + with pytest.raises(TypeError, match=msg): + NA | 5 + + +def test_logical_xor(): + assert NA ^ True is NA + assert True ^ NA is NA + assert NA ^ False is NA + assert False ^ NA is NA + assert NA ^ NA is NA + + # GH#58427 + assert NA ^ np.bool_(True) is NA + assert np.bool_(True) ^ NA is NA + assert NA ^ np.bool_(False) is NA + assert np.bool_(False) ^ NA is NA + + msg = "unsupported operand type" + with pytest.raises(TypeError, match=msg): + NA ^ 5 + + +def test_logical_not(): + assert ~NA is NA + + +@pytest.mark.parametrize("shape", [(3,), (3, 3), (1, 2, 3)]) +def test_arithmetic_ndarray(shape, all_arithmetic_functions): + op = all_arithmetic_functions + a = np.zeros(shape) + if op.__name__ == "pow": + a += 5 + result = op(NA, a) + expected = np.full(a.shape, NA, dtype=object) + tm.assert_numpy_array_equal(result, expected) + + +def test_is_scalar(): + assert is_scalar(NA) is True + + +def test_isna(): + assert pd.isna(NA) is True + assert pd.notna(NA) is False + + +def test_series_isna(): + s = pd.Series([1, NA], dtype=object) + expected = pd.Series([False, True]) + tm.assert_series_equal(s.isna(), expected) + + +def test_ufunc(): + assert np.log(NA) is NA + assert np.add(NA, 1) is NA + result = np.divmod(NA, 1) + assert result[0] is NA and result[1] is NA + + result = np.frexp(NA) + assert result[0] is NA and result[1] is NA + + +def test_ufunc_raises(): + msg = "ufunc method 'at'" + with pytest.raises(ValueError, match=msg): + np.log.at(NA, 0) + + +def test_binary_input_not_dunder(): + a = np.array([1, 2, 3]) + expected = np.array([NA, NA, NA], dtype=object) + result = np.logaddexp(a, NA) + tm.assert_numpy_array_equal(result, expected) + + result = np.logaddexp(NA, a) + tm.assert_numpy_array_equal(result, expected) + + # all NA, multiple inputs + assert np.logaddexp(NA, NA) is NA + + result = np.modf(NA, NA) + assert len(result) == 2 + assert all(x is NA for x in result) + + +def test_divmod_ufunc(): + # binary in, binary out. + a = np.array([1, 2, 3]) + expected = np.array([NA, NA, NA], dtype=object) + + result = np.divmod(a, NA) + assert isinstance(result, tuple) + for arr in result: + tm.assert_numpy_array_equal(arr, expected) + tm.assert_numpy_array_equal(arr, expected) + + result = np.divmod(NA, a) + for arr in result: + tm.assert_numpy_array_equal(arr, expected) + tm.assert_numpy_array_equal(arr, expected) + + +def test_integer_hash_collision_dict(): + # GH 30013 + result = {NA: "foo", hash(NA): "bar"} + + assert result[NA] == "foo" + assert result[hash(NA)] == "bar" + + +def test_integer_hash_collision_set(): + # GH 30013 + result = {NA, hash(NA)} + + assert len(result) == 2 + assert NA in result + assert hash(NA) in result + + +def test_pickle_roundtrip(): + # https://github.com/pandas-dev/pandas/issues/31847 + result = pickle.loads(pickle.dumps(NA)) + assert result is NA + + +def test_pickle_roundtrip_pandas(temp_file): + result = tm.round_trip_pickle(NA, temp_file) + assert result is NA + + +@pytest.mark.parametrize( + "values, dtype", [([1, 2, NA], "Int64"), (["A", "B", NA], "string")] +) +@pytest.mark.parametrize("as_frame", [True, False]) +def test_pickle_roundtrip_containers(as_frame, values, dtype, temp_file): + s = pd.Series(pd.array(values, dtype=dtype)) + if as_frame: + s = s.to_frame(name="A") + result = tm.round_trip_pickle(s, temp_file) + tm.assert_equal(result, s) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/test_nat.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/test_nat.py new file mode 100644 index 0000000000000000000000000000000000000000..cf9484ff97651eaa2cce5d05951d6390e3abfe50 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/test_nat.py @@ -0,0 +1,684 @@ +from datetime import ( + datetime, + timedelta, +) +import operator +import zoneinfo + +import numpy as np +import pytest + +from pandas._libs.tslibs import iNaT + +from pandas import ( + DatetimeIndex, + DatetimeTZDtype, + Index, + NaT, + Period, + Series, + Timedelta, + TimedeltaIndex, + Timestamp, + isna, + offsets, +) +import pandas._testing as tm +from pandas.core import roperator +from pandas.core.arrays import ( + DatetimeArray, + PeriodArray, + TimedeltaArray, +) + + +class TestNaTFormatting: + def test_repr(self): + assert repr(NaT) == "NaT" + + def test_str(self): + assert str(NaT) == "NaT" + + def test_isoformat(self): + assert NaT.isoformat() == "NaT" + + +@pytest.mark.parametrize( + "nat,idx", + [ + (Timestamp("NaT"), DatetimeArray), + (Timedelta("NaT"), TimedeltaArray), + (Period("NaT", freq="M"), PeriodArray), + ], +) +def test_nat_fields(nat, idx): + for field in idx._field_ops: + # weekday is a property of DTI, but a method + # on NaT/Timestamp for compat with datetime + if field == "weekday": + continue + + result = getattr(NaT, field) + assert np.isnan(result) + + result = getattr(nat, field) + assert np.isnan(result) + + for field in idx._bool_ops: + result = getattr(NaT, field) + assert result is False + + result = getattr(nat, field) + assert result is False + + +def test_nat_vector_field_access(): + idx = DatetimeIndex(["1/1/2000", None, None, "1/4/2000"]) + + for field in DatetimeArray._field_ops: + # weekday is a property of DTI, but a method + # on NaT/Timestamp for compat with datetime + if field == "weekday": + continue + + result = getattr(idx, field) + expected = Index([getattr(x, field) for x in idx]) + tm.assert_index_equal(result, expected) + + ser = Series(idx) + + for field in DatetimeArray._field_ops: + # weekday is a property of DTI, but a method + # on NaT/Timestamp for compat with datetime + if field == "weekday": + continue + + result = getattr(ser.dt, field) + expected = [getattr(x, field) for x in idx] + tm.assert_series_equal(result, Series(expected)) + + for field in DatetimeArray._bool_ops: + result = getattr(ser.dt, field) + expected = [getattr(x, field) for x in idx] + tm.assert_series_equal(result, Series(expected)) + + +@pytest.mark.parametrize("klass", [Timestamp, Timedelta, Period]) +@pytest.mark.parametrize( + "value", [None, np.nan, iNaT, float("nan"), NaT, "NaT", "nat", "", "NAT"] +) +def test_identity(klass, value): + assert klass(value) is NaT + + +@pytest.mark.parametrize("klass", [Timestamp, Timedelta]) +@pytest.mark.parametrize("method", ["round", "floor", "ceil"]) +@pytest.mark.parametrize("freq", ["s", "5s", "min", "5min", "h", "5h"]) +def test_round_nat(klass, method, freq): + # see gh-14940 + ts = klass("nat") + + round_method = getattr(ts, method) + assert round_method(freq) is ts + + +@pytest.mark.parametrize( + "method", + [ + "astimezone", + "combine", + "ctime", + "dst", + "fromordinal", + "fromtimestamp", + "fromisocalendar", + "isocalendar", + "strftime", + "strptime", + "time", + "timestamp", + "timetuple", + "timetz", + "toordinal", + "tzname", + "utcfromtimestamp", + "utcnow", + "utcoffset", + "utctimetuple", + ], +) +def test_nat_methods_raise(method): + # see gh-9513, gh-17329 + msg = f"NaTType does not support {method}" + + with pytest.raises(ValueError, match=msg): + getattr(NaT, method)() + + +@pytest.mark.parametrize("method", ["weekday", "isoweekday"]) +def test_nat_methods_nan(method): + # see gh-9513, gh-17329 + assert np.isnan(getattr(NaT, method)()) + + +@pytest.mark.parametrize( + "method", ["date", "now", "replace", "today", "tz_convert", "tz_localize"] +) +def test_nat_methods_nat(method): + # see gh-8254, gh-9513, gh-17329 + assert getattr(NaT, method)() is NaT + + +@pytest.mark.parametrize( + "get_nat", [lambda x: NaT, lambda x: Timedelta(x), lambda x: Timestamp(x)] +) +def test_nat_iso_format(get_nat): + # see gh-12300 + assert get_nat("NaT").isoformat() == "NaT" + assert get_nat("NaT").isoformat(timespec="nanoseconds") == "NaT" + + +@pytest.mark.parametrize( + "klass,expected", + [ + (Timestamp, ["normalize", "to_julian_date", "to_period", "unit"]), + ( + Timedelta, + [ + "components", + "resolution_string", + "to_pytimedelta", + "to_timedelta64", + "unit", + "view", + ], + ), + ], +) +def test_missing_public_nat_methods(klass, expected): + # see gh-17327 + # + # NaT should have *most* of the Timestamp and Timedelta methods. + # Here, we check which public methods NaT does not have. We + # ignore any missing private methods. + nat_names = dir(NaT) + klass_names = dir(klass) + + missing = [x for x in klass_names if x not in nat_names and not x.startswith("_")] + missing.sort() + + assert missing == expected + + +def _get_overlap_public_nat_methods(klass, as_tuple=False): + """ + Get overlapping public methods between NaT and another class. + + Parameters + ---------- + klass : type + The class to compare with NaT + as_tuple : bool, default False + Whether to return a list of tuples of the form (klass, method). + + Returns + ------- + overlap : list + """ + nat_names = dir(NaT) + klass_names = dir(klass) + + overlap = [ + x + for x in nat_names + if x in klass_names and not x.startswith("_") and callable(getattr(klass, x)) + ] + + # Timestamp takes precedence over Timedelta in terms of overlap. + if klass is Timedelta: + ts_names = dir(Timestamp) + overlap = [x for x in overlap if x not in ts_names] + + if as_tuple: + overlap = [(klass, method) for method in overlap] + + overlap.sort() + return overlap + + +@pytest.mark.parametrize( + "klass,expected", + [ + ( + Timestamp, + [ + "as_unit", + "astimezone", + "ceil", + "combine", + "ctime", + "date", + "day_name", + "dst", + "floor", + "fromisocalendar", + "fromisoformat", + "fromordinal", + "fromtimestamp", + "isocalendar", + "isoformat", + "isoweekday", + "month_name", + "now", + "replace", + "round", + "strftime", + "strptime", + "time", + "timestamp", + "timetuple", + "timetz", + "to_datetime64", + "to_numpy", + "to_pydatetime", + "today", + "toordinal", + "tz_convert", + "tz_localize", + "tzname", + "utcfromtimestamp", + "utcnow", + "utcoffset", + "utctimetuple", + "weekday", + ], + ), + (Timedelta, ["total_seconds"]), + ], +) +def test_overlap_public_nat_methods(klass, expected): + # see gh-17327 + # + # NaT should have *most* of the Timestamp and Timedelta methods. + # In case when Timestamp, Timedelta, and NaT are overlap, the overlap + # is considered to be with Timestamp and NaT, not Timedelta. + assert _get_overlap_public_nat_methods(klass) == expected + + +@pytest.mark.parametrize( + "compare", + ( + _get_overlap_public_nat_methods(Timestamp, True) + + _get_overlap_public_nat_methods(Timedelta, True) + ), + ids=lambda x: f"{x[0].__name__}.{x[1]}", +) +def test_nat_doc_strings(compare): + # see gh-17327 + # + # The docstrings for overlapping methods should match. + klass, method = compare + klass_doc = getattr(klass, method).__doc__ + + if klass == Timestamp and method == "isoformat": + pytest.skip( + "Ignore differences with Timestamp.isoformat() as they're intentional" + ) + + if method == "to_numpy": + # GH#44460 can return either dt64 or td64 depending on dtype, + # different docstring is intentional + pytest.skip(f"different docstring for {method} is intentional") + + nat_doc = getattr(NaT, method).__doc__ + assert klass_doc == nat_doc + + +_ops = { + "left_plus_right": lambda a, b: a + b, + "right_plus_left": lambda a, b: b + a, + "left_minus_right": lambda a, b: a - b, + "right_minus_left": lambda a, b: b - a, + "left_times_right": lambda a, b: a * b, + "right_times_left": lambda a, b: b * a, + "left_div_right": lambda a, b: a / b, + "right_div_left": lambda a, b: b / a, +} + + +@pytest.mark.parametrize("op_name", list(_ops.keys())) +@pytest.mark.parametrize( + "value,val_type", + [ + (2, "scalar"), + (1.5, "floating"), + (np.nan, "floating"), + ("foo", "str"), + (timedelta(3600), "timedelta"), + (Timedelta("5s"), "timedelta"), + (datetime(2014, 1, 1), "timestamp"), + (Timestamp("2014-01-01"), "timestamp"), + (Timestamp("2014-01-01", tz="UTC"), "timestamp"), + (Timestamp("2014-01-01", tz="US/Eastern"), "timestamp"), + (datetime(2014, 1, 1).astimezone(zoneinfo.ZoneInfo("Asia/Tokyo")), "timestamp"), + ], +) +def test_nat_arithmetic_scalar(op_name, value, val_type): + # see gh-6873 + invalid_ops = { + "scalar": {"right_div_left"}, + "floating": { + "right_div_left", + "left_minus_right", + "right_minus_left", + "left_plus_right", + "right_plus_left", + }, + "str": set(_ops.keys()), + "timedelta": {"left_times_right", "right_times_left"}, + "timestamp": { + "left_times_right", + "right_times_left", + "left_div_right", + "right_div_left", + }, + } + + op = _ops[op_name] + + if op_name in invalid_ops.get(val_type, set()): + if ( + val_type == "timedelta" + and "times" in op_name + and isinstance(value, Timedelta) + ): + typs = "(Timedelta|NaTType)" + msg = rf"unsupported operand type\(s\) for \*: '{typs}' and '{typs}'" + elif val_type == "str": + # un-specific check here because the message comes from str + # and varies by method + msg = "|".join( + [ + "can only concatenate str", + "unsupported operand type", + "can't multiply sequence", + "Can't convert 'NaTType'", + "must be str, not NaTType", + ] + ) + else: + msg = "unsupported operand type" + + with pytest.raises(TypeError, match=msg): + op(NaT, value) + else: + if val_type == "timedelta" and "div" in op_name: + expected = np.nan + else: + expected = NaT + + assert op(NaT, value) is expected + + +@pytest.mark.parametrize( + "val,expected", + [(np.nan, NaT), (NaT, np.nan), (np.timedelta64("NaT", "ns"), np.nan)], +) +def test_nat_rfloordiv_timedelta(val, expected): + # see gh-#18846 + # + # See also test_timedelta.TestTimedeltaArithmetic.test_floordiv + td = Timedelta(hours=3, minutes=4) + assert td // val is expected + + +@pytest.mark.parametrize( + "op_name", + ["left_plus_right", "right_plus_left", "left_minus_right", "right_minus_left"], +) +@pytest.mark.parametrize( + "value", + [ + DatetimeIndex(["2011-01-01", "2011-01-02"], dtype="M8[ns]", name="x"), + DatetimeIndex( + ["2011-01-01", "2011-01-02"], dtype="M8[ns, US/Eastern]", name="x" + ), + DatetimeArray._from_sequence(["2011-01-01", "2011-01-02"], dtype="M8[ns]"), + DatetimeArray._from_sequence( + ["2011-01-01", "2011-01-02"], dtype=DatetimeTZDtype(tz="US/Pacific") + ), + TimedeltaIndex(["1 day", "2 day"], name="x"), + ], +) +def test_nat_arithmetic_index(op_name, value): + # see gh-11718 + exp_name = "x" + exp_data = [NaT] * 2 + + if value.dtype.kind == "M" and "plus" in op_name: + expected = DatetimeIndex(exp_data, tz=value.tz, name=exp_name) + else: + expected = TimedeltaIndex(exp_data, name=exp_name) + expected = expected.as_unit(value.unit) + + if not isinstance(value, Index): + expected = expected.array + + op = _ops[op_name] + result = op(NaT, value) + tm.assert_equal(result, expected) + + +@pytest.mark.parametrize( + "op_name", + ["left_plus_right", "right_plus_left", "left_minus_right", "right_minus_left"], +) +@pytest.mark.parametrize("box", [TimedeltaIndex, Series, TimedeltaArray._from_sequence]) +def test_nat_arithmetic_td64_vector(op_name, box): + # see gh-19124 + vec = box(["1 day", "2 day"], dtype="timedelta64[ns]") + box_nat = box([NaT, NaT], dtype="timedelta64[ns]") + tm.assert_equal(_ops[op_name](vec, NaT), box_nat) + + +@pytest.mark.parametrize( + "dtype,op,out_dtype", + [ + ("datetime64[ns]", operator.add, "datetime64[ns]"), + ("datetime64[ns]", roperator.radd, "datetime64[ns]"), + ("datetime64[ns]", operator.sub, "timedelta64[ns]"), + ("datetime64[ns]", roperator.rsub, "timedelta64[ns]"), + ("timedelta64[ns]", operator.add, "datetime64[ns]"), + ("timedelta64[ns]", roperator.radd, "datetime64[ns]"), + ("timedelta64[ns]", operator.sub, "datetime64[ns]"), + ("timedelta64[ns]", roperator.rsub, "timedelta64[ns]"), + ], +) +def test_nat_arithmetic_ndarray(dtype, op, out_dtype): + other = np.arange(10).astype(dtype) + result = op(NaT, other) + + expected = np.empty(other.shape, dtype=out_dtype) + expected.fill("NaT") + tm.assert_numpy_array_equal(result, expected) + + +def test_nat_pinned_docstrings(): + # see gh-17327 + assert NaT.ctime.__doc__ == Timestamp.ctime.__doc__ + + +def test_to_numpy_alias(): + # GH 24653: alias .to_numpy() for scalars + expected = NaT.to_datetime64() + result = NaT.to_numpy() + + assert isna(expected) and isna(result) + + # GH#44460 + result = NaT.to_numpy("M8[s]") + assert isinstance(result, np.datetime64) + assert result.dtype == "M8[s]" + + result = NaT.to_numpy("m8[ns]") + assert isinstance(result, np.timedelta64) + assert result.dtype == "m8[ns]" + + result = NaT.to_numpy("m8[s]") + assert isinstance(result, np.timedelta64) + assert result.dtype == "m8[s]" + + with pytest.raises(ValueError, match="NaT.to_numpy dtype must be a "): + NaT.to_numpy(np.int64) + + +@pytest.mark.parametrize( + "other", + [ + Timedelta(0), + Timedelta(0).to_pytimedelta(), + Timedelta(0).to_timedelta64(), + Timestamp(0), + Timestamp(0).to_pydatetime(), + Timestamp(0).to_datetime64(), + Timestamp(0).tz_localize("UTC"), + NaT, + ], +) +def test_nat_comparisons(compare_operators_no_eq_ne, other): + # GH 26039 + opname = compare_operators_no_eq_ne + + assert getattr(NaT, opname)(other) is False + + op = getattr(operator, opname.strip("_")) + assert op(NaT, other) is False + assert op(other, NaT) is False + + +@pytest.mark.parametrize("other_and_type", [("foo", "str"), (2, "int"), (2.0, "float")]) +@pytest.mark.parametrize( + "symbol_and_op", + [("<=", operator.le), ("<", operator.lt), (">=", operator.ge), (">", operator.gt)], +) +def test_nat_comparisons_invalid(other_and_type, symbol_and_op): + # GH#35585 + other, other_type = other_and_type + symbol, op = symbol_and_op + + assert not NaT == other + assert not other == NaT + + assert NaT != other + assert other != NaT + + msg = f"'{symbol}' not supported between instances of 'NaTType' and '{other_type}'" + with pytest.raises(TypeError, match=msg): + op(NaT, other) + + msg = f"'{symbol}' not supported between instances of '{other_type}' and 'NaTType'" + with pytest.raises(TypeError, match=msg): + op(other, NaT) + + +@pytest.mark.parametrize( + "other", + [ + np.array(["foo"] * 2, dtype=object), + np.array([2, 3], dtype="int64"), + np.array([2.0, 3.5], dtype="float64"), + ], + ids=["str", "int", "float"], +) +def test_nat_comparisons_invalid_ndarray(other): + # GH#40722 + expected = np.array([False, False]) + result = NaT == other + tm.assert_numpy_array_equal(result, expected) + result = other == NaT + tm.assert_numpy_array_equal(result, expected) + + expected = np.array([True, True]) + result = NaT != other + tm.assert_numpy_array_equal(result, expected) + result = other != NaT + tm.assert_numpy_array_equal(result, expected) + + for symbol, op in [ + ("<=", operator.le), + ("<", operator.lt), + (">=", operator.ge), + (">", operator.gt), + ]: + msg = f"'{symbol}' not supported between" + + with pytest.raises(TypeError, match=msg): + op(NaT, other) + + if other.dtype == np.dtype("object"): + # uses the reverse operator, so symbol changes + msg = None + with pytest.raises(TypeError, match=msg): + op(other, NaT) + + +def test_compare_date(fixed_now_ts): + # GH#39151 comparing NaT with date object is deprecated + # See also: tests.scalar.timestamps.test_comparisons::test_compare_date + + dt = fixed_now_ts.to_pydatetime().date() + + msg = "Cannot compare NaT with datetime.date object" + for left, right in [(NaT, dt), (dt, NaT)]: + assert not left == right + assert left != right + + with pytest.raises(TypeError, match=msg): + left < right + with pytest.raises(TypeError, match=msg): + left <= right + with pytest.raises(TypeError, match=msg): + left > right + with pytest.raises(TypeError, match=msg): + left >= right + + +@pytest.mark.parametrize( + "obj", + [ + offsets.YearEnd(2), + offsets.YearBegin(2), + offsets.MonthBegin(1), + offsets.MonthEnd(2), + offsets.MonthEnd(12), + offsets.Day(2), + offsets.Day(5), + offsets.Hour(24), + offsets.Hour(3), + offsets.Minute(), + np.timedelta64(3, "h"), + np.timedelta64(4, "h"), + np.timedelta64(3200, "s"), + np.timedelta64(3600, "s"), + np.timedelta64(3600 * 24, "s"), + np.timedelta64(2, "D"), + np.timedelta64(365, "D"), + timedelta(-2), + timedelta(365), + timedelta(minutes=120), + timedelta(days=4, minutes=180), + timedelta(hours=23), + timedelta(hours=23, minutes=30), + timedelta(hours=48), + ], +) +def test_nat_addsub_tdlike_scalar(obj): + assert NaT + obj is NaT + assert obj + NaT is NaT + assert NaT - obj is NaT + + +def test_pickle(temp_file): + # GH#4606 + p = tm.round_trip_pickle(NaT, temp_file) + assert p is NaT diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/__init__.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/methods/__init__.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/methods/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/methods/test_as_unit.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/methods/test_as_unit.py new file mode 100644 index 0000000000000000000000000000000000000000..005c4d1d5fa999882907c8731caef25ad3ab2624 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/methods/test_as_unit.py @@ -0,0 +1,80 @@ +import pytest + +from pandas._libs.tslibs.dtypes import NpyDatetimeUnit +from pandas.errors import OutOfBoundsTimedelta + +from pandas import Timedelta + + +class TestAsUnit: + def test_as_unit(self): + td = Timedelta(days=1) + + assert td.as_unit("us") is td + + res = td.as_unit("ns") + assert res._value == td._value * 1000 + assert res._creso == NpyDatetimeUnit.NPY_FR_ns.value + + rt = res.as_unit("us") + assert rt._value == td._value + assert rt._creso == td._creso + + res = td.as_unit("ms") + assert res._value == td._value // 1_000 + assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value + + rt = res.as_unit("us") + assert rt._value == td._value + assert rt._creso == td._creso + + res = td.as_unit("s") + assert res._value == td._value // 1_000_000 + assert res._creso == NpyDatetimeUnit.NPY_FR_s.value + + rt = res.as_unit("us") + assert rt._value == td._value + assert rt._creso == td._creso + + def test_as_unit_overflows(self): + # microsecond that would be just out of bounds for nano + us = 9223372800000000 + td = Timedelta._from_value_and_reso(us, NpyDatetimeUnit.NPY_FR_us.value) + + msg = "Cannot cast 106752 days 00:00:00 to unit='ns' without overflow" + with pytest.raises(OutOfBoundsTimedelta, match=msg): + td.as_unit("ns") + + res = td.as_unit("ms") + assert res._value == us // 1000 + assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value + + def test_as_unit_rounding(self): + td = Timedelta(microseconds=1500) + res = td.as_unit("ms") + + expected = Timedelta(milliseconds=1) + assert res == expected + + assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value + assert res._value == 1 + + with pytest.raises(ValueError, match="Cannot losslessly convert units"): + td.as_unit("ms", round_ok=False) + + def test_as_unit_non_nano(self): + # case where we are going neither to nor from nano + td = Timedelta(days=1).as_unit("ms") + assert td.days == 1 + assert td._value == 86_400_000 + assert td.components.days == 1 + assert td._d == 1 + assert td.total_seconds() == 86400 + + res = td.as_unit("us") + assert res._value == 86_400_000_000 + assert res.components.days == 1 + assert res.components.hours == 0 + assert res._d == 1 + assert res._h == 0 + assert res.total_seconds() == 86400 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/methods/test_round.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/methods/test_round.py new file mode 100644 index 0000000000000000000000000000000000000000..a0bdd5fb3b626d0ec73a4df645d00e186b2a408f --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/methods/test_round.py @@ -0,0 +1,195 @@ +from hypothesis import ( + given, + strategies as st, +) +import numpy as np +import pytest + +from pandas._libs import lib +from pandas._libs.tslibs import iNaT +from pandas.errors import OutOfBoundsTimedelta + +from pandas import Timedelta + + +class TestTimedeltaRound: + @pytest.mark.parametrize( + "freq,s1,s2", + [ + # This first case has s1, s2 being the same as t1,t2 below + ( + "ns", + "1 days 02:34:56.789123456", + "-1 days 02:34:56.789123456", + ), + ( + "us", + "1 days 02:34:56.789123000", + "-1 days 02:34:56.789123000", + ), + ( + "ms", + "1 days 02:34:56.789000000", + "-1 days 02:34:56.789000000", + ), + ("s", "1 days 02:34:57", "-1 days 02:34:57"), + ("2s", "1 days 02:34:56", "-1 days 02:34:56"), + ("5s", "1 days 02:34:55", "-1 days 02:34:55"), + ("min", "1 days 02:35:00", "-1 days 02:35:00"), + ("12min", "1 days 02:36:00", "-1 days 02:36:00"), + ("h", "1 days 03:00:00", "-1 days 03:00:00"), + ("D", "1 days", "-1 days"), + ], + ) + def test_round(self, freq, s1, s2): + s1 = Timedelta(s1) + s2 = Timedelta(s2) + t1 = Timedelta("1 days 02:34:56.789123456") + t2 = Timedelta("-1 days 02:34:56.789123456") + + r1 = t1.round(freq) + assert r1 == s1 + r2 = t2.round(freq) + assert r2 == s2 + + def test_round_invalid(self): + t1 = Timedelta("1 days 02:34:56.789123456") + + for freq, msg in [ + ("YE", " is a non-fixed frequency"), + ("ME", " is a non-fixed frequency"), + ("foobar", "Invalid frequency: foobar"), + ]: + with pytest.raises(ValueError, match=msg): + t1.round(freq) + + def test_round_implementation_bounds(self): + # See also: analogous test for Timestamp + # GH#38964 + result = Timedelta.min.ceil("s") + expected = Timedelta.min + Timedelta(seconds=1) - Timedelta(145224193) + assert result == expected + + result = Timedelta.max.floor("s") + expected = Timedelta.max - Timedelta(854775807) + assert result == expected + + msg = ( + r"Cannot round -106752 days \+00:12:43.145224193 to freq=s without overflow" + ) + with pytest.raises(OutOfBoundsTimedelta, match=msg): + Timedelta.min.floor("s") + with pytest.raises(OutOfBoundsTimedelta, match=msg): + Timedelta.min.round("s") + + msg = "Cannot round 106751 days 23:47:16.854775807 to freq=s without overflow" + with pytest.raises(OutOfBoundsTimedelta, match=msg): + Timedelta.max.ceil("s") + with pytest.raises(OutOfBoundsTimedelta, match=msg): + Timedelta.max.round("s") + + @pytest.mark.slow + @given(val=st.integers(min_value=iNaT + 1, max_value=lib.i8max)) + @pytest.mark.parametrize( + "method", [Timedelta.round, Timedelta.floor, Timedelta.ceil] + ) + def test_round_sanity(self, val, method): + cls = Timedelta + err_cls = OutOfBoundsTimedelta + + val = np.int64(val) + td = cls(val) + + def checker(ts, nanos, unit): + # First check that we do raise in cases where we should + if nanos == 1: + pass + else: + div, mod = divmod(ts._value, nanos) + diff = int(nanos - mod) + lb = ts._value - mod + assert lb <= ts._value # i.e. no overflows with python ints + ub = ts._value + diff + assert ub > ts._value # i.e. no overflows with python ints + + msg = "without overflow" + if mod == 0: + # We should never be raising in this + pass + elif method is cls.ceil: + if ub > cls.max._value: + with pytest.raises(err_cls, match=msg): + method(ts, unit) + return + elif method is cls.floor: + if lb < cls.min._value: + with pytest.raises(err_cls, match=msg): + method(ts, unit) + return + elif mod >= diff: + if ub > cls.max._value: + with pytest.raises(err_cls, match=msg): + method(ts, unit) + return + elif lb < cls.min._value: + with pytest.raises(err_cls, match=msg): + method(ts, unit) + return + + res = method(ts, unit) + + td = res - ts + diff = abs(td._value) + assert diff < nanos + assert res._value % nanos == 0 + + if method is cls.round: + assert diff <= nanos / 2 + elif method is cls.floor: + assert res <= ts + elif method is cls.ceil: + assert res >= ts + + nanos = 1 + checker(td, nanos, "ns") + + nanos = 1000 + checker(td, nanos, "us") + + nanos = 1_000_000 + checker(td, nanos, "ms") + + nanos = 1_000_000_000 + checker(td, nanos, "s") + + nanos = 60 * 1_000_000_000 + checker(td, nanos, "min") + + nanos = 60 * 60 * 1_000_000_000 + checker(td, nanos, "h") + + nanos = 24 * 60 * 60 * 1_000_000_000 + checker(td, nanos, "D") + + def test_round_non_nano(self, unit): + td = Timedelta("1 days 02:34:57").as_unit(unit) + + res = td.round("min") + assert res == Timedelta("1 days 02:35:00") + assert res._creso == td._creso + + res = td.floor("min") + assert res == Timedelta("1 days 02:34:00") + assert res._creso == td._creso + + res = td.ceil("min") + assert res == Timedelta("1 days 02:35:00") + assert res._creso == td._creso + + def test_round_freq_finer_than_resolution(self): + # GH#64828 + td = Timedelta(1.0, unit="days").as_unit("s") + assert td.unit == "s" + assert td.round("100ms") == Timedelta("1 days 00:00:00") + assert td.floor("100ms") == Timedelta("1 days 00:00:00") + assert td.ceil("100ms") == Timedelta("1 days 00:00:00") diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_arithmetic.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_arithmetic.py new file mode 100644 index 0000000000000000000000000000000000000000..8dc1cfa544d6a9f586fbbd6155eb697cb47aea1d --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_arithmetic.py @@ -0,0 +1,1251 @@ +""" +Tests for scalar Timedelta arithmetic ops +""" + +from datetime import ( + datetime, + timedelta, +) +import operator + +import numpy as np +import pytest + +from pandas.errors import ( + OutOfBoundsTimedelta, + Pandas4Warning, +) + +import pandas as pd +from pandas import ( + NaT, + Timedelta, + Timestamp, + offsets, +) +import pandas._testing as tm +from pandas.core import ops + + +class TestTimedeltaAdditionSubtraction: + """ + Tests for Timedelta methods: + + __add__, __radd__, + __sub__, __rsub__ + """ + + def test_td_add_sub_pydatetime(self, unit): + # GH#53643 + td = Timedelta(hours=23).as_unit(unit) + dt = datetime(2016, 1, 1) + + expected = datetime(2016, 1, 1, 23) + result = dt + td + assert result == expected + result = td + dt + assert result == expected + + expected = datetime(2015, 12, 31, 1) + result = dt - td + assert result == expected + + @pytest.mark.parametrize( + "ten_seconds", + [ + Timedelta(10, unit="s"), + timedelta(seconds=10), + np.timedelta64(10, "s"), + np.timedelta64(10000000000, "ns"), + offsets.Second(10), + ], + ) + def test_td_add_sub_ten_seconds(self, ten_seconds): + # GH#6808 + base = Timestamp("20130101 09:01:12.123456") + expected_add = Timestamp("20130101 09:01:22.123456") + expected_sub = Timestamp("20130101 09:01:02.123456") + + result = base + ten_seconds + assert result == expected_add + + result = base - ten_seconds + assert result == expected_sub + + @pytest.mark.parametrize( + "one_day_ten_secs", + [ + Timedelta("1 day, 00:00:10"), + Timedelta("1 days, 00:00:10"), + timedelta(days=1, seconds=10), + np.timedelta64(1, "D") + np.timedelta64(10, "s"), + offsets.Day() + offsets.Second(10), + ], + ) + def test_td_add_sub_one_day_ten_seconds(self, one_day_ten_secs): + # GH#6808 + base = Timestamp("20130102 09:01:12.123456") + expected_add = Timestamp("20130103 09:01:22.123456") + expected_sub = Timestamp("20130101 09:01:02.123456") + + result = base + one_day_ten_secs + assert result == expected_add + + result = base - one_day_ten_secs + assert result == expected_sub + + @pytest.mark.parametrize("op", [operator.add, ops.radd]) + def test_td_add_datetimelike_scalar(self, op): + # GH#19738 + td = Timedelta(10, unit="D") + + result = op(td, datetime(2016, 1, 1)) + if op is operator.add: + # datetime + Timedelta does _not_ call Timedelta.__radd__, + # so we get a datetime back instead of a Timestamp + assert isinstance(result, Timestamp) + assert result == Timestamp(2016, 1, 11) + + result = op(td, Timestamp("2018-01-12 18:09")) + assert isinstance(result, Timestamp) + assert result == Timestamp("2018-01-22 18:09") + + result = op(td, np.datetime64("2018-01-12")) + assert isinstance(result, Timestamp) + assert result == Timestamp("2018-01-22") + + result = op(td, NaT) + assert result is NaT + + def test_td_add_timestamp_overflow(self): + ts = Timestamp("1700-01-01").as_unit("ns") + msg = "Cannot cast 259987 days 00:00:00 to unit='ns' without overflow." + with pytest.raises(OutOfBoundsTimedelta, match=msg): + ts + Timedelta(13 * 19999, unit="D") + + msg = "Cannot cast 259987 days 00:00:00 to unit='ns' without overflow" + with pytest.raises(OutOfBoundsTimedelta, match=msg): + ts + timedelta(days=13 * 19999) + + @pytest.mark.parametrize("op", [operator.add, ops.radd]) + def test_td_add_td(self, op): + td = Timedelta(10, unit="D") + + result = op(td, Timedelta(days=10)) + assert isinstance(result, Timedelta) + assert result == Timedelta(days=20) + + @pytest.mark.parametrize("op", [operator.add, ops.radd]) + def test_td_add_pytimedelta(self, op): + td = Timedelta(10, unit="D") + result = op(td, timedelta(days=9)) + assert isinstance(result, Timedelta) + assert result == Timedelta(days=19) + + @pytest.mark.parametrize("op", [operator.add, ops.radd]) + def test_td_add_timedelta64(self, op): + td = Timedelta(10, unit="D") + result = op(td, np.timedelta64(-4, "D")) + assert isinstance(result, Timedelta) + assert result == Timedelta(days=6) + + @pytest.mark.parametrize("op", [operator.add, ops.radd]) + def test_td_add_offset(self, op): + td = Timedelta(10, unit="D") + + result = op(td, offsets.Hour(6)) + assert isinstance(result, Timedelta) + assert result == Timedelta(days=10, hours=6) + + def test_td_sub_td(self): + td = Timedelta(10, unit="D") + expected = Timedelta(0, unit="ns") + result = td - td + assert isinstance(result, Timedelta) + assert result == expected + + def test_td_sub_pytimedelta(self): + td = Timedelta(10, unit="D") + expected = Timedelta(0, unit="ns") + + result = td - td.to_pytimedelta() + assert isinstance(result, Timedelta) + assert result == expected + + result = td.to_pytimedelta() - td + assert isinstance(result, Timedelta) + assert result == expected + + def test_td_sub_timedelta64(self): + td = Timedelta(10, unit="D") + expected = Timedelta(0, unit="ns") + + result = td - td.to_timedelta64() + assert isinstance(result, Timedelta) + assert result == expected + + result = td.to_timedelta64() - td + assert isinstance(result, Timedelta) + assert result == expected + + def test_td_sub_nat(self): + # In this context pd.NaT is treated as timedelta-like + td = Timedelta(10, unit="D") + result = td - NaT + assert result is NaT + + def test_td_sub_td64_nat(self): + td = Timedelta(10, unit="D") + td_nat = np.timedelta64("NaT", "ns") + + result = td - td_nat + assert result is NaT + + result = td_nat - td + assert result is NaT + + def test_td_sub_offset(self): + td = Timedelta(10, unit="D") + result = td - offsets.Hour(1) + assert isinstance(result, Timedelta) + assert result == Timedelta(239, unit="h") + + def test_td_add_sub_numeric_raises(self): + td = Timedelta(10, unit="D") + msg = "unsupported operand type" + for other in [2, 2.0, np.int64(2), np.float64(2)]: + with pytest.raises(TypeError, match=msg): + td + other + with pytest.raises(TypeError, match=msg): + other + td + with pytest.raises(TypeError, match=msg): + td - other + with pytest.raises(TypeError, match=msg): + other - td + + def test_td_add_sub_int_ndarray(self): + td = Timedelta("1 day") + other = np.array([1]) + + msg = r"unsupported operand type\(s\) for \+: 'Timedelta' and 'int'" + with pytest.raises(TypeError, match=msg): + td + np.array([1]) + + msg = "|".join( + [ + ( + r"unsupported operand type\(s\) for \+: 'numpy.ndarray' " + "and 'Timedelta'" + ), + # This message goes on to say "Please do not rely on this error; + # it may not be given on all Python implementations" + "Concatenation operation is not implemented for NumPy arrays", + ] + ) + with pytest.raises(TypeError, match=msg): + other + td + msg = r"unsupported operand type\(s\) for -: 'Timedelta' and 'int'" + with pytest.raises(TypeError, match=msg): + td - other + msg = r"unsupported operand type\(s\) for -: 'numpy.ndarray' and 'Timedelta'" + with pytest.raises(TypeError, match=msg): + other - td + + def test_td_rsub_nat(self): + td = Timedelta(10, unit="D") + result = NaT - td + assert result is NaT + + result = np.datetime64("NaT", "ns") - td + assert result is NaT + + def test_td_rsub_offset(self): + result = offsets.Hour(1) - Timedelta(10, unit="D") + assert isinstance(result, Timedelta) + assert result == Timedelta(-239, unit="h") + + def test_td_sub_timedeltalike_object_dtype_array(self): + # GH#21980 + arr = np.array([Timestamp("20130101 9:01"), Timestamp("20121230 9:02")]) + exp = np.array([Timestamp("20121231 9:01"), Timestamp("20121229 9:02")]) + res = arr - Timedelta("1D") + tm.assert_numpy_array_equal(res, exp) + + def test_td_sub_mixed_most_timedeltalike_object_dtype_array(self): + # GH#21980 + now = Timestamp("2021-11-09 09:54:00") + arr = np.array([now, Timedelta("1D"), np.timedelta64(2, "h")]) + exp = np.array( + [ + now - Timedelta("1D"), + Timedelta("0D"), + np.timedelta64(2, "h") - Timedelta("1D"), + ] + ) + res = arr - Timedelta("1D") + tm.assert_numpy_array_equal(res, exp) + + def test_td_rsub_mixed_most_timedeltalike_object_dtype_array(self): + # GH#21980 + now = Timestamp("2021-11-09 09:54:00") + arr = np.array([now, Timedelta("1D"), np.timedelta64(2, "h")]) + msg = r"unsupported operand type\(s\) for \-: 'Timedelta' and 'Timestamp'" + with pytest.raises(TypeError, match=msg): + Timedelta("1D") - arr + + @pytest.mark.parametrize("op", [operator.add, ops.radd]) + def test_td_add_timedeltalike_object_dtype_array(self, op): + # GH#21980 + arr = np.array([Timestamp("20130101 9:01"), Timestamp("20121230 9:02")]) + exp = np.array([Timestamp("20130102 9:01"), Timestamp("20121231 9:02")]) + res = op(arr, Timedelta("1D")) + tm.assert_numpy_array_equal(res, exp) + + @pytest.mark.parametrize("op", [operator.add, ops.radd]) + def test_td_add_mixed_timedeltalike_object_dtype_array(self, op): + # GH#21980 + now = Timestamp("2021-11-09 09:54:00") + arr = np.array([now, Timedelta("1D")]) + exp = np.array([now + Timedelta("1D"), Timedelta("2D")]) + res = op(arr, Timedelta("1D")) + tm.assert_numpy_array_equal(res, exp) + + def test_td_add_sub_td64_ndarray(self): + td = Timedelta("1 day") + + other = np.array([td.to_timedelta64()]) + expected = np.array([Timedelta("2 Days").to_timedelta64()]) + + result = td + other + tm.assert_numpy_array_equal(result, expected) + result = other + td + tm.assert_numpy_array_equal(result, expected) + + result = td - other + tm.assert_numpy_array_equal(result, expected * 0) + result = other - td + tm.assert_numpy_array_equal(result, expected * 0) + + def test_td_add_sub_dt64_ndarray(self): + td = Timedelta("1 day") + other = np.array(["2000-01-01"], dtype="M8[ns]") + + expected = np.array(["2000-01-02"], dtype="M8[ns]") + tm.assert_numpy_array_equal(td + other, expected) + tm.assert_numpy_array_equal(other + td, expected) + + expected = np.array(["1999-12-31"], dtype="M8[ns]") + tm.assert_numpy_array_equal(-td + other, expected) + tm.assert_numpy_array_equal(other - td, expected) + + def test_td_add_sub_ndarray_0d(self): + td = Timedelta("1 day") + other = np.array(td.asm8) + + result = td + other + assert isinstance(result, Timedelta) + assert result == 2 * td + + result = other + td + assert isinstance(result, Timedelta) + assert result == 2 * td + + result = other - td + assert isinstance(result, Timedelta) + assert result == 0 * td + + result = td - other + assert isinstance(result, Timedelta) + assert result == 0 * td + + +class TestTimedeltaMultiplicationDivision: + """ + Tests for Timedelta methods: + + __mul__, __rmul__, + __div__, __rdiv__, + __truediv__, __rtruediv__, + __floordiv__, __rfloordiv__, + __mod__, __rmod__, + __divmod__, __rdivmod__ + """ + + # --------------------------------------------------------------- + # Timedelta.__mul__, __rmul__ + + @pytest.mark.parametrize("td_nat", [NaT, np.timedelta64("NaT", "ns")]) + @pytest.mark.parametrize("op", [operator.mul, ops.rmul]) + def test_td_mul_nat(self, op, td_nat): + # GH#19819 + td = Timedelta(10, unit="D") + typs = "|".join(["numpy.timedelta64", "NaTType", "Timedelta"]) + msg = "|".join( + [ + rf"unsupported operand type\(s\) for \*: '{typs}' and '{typs}'", + r"ufunc '?multiply'? cannot use operands with types", + ] + ) + with pytest.raises(TypeError, match=msg): + op(td, td_nat) + + @pytest.mark.parametrize("nan", [np.nan, np.float64("NaN"), float("nan")]) + @pytest.mark.parametrize("op", [operator.mul, ops.rmul]) + def test_td_mul_nan(self, op, nan): + # np.float64('NaN') has a 'dtype' attr, avoid treating as array + td = Timedelta(10, unit="D") + result = op(td, nan) + assert result is NaT + + @pytest.mark.parametrize("op", [operator.mul, ops.rmul]) + def test_td_mul_scalar(self, op): + # GH#19738 + td = Timedelta(minutes=3) + + result = op(td, 2) + assert result == Timedelta(minutes=6) + + result = op(td, 1.5) + assert result == Timedelta(minutes=4, seconds=30) + + assert op(td, np.nan) is NaT + + assert op(-1, td)._value == -1 * td._value + assert op(-1.0, td)._value == -1.0 * td._value + + msg = "unsupported operand type" + with pytest.raises(TypeError, match=msg): + # timedelta * datetime is gibberish + op(td, Timestamp(2016, 1, 2)) + + with pytest.raises(TypeError, match=msg): + # invalid multiply with another timedelta + op(td, td) + + def test_td_mul_numeric_ndarray(self): + td = Timedelta("1 day") + other = np.array([2]) + expected = np.array([Timedelta("2 Days").to_timedelta64()]) + + result = td * other + tm.assert_numpy_array_equal(result, expected) + + result = other * td + tm.assert_numpy_array_equal(result, expected) + + def test_td_mul_numeric_ndarray_0d(self): + td = Timedelta("1 day") + other = np.array(2, dtype=np.int64) + assert other.ndim == 0 + expected = Timedelta("2 days") + + res = td * other + assert type(res) is Timedelta + assert res == expected + + res = other * td + assert type(res) is Timedelta + assert res == expected + + def test_td_mul_td64_ndarray_invalid(self): + td = Timedelta("1 day") + other = np.array([Timedelta("2 Days").to_timedelta64()]) + + msg = ( + "ufunc '?multiply'? cannot use operands with types " + rf"dtype\('{tm.ENDIAN}m8\[us\]'\) and dtype\('{tm.ENDIAN}m8\[us\]'\)" + ) + with pytest.raises(TypeError, match=msg): + td * other + with pytest.raises(TypeError, match=msg): + other * td + + # --------------------------------------------------------------- + # Timedelta.__div__, __truediv__ + + def test_td_div_timedeltalike_scalar(self): + # GH#19738 + td = Timedelta(10, unit="D") + + result = td / offsets.Hour(1) + assert result == 240 + + assert td / td == 1 + assert td / np.timedelta64(60, "h") == 4 + + assert np.isnan(td / NaT) + + def test_td_div_td64_non_nano(self): + # truediv + td = Timedelta("1 days 2 hours 3 ns") + result = td / np.timedelta64(1, "D") + assert result == td._value / (86400 * 10**9) + result = td / np.timedelta64(1, "s") + assert result == td._value / 10**9 + result = td / np.timedelta64(1, "ns") + assert result == td._value + + # floordiv + td = Timedelta("1 days 2 hours 3 ns") + result = td // np.timedelta64(1, "D") + assert result == 1 + result = td // np.timedelta64(1, "s") + assert result == 93600 + result = td // np.timedelta64(1, "ns") + assert result == td._value + + def test_td_div_numeric_scalar(self): + # GH#19738 + td = Timedelta(10, unit="D") + + result = td / 2 + assert isinstance(result, Timedelta) + assert result == Timedelta(days=5) + + result = td / 5 + assert isinstance(result, Timedelta) + assert result == Timedelta(days=2) + + @pytest.mark.parametrize( + "nan", + [ + np.nan, + np.float64("NaN"), + float("nan"), + ], + ) + def test_td_div_nan(self, nan): + # np.float64('NaN') has a 'dtype' attr, avoid treating as array + td = Timedelta(10, unit="D") + result = td / nan + assert result is NaT + + result = td // nan + assert result is NaT + + def test_td_div_td64_ndarray(self): + td = Timedelta("1 day") + + other = np.array([Timedelta("2 Days").to_timedelta64()]) + expected = np.array([0.5]) + + result = td / other + tm.assert_numpy_array_equal(result, expected) + + result = other / td + tm.assert_numpy_array_equal(result, expected * 4) + + def test_td_div_ndarray_0d(self): + td = Timedelta("1 day") + + other = np.array(1) + res = td / other + assert isinstance(res, Timedelta) + assert res == td + + # --------------------------------------------------------------- + # Timedelta.__rdiv__ + + def test_td_rdiv_timedeltalike_scalar(self): + # GH#19738 + td = Timedelta(10, unit="D") + result = offsets.Hour(1) / td + assert result == 1 / 240.0 + + assert np.timedelta64(60, "h") / td == 0.25 + + def test_td_rdiv_na_scalar(self): + # GH#31869 None gets cast to NaT + td = Timedelta(10, unit="D") + + result = NaT / td + assert np.isnan(result) + + result = None / td + assert np.isnan(result) + + result = np.timedelta64("NaT", "ns") / td + assert np.isnan(result) + + msg = r"unsupported operand type\(s\) for /: 'numpy.datetime64' and 'Timedelta'" + with pytest.raises(TypeError, match=msg): + np.datetime64("NaT", "ns") / td + + msg = r"unsupported operand type\(s\) for /: 'float' and 'Timedelta'" + with pytest.raises(TypeError, match=msg): + np.nan / td + + def test_td_rdiv_ndarray(self): + td = Timedelta(10, unit="D") + + arr = np.array([td], dtype=object) + result = arr / td + expected = np.array([1], dtype=np.float64) + tm.assert_numpy_array_equal(result, expected) + + arr = np.array([None]) + result = arr / td + expected = np.array([np.nan]) + tm.assert_numpy_array_equal(result, expected) + + arr = np.array([np.nan], dtype=object) + msg = r"unsupported operand type\(s\) for /: 'float' and 'Timedelta'" + with pytest.raises(TypeError, match=msg): + arr / td + + arr = np.array([np.nan], dtype=np.float64) + msg = "cannot use operands with types dtype" + with pytest.raises(TypeError, match=msg): + arr / td + + def test_td_rdiv_ndarray_0d(self): + td = Timedelta(10, unit="D") + + arr = np.array(td.asm8) + + assert arr / td == 1 + + # --------------------------------------------------------------- + # Timedelta.__floordiv__ + + def test_td_floordiv_timedeltalike_scalar(self): + # GH#18846 + td = Timedelta(hours=3, minutes=4) + scalar = Timedelta(hours=3, minutes=3) + + assert td // scalar == 1 + assert -td // scalar.to_pytimedelta() == -2 + assert (2 * td) // scalar.to_timedelta64() == 2 + + def test_td_floordiv_null_scalar(self): + # GH#18846 + td = Timedelta(hours=3, minutes=4) + + assert td // np.nan is NaT + assert np.isnan(td // NaT) + assert np.isnan(td // np.timedelta64("NaT", "ns")) + + def test_td_floordiv_offsets(self): + # GH#19738 + td = Timedelta(hours=3, minutes=4) + assert td // offsets.Hour(1) == 3 + assert td // offsets.Minute(2) == 92 + + def test_td_floordiv_invalid_scalar(self): + # GH#18846 + td = Timedelta(hours=3, minutes=4) + + msg = "|".join( + [ + r"Invalid dtype datetime64\[D\] for __floordiv__", + "'dtype' is an invalid keyword argument for this function", + "this function got an unexpected keyword argument 'dtype'", + r"ufunc '?floor_divide'? cannot use operands with types", + ] + ) + with pytest.raises(TypeError, match=msg): + td // np.datetime64("2016-01-01", dtype="datetime64[us]") + + def test_td_floordiv_numeric_scalar(self): + # GH#18846 + td = Timedelta(hours=3, minutes=4) + + expected = Timedelta(hours=1, minutes=32) + assert td // 2 == expected + assert td // 2.0 == expected + assert td // np.float64(2.0) == expected + assert td // np.int32(2.0) == expected + assert td // np.uint8(2.0) == expected + + def test_td_floordiv_timedeltalike_array(self): + # GH#18846 + td = Timedelta(hours=3, minutes=4) + scalar = Timedelta(hours=3, minutes=3) + + # Array-like others + assert td // np.array(scalar.to_timedelta64()) == 1 + + res = (3 * td) // np.array([scalar.to_timedelta64()]) + expected = np.array([3], dtype=np.int64) + tm.assert_numpy_array_equal(res, expected) + + res = (10 * td) // np.array( + [scalar.to_timedelta64(), np.timedelta64("NaT", "ns")] + ) + expected = np.array([10, np.nan]) + tm.assert_numpy_array_equal(res, expected) + + def test_td_floordiv_numeric_series(self): + # GH#18846 + td = Timedelta(hours=3, minutes=4) + ser = pd.Series([1], dtype=np.int64) + res = td // ser + assert res.dtype.kind == "m" + + # --------------------------------------------------------------- + # Timedelta.__rfloordiv__ + + def test_td_rfloordiv_timedeltalike_scalar(self): + # GH#18846 + td = Timedelta(hours=3, minutes=3) + scalar = Timedelta(hours=3, minutes=4) + + # scalar others + # x // Timedelta is defined only for timedelta-like x. int-like, + # float-like, and date-like, in particular, should all either + # a) raise TypeError directly or + # b) return NotImplemented, following which the reversed + # operation will raise TypeError. + assert td.__rfloordiv__(scalar) == 1 + assert (-td).__rfloordiv__(scalar.to_pytimedelta()) == -2 + assert (2 * td).__rfloordiv__(scalar.to_timedelta64()) == 0 + + def test_td_rfloordiv_null_scalar(self): + # GH#18846 + td = Timedelta(hours=3, minutes=3) + + assert np.isnan(td.__rfloordiv__(NaT)) + assert np.isnan(td.__rfloordiv__(np.timedelta64("NaT", "ns"))) + + def test_td_rfloordiv_offsets(self): + # GH#19738 + assert offsets.Hour(1) // Timedelta(minutes=25) == 2 + + def test_td_rfloordiv_invalid_scalar(self): + # GH#18846 + td = Timedelta(hours=3, minutes=3) + + dt64 = np.datetime64("2016-01-01", "us") + + assert td.__rfloordiv__(dt64) is NotImplemented + + msg = ( + r"unsupported operand type\(s\) for //: 'numpy.datetime64' and 'Timedelta'" + ) + with pytest.raises(TypeError, match=msg): + dt64 // td + + def test_td_rfloordiv_numeric_scalar(self): + # GH#18846 + td = Timedelta(hours=3, minutes=3) + + assert td.__rfloordiv__(np.nan) is NotImplemented + assert td.__rfloordiv__(3.5) is NotImplemented + assert td.__rfloordiv__(2) is NotImplemented + assert td.__rfloordiv__(np.float64(2.0)) is NotImplemented + assert td.__rfloordiv__(np.uint8(9)) is NotImplemented + assert td.__rfloordiv__(np.int32(2.0)) is NotImplemented + + msg = r"unsupported operand type\(s\) for //: '.*' and 'Timedelta" + with pytest.raises(TypeError, match=msg): + np.float64(2.0) // td + with pytest.raises(TypeError, match=msg): + np.uint8(9) // td + with pytest.raises(TypeError, match=msg): + # deprecated GH#19761, enforced GH#29797 + np.int32(2.0) // td + + def test_td_rfloordiv_timedeltalike_array(self): + # GH#18846 + td = Timedelta(hours=3, minutes=3) + scalar = Timedelta(hours=3, minutes=4) + + # Array-like others + assert td.__rfloordiv__(np.array(scalar.to_timedelta64())) == 1 + + res = td.__rfloordiv__(np.array([(3 * scalar).to_timedelta64()])) + expected = np.array([3], dtype=np.int64) + tm.assert_numpy_array_equal(res, expected) + + arr = np.array([(10 * scalar).to_timedelta64(), np.timedelta64("NaT", "ns")]) + res = td.__rfloordiv__(arr) + expected = np.array([10, np.nan]) + tm.assert_numpy_array_equal(res, expected) + + def test_td_rfloordiv_intarray(self): + # deprecated GH#19761, enforced GH#29797 + ints = np.array([1349654400, 1349740800, 1349827200, 1349913600]) * 10**9 + + msg = "Invalid dtype" + with pytest.raises(TypeError, match=msg): + ints // Timedelta(1, unit="s") + + def test_td_rfloordiv_numeric_series(self): + # GH#18846 + td = Timedelta(hours=3, minutes=3) + ser = pd.Series([1], dtype=np.int64) + res = td.__rfloordiv__(ser) + assert res is NotImplemented + + msg = "Invalid dtype" + with pytest.raises(TypeError, match=msg): + # Deprecated GH#19761, enforced GH#29797 + ser // td + + # ---------------------------------------------------------------- + # Timedelta.__mod__, __rmod__ + + def test_mod_timedeltalike(self): + # GH#19365 + td = Timedelta(hours=37) + + # Timedelta-like others + result = td % Timedelta(hours=6) + assert isinstance(result, Timedelta) + assert result == Timedelta(hours=1) + + result = td % timedelta(minutes=60) + assert isinstance(result, Timedelta) + assert result == Timedelta(0) + + result = td % NaT + assert result is NaT + + def test_mod_timedelta64_nat(self): + # GH#19365 + td = Timedelta(hours=37) + + result = td % np.timedelta64("NaT", "ns") + assert result is NaT + + def test_mod_timedelta64(self): + # GH#19365 + td = Timedelta(hours=37) + + result = td % np.timedelta64(2, "h") + assert isinstance(result, Timedelta) + assert result == Timedelta(hours=1) + + def test_mod_offset(self): + # GH#19365 + td = Timedelta(hours=37) + + result = td % offsets.Hour(5) + assert isinstance(result, Timedelta) + assert result == Timedelta(hours=2) + + def test_mod_numeric(self): + # GH#19365 + td = Timedelta(hours=37) + + # Numeric Others + result = td % 2 + assert isinstance(result, Timedelta) + assert result == Timedelta(0) + + result = td % 1e9 + assert isinstance(result, Timedelta) + assert result == Timedelta(minutes=3, seconds=20) + + result = td % int(1e9) + assert isinstance(result, Timedelta) + assert result == Timedelta(minutes=3, seconds=20) + + def test_mod_invalid(self): + # GH#19365 + td = Timedelta(hours=37) + msg = "unsupported operand type" + with pytest.raises(TypeError, match=msg): + td % Timestamp("2018-01-22") + + with pytest.raises(TypeError, match=msg): + td % [] + + def test_rmod_pytimedelta(self): + # GH#19365 + td = Timedelta(minutes=3) + + result = timedelta(minutes=4) % td + assert isinstance(result, Timedelta) + assert result == Timedelta(minutes=1) + + def test_rmod_timedelta64(self): + # GH#19365 + td = Timedelta(minutes=3) + result = np.timedelta64(5, "m") % td + assert isinstance(result, Timedelta) + assert result == Timedelta(minutes=2) + + def test_rmod_invalid(self): + # GH#19365 + td = Timedelta(minutes=3) + + msg = "unsupported operand" + with pytest.raises(TypeError, match=msg): + Timestamp("2018-01-22") % td + + with pytest.raises(TypeError, match=msg): + 15 % td + + with pytest.raises(TypeError, match=msg): + 16.0 % td + + msg = "Invalid dtype int" + with pytest.raises(TypeError, match=msg): + np.array([22, 24]) % td + + # ---------------------------------------------------------------- + # Timedelta.__divmod__, __rdivmod__ + + def test_divmod_numeric(self): + # GH#19365 + td = Timedelta(days=2, hours=6) + + result = divmod(td, 53 * 3600 * 1e6) + assert result[0] == Timedelta(1, unit="us").as_unit("us") + assert isinstance(result[1], Timedelta) + assert result[1] == Timedelta(hours=1) + + assert result + result = divmod(td, np.nan) + assert result[0] is NaT + assert result[1] is NaT + + def test_divmod(self): + # GH#19365 + td = Timedelta(days=2, hours=6) + + result = divmod(td, timedelta(days=1)) + assert result[0] == 2 + assert isinstance(result[1], Timedelta) + assert result[1] == Timedelta(hours=6) + + result = divmod(td, 54) + assert result[0] == Timedelta(hours=1) + assert isinstance(result[1], Timedelta) + assert result[1] == Timedelta(0) + + result = divmod(td, NaT) + assert np.isnan(result[0]) + assert result[1] is NaT + + def test_divmod_offset(self): + # GH#19365 + td = Timedelta(days=2, hours=6) + + result = divmod(td, offsets.Hour(-4)) + assert result[0] == -14 + assert isinstance(result[1], Timedelta) + assert result[1] == Timedelta(hours=-2) + + def test_divmod_invalid(self): + # GH#19365 + td = Timedelta(days=2, hours=6) + + msg = r"unsupported operand type\(s\) for //: 'Timedelta' and 'Timestamp'" + with pytest.raises(TypeError, match=msg): + divmod(td, Timestamp("2018-01-22")) + + def test_rdivmod_pytimedelta(self): + # GH#19365 + result = divmod(timedelta(days=2, hours=6), Timedelta(days=1)) + assert result[0] == 2 + assert isinstance(result[1], Timedelta) + assert result[1] == Timedelta(hours=6) + + def test_rdivmod_offset(self): + result = divmod(offsets.Hour(54), Timedelta(hours=-4)) + assert result[0] == -14 + assert isinstance(result[1], Timedelta) + assert result[1] == Timedelta(hours=-2) + + def test_rdivmod_invalid(self): + # GH#19365 + td = Timedelta(minutes=3) + msg = "unsupported operand type" + + with pytest.raises(TypeError, match=msg): + divmod(Timestamp("2018-01-22"), td) + + with pytest.raises(TypeError, match=msg): + divmod(15, td) + + with pytest.raises(TypeError, match=msg): + divmod(16.0, td) + + msg = "Invalid dtype int" + with pytest.raises(TypeError, match=msg): + divmod(np.array([22, 24]), td) + + # ---------------------------------------------------------------- + + @pytest.mark.parametrize( + "op", [operator.mul, ops.rmul, operator.truediv, ops.rdiv, ops.rsub] + ) + @pytest.mark.parametrize( + "arr", + [ + [Timestamp("20130101 9:01"), Timestamp("20121230 9:02")], + [Timestamp("2021-11-09 09:54:00"), Timedelta("1D")], + ], + ) + def test_td_op_timedelta_timedeltalike_array(self, op, arr): + arr = np.array(arr) + msg = "unsupported operand type|cannot use operands with types" + with pytest.raises(TypeError, match=msg): + op(arr, Timedelta("1D")) + + def test_mul_bool_invalid(self): + # GH#62316 + msg = "Cannot multiply Timedelta by bool. Explicitly cast to integer" + with pytest.raises(TypeError, match=msg): + Timedelta("1 day") * True + + +class TestTimedeltaComparison: + def test_compare_pytimedelta_bounds(self): + # GH#49021 don't overflow on comparison with very large pytimedeltas + + for unit in ["ns", "us"]: + tdmax = Timedelta.max.as_unit(unit).max + tdmin = Timedelta.min.as_unit(unit).min + + assert tdmax < timedelta.max + assert tdmax <= timedelta.max + assert not tdmax > timedelta.max + assert not tdmax >= timedelta.max + assert tdmax != timedelta.max + assert not tdmax == timedelta.max + + assert tdmin > timedelta.min + assert tdmin >= timedelta.min + assert not tdmin < timedelta.min + assert not tdmin <= timedelta.min + assert tdmin != timedelta.min + assert not tdmin == timedelta.min + + # But the "ms" and "s"-reso bounds extend pass pytimedelta + for unit in ["ms", "s"]: + tdmax = Timedelta.max.as_unit(unit).max + tdmin = Timedelta.min.as_unit(unit).min + + assert tdmax > timedelta.max + assert tdmax >= timedelta.max + assert not tdmax < timedelta.max + assert not tdmax <= timedelta.max + assert tdmax != timedelta.max + assert not tdmax == timedelta.max + + assert tdmin < timedelta.min + assert tdmin <= timedelta.min + assert not tdmin > timedelta.min + assert not tdmin >= timedelta.min + assert tdmin != timedelta.min + assert not tdmin == timedelta.min + + def test_compare_pytimedelta_bounds2(self): + # a pytimedelta outside the microsecond bounds + pytd = timedelta(days=999999999, seconds=86399) + # NB: np.timedelta64(td, "s"") incorrectly overflows + td64 = np.timedelta64(pytd.days, "D") + np.timedelta64(pytd.seconds, "s") + td = Timedelta(td64) + assert td.days == pytd.days + assert td.seconds == pytd.seconds + + assert td == pytd + assert not td != pytd + assert not td < pytd + assert not td > pytd + assert td <= pytd + assert td >= pytd + + td2 = td - Timedelta(seconds=1).as_unit("s") + assert td2 != pytd + assert not td2 == pytd + assert td2 < pytd + assert td2 <= pytd + assert not td2 > pytd + assert not td2 >= pytd + + def test_compare_tick(self, tick_classes): + cls = tick_classes + + off = cls(4) + td = off._as_pd_timedelta + assert isinstance(td, Timedelta) + + assert td == off + assert not td != off + assert td <= off + assert td >= off + assert not td < off + assert not td > off + + assert not td == 2 * off + assert td != 2 * off + assert td <= 2 * off + assert td < 2 * off + assert not td >= 2 * off + assert not td > 2 * off + + def test_comparison_object_array(self): + # analogous to GH#15183 + td = Timedelta("2 days") + other = Timedelta("3 hours") + + arr = np.array([other, td], dtype=object) + res = arr == td + expected = np.array([False, True], dtype=bool) + assert (res == expected).all() + + # 2D case + arr = np.array([[other, td], [td, other]], dtype=object) + res = arr != td + expected = np.array([[True, False], [False, True]], dtype=bool) + assert res.shape == expected.shape + assert (res == expected).all() + + def test_compare_timedelta_ndarray(self): + # GH#11835 + periods = [Timedelta("0 days 01:00:00"), Timedelta("0 days 01:00:00")] + arr = np.array(periods) + result = arr[0] > arr + expected = np.array([False, False]) + tm.assert_numpy_array_equal(result, expected) + + def test_compare_td64_ndarray(self): + # GG#33441 + arr = np.arange(5).astype("timedelta64[ns]") + td = Timedelta(arr[1]) + + expected = np.array([False, True, False, False, False], dtype=bool) + + result = td == arr + tm.assert_numpy_array_equal(result, expected) + + result = arr == td + tm.assert_numpy_array_equal(result, expected) + + result = td != arr + tm.assert_numpy_array_equal(result, ~expected) + + result = arr != td + tm.assert_numpy_array_equal(result, ~expected) + + def test_compare_custom_object(self): + """ + Make sure non supported operations on Timedelta returns NonImplemented + and yields to other operand (GH#20829). + """ + + class CustomClass: + def __init__(self, cmp_result=None) -> None: + self.cmp_result = cmp_result + + def generic_result(self): + if self.cmp_result is None: + return NotImplemented + else: + return self.cmp_result + + def __eq__(self, other): + return self.generic_result() + + def __gt__(self, other): + return self.generic_result() + + t = Timedelta("1s") + + assert t != "string" + assert t != 1 + assert t != CustomClass() + assert t != CustomClass(cmp_result=False) + + assert t < CustomClass(cmp_result=True) + assert not t < CustomClass(cmp_result=False) + + assert t == CustomClass(cmp_result=True) + + @pytest.mark.parametrize("val", ["string", 1]) + def test_compare_unknown_type(self, val): + # GH#20829 + t = Timedelta("1s") + msg = "not supported between instances of 'Timedelta' and '(int|str)'" + with pytest.raises(TypeError, match=msg): + t >= val + with pytest.raises(TypeError, match=msg): + t > val + with pytest.raises(TypeError, match=msg): + t <= val + with pytest.raises(TypeError, match=msg): + t < val + + +def test_ops_notimplemented(): + class Other: + pass + + other = Other() + + td = Timedelta("1 day") + assert td.__add__(other) is NotImplemented + assert td.__sub__(other) is NotImplemented + assert td.__truediv__(other) is NotImplemented + assert td.__mul__(other) is NotImplemented + assert td.__floordiv__(other) is NotImplemented + + +def test_ops_error_str(): + # GH#13624 + td = Timedelta("1 day") + + for left, right in [(td, "a"), ("a", td)]: + msg = "|".join( + [ + "unsupported operand type", + r'can only concatenate str \(not "Timedelta"\) to str', + "must be str, not Timedelta", + ] + ) + with pytest.raises(TypeError, match=msg): + left + right + + msg = "not supported between instances of" + with pytest.raises(TypeError, match=msg): + left > right + + assert not left == right + assert left != right + + +@pytest.mark.parametrize("box", [True, False]) +def test_ops_str_deprecated(box): + # GH#59653 + td = Timedelta("1 day") + item = "1" + if box: + item = np.array([item], dtype=object) + + msg = "Scalar operations between Timedelta and string are deprecated" + with tm.assert_produces_warning(Pandas4Warning, match=msg): + td + item + with tm.assert_produces_warning(Pandas4Warning, match=msg): + item + td + with tm.assert_produces_warning(Pandas4Warning, match=msg): + td - item + with tm.assert_produces_warning(Pandas4Warning, match=msg): + item - td + with tm.assert_produces_warning(Pandas4Warning, match=msg): + item / td + if not box: + with tm.assert_produces_warning(Pandas4Warning, match=msg): + td / item + with tm.assert_produces_warning(Pandas4Warning, match=msg): + item // td + with tm.assert_produces_warning(Pandas4Warning, match=msg): + td // item + else: + msg = "|".join( + [ + "ufunc 'divide' cannot use operands", + "Invalid dtype object for __floordiv__", + r"unsupported operand type\(s\) for /: 'int' and 'str'", + r"unsupported operand type\(s\) for /: 'datetime.timedelta' and 'str'", + ] + ) + with pytest.raises(TypeError, match=msg): + td / item + with pytest.raises(TypeError, match=msg): + item // td + with pytest.raises(TypeError, match=msg): + td // item diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_constructors.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_constructors.py new file mode 100644 index 0000000000000000000000000000000000000000..6adf8c0016afd715f8b145647976c5e89cdd88d3 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_constructors.py @@ -0,0 +1,795 @@ +from datetime import timedelta +from itertools import product + +import numpy as np +import pytest + +from pandas._libs.tslibs import OutOfBoundsTimedelta +from pandas._libs.tslibs.dtypes import NpyDatetimeUnit +from pandas.compat.numpy import ( + is_numpy_dev, + np_version_gt2_5, +) +from pandas.errors import Pandas4Warning + +from pandas import ( + Index, + NaT, + Timedelta, + TimedeltaIndex, + offsets, + to_timedelta, +) +import pandas._testing as tm + + +class TestTimedeltaConstructorKeywordBased: + # Tests for constructing a Timedelta from keywords like the pytimedelta + # base class + def test_nanosecond_keyword(self): + # GH#63216 + td = Timedelta(nanoseconds=1000) + assert td.unit == "ns" + + def test_noninteger_microseconds(self): + # GH#63216 + td = Timedelta(microseconds=1.5) + assert td.unit == "ns" + assert td == Timedelta(nanoseconds=1500) + + +class TestTimedeltaConstructorUnitKeyword: + def test_result_unit(self): + # For supported units, we get result.unit == unit + for unit in ["s", "ms", "us", "ns"]: + td = Timedelta(1, unit=unit) + assert td.unit == unit + + td = to_timedelta(1, unit=unit) + assert td.unit == unit + + # For non-supported units we get the closest-supported unit + for unit in ["W", "D", "h", "m"]: + td = Timedelta(1, unit=unit) + assert td.unit == "s" + + td = to_timedelta(1, unit=unit) + assert td.unit == "s" + + @pytest.mark.parametrize("unit", ["Y", "y", "M"]) + def test_unit_m_y_raises(self, unit): + msg = "Units 'M', 'Y', and 'y' are no longer supported" + + with pytest.raises(ValueError, match=msg): + Timedelta(10, unit) + + with pytest.raises(ValueError, match=msg): + to_timedelta(10, unit) + + with pytest.raises(ValueError, match=msg): + to_timedelta([1, 2], unit) + + @pytest.mark.parametrize( + "unit,unit_depr", + [ + ("W", "w"), + ("D", "d"), + ("min", "MIN"), + ("s", "S"), + ("h", "H"), + ("ms", "MS"), + ("us", "US"), + ], + ) + def test_unit_deprecated(self, unit, unit_depr): + # GH#52536 + msg = f"'{unit_depr}' is deprecated and will be removed in a future version." + + expected = Timedelta(1, unit=unit) + with tm.assert_produces_warning(Pandas4Warning, match=msg): + result = Timedelta(1, unit=unit_depr) + tm.assert_equal(result, expected) + + @pytest.mark.parametrize( + "unit, np_unit", + [("W", "W")] + + [(value, "D") for value in ["D", "days", "day", "Days", "Day"]] + + [ + (value, "m") + for value in [ + "m", + "minute", + "min", + "minutes", + "Minute", + "Min", + "Minutes", + ] + ] + + [ + (value, "s") + for value in [ + "s", + "seconds", + "sec", + "second", + "Seconds", + "Sec", + "Second", + ] + ] + + [ + (value, "ms") + for value in [ + "ms", + "milliseconds", + "millisecond", + "milli", + "millis", + "Milliseconds", + "Millisecond", + "Milli", + "Millis", + ] + ] + + [ + (value, "us") + for value in [ + "us", + "microseconds", + "microsecond", + "micro", + "micros", + "Microseconds", + "Microsecond", + "Micro", + "Micros", + ] + ] + + [ + (value, "ns") + for value in [ + "ns", + "nanoseconds", + "nanosecond", + "nano", + "nanos", + "Nanoseconds", + "Nanosecond", + "Nano", + "Nanos", + ] + ], + ) + @pytest.mark.parametrize("wrapper", [np.array, list, Index]) + def test_unit_parser(self, unit, np_unit, wrapper): + # validate all units, GH 6855, GH 21762 + # array-likes + exp_unit = np_unit if np_unit not in ["W", "D", "m"] else "s" + expected = TimedeltaIndex( + [np.timedelta64(i, np_unit) for i in np.arange(5).tolist()], + dtype=f"m8[{exp_unit}]", + ) + + result = to_timedelta(wrapper(range(5)), unit=unit) + tm.assert_index_equal(result, expected) + + str_repr = [f"{x}{unit}" for x in np.arange(5)] + exp_unit = "us" if np_unit != "ns" else "ns" + result = to_timedelta(wrapper(str_repr)) + tm.assert_index_equal(result, expected.as_unit(exp_unit)) + result = to_timedelta(wrapper(str_repr)) + tm.assert_index_equal(result, expected.as_unit(exp_unit)) + + # scalar + expected = Timedelta(np.timedelta64(2, np_unit)).as_unit(exp_unit) + result = to_timedelta(2, unit=unit) + assert result == expected + result = Timedelta(2, unit=unit) + assert result == expected + + result = to_timedelta(f"2{unit}") + assert result == expected + result = Timedelta(f"2{unit}") + assert result == expected + + @pytest.mark.parametrize("unit", ["T", "t", "L", "l", "U", "u", "N", "n"]) + def test_unit_T_L_N_U_raises(self, unit): + msg = f"invalid unit abbreviation: {unit}" + with pytest.raises(ValueError, match=msg): + Timedelta(1, unit=unit) + + with pytest.raises(ValueError, match=msg): + to_timedelta(10, unit) + + with pytest.raises(ValueError, match=msg): + to_timedelta([1, 2], unit) + + def test_unit_round_float(self): + # When the float is round, we give the requested unit + # (or nearest-supported) like we do with integers + td = Timedelta(45.0, unit="s") + assert td.unit == "s" + assert td == Timedelta(45, unit="s") + + td = to_timedelta(45.0, unit="s") + assert td.unit == "s" + assert td == Timedelta(45, unit="s") + + def test_unit_non_round_float(self): + # With non-round floats, we have to give nanosecond + td = Timedelta(45.5, unit="s") + assert td.unit == "ns" + assert td == Timedelta(45_500, unit="ms") + + td = to_timedelta(45.5, unit="s") + assert td.unit == "ns" + assert td == Timedelta(45_500, unit="ms") + + +def test_construct_from_kwargs_overflow(): + # GH#55503 + msg = "seconds=86400000000000000000, milliseconds=0, microseconds=0, nanoseconds=0" + with pytest.raises(OutOfBoundsTimedelta, match=msg): + Timedelta(days=10**6) + msg = "seconds=60000000000000000000, milliseconds=0, microseconds=0, nanoseconds=0" + with pytest.raises(OutOfBoundsTimedelta, match=msg): + Timedelta(minutes=10**9) + + +def test_construct_with_weeks_unit_overflow(): + # GH#47268 don't silently wrap around + msg = "1000000000000000000 weeks" + with pytest.raises(OutOfBoundsTimedelta, match=msg): + Timedelta(1000000000000000000, unit="W") + + with pytest.raises(OutOfBoundsTimedelta, match=msg): + Timedelta(1000000000000000000.0, unit="W") + + +def test_construct_from_td64_with_unit(): + # ignore the unit, as it may cause silently overflows leading to incorrect + # results, and in non-overflow cases is irrelevant GH#46827 + obj = np.timedelta64(123456789000000000, "h") + + msg = "The 'unit' keyword is only used when the Timedelta input is" + + with pytest.raises(OutOfBoundsTimedelta, match="123456789000000000 hours"): + with tm.assert_produces_warning(UserWarning, match=msg): + Timedelta(obj, unit="ps") + + with pytest.raises(OutOfBoundsTimedelta, match="123456789000000000 hours"): + with tm.assert_produces_warning(UserWarning, match=msg): + Timedelta(obj, unit="ns") + + with pytest.raises(OutOfBoundsTimedelta, match="123456789000000000 hours"): + Timedelta(obj) + + +def test_from_td64_retain_resolution(): + # case where we retain millisecond resolution + obj = np.timedelta64(12345, "ms") + + td = Timedelta(obj) + assert td._value == obj.view("i8") + assert td._creso == NpyDatetimeUnit.NPY_FR_ms.value + + # Case where we cast to nearest-supported reso + obj2 = np.timedelta64(1234, "D") + td2 = Timedelta(obj2) + assert td2._creso == NpyDatetimeUnit.NPY_FR_s.value + assert td2 == obj2 + assert td2.days == 1234 + + # Case that _would_ overflow if we didn't support non-nano + obj3 = np.timedelta64(1000000000000000000, "us") + td3 = Timedelta(obj3) + assert td3.total_seconds() == 1000000000000 + assert td3._creso == NpyDatetimeUnit.NPY_FR_us.value + + +def test_from_pytimedelta_us_reso(): + # pytimedelta has microsecond resolution, so Timedelta(pytd) inherits that + td = timedelta(days=4, minutes=3) + result = Timedelta(td) + assert result.to_pytimedelta() == td + assert result._creso == NpyDatetimeUnit.NPY_FR_us.value + + +def test_from_tick_reso(): + tick = offsets.Nano() + assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_ns.value + + tick = offsets.Micro() + assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_us.value + + tick = offsets.Milli() + assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_ms.value + + tick = offsets.Second() + assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_s.value + + # everything above Second gets cast to the closest supported reso: second + tick = offsets.Minute() + assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_s.value + + tick = offsets.Hour() + assert Timedelta(tick)._creso == NpyDatetimeUnit.NPY_FR_s.value + + tick = offsets.Day() + msg = ( + "Value must be Timedelta, string, integer, float, timedelta " + "or convertible, not Day" + ) + with pytest.raises(ValueError, match=msg): + # GH#41943 Day is no longer a Tick + Timedelta(tick) + + +def test_construction(): + expected = np.timedelta64(10, "D").astype("m8[ns]").view("i8") + assert Timedelta(10, unit="D")._value == expected // 10**9 + assert Timedelta(10.0, unit="D")._value == expected // 10**9 + assert Timedelta("10 days")._value == expected // 1000 + assert Timedelta(days=10)._value == expected // 1000 + assert Timedelta(days=10.0)._value == expected // 1000 + + expected += np.timedelta64(10, "s").astype("m8[ns]").view("i8") + assert Timedelta("10 days 00:00:10")._value == expected // 1000 + assert Timedelta(days=10, seconds=10)._value == expected // 1000 + assert Timedelta(days=10, milliseconds=10 * 1000)._value == expected // 1000 + assert Timedelta(days=10, microseconds=10 * 1000 * 1000)._value == expected // 1000 + + # rounding cases + assert Timedelta(82739999850000)._value == 82739999850000 + assert "0 days 22:58:59.999850" in str(Timedelta(82739999850000)) + assert Timedelta(123072001000000)._value == 123072001000000 + assert "1 days 10:11:12.001" in str(Timedelta(123072001000000)) + + # string conversion with/without leading zero + # GH#9570 + assert Timedelta("0:00:00") == timedelta(hours=0) + assert Timedelta("00:00:00") == timedelta(hours=0) + assert Timedelta("-1:00:00") == -timedelta(hours=1) + assert Timedelta("-01:00:00") == -timedelta(hours=1) + + # more strings & abbrevs + # GH#8190 + assert Timedelta("1 h") == timedelta(hours=1) + assert Timedelta("1 hour") == timedelta(hours=1) + assert Timedelta("1 hr") == timedelta(hours=1) + assert Timedelta("1 hours") == timedelta(hours=1) + assert Timedelta("-1 hours") == -timedelta(hours=1) + assert Timedelta("1 m") == timedelta(minutes=1) + assert Timedelta("1.5 m") == timedelta(seconds=90) + assert Timedelta("1 minute") == timedelta(minutes=1) + assert Timedelta("1 minutes") == timedelta(minutes=1) + assert Timedelta("1 s") == timedelta(seconds=1) + assert Timedelta("1 second") == timedelta(seconds=1) + assert Timedelta("1 seconds") == timedelta(seconds=1) + assert Timedelta("1 ms") == timedelta(milliseconds=1) + assert Timedelta("1 milli") == timedelta(milliseconds=1) + assert Timedelta("1 millisecond") == timedelta(milliseconds=1) + assert Timedelta("1 us") == timedelta(microseconds=1) + assert Timedelta("1 µs") == timedelta(microseconds=1) + assert Timedelta("1 micros") == timedelta(microseconds=1) + assert Timedelta("1 microsecond") == timedelta(microseconds=1) + assert Timedelta("1.5 microsecond") == Timedelta("00:00:00.000001500") + assert Timedelta("1 ns") == Timedelta("00:00:00.000000001") + assert Timedelta("1 nano") == Timedelta("00:00:00.000000001") + assert Timedelta("1 nanosecond") == Timedelta("00:00:00.000000001") + + # combos + assert Timedelta("10 days 1 hour") == timedelta(days=10, hours=1) + assert Timedelta("10 days 1 h") == timedelta(days=10, hours=1) + assert Timedelta("10 days 1 h 1m 1s") == timedelta( + days=10, hours=1, minutes=1, seconds=1 + ) + assert Timedelta("-10 days 1 h 1m 1s") == -timedelta( + days=10, hours=1, minutes=1, seconds=1 + ) + assert Timedelta("-10 days 1 h 1m 1s") == -timedelta( + days=10, hours=1, minutes=1, seconds=1 + ) + assert Timedelta("-10 days 1 h 1m 1s 3us") == -timedelta( + days=10, hours=1, minutes=1, seconds=1, microseconds=3 + ) + assert Timedelta("-10 days 1 h 1.5m 1s 3us") == -timedelta( + days=10, hours=1, minutes=1, seconds=31, microseconds=3 + ) + + # Currently invalid as it has a - on the hh:mm:dd part + # (only allowed on the days) + msg = "only leading negative signs are allowed" + with pytest.raises(ValueError, match=msg): + Timedelta("-10 days -1 h 1.5m 1s 3us") + + # only leading neg signs are allowed + with pytest.raises(ValueError, match=msg): + Timedelta("10 days -1 h 1.5m 1s 3us") + + # no units specified + msg = "no units specified" + with pytest.raises(ValueError, match=msg): + Timedelta("3.1415") + + # invalid construction + msg = "cannot construct a Timedelta" + with pytest.raises(ValueError, match=msg): + Timedelta() + + msg = "unit abbreviation w/o a number" + with pytest.raises(ValueError, match=msg): + Timedelta("foo") + + msg = ( + "cannot construct a Timedelta from the passed arguments, allowed keywords are " + ) + with pytest.raises(ValueError, match=msg): + Timedelta(day=10) + + # floats + expected = np.timedelta64(10, "s").astype("m8[ns]").view("i8") + np.timedelta64( + 500, "ms" + ).astype("m8[ns]").view("i8") + assert Timedelta(10.5, unit="s")._value == expected + + # offset + assert to_timedelta(offsets.Hour(2)) == Timedelta(hours=2) + assert Timedelta(offsets.Hour(2)) == Timedelta(hours=2) + assert Timedelta(offsets.Second(2)) == Timedelta(seconds=2) + + # GH#11995: unicode + expected = Timedelta("1h") + result = Timedelta("1h") + assert result == expected + assert to_timedelta(offsets.Hour(2)) == Timedelta("0 days, 02:00:00") + + msg = "unit abbreviation w/o a number" + with pytest.raises(ValueError, match=msg): + Timedelta("foo bar") + + +@pytest.mark.parametrize( + "item", + list( + { + "days": "D", + "seconds": "s", + "microseconds": "us", + "milliseconds": "ms", + "minutes": "m", + "hours": "h", + "weeks": "W", + }.items() + ), +) +@pytest.mark.parametrize( + "npdtype", [np.int64, np.int32, np.int16, np.float64, np.float32, np.float16] +) +def test_td_construction_with_np_dtypes(npdtype, item): + # GH#8757: test construction with np dtypes + pykwarg, npkwarg = item + expected = np.timedelta64(1, npkwarg).astype("m8[us]").view("i8") + assert Timedelta(**{pykwarg: npdtype(1)})._value == expected + + +@pytest.mark.parametrize( + "val", + [ + "1s", + "-1s", + "1us", + "-1us", + "1 day", + "-1 day", + "-23:59:59.999999", + "-1 days +23:59:59.999999", + "-1ns", + "1ns", + "-23:59:59.999999999", + ], +) +def test_td_from_repr_roundtrip(val): + # round-trip both for string and value + td = Timedelta(val) + assert Timedelta(td.value) == td + + assert Timedelta(str(td)) == td + assert Timedelta(td._repr_base(format="all")) == td + assert Timedelta(td._repr_base()) == td + + +def test_overflow_on_construction(): + # GH#3374 + value = Timedelta("1day").as_unit("ns")._value * 20169940 + msg = "Cannot cast 1742682816000000000000 from ns to 'ns' without overflow" + with pytest.raises(OutOfBoundsTimedelta, match=msg): + Timedelta(value) + + # xref GH#17637 + # used to overflows before we changed output unit to "s" + td = Timedelta(7 * 19999, unit="D") + assert td.unit == "s" + + # used to overflow before non-ns support + td = Timedelta(timedelta(days=13 * 19999)) + assert td._creso == NpyDatetimeUnit.NPY_FR_us.value + assert td.days == 13 * 19999 + + +@pytest.mark.parametrize( + "val, unit", + [ + (15251, "W"), # 1 + (106752, "D"), # change from previous: + (2562048, "h"), # 0 hours + (153722868, "m"), # 13 minutes + (9223372037, "s"), # 44 seconds + ], +) +def test_construction_out_of_bounds_td64ns(val, unit): + # TODO: parametrize over units just above/below the implementation bounds + # once GH#38964 is resolved + + # Timedelta.max is just under 106752 days + td64 = np.timedelta64(val, unit) + if is_numpy_dev or np_version_gt2_5: + # numpy now raises instead of silently overflowing + with pytest.raises(OverflowError, match="Overflow"): + td64.astype("m8[ns]") + else: + assert td64.astype("m8[ns]").view("i8") < 0 # i.e. naive astype will be wrong + + td = Timedelta(td64) + if unit != "M": + # with unit="M" the conversion to "s" is poorly defined + # (and numpy issues DeprecationWarning) + assert td.asm8 == td64 + assert td.asm8.dtype == "m8[s]" + msg = r"Cannot cast 1067\d\d days .* to unit='ns' without overflow" + with pytest.raises(OutOfBoundsTimedelta, match=msg): + td.as_unit("ns") + + # But just back in bounds and we are OK + one = np.timedelta64(1, unit) + assert Timedelta(td64 - one) == td64 - one + + td64 = np.timedelta64(-val, unit) + if is_numpy_dev or np_version_gt2_5: + with pytest.raises(OverflowError, match="Overflow"): + td64.astype("m8[ns]") + else: + assert td64.astype("m8[ns]").view("i8") > 0 # i.e. naive astype will be wrong + + td2 = Timedelta(td64) + msg = r"Cannot cast -1067\d\d days .* to unit='ns' without overflow" + with pytest.raises(OutOfBoundsTimedelta, match=msg): + td2.as_unit("ns") + + # But just back in bounds and we are OK + assert Timedelta(td64 + one) == td64 + one + + +@pytest.mark.parametrize( + "val, unit", + [ + (15251 * 10**9, "W"), + (106752 * 10**9, "D"), + (2562048 * 10**9, "h"), + (153722868 * 10**9, "m"), + ], +) +def test_construction_out_of_bounds_td64s(val, unit): + td64 = np.timedelta64(val, unit) + with pytest.raises(OutOfBoundsTimedelta, match=str(td64)): + Timedelta(td64) + + # But just back in bounds and we are OK + offset = np.timedelta64(10**9, unit) + assert Timedelta(td64 - offset) == td64 - offset + + +@pytest.mark.parametrize( + "fmt,exp", + [ + ( + "P6DT0H50M3.010010012S", + Timedelta( + days=6, + minutes=50, + seconds=3, + milliseconds=10, + microseconds=10, + nanoseconds=12, + ), + ), + ( + "P-6DT0H50M3.010010012S", + Timedelta( + days=-6, + minutes=50, + seconds=3, + milliseconds=10, + microseconds=10, + nanoseconds=12, + ), + ), + ("P4DT12H30M5S", Timedelta(days=4, hours=12, minutes=30, seconds=5)), + ("P0DT0H0M0.000000123S", Timedelta(nanoseconds=123)), + ("P0DT0H0M0.00001S", Timedelta(microseconds=10)), + ("P0DT0H0M0.001S", Timedelta(milliseconds=1)), + ("P0DT0H1M0S", Timedelta(minutes=1)), + ("P1DT25H61M61S", Timedelta(days=1, hours=25, minutes=61, seconds=61)), + ("PT1S", Timedelta(seconds=1)), + ("PT0S", Timedelta(seconds=0)), + ("P1WT0S", Timedelta(days=7, seconds=0)), + ("P1D", Timedelta(days=1)), + ("P1DT1H", Timedelta(days=1, hours=1)), + ("P1W", Timedelta(days=7)), + ("PT300S", Timedelta(seconds=300)), + ("P1DT0H0M00000000000S", Timedelta(days=1)), + ("PT-6H3M", Timedelta(hours=-6, minutes=3)), + ("-PT6H3M", Timedelta(hours=-6, minutes=-3)), + ("-PT-6H+3M", Timedelta(hours=6, minutes=-3)), + ], +) +def test_iso_constructor(fmt, exp): + assert Timedelta(fmt) == exp + + +@pytest.mark.parametrize( + "fmt", + [ + "PPPPPPPPPPPP", + "PDTHMS", + "P0DT999H999M999S", + "P1DT0H0M0.0000000000000S", + "P1DT0H0M0.S", + "P", + "-P", + ], +) +def test_iso_constructor_raises(fmt): + msg = f"Invalid ISO 8601 Duration format - {fmt}" + with pytest.raises(ValueError, match=msg): + Timedelta(fmt) + + +@pytest.mark.parametrize( + "constructed_td, conversion", + [ + (Timedelta(nanoseconds=100), "100ns"), + ( + Timedelta( + days=1, + hours=1, + minutes=1, + weeks=1, + seconds=1, + milliseconds=1, + microseconds=1, + nanoseconds=1, + ), + 694861001001001, + ), + (Timedelta(microseconds=1) + Timedelta(nanoseconds=1), "1us1ns"), + (Timedelta(microseconds=1) - Timedelta(nanoseconds=1), "999ns"), + (Timedelta(microseconds=1) + 5 * Timedelta(nanoseconds=-2), "990ns"), + ], +) +def test_td_constructor_on_nanoseconds(constructed_td, conversion): + # GH#9273 + assert constructed_td == Timedelta(conversion) + + +def test_td_constructor_value_error(): + msg = "Invalid type . Must be int or float." + with pytest.raises(TypeError, match=msg): + Timedelta(nanoseconds="abc") + + +def test_timedelta_constructor_identity(): + # Test for #30543 + expected = Timedelta(np.timedelta64(1, "s")) + result = Timedelta(expected) + assert result is expected + + +def test_timedelta_pass_td_and_kwargs_raises(): + # don't silently ignore the kwargs GH#48898 + td = Timedelta(days=1) + msg = ( + "Cannot pass both a Timedelta input and timedelta keyword arguments, " + r"got \['days'\]" + ) + with pytest.raises(ValueError, match=msg): + Timedelta(td, days=2) + + +@pytest.mark.filterwarnings( + "ignore:The 'unit' keyword is only used when the Timedelta input:UserWarning" +) +@pytest.mark.parametrize( + "constructor, value, unit", + [ + (Timedelta, "10s", "ms"), + (to_timedelta, "10s", "ms"), + (to_timedelta, ["1", 2, 3], "s"), + ], +) +def test_string_with_unit(constructor, value, unit): + with pytest.raises(ValueError, match="unit must not be specified"): + constructor(value, unit=unit) + + +@pytest.mark.parametrize( + "value", + [ + "".join(elements) + for repetition in (1, 2) + for elements in product("+-, ", repeat=repetition) + ], +) +def test_string_without_numbers(value): + # GH39710 Timedelta input string with only symbols and no digits raises an error + msg = ( + "symbols w/o a number" + if value != "--" + else "only leading negative signs are allowed" + ) + with pytest.raises(ValueError, match=msg): + Timedelta(value) + + +def test_timedelta_new_npnat(): + # GH#48898 + nat = np.timedelta64("NaT", "h") + assert Timedelta(nat) is NaT + + +def test_subclass_respected(): + # GH#49579 + class MyCustomTimedelta(Timedelta): + pass + + td = MyCustomTimedelta("1 minute") + assert isinstance(td, MyCustomTimedelta) + + +def test_non_nano_value(): + # https://github.com/pandas-dev/pandas/issues/49076 + result = Timedelta(10, unit="D").as_unit("s").value + # `.value` shows nanoseconds, even though unit is 's' + assert result == 864000000000000 + + # out-of-nanoseconds-bounds `.value` raises informative message + msg = ( + r"Cannot convert Timedelta to nanoseconds without overflow. " + r"Use `.asm8.view\('i8'\)` to cast represent Timedelta in its " + r"own unit \(here, s\).$" + ) + td = Timedelta(1_000, "D").as_unit("s") * 1_000 + with pytest.raises(OverflowError, match=msg): + td.value + # check that the suggested workaround actually works + result = td.asm8.view("i8") + assert result == 86400000000 + + +def test_parsed_unit(): + td = Timedelta("1 Day") + assert td.unit == "us" + + td = Timedelta("1 Day 2 hours 3 minutes 4 ns") + assert td.unit == "ns" + + td = Timedelta("1 Day 2:03:04.012345") + assert td.unit == "us" + + td = Timedelta("1 Day 2:03:04.012345000") + assert td.unit == "ns" + + # 7 digits after the decimal + td = Timedelta("1 Day 2:03:04.0123450") + assert td.unit == "ns" diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_formats.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_formats.py new file mode 100644 index 0000000000000000000000000000000000000000..1aafeec2ceed5889adad416f0f3ccc63fc86aa3a --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_formats.py @@ -0,0 +1,109 @@ +import pytest + +from pandas import Timedelta + + +@pytest.mark.parametrize( + "td, expected_repr", + [ + (Timedelta(10, unit="D"), "Timedelta('10 days 00:00:00')"), + (Timedelta(10, unit="s"), "Timedelta('0 days 00:00:10')"), + (Timedelta(10, unit="ms"), "Timedelta('0 days 00:00:00.010000')"), + (Timedelta(-10, unit="ms"), "Timedelta('-1 days +23:59:59.990000')"), + ], +) +def test_repr(td, expected_repr): + assert repr(td) == expected_repr + + +@pytest.mark.parametrize( + "td, expected_iso", + [ + ( + Timedelta( + days=6, + minutes=50, + seconds=3, + milliseconds=10, + microseconds=10, + nanoseconds=12, + ), + "P6DT0H50M3.010010012S", + ), + (Timedelta(days=4, hours=12, minutes=30, seconds=5), "P4DT12H30M5S"), + (Timedelta(nanoseconds=123), "P0DT0H0M0.000000123S"), + # trim nano + (Timedelta(microseconds=10), "P0DT0H0M0.00001S"), + # trim micro + (Timedelta(milliseconds=1), "P0DT0H0M0.001S"), + # don't strip every 0 + (Timedelta(minutes=1), "P0DT0H1M0S"), + ], +) +def test_isoformat(td, expected_iso): + assert td.isoformat() == expected_iso + + +class TestReprBase: + def test_none(self): + delta_1d = Timedelta(1, unit="D") + delta_0d = Timedelta(0, unit="D") + delta_1s = Timedelta(1, unit="s") + delta_500ms = Timedelta(500, unit="ms") + + drepr = lambda x: x._repr_base() + assert drepr(delta_1d) == "1 days" + assert drepr(-delta_1d) == "-1 days" + assert drepr(delta_0d) == "0 days" + assert drepr(delta_1s) == "0 days 00:00:01" + assert drepr(delta_500ms) == "0 days 00:00:00.500000" + assert drepr(delta_1d + delta_1s) == "1 days 00:00:01" + assert drepr(-delta_1d + delta_1s) == "-1 days +00:00:01" + assert drepr(delta_1d + delta_500ms) == "1 days 00:00:00.500000" + assert drepr(-delta_1d + delta_500ms) == "-1 days +00:00:00.500000" + + def test_sub_day(self): + delta_1d = Timedelta(1, unit="D") + delta_0d = Timedelta(0, unit="D") + delta_1s = Timedelta(1, unit="s") + delta_500ms = Timedelta(500, unit="ms") + + drepr = lambda x: x._repr_base(format="sub_day") + assert drepr(delta_1d) == "1 days" + assert drepr(-delta_1d) == "-1 days" + assert drepr(delta_0d) == "00:00:00" + assert drepr(delta_1s) == "00:00:01" + assert drepr(delta_500ms) == "00:00:00.500000" + assert drepr(delta_1d + delta_1s) == "1 days 00:00:01" + assert drepr(-delta_1d + delta_1s) == "-1 days +00:00:01" + assert drepr(delta_1d + delta_500ms) == "1 days 00:00:00.500000" + assert drepr(-delta_1d + delta_500ms) == "-1 days +00:00:00.500000" + + def test_long(self): + delta_1d = Timedelta(1, unit="D") + delta_0d = Timedelta(0, unit="D") + delta_1s = Timedelta(1, unit="s") + delta_500ms = Timedelta(500, unit="ms") + + drepr = lambda x: x._repr_base(format="long") + assert drepr(delta_1d) == "1 days 00:00:00" + assert drepr(-delta_1d) == "-1 days +00:00:00" + assert drepr(delta_0d) == "0 days 00:00:00" + assert drepr(delta_1s) == "0 days 00:00:01" + assert drepr(delta_500ms) == "0 days 00:00:00.500000" + assert drepr(delta_1d + delta_1s) == "1 days 00:00:01" + assert drepr(-delta_1d + delta_1s) == "-1 days +00:00:01" + assert drepr(delta_1d + delta_500ms) == "1 days 00:00:00.500000" + assert drepr(-delta_1d + delta_500ms) == "-1 days +00:00:00.500000" + + def test_all(self): + delta_1d = Timedelta(1, unit="D") + delta_0d = Timedelta(0, unit="D") + delta_1ns = Timedelta(1, unit="ns") + + drepr = lambda x: x._repr_base(format="all") + assert drepr(delta_1d) == "1 days 00:00:00.000000000" + assert drepr(-delta_1d) == "-1 days +00:00:00.000000000" + assert drepr(delta_0d) == "0 days 00:00:00.000000000" + assert drepr(delta_1ns) == "0 days 00:00:00.000000001" + assert drepr(-delta_1d + delta_1ns) == "-1 days +00:00:00.000000001" diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_timedelta.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_timedelta.py new file mode 100644 index 0000000000000000000000000000000000000000..10a157cccdf38507dce4d78433cfb974be27e682 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timedelta/test_timedelta.py @@ -0,0 +1,740 @@ +"""test the scalar Timedelta""" + +from datetime import timedelta +import sys + +from hypothesis import ( + given, + strategies as st, +) +import numpy as np +import pytest + +from pandas._libs import lib +from pandas._libs.missing import NA +from pandas._libs.tslibs import ( + NaT, + iNaT, +) +from pandas._libs.tslibs.dtypes import NpyDatetimeUnit +from pandas.errors import ( + OutOfBoundsTimedelta, + Pandas4Warning, +) + +from pandas import ( + Timedelta, + to_timedelta, +) +import pandas._testing as tm + + +class TestNonNano: + @pytest.fixture(params=["s", "ms", "us"]) + def unit_str(self, request): + return request.param + + @pytest.fixture + def unit(self, unit_str): + # 7, 8, 9 correspond to second, millisecond, and microsecond, respectively + attr = f"NPY_FR_{unit_str}" + return getattr(NpyDatetimeUnit, attr).value + + @pytest.fixture + def val(self, unit): + # microsecond that would be just out of bounds for nano + us = 9223372800000000 + if unit == NpyDatetimeUnit.NPY_FR_us.value: + value = us + elif unit == NpyDatetimeUnit.NPY_FR_ms.value: + value = us // 1000 + else: + value = us // 1_000_000 + return value + + @pytest.fixture + def td(self, unit, val): + return Timedelta._from_value_and_reso(val, unit) + + def test_from_value_and_reso(self, unit, val): + # Just checking that the fixture is giving us what we asked for + td = Timedelta._from_value_and_reso(val, unit) + assert td._value == val + assert td._creso == unit + assert td.days == 106752 + + def test_unary_non_nano(self, td, unit): + assert abs(td)._creso == unit + assert (-td)._creso == unit + assert (+td)._creso == unit + + def test_sub_preserves_reso(self, td, unit): + res = td - td + expected = Timedelta._from_value_and_reso(0, unit) + assert res == expected + assert res._creso == unit + + def test_mul_preserves_reso(self, td, unit): + # The td fixture should always be far from the implementation + # bound, so doubling does not risk overflow. + res = td * 2 + assert res._value == td._value * 2 + assert res._creso == unit + + def test_cmp_cross_reso(self, td): + # numpy gets this wrong because of silent overflow + other = Timedelta(days=106751) + assert other < td + assert td > other + assert not other == td + assert td != other + + def test_to_pytimedelta(self, td): + res = td.to_pytimedelta() + expected = timedelta(days=106752) + assert type(res) is timedelta + assert res == expected + + def test_to_timedelta64(self, td, unit): + for res in [td.to_timedelta64(), td.to_numpy(), td.asm8]: + assert isinstance(res, np.timedelta64) + assert res.view("i8") == td._value + if unit == NpyDatetimeUnit.NPY_FR_s.value: + assert res.dtype == "m8[s]" + elif unit == NpyDatetimeUnit.NPY_FR_ms.value: + assert res.dtype == "m8[ms]" + elif unit == NpyDatetimeUnit.NPY_FR_us.value: + assert res.dtype == "m8[us]" + + def test_truediv_timedeltalike(self, td): + assert td / td == 1 + assert (2.5 * td) / td == 2.5 + + other = Timedelta(td._value) + msg = "Cannot cast 106752 days 00:00:00 to unit='ns' without overflow." + with pytest.raises(OutOfBoundsTimedelta, match=msg): + td / other + + # Timedelta(other.to_pytimedelta()) has microsecond resolution, + # so the division doesn't require casting all the way to nanos, + # so succeeds + res = other.to_pytimedelta() / td + expected = other.to_pytimedelta() / td.to_pytimedelta() + assert res == expected + + # if there's no overflow, we cast to the higher reso + left = Timedelta._from_value_and_reso(50, NpyDatetimeUnit.NPY_FR_us.value) + right = Timedelta._from_value_and_reso(50, NpyDatetimeUnit.NPY_FR_ms.value) + result = left / right + assert result == 0.001 + + result = right / left + assert result == 1000 + + def test_truediv_numeric(self, td): + assert td / np.nan is NaT + + res = td / 2 + assert res._value == td._value / 2 + assert res._creso == td._creso + + res = td / 2.0 + assert res._value == td._value / 2 + assert res._creso == td._creso + + def test_truediv_na_type_not_supported(self, td): + msg_td_floordiv_na = ( + r"unsupported operand type\(s\) for /: 'Timedelta' and 'NAType'" + ) + with pytest.raises(TypeError, match=msg_td_floordiv_na): + td / NA + + msg_na_floordiv_td = ( + r"unsupported operand type\(s\) for /: 'NAType' and 'Timedelta'" + ) + with pytest.raises(TypeError, match=msg_na_floordiv_td): + NA / td + + def test_floordiv_timedeltalike(self, td): + assert td // td == 1 + assert (2.5 * td) // td == 2 + + other = Timedelta(td._value) + msg = "Cannot cast 106752 days 00:00:00 to unit='ns' without overflow" + with pytest.raises(OutOfBoundsTimedelta, match=msg): + td // other + + # Timedelta(other.to_pytimedelta()) has microsecond resolution, + # so the floordiv doesn't require casting all the way to nanos, + # so succeeds + res = other.to_pytimedelta() // td + assert res == 0 + + # if there's no overflow, we cast to the higher reso + left = Timedelta._from_value_and_reso(50050, NpyDatetimeUnit.NPY_FR_us.value) + right = Timedelta._from_value_and_reso(50, NpyDatetimeUnit.NPY_FR_ms.value) + result = left // right + assert result == 1 + result = right // left + assert result == 0 + + def test_floordiv_numeric(self, td): + assert td // np.nan is NaT + + res = td // 2 + assert res._value == td._value // 2 + assert res._creso == td._creso + + res = td // 2.0 + assert res._value == td._value // 2 + assert res._creso == td._creso + + assert td // np.array(np.nan) is NaT + + res = td // np.array(2) + assert res._value == td._value // 2 + assert res._creso == td._creso + + res = td // np.array(2.0) + assert res._value == td._value // 2 + assert res._creso == td._creso + + def test_floordiv_na_type_not_supported(self, td): + msg_td_floordiv_na = ( + r"unsupported operand type\(s\) for //: 'Timedelta' and 'NAType'" + ) + with pytest.raises(TypeError, match=msg_td_floordiv_na): + td // NA + + msg_na_floordiv_td = ( + r"unsupported operand type\(s\) for //: 'NAType' and 'Timedelta'" + ) + with pytest.raises(TypeError, match=msg_na_floordiv_td): + NA // td + + def test_addsub_mismatched_reso(self, td): + # need to cast to since td is out of bounds for ns, so + # so we would raise OverflowError without casting + other = Timedelta(days=1).as_unit("us") + + # td is out of bounds for ns + result = td + other + assert result._creso == other._creso + assert result.days == td.days + 1 + + result = other + td + assert result._creso == other._creso + assert result.days == td.days + 1 + + result = td - other + assert result._creso == other._creso + assert result.days == td.days - 1 + + result = other - td + assert result._creso == other._creso + assert result.days == 1 - td.days + + other2 = Timedelta(500) + msg = "Cannot cast 106752 days 00:00:00 to unit='ns' without overflow" + with pytest.raises(OutOfBoundsTimedelta, match=msg): + td + other2 + with pytest.raises(OutOfBoundsTimedelta, match=msg): + other2 + td + with pytest.raises(OutOfBoundsTimedelta, match=msg): + td - other2 + with pytest.raises(OutOfBoundsTimedelta, match=msg): + other2 - td + + def test_min(self, td): + assert td.min <= td + assert td.min._creso == td._creso + assert td.min._value == NaT._value + 1 + + def test_max(self, td): + assert td.max >= td + assert td.max._creso == td._creso + assert td.max._value == np.iinfo(np.int64).max + + def test_resolution(self, td): + expected = Timedelta._from_value_and_reso(1, td._creso) + result = td.resolution + assert result == expected + assert result._creso == expected._creso + + def test_hash(self) -> None: + # GH#54037 + second_resolution_max = Timedelta(0).as_unit("s").max + + assert hash(second_resolution_max) + + +def test_timedelta_class_min_max_resolution(): + # when accessed on the class (as opposed to an instance), we default + # to nanoseconds + assert Timedelta.min == Timedelta(NaT._value + 1) + assert Timedelta.min._creso == NpyDatetimeUnit.NPY_FR_ns.value + + assert Timedelta.max == Timedelta(np.iinfo(np.int64).max) + assert Timedelta.max._creso == NpyDatetimeUnit.NPY_FR_ns.value + + assert Timedelta.resolution == Timedelta(1) + assert Timedelta.resolution._creso == NpyDatetimeUnit.NPY_FR_ns.value + + +class TestTimedeltaUnaryOps: + def test_invert(self): + td = Timedelta(10, unit="D") + + msg = "bad operand type for unary ~" + with pytest.raises(TypeError, match=msg): + ~td + + # check this matches pytimedelta and timedelta64 + with pytest.raises(TypeError, match=msg): + ~(td.to_pytimedelta()) + + umsg = "ufunc 'invert' not supported for the input types" + with pytest.raises(TypeError, match=umsg): + ~(td.to_timedelta64()) + + def test_unary_ops(self): + td = Timedelta(10, unit="D") + + # __neg__, __pos__ + assert -td == Timedelta(-10, unit="D") + assert -td == Timedelta("-10D") + assert +td == Timedelta(10, unit="D") + + # __abs__, __abs__(__neg__) + assert abs(td) == td + assert abs(-td) == td + assert abs(-td) == Timedelta("10D") + + +class TestTimedeltas: + @pytest.mark.parametrize( + "unit, value, expected", + [ + ("us", 9.999, 9999), + ("ms", 9.999999, 9999999), + ("s", 9.999999999, 9999999999), + ], + ) + def test_rounding_on_int_unit_construction(self, unit, value, expected): + # GH 12690 + result = Timedelta(value, unit=unit) + assert result._value == expected + result = Timedelta(str(value) + unit) + assert result._value == expected + + def test_total_seconds_scalar(self): + # see gh-10939 + rng = Timedelta("1 days, 10:11:12.100123456") + expt = 1 * 86400 + 10 * 3600 + 11 * 60 + 12 + 100123456.0 / 1e9 + tm.assert_almost_equal(rng.total_seconds(), expt) + + rng = Timedelta(np.nan) + assert np.isnan(rng.total_seconds()) + + def test_conversion(self): + for td in [Timedelta(10, unit="D"), Timedelta("1 days, 10:11:12.012345")]: + td = td.as_unit("ns") + pydt = td.to_pytimedelta() + assert td == Timedelta(pydt) + assert td == pydt + assert isinstance(pydt, timedelta) and not isinstance(pydt, Timedelta) + + assert td == np.timedelta64(td._value, "ns") + td64 = td.to_timedelta64() + + assert td64 == np.timedelta64(td._value, "ns") + assert td == td64 + + assert isinstance(td64, np.timedelta64) + + # this is NOT equal and cannot be roundtripped (because of the nanos) + td = Timedelta("1 days, 10:11:12.012345678") + assert td != td.to_pytimedelta() + + def test_fields(self): + def check(value): + # that we are int + assert isinstance(value, int) + + # compat to datetime.timedelta + rng = to_timedelta("1 days, 10:11:12") + assert rng.days == 1 + assert rng.seconds == 10 * 3600 + 11 * 60 + 12 + assert rng.microseconds == 0 + assert rng.nanoseconds == 0 + + msg = "'Timedelta' object has no attribute '{}'" + with pytest.raises(AttributeError, match=msg.format("hours")): + rng.hours + with pytest.raises(AttributeError, match=msg.format("minutes")): + rng.minutes + with pytest.raises(AttributeError, match=msg.format("milliseconds")): + rng.milliseconds + + # GH 10050 + check(rng.days) + check(rng.seconds) + check(rng.microseconds) + check(rng.nanoseconds) + + td = Timedelta("-1 days, 10:11:12") + assert abs(td) == Timedelta("13:48:48") + assert str(td) == "-1 days +10:11:12" + assert -td == Timedelta("0 days 13:48:48") + assert -Timedelta("-1 days, 10:11:12")._value == 49728000000 + assert Timedelta("-1 days, 10:11:12")._value == -49728000000 + + rng = to_timedelta("-1 days, 10:11:12.100123456") + assert rng.days == -1 + assert rng.seconds == 10 * 3600 + 11 * 60 + 12 + assert rng.microseconds == 100 * 1000 + 123 + assert rng.nanoseconds == 456 + msg = "'Timedelta' object has no attribute '{}'" + with pytest.raises(AttributeError, match=msg.format("hours")): + rng.hours + with pytest.raises(AttributeError, match=msg.format("minutes")): + rng.minutes + with pytest.raises(AttributeError, match=msg.format("milliseconds")): + rng.milliseconds + + # components + tup = to_timedelta(-1, "us").components + assert tup.days == -1 + assert tup.hours == 23 + assert tup.minutes == 59 + assert tup.seconds == 59 + assert tup.milliseconds == 999 + assert tup.microseconds == 999 + assert tup.nanoseconds == 0 + + # GH 10050 + check(tup.days) + check(tup.hours) + check(tup.minutes) + check(tup.seconds) + check(tup.milliseconds) + check(tup.microseconds) + check(tup.nanoseconds) + + tup = Timedelta("-1 days 1 us").components + assert tup.days == -2 + assert tup.hours == 23 + assert tup.minutes == 59 + assert tup.seconds == 59 + assert tup.milliseconds == 999 + assert tup.microseconds == 999 + assert tup.nanoseconds == 0 + + # TODO: this is a test of to_timedelta string parsing + def test_iso_conversion(self): + # GH #21877 + expected = Timedelta(1, unit="s") + assert to_timedelta("P0DT0H0M1S") == expected + + # TODO: this is a test of to_timedelta returning NaT + def test_nat_converters(self): + result = to_timedelta("nat").to_numpy() + assert result.dtype.kind == "M" + assert result.astype("int64") == iNaT + + result = to_timedelta("nan").to_numpy() + assert result.dtype.kind == "M" + assert result.astype("int64") == iNaT + + def test_numeric_conversions(self): + assert Timedelta(0) == np.timedelta64(0, "ns") + assert Timedelta(10) == np.timedelta64(10, "ns") + assert Timedelta(10, unit="ns") == np.timedelta64(10, "ns") + + assert Timedelta(10, unit="us") == np.timedelta64(10, "us") + assert Timedelta(10, unit="ms") == np.timedelta64(10, "ms") + assert Timedelta(10, unit="s") == np.timedelta64(10, "s") + assert Timedelta(10, unit="D") == np.timedelta64(10, "D") + + def test_timedelta_conversions(self): + assert Timedelta(timedelta(seconds=1)) == np.timedelta64(1, "s").astype( + "m8[ns]" + ) + assert Timedelta(timedelta(microseconds=1)) == np.timedelta64(1, "us").astype( + "m8[ns]" + ) + assert Timedelta(timedelta(days=1)) == np.timedelta64(1, "D").astype("m8[ns]") + + def test_to_numpy_alias(self): + # GH 24653: alias .to_numpy() for scalars + td = Timedelta("10m7s") + assert td.to_timedelta64() == td.to_numpy() + + # GH#44460 + msg = "dtype and copy arguments are ignored" + with pytest.raises(ValueError, match=msg): + td.to_numpy("m8[s]") + with pytest.raises(ValueError, match=msg): + td.to_numpy(copy=True) + + def test_identity(self): + td = Timedelta(10, unit="D") + assert isinstance(td, Timedelta) + assert isinstance(td, timedelta) + + def test_short_format_converters(self): + def conv(v): + return v.astype("m8[ns]") + + assert Timedelta("10") == np.timedelta64(10, "ns") + assert Timedelta("10ns") == np.timedelta64(10, "ns") + assert Timedelta("100") == np.timedelta64(100, "ns") + assert Timedelta("100ns") == np.timedelta64(100, "ns") + + assert Timedelta("1000") == np.timedelta64(1000, "ns") + assert Timedelta("1000ns") == np.timedelta64(1000, "ns") + + msg = "'NS' is deprecated and will be removed in a future version." + with tm.assert_produces_warning(Pandas4Warning, match=msg): + assert Timedelta("1000NS") == np.timedelta64(1000, "ns") + + assert Timedelta("10us") == np.timedelta64(10000, "ns") + assert Timedelta("100us") == np.timedelta64(100000, "ns") + assert Timedelta("1000us") == np.timedelta64(1000000, "ns") + assert Timedelta("1000Us") == np.timedelta64(1000000, "ns") + assert Timedelta("1000uS") == np.timedelta64(1000000, "ns") + + assert Timedelta("1ms") == np.timedelta64(1000000, "ns") + assert Timedelta("10ms") == np.timedelta64(10000000, "ns") + assert Timedelta("100ms") == np.timedelta64(100000000, "ns") + assert Timedelta("1000ms") == np.timedelta64(1000000000, "ns") + + assert Timedelta("-1s") == -np.timedelta64(1000000000, "ns") + assert Timedelta("1s") == np.timedelta64(1000000000, "ns") + assert Timedelta("10s") == np.timedelta64(10000000000, "ns") + assert Timedelta("100s") == np.timedelta64(100000000000, "ns") + assert Timedelta("1000s") == np.timedelta64(1000000000000, "ns") + + msg = "'d' is deprecated and will be removed in a future version." + with tm.assert_produces_warning(Pandas4Warning, match=msg): + assert Timedelta("1d") == conv(np.timedelta64(1, "D")) + assert Timedelta("-1D") == -conv(np.timedelta64(1, "D")) + assert Timedelta("1D") == conv(np.timedelta64(1, "D")) + assert Timedelta("10D") == conv(np.timedelta64(10, "D")) + assert Timedelta("100D") == conv(np.timedelta64(100, "D")) + assert Timedelta("1000D") == conv(np.timedelta64(1000, "D")) + assert Timedelta("10000D") == conv(np.timedelta64(10000, "D")) + + # space + assert Timedelta(" 10000D ") == conv(np.timedelta64(10000, "D")) + assert Timedelta(" - 10000D ") == -conv(np.timedelta64(10000, "D")) + + # invalid + msg = "invalid unit abbreviation" + with pytest.raises(ValueError, match=msg): + Timedelta("1foo") + msg = "unit abbreviation w/o a number" + with pytest.raises(ValueError, match=msg): + Timedelta("foo") + + def test_full_format_converters(self): + def conv(v): + return v.astype("m8[ns]") + + d1 = np.timedelta64(1, "D") + + assert Timedelta("1days") == conv(d1) + assert Timedelta("1days,") == conv(d1) + assert Timedelta("- 1days,") == -conv(d1) + + assert Timedelta("00:00:01") == conv(np.timedelta64(1, "s")) + assert Timedelta("06:00:01") == conv(np.timedelta64(6 * 3600 + 1, "s")) + assert Timedelta("06:00:01.0") == conv(np.timedelta64(6 * 3600 + 1, "s")) + assert Timedelta("06:00:01.01") == conv( + np.timedelta64(1000 * (6 * 3600 + 1) + 10, "ms") + ) + + assert Timedelta("- 1days, 00:00:01") == conv(-d1 + np.timedelta64(1, "s")) + assert Timedelta("1days, 06:00:01") == conv( + d1 + np.timedelta64(6 * 3600 + 1, "s") + ) + assert Timedelta("1days, 06:00:01.01") == conv( + d1 + np.timedelta64(1000 * (6 * 3600 + 1) + 10, "ms") + ) + + # invalid + msg = "have leftover units" + with pytest.raises(ValueError, match=msg): + Timedelta("- 1days, 00") + + def test_pickle(self, temp_file): + v = Timedelta("1 days 10:11:12.0123456") + v_p = tm.round_trip_pickle(v, temp_file) + assert v == v_p + + def test_timedelta_hash_equality(self): + # GH 11129 + v = Timedelta(1, "D") + td = timedelta(days=1) + assert hash(v) == hash(td) + + d = {td: 2} + assert d[v] == 2 + + tds = [Timedelta(seconds=1) + Timedelta(days=n) for n in range(20)] + assert all(hash(td) == hash(td.to_pytimedelta()) for td in tds) + + # python timedeltas drop ns resolution + ns_td = Timedelta(1, "ns") + assert hash(ns_td) != hash(ns_td.to_pytimedelta()) + + @pytest.mark.slow + @pytest.mark.xfail( + reason="pd.Timedelta violates the Python hash invariant (GH#44504).", + ) + @given( + st.integers( + min_value=(-sys.maxsize - 1) // 500, + max_value=sys.maxsize // 500, + ) + ) + def test_hash_equality_invariance(self, half_microseconds: int) -> None: + # GH#44504 + + nanoseconds = half_microseconds * 500 + + pandas_timedelta = Timedelta(nanoseconds) + numpy_timedelta = np.timedelta64(nanoseconds) + + # See: https://docs.python.org/3/glossary.html#term-hashable + # Hashable objects which compare equal must have the same hash value. + assert pandas_timedelta != numpy_timedelta or hash(pandas_timedelta) == hash( + numpy_timedelta + ) + + def test_implementation_limits(self): + min_td = Timedelta(Timedelta.min) + max_td = Timedelta(Timedelta.max) + + # GH 12727 + # timedelta limits correspond to int64 boundaries + assert min_td._value == iNaT + 1 + assert max_td._value == lib.i8max + + # Beyond lower limit, a NAT before the Overflow + assert (min_td - Timedelta(1, "ns")) is NaT + + msg = "int too (large|big) to convert" + with pytest.raises(OverflowError, match=msg): + min_td - Timedelta(2, "ns") + + with pytest.raises(OverflowError, match=msg): + max_td + Timedelta(1, "ns") + + # Same tests using the internal nanosecond values + td = Timedelta(min_td._value - 1, "ns") + assert td is NaT + + msg = "Cannot cast -9223372036854775809 from ns to 'ns' without overflow" + with pytest.raises(OutOfBoundsTimedelta, match=msg): + Timedelta(min_td._value - 2, "ns") + + msg = "Cannot cast 9223372036854775808 from ns to 'ns' without overflow" + with pytest.raises(OutOfBoundsTimedelta, match=msg): + Timedelta(max_td._value + 1, "ns") + + def test_total_seconds_precision(self): + # GH 19458 + assert Timedelta("30s").total_seconds() == 30.0 + assert Timedelta("0").total_seconds() == 0.0 + assert Timedelta("-2s").total_seconds() == -2.0 + assert Timedelta("5.324s").total_seconds() == 5.324 + assert (Timedelta("30s").total_seconds() - 30.0) < 1e-20 + assert (30.0 - Timedelta("30s").total_seconds()) < 1e-20 + + def test_resolution_string(self): + assert Timedelta(days=1).resolution_string == "D" + assert Timedelta(days=1, hours=6).resolution_string == "h" + assert Timedelta(days=1, minutes=6).resolution_string == "min" + assert Timedelta(days=1, seconds=6).resolution_string == "s" + assert Timedelta(days=1, milliseconds=6).resolution_string == "ms" + assert Timedelta(days=1, microseconds=6).resolution_string == "us" + assert Timedelta(days=1, nanoseconds=6).resolution_string == "ns" + + def test_resolution_deprecated(self): + # GH#21344 + td = Timedelta(days=4, hours=3) + result = td.resolution + assert result == Timedelta(microseconds=1) + + # Check that the attribute is available on the class, mirroring + # the stdlib timedelta behavior + result = Timedelta.resolution + assert result == Timedelta(nanoseconds=1) + + @pytest.mark.parametrize( + "unit,unit_depr", + [ + ("W", "w"), + ("D", "d"), + ("min", "MIN"), + ("s", "S"), + ("h", "H"), + ("ms", "MS"), + ("us", "US"), + ], + ) + def test_unit_deprecated(self, unit, unit_depr): + # GH#59051 + msg = f"'{unit_depr}' is deprecated and will be removed in a future version." + + with tm.assert_produces_warning(Pandas4Warning, match=msg): + result = Timedelta(1, unit_depr) + assert result == Timedelta(1, unit) + + +@pytest.mark.parametrize( + "value, expected", + [ + (Timedelta("10s"), True), + (Timedelta("-10s"), True), + (Timedelta(10, unit="ns"), True), + (Timedelta(0, unit="ns"), False), + (Timedelta(-10, unit="ns"), True), + (Timedelta(None), True), + (NaT, True), + ], +) +def test_truthiness(value, expected): + # https://github.com/pandas-dev/pandas/issues/21484 + assert bool(value) is expected + + +def test_timedelta_attribute_precision(): + # GH 31354 + td = Timedelta(1552211999999999872, unit="ns") + result = td.days * 86400 + result += td.seconds + result *= 1000000 + result += td.microseconds + result *= 1000 + result += td.nanoseconds + expected = td._value + assert result == expected + + +def test_to_pytimedelta_large_values(): + td = Timedelta(1152921504609987375, unit="ns") + result = td.to_pytimedelta() + expected = timedelta(days=13343, seconds=86304, microseconds=609987) + assert result == expected + + +def test_timedelta_week_suffix(): + # GH#12691 ensure 'W' suffix works as a string passed to Timedelta + expected = Timedelta("7 days") + result = Timedelta(1, unit="W") + assert result == expected + + result = Timedelta("1W") + assert result == expected diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/__init__.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/__init__.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_as_unit.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_as_unit.py new file mode 100644 index 0000000000000000000000000000000000000000..5572c5936fb12718a59dcc3aca2ac1206bcfb965 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_as_unit.py @@ -0,0 +1,86 @@ +import pytest + +from pandas._libs.tslibs.dtypes import NpyDatetimeUnit +from pandas.errors import OutOfBoundsDatetime + +from pandas import Timestamp + + +class TestTimestampAsUnit: + def test_as_unit(self): + ts = Timestamp("1970-01-01").as_unit("ns") + assert ts.unit == "ns" + + assert ts.as_unit("ns") is ts + + res = ts.as_unit("us") + assert res._value == ts._value // 1000 + assert res._creso == NpyDatetimeUnit.NPY_FR_us.value + + rt = res.as_unit("ns") + assert rt._value == ts._value + assert rt._creso == ts._creso + + res = ts.as_unit("ms") + assert res._value == ts._value // 1_000_000 + assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value + + rt = res.as_unit("ns") + assert rt._value == ts._value + assert rt._creso == ts._creso + + res = ts.as_unit("s") + assert res._value == ts._value // 1_000_000_000 + assert res._creso == NpyDatetimeUnit.NPY_FR_s.value + + rt = res.as_unit("ns") + assert rt._value == ts._value + assert rt._creso == ts._creso + + def test_as_unit_overflows(self): + # microsecond that would be just out of bounds for nano + us = 9223372800000000 + ts = Timestamp._from_value_and_reso(us, NpyDatetimeUnit.NPY_FR_us.value, None) + + msg = "Cannot cast 2262-04-12 00:00:00 to unit='ns' without overflow" + with pytest.raises(OutOfBoundsDatetime, match=msg): + ts.as_unit("ns") + + res = ts.as_unit("ms") + assert res._value == us // 1000 + assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value + + def test_as_unit_rounding(self): + ts = Timestamp(1_500_000) # i.e. 1500 microseconds + res = ts.as_unit("ms") + + expected = Timestamp(1_000_000) # i.e. 1 millisecond + assert res == expected + + assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value + assert res._value == 1 + + with pytest.raises(ValueError, match="Cannot losslessly convert units"): + ts.as_unit("ms", round_ok=False) + + def test_as_unit_non_nano(self): + # case where we are going neither to nor from nano + ts = Timestamp("1970-01-02").as_unit("ms") + assert ts.year == 1970 + assert ts.month == 1 + assert ts.day == 2 + assert ts.hour == ts.minute == ts.second == ts.microsecond == ts.nanosecond == 0 + + res = ts.as_unit("s") + assert res._value == 24 * 3600 + assert res.year == 1970 + assert res.month == 1 + assert res.day == 2 + assert ( + res.hour + == res.minute + == res.second + == res.microsecond + == res.nanosecond + == 0 + ) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_normalize.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_normalize.py new file mode 100644 index 0000000000000000000000000000000000000000..4e8a6e1bc71976990b865e1a6e64bc068b0f0839 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_normalize.py @@ -0,0 +1,29 @@ +import pytest + +from pandas._libs.tslibs import Timestamp +from pandas._libs.tslibs.dtypes import NpyDatetimeUnit + + +class TestTimestampNormalize: + @pytest.mark.parametrize("arg", ["2013-11-30", "2013-11-30 12:00:00"]) + def test_normalize(self, tz_naive_fixture, arg, unit): + tz = tz_naive_fixture + ts = Timestamp(arg, tz=tz).as_unit(unit) + result = ts.normalize() + expected = Timestamp("2013-11-30", tz=tz) + assert result == expected + assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value + + def test_normalize_pre_epoch_dates(self): + # GH: 36294 + result = Timestamp("1969-01-01 09:00:00").normalize() + expected = Timestamp("1969-01-01 00:00:00") + assert result == expected + + def test_normalize_overflow_raises(self): + # GH#60583 + ts = Timestamp.min + + msg = "Cannot normalize Timestamp without integer overflow" + with pytest.raises(ValueError, match=msg): + ts.normalize() diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_replace.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_replace.py new file mode 100644 index 0000000000000000000000000000000000000000..0f61736161cb178e54f2935e2b6b8d46d0e63236 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_replace.py @@ -0,0 +1,211 @@ +from datetime import datetime +import zoneinfo + +from dateutil.tz import gettz +import numpy as np +import pytest + +from pandas._libs.tslibs import ( + OutOfBoundsDatetime, + Timestamp, + conversion, +) +from pandas._libs.tslibs.dtypes import NpyDatetimeUnit +from pandas.compat import WASM +import pandas.util._test_decorators as td + +import pandas._testing as tm + + +class TestTimestampReplace: + def test_replace_out_of_pydatetime_bounds(self): + # GH#50348 + ts = Timestamp("2016-01-01").as_unit("ns") + + msg = "Out of bounds timestamp: 99999-01-01 00:00:00 with frequency 'ns'" + with pytest.raises(OutOfBoundsDatetime, match=msg): + ts.replace(year=99_999) + + ts = ts.as_unit("ms") + result = ts.replace(year=99_999) + assert result.year == 99_999 + assert result._value == Timestamp(np.datetime64("99999-01-01", "ms"))._value + + def test_replace_non_nano(self): + ts = Timestamp._from_value_and_reso( + 91514880000000000, NpyDatetimeUnit.NPY_FR_us.value, None + ) + assert ts.to_pydatetime() == datetime(4869, 12, 28) + + result = ts.replace(year=4900) + assert result._creso == ts._creso + assert result.to_pydatetime() == datetime(4900, 12, 28) + + def test_replace_naive(self): + # GH#14621, GH#7825 + ts = Timestamp("2016-01-01 09:00:00") + result = ts.replace(hour=0) + expected = Timestamp("2016-01-01 00:00:00") + assert result == expected + + def test_replace_aware(self, tz_aware_fixture): + tz = tz_aware_fixture + # GH#14621, GH#7825 + # replacing datetime components with and w/o presence of a timezone + ts = Timestamp("2016-01-01 09:00:00", tz=tz) + result = ts.replace(hour=0) + expected = Timestamp("2016-01-01 00:00:00", tz=tz) + assert result == expected + + def test_replace_preserves_nanos(self, tz_aware_fixture): + tz = tz_aware_fixture + # GH#14621, GH#7825 + ts = Timestamp("2016-01-01 09:00:00.000000123", tz=tz) + result = ts.replace(hour=0) + expected = Timestamp("2016-01-01 00:00:00.000000123", tz=tz) + assert result == expected + + def test_replace_multiple(self, tz_aware_fixture): + tz = tz_aware_fixture + # GH#14621, GH#7825 + # replacing datetime components with and w/o presence of a timezone + # test all + ts = Timestamp("2016-01-01 09:00:00.000000123", tz=tz) + result = ts.replace( + year=2015, + month=2, + day=2, + hour=0, + minute=5, + second=5, + microsecond=5, + nanosecond=5, + ) + expected = Timestamp("2015-02-02 00:05:05.000005005", tz=tz) + assert result == expected + + def test_replace_invalid_kwarg(self, tz_aware_fixture): + tz = tz_aware_fixture + # GH#14621, GH#7825 + ts = Timestamp("2016-01-01 09:00:00.000000123", tz=tz) + msg = r"replace\(\) got an unexpected keyword argument" + with pytest.raises(TypeError, match=msg): + ts.replace(foo=5) + + def test_replace_integer_args(self, tz_aware_fixture): + tz = tz_aware_fixture + # GH#14621, GH#7825 + ts = Timestamp("2016-01-01 09:00:00.000000123", tz=tz) + msg = "value must be an integer, received for hour" + with pytest.raises(ValueError, match=msg): + ts.replace(hour=0.1) + + @pytest.mark.skipif(WASM, reason="tzset is not available on WASM") + def test_replace_tzinfo_equiv_tz_localize_none(self): + # GH#14621, GH#7825 + # assert conversion to naive is the same as replacing tzinfo with None + ts = Timestamp("2013-11-03 01:59:59.999999-0400", tz="US/Eastern") + assert ts.tz_localize(None) == ts.replace(tzinfo=None) + + @td.skip_if_windows + @pytest.mark.skipif(WASM, reason="tzset is not available on WASM") + def test_replace_tzinfo(self): + # GH#15683 + dt = datetime(2016, 3, 27, 1, fold=1) + tzinfo = dt.astimezone(zoneinfo.ZoneInfo("Europe/Berlin")).tzinfo + + result_dt = dt.replace(tzinfo=tzinfo) + result_pd = Timestamp(dt).replace(tzinfo=tzinfo) + + # datetime.timestamp() converts in the local timezone + with tm.set_timezone("UTC"): + assert result_dt.timestamp() == result_pd.timestamp() + + assert result_dt == result_pd + assert result_dt == result_pd.to_pydatetime() + + result_dt = dt.replace(tzinfo=tzinfo).replace(tzinfo=None) + result_pd = Timestamp(dt).replace(tzinfo=tzinfo).replace(tzinfo=None) + + # datetime.timestamp() converts in the local timezone + with tm.set_timezone("UTC"): + assert result_dt.timestamp() == result_pd.timestamp() + + assert result_dt == result_pd + assert result_dt == result_pd.to_pydatetime() + + @pytest.mark.parametrize( + "tz, normalize", + [ + ("pytz/US/Eastern", lambda x: x.tzinfo.normalize(x)), + (gettz("US/Eastern"), lambda x: x), + ], + ) + def test_replace_across_dst(self, tz, normalize): + # GH#18319 check that 1) timezone is correctly normalized and + # 2) that hour is not incorrectly changed by this normalization + if isinstance(tz, str) and tz.startswith("pytz/"): + pytz = pytest.importorskip("pytz") + tz = pytz.timezone(tz.removeprefix("pytz/")) + ts_naive = Timestamp("2017-12-03 16:03:30") + ts_aware = conversion.localize_pydatetime(ts_naive, tz) + + # Preliminary sanity-check + assert ts_aware == normalize(ts_aware) + + # Replace across DST boundary + ts2 = ts_aware.replace(month=6) + + # Check that `replace` preserves hour literal + assert (ts2.hour, ts2.minute) == (ts_aware.hour, ts_aware.minute) + + # Check that post-replace object is appropriately normalized + ts2b = normalize(ts2) + assert ts2 == ts2b + + def test_replace_dst_border(self, unit): + # Gh 7825 + t = Timestamp("2013-11-3", tz="America/Chicago").as_unit(unit) + result = t.replace(hour=3) + expected = Timestamp("2013-11-3 03:00:00", tz="America/Chicago") + assert result == expected + assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value + + @pytest.mark.parametrize("fold", [0, 1]) + @pytest.mark.parametrize("tz", ["dateutil/Europe/London", "Europe/London"]) + def test_replace_dst_fold(self, fold, tz, unit): + # GH 25017 + d = datetime(2019, 10, 27, 2, 30) + ts = Timestamp(d, tz=tz).as_unit(unit) + result = ts.replace(hour=1, fold=fold) + expected = Timestamp(datetime(2019, 10, 27, 1, 30)).tz_localize( + tz, ambiguous=not fold + ) + assert result == expected + assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value + + @pytest.mark.parametrize("fold", [0, 1]) + def test_replace_preserves_fold(self, fold): + # GH#37610. Check that replace preserves Timestamp fold property + tz = gettz("Europe/Moscow") + + ts = Timestamp( + year=2009, month=10, day=25, hour=2, minute=30, fold=fold, tzinfo=tz + ) + ts_replaced = ts.replace(second=1) + + assert ts_replaced.fold == fold + + def test_replace_updates_unit(self): + # GH#57749 + ts = Timestamp("2023-07-15 23:08:12.134567123") + ts2 = Timestamp("2023-07-15 23:08:12.000000") + assert ts2.unit == "us" + result = ts2.replace(microsecond=ts.microsecond, nanosecond=ts.nanosecond) + assert result.unit == "ns" + assert result == ts + + ts3 = Timestamp("2023-07-15 23:08:12").as_unit("s") + result = ts3.replace(microsecond=ts2.microsecond) + assert result.unit == "us" + assert result == ts2 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_round.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_round.py new file mode 100644 index 0000000000000000000000000000000000000000..3c001aab01472c2e93ffbbe8bb038405ee1037ef --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_round.py @@ -0,0 +1,372 @@ +from hypothesis import ( + given, + strategies as st, +) +import numpy as np +import pytest + +from pandas._libs import lib +from pandas._libs.tslibs import ( + NaT, + OutOfBoundsDatetime, + Timedelta, + Timestamp, + iNaT, + to_offset, +) +from pandas._libs.tslibs.dtypes import NpyDatetimeUnit +from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG + +import pandas._testing as tm + + +class TestTimestampRound: + def test_round_division_by_zero_raises(self): + ts = Timestamp("2016-01-01") + + msg = "Division by zero in rounding" + with pytest.raises(ValueError, match=msg): + ts.round("0ns") + + @pytest.mark.parametrize( + "timestamp, freq, expected", + [ + ("20130101 09:10:11", "D", "20130101"), + ("20130101 19:10:11", "D", "20130102"), + ("20130201 12:00:00", "D", "20130202"), + ("20130104 12:00:00", "D", "20130105"), + ("2000-01-05 05:09:15.13", "D", "2000-01-05 00:00:00"), + ("2000-01-05 05:09:15.13", "h", "2000-01-05 05:00:00"), + ("2000-01-05 05:09:15.13", "s", "2000-01-05 05:09:15"), + ], + ) + def test_round_frequencies(self, timestamp, freq, expected): + dt = Timestamp(timestamp) + result = dt.round(freq) + expected = Timestamp(expected) + assert result == expected + + def test_round_tzaware(self): + dt = Timestamp("20130101 09:10:11", tz="US/Eastern") + result = dt.round("D") + expected = Timestamp("20130101", tz="US/Eastern") + assert result == expected + + dt = Timestamp("20130101 09:10:11", tz="US/Eastern") + result = dt.round("s") + assert result == dt + + def test_round_30min(self): + # round + dt = Timestamp("20130104 12:32:00") + result = dt.round("30Min") + expected = Timestamp("20130104 12:30:00") + assert result == expected + + def test_round_subsecond(self): + # GH#14440 & GH#15578 + result = Timestamp("2016-10-17 12:00:00.0015").round("ms") + expected = Timestamp("2016-10-17 12:00:00.002000") + assert result == expected + + result = Timestamp("2016-10-17 12:00:00.00149").round("ms") + expected = Timestamp("2016-10-17 12:00:00.001000") + assert result == expected + + ts = Timestamp("2016-10-17 12:00:00.0015") + for freq in ["us", "ns"]: + assert ts == ts.round(freq) + + result = Timestamp("2016-10-17 12:00:00.001501031").round("10ns") + expected = Timestamp("2016-10-17 12:00:00.001501030") + assert result == expected + + def test_round_nonstandard_freq(self): + with tm.assert_produces_warning(False): + Timestamp("2016-10-17 12:00:00.001501031").round("1010ns") + + def test_round_invalid_arg(self): + stamp = Timestamp("2000-01-05 05:09:15.13") + with pytest.raises(ValueError, match=INVALID_FREQ_ERR_MSG): + stamp.round("foo") + + @pytest.mark.parametrize( + "test_input, rounder, freq, expected", + [ + ("2117-01-01 00:00:45", "floor", "15s", "2117-01-01 00:00:45"), + ("2117-01-01 00:00:45", "ceil", "15s", "2117-01-01 00:00:45"), + ( + "2117-01-01 00:00:45.000000012", + "floor", + "10ns", + "2117-01-01 00:00:45.000000010", + ), + ( + "1823-01-01 00:00:01.000000012", + "ceil", + "10ns", + "1823-01-01 00:00:01.000000020", + ), + ("1823-01-01 00:00:01", "floor", "1s", "1823-01-01 00:00:01"), + ("1823-01-01 00:00:01", "ceil", "1s", "1823-01-01 00:00:01"), + ("NaT", "floor", "1s", "NaT"), + ("NaT", "ceil", "1s", "NaT"), + ], + ) + def test_ceil_floor_edge(self, test_input, rounder, freq, expected): + dt = Timestamp(test_input) + func = getattr(dt, rounder) + result = func(freq) + + if dt is NaT: + assert result is NaT + else: + expected = Timestamp(expected) + assert result == expected + + @pytest.mark.parametrize( + "test_input, freq, expected", + [ + ("2018-01-01 00:02:06", "2s", "2018-01-01 00:02:06"), + ("2018-01-01 00:02:00", "2min", "2018-01-01 00:02:00"), + ("2018-01-01 00:04:00", "4min", "2018-01-01 00:04:00"), + ("2018-01-01 00:15:00", "15min", "2018-01-01 00:15:00"), + ("2018-01-01 00:20:00", "20min", "2018-01-01 00:20:00"), + ("2018-01-01 03:00:00", "3h", "2018-01-01 03:00:00"), + ], + ) + @pytest.mark.parametrize("rounder", ["ceil", "floor", "round"]) + def test_round_minute_freq(self, test_input, freq, expected, rounder): + # Ensure timestamps that shouldn't round dont! + # GH#21262 + + dt = Timestamp(test_input) + expected = Timestamp(expected) + func = getattr(dt, rounder) + result = func(freq) + assert result == expected + + def test_ceil(self, unit): + dt = Timestamp("20130101 09:10:11").as_unit(unit) + result = dt.ceil("D") + expected = Timestamp("20130102") + assert result == expected + assert result._creso == dt._creso + + def test_floor(self, unit): + dt = Timestamp("20130101 09:10:11").as_unit(unit) + result = dt.floor("D") + expected = Timestamp("20130101") + assert result == expected + assert result._creso == dt._creso + + @pytest.mark.parametrize("method", ["ceil", "round", "floor"]) + def test_round_dst_border_ambiguous(self, method, unit): + # GH 18946 round near "fall back" DST + ts = Timestamp("2017-10-29 00:00:00", tz="UTC").tz_convert("Europe/Madrid") + ts = ts.as_unit(unit) + result = getattr(ts, method)("h", ambiguous=True) + assert result == ts + assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value + + result = getattr(ts, method)("h", ambiguous=False) + expected = Timestamp("2017-10-29 01:00:00", tz="UTC").tz_convert( + "Europe/Madrid" + ) + assert result == expected + assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value + + result = getattr(ts, method)("h", ambiguous="NaT") + assert result is NaT + + msg = "Cannot infer dst time" + with pytest.raises(ValueError, match=msg): + getattr(ts, method)("h", ambiguous="raise") + + @pytest.mark.parametrize( + "method, ts_str, freq", + [ + ["ceil", "2018-03-11 01:59:00-0600", "5min"], + ["round", "2018-03-11 01:59:00-0600", "5min"], + ["floor", "2018-03-11 03:01:00-0500", "2h"], + ], + ) + def test_round_dst_border_nonexistent(self, method, ts_str, freq, unit): + # GH 23324 round near "spring forward" DST + ts = Timestamp(ts_str, tz="America/Chicago").as_unit(unit) + result = getattr(ts, method)(freq, nonexistent="shift_forward") + expected = Timestamp("2018-03-11 03:00:00", tz="America/Chicago") + assert result == expected + assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value + + result = getattr(ts, method)(freq, nonexistent="NaT") + assert result is NaT + + msg = "2018-03-11 02:00:00" + with pytest.raises(ValueError, match=msg): + getattr(ts, method)(freq, nonexistent="raise") + + @pytest.mark.parametrize( + "timestamp", + [ + "2018-01-01 0:0:0.124999360", + "2018-01-01 0:0:0.125000367", + "2018-01-01 0:0:0.125500", + "2018-01-01 0:0:0.126500", + "2018-01-01 12:00:00", + "2019-01-01 12:00:00", + ], + ) + @pytest.mark.parametrize( + "freq", + [ + "2ns", + "3ns", + "4ns", + "5ns", + "6ns", + "7ns", + "250ns", + "500ns", + "750ns", + "1us", + "19us", + "250us", + "500us", + "750us", + "1s", + "2s", + "3s", + "1D", + ], + ) + def test_round_int64(self, timestamp, freq): + # check that all rounding modes are accurate to int64 precision + # see GH#22591 + dt = Timestamp(timestamp).as_unit("ns") + unit = to_offset(freq).nanos + + # test floor + result = dt.floor(freq) + assert result._value % unit == 0, f"floor not a {freq} multiple" + assert 0 <= dt._value - result._value < unit, "floor error" + + # test ceil + result = dt.ceil(freq) + assert result._value % unit == 0, f"ceil not a {freq} multiple" + assert 0 <= result._value - dt._value < unit, "ceil error" + + # test round + result = dt.round(freq) + assert result._value % unit == 0, f"round not a {freq} multiple" + assert abs(result._value - dt._value) <= unit // 2, "round error" + if unit % 2 == 0 and abs(result._value - dt._value) == unit // 2: + # round half to even + assert result._value // unit % 2 == 0, "round half to even error" + + def test_round_implementation_bounds(self): + # See also: analogous test for Timedelta + result = Timestamp.min.ceil("s") + expected = Timestamp(1677, 9, 21, 0, 12, 44) + assert result == expected + + result = Timestamp.max.floor("s") + expected = Timestamp.max - Timedelta(854775807) + assert result == expected + + msg = "Cannot round 1677-09-21 00:12:43.145224193 to freq=" + with pytest.raises(OutOfBoundsDatetime, match=msg): + Timestamp.min.floor("s") + + with pytest.raises(OutOfBoundsDatetime, match=msg): + Timestamp.min.round("s") + + msg = "Cannot round 2262-04-11 23:47:16.854775807 to freq=" + with pytest.raises(OutOfBoundsDatetime, match=msg): + Timestamp.max.ceil("s") + + with pytest.raises(OutOfBoundsDatetime, match=msg): + Timestamp.max.round("s") + + @pytest.mark.slow + @given(val=st.integers(iNaT + 1, lib.i8max)) + @pytest.mark.parametrize( + "method", [Timestamp.round, Timestamp.floor, Timestamp.ceil] + ) + def test_round_sanity(self, val, method): + cls = Timestamp + err_cls = OutOfBoundsDatetime + + val = np.int64(val) + ts = cls(val) + + def checker(ts, nanos, unit): + # First check that we do raise in cases where we should + if nanos == 1: + pass + else: + div, mod = divmod(ts._value, nanos) + diff = int(nanos - mod) + lb = ts._value - mod + assert lb <= ts._value # i.e. no overflows with python ints + ub = ts._value + diff + assert ub > ts._value # i.e. no overflows with python ints + + msg = "without overflow" + if mod == 0: + # We should never be raising in this + pass + elif method is cls.ceil: + if ub > cls.max._value: + with pytest.raises(err_cls, match=msg): + method(ts, unit) + return + elif method is cls.floor: + if lb < cls.min._value: + with pytest.raises(err_cls, match=msg): + method(ts, unit) + return + elif mod >= diff: + if ub > cls.max._value: + with pytest.raises(err_cls, match=msg): + method(ts, unit) + return + elif lb < cls.min._value: + with pytest.raises(err_cls, match=msg): + method(ts, unit) + return + + res = method(ts, unit) + + td = res - ts + diff = abs(td._value) + assert diff < nanos + assert res._value % nanos == 0 + + if method is cls.round: + assert diff <= nanos / 2 + elif method is cls.floor: + assert res <= ts + elif method is cls.ceil: + assert res >= ts + + nanos = 1 + checker(ts, nanos, "ns") + + nanos = 1000 + checker(ts, nanos, "us") + + nanos = 1_000_000 + checker(ts, nanos, "ms") + + nanos = 1_000_000_000 + checker(ts, nanos, "s") + + nanos = 60 * 1_000_000_000 + checker(ts, nanos, "min") + + nanos = 60 * 60 * 1_000_000_000 + checker(ts, nanos, "h") + + nanos = 24 * 60 * 60 * 1_000_000_000 + checker(ts, nanos, "D") diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_timestamp_method.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_timestamp_method.py new file mode 100644 index 0000000000000000000000000000000000000000..beacaaf04e6b2578d89aa424ec4a2540ec07a9de --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_timestamp_method.py @@ -0,0 +1,34 @@ +# NB: This is for the Timestamp.timestamp *method* specifically, not +# the Timestamp class in general. +from datetime import timezone + +import pytest + +from pandas._libs.tslibs import Timestamp +from pandas.compat import WASM +import pandas.util._test_decorators as td + +import pandas._testing as tm + + +class TestTimestampMethod: + @td.skip_if_windows + @pytest.mark.skipif(WASM, reason="tzset is not available on WASM") + def test_timestamp(self, fixed_now_ts): + # GH#17329 + # tz-naive --> treat it as if it were UTC for purposes of timestamp() + ts = fixed_now_ts + uts = ts.replace(tzinfo=timezone.utc) + assert ts.timestamp() == uts.timestamp() + + tsc = Timestamp("2014-10-11 11:00:01.12345678", tz="US/Central") + utsc = tsc.tz_convert("UTC") + + # utsc is a different representation of the same time + assert tsc.timestamp() == utsc.timestamp() + + # datetime.timestamp() converts in the local timezone + with tm.set_timezone("UTC"): + # should agree with datetime.timestamp method + dt = ts.to_pydatetime() + assert dt.timestamp() == ts.timestamp() diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_to_julian_date.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_to_julian_date.py new file mode 100644 index 0000000000000000000000000000000000000000..7769614b601a4842a7273b441af6552956ff2e72 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_to_julian_date.py @@ -0,0 +1,28 @@ +from pandas import Timestamp + + +class TestTimestampToJulianDate: + def test_compare_1700(self): + ts = Timestamp("1700-06-23") + res = ts.to_julian_date() + assert res == 2_342_145.5 + + def test_compare_2000(self): + ts = Timestamp("2000-04-12") + res = ts.to_julian_date() + assert res == 2_451_646.5 + + def test_compare_2100(self): + ts = Timestamp("2100-08-12") + res = ts.to_julian_date() + assert res == 2_488_292.5 + + def test_compare_hour01(self): + ts = Timestamp("2000-08-12T01:00:00") + res = ts.to_julian_date() + assert res == 2_451_768.5416666666666666 + + def test_compare_hour13(self): + ts = Timestamp("2000-08-12T13:00:00") + res = ts.to_julian_date() + assert res == 2_451_769.0416666666666666 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_to_pydatetime.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_to_pydatetime.py new file mode 100644 index 0000000000000000000000000000000000000000..07e57b51a7f1ed22b35a46e770d3fac499b4b65f --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_to_pydatetime.py @@ -0,0 +1,83 @@ +from datetime import ( + datetime, + timedelta, +) + +import pytest + +from pandas._libs.tslibs.timezones import dateutil_gettz as gettz +import pandas.util._test_decorators as td + +from pandas import Timestamp +import pandas._testing as tm + + +class TestTimestampToPyDatetime: + def test_to_pydatetime_fold(self): + # GH#45087 + tzstr = "dateutil/usr/share/zoneinfo/America/Chicago" + ts = Timestamp(year=2013, month=11, day=3, hour=1, minute=0, fold=1, tz=tzstr) + dt = ts.to_pydatetime() + assert dt.fold == 1 + + def test_to_pydatetime_nonzero_nano(self): + ts = Timestamp("2011-01-01 9:00:00.123456789") + + # Warn the user of data loss (nanoseconds). + msg = "Discarding nonzero nanoseconds in conversion" + with tm.assert_produces_warning(UserWarning, match=msg): + expected = datetime(2011, 1, 1, 9, 0, 0, 123456) + result = ts.to_pydatetime() + assert result == expected + + def test_timestamp_to_datetime(self): + stamp = Timestamp("20090415", tz="US/Eastern") + dtval = stamp.to_pydatetime() + assert stamp == dtval + assert stamp.tzinfo == dtval.tzinfo + + def test_timestamp_to_pydatetime_dateutil(self): + stamp = Timestamp("20090415", tz="dateutil/US/Eastern") + dtval = stamp.to_pydatetime() + assert stamp == dtval + assert stamp.tzinfo == dtval.tzinfo + + def test_timestamp_to_pydatetime_explicit_pytz(self): + pytz = pytest.importorskip("pytz") + stamp = Timestamp("20090415", tz=pytz.timezone("US/Eastern")) + dtval = stamp.to_pydatetime() + assert stamp == dtval + assert stamp.tzinfo == dtval.tzinfo + + @td.skip_if_windows + def test_timestamp_to_pydatetime_explicit_dateutil(self): + stamp = Timestamp("20090415", tz=gettz("US/Eastern")) + dtval = stamp.to_pydatetime() + assert stamp == dtval + assert stamp.tzinfo == dtval.tzinfo + + def test_to_pydatetime_bijective(self): + # Ensure that converting to datetime and back only loses precision + # by going from nanoseconds to microseconds. + exp_warning = None if Timestamp.max.nanosecond == 0 else UserWarning + with tm.assert_produces_warning(exp_warning): + pydt_max = Timestamp.max.to_pydatetime() + + assert ( + Timestamp(pydt_max).as_unit("ns")._value / 1000 + == Timestamp.max._value / 1000 + ) + + exp_warning = None if Timestamp.min.nanosecond == 0 else UserWarning + with tm.assert_produces_warning(exp_warning): + pydt_min = Timestamp.min.to_pydatetime() + + # The next assertion can be enabled once GH#39221 is merged + # assert pydt_min < Timestamp.min # this is bc nanos are dropped + tdus = timedelta(microseconds=1) + assert pydt_min + tdus > Timestamp.min + + assert ( + Timestamp(pydt_min + tdus).as_unit("ns")._value / 1000 + == Timestamp.min._value / 1000 + ) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_tz_convert.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_tz_convert.py new file mode 100644 index 0000000000000000000000000000000000000000..a7bb3b90805ab4a7b5d2a0ec08b795a2118daf36 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_tz_convert.py @@ -0,0 +1,51 @@ +import dateutil +import pytest + +from pandas._libs.tslibs import timezones +import pandas.util._test_decorators as td + +from pandas import Timestamp + + +class TestTimestampTZConvert: + @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) + def test_astimezone(self, tzstr): + # astimezone is an alias for tz_convert, so keep it with + # the tz_convert tests + utcdate = Timestamp("3/11/2012 22:00", tz="UTC") + expected = utcdate.tz_convert(tzstr) + result = utcdate.astimezone(tzstr) + assert expected == result + assert isinstance(result, Timestamp) + + @pytest.mark.parametrize( + "stamp", + [ + "2014-02-01 09:00", + "2014-07-08 09:00", + "2014-11-01 17:00", + "2014-11-05 00:00", + ], + ) + def test_tz_convert_roundtrip(self, stamp, tz_aware_fixture): + tz = tz_aware_fixture + + ts = Timestamp(stamp, tz="UTC") + converted = ts.tz_convert(tz) + + reset = converted.tz_convert(None) + assert reset == Timestamp(stamp) + assert reset.tzinfo is None + assert reset == converted.tz_convert("UTC").tz_localize(None) + + @td.skip_if_windows + def test_tz_convert_utc_with_system_utc(self): + # from system utc to real utc + ts = Timestamp("2001-01-05 11:56", tz=timezones.maybe_get_tz("dateutil/UTC")) + # check that the time hasn't changed. + assert ts == ts.tz_convert(dateutil.tz.tzutc()) + + # from system utc to real utc + ts = Timestamp("2001-01-05 11:56", tz=timezones.maybe_get_tz("dateutil/UTC")) + # check that the time hasn't changed. + assert ts == ts.tz_convert(dateutil.tz.tzutc()) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_tz_localize.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_tz_localize.py new file mode 100644 index 0000000000000000000000000000000000000000..80a9a331b3e8e656d0fdb83798541037bdeb21b2 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/methods/test_tz_localize.py @@ -0,0 +1,364 @@ +from datetime import timedelta +import re +import zoneinfo + +from dateutil.tz import gettz +import pytest + +from pandas._libs.tslibs.dtypes import NpyDatetimeUnit +from pandas.errors import OutOfBoundsDatetime + +from pandas import ( + NaT, + Timestamp, +) + + +def _distant_date_only_for_zoneinfo(year, tz): + if int(year) >= 2200: + if isinstance(tz, zoneinfo.ZoneInfo): + return + elif isinstance(tz, str) and not tz.startswith(("dateutil/", "pytz/")): + # zoneinfo tz passed as string + return + else: + pytest.skip("ZoneInfo required for year >= 2200") + + +class TestTimestampTZLocalize: + def test_tz_localize_pushes_out_of_bounds(self): + # GH#12677 + # tz_localize that pushes away from the boundary is OK + pytz = pytest.importorskip("pytz") + msg = ( + f"Converting {Timestamp.min.strftime('%Y-%m-%d %H:%M:%S')} " + f"underflows past {Timestamp.min}" + ) + pac = Timestamp.min.tz_localize(pytz.timezone("US/Pacific")) + assert pac._value > Timestamp.min._value + pac.tz_convert("Asia/Tokyo") # tz_convert doesn't change value + with pytest.raises(OutOfBoundsDatetime, match=msg): + Timestamp.min.tz_localize(pytz.timezone("Asia/Tokyo")) + + # tz_localize that pushes away from the boundary is OK + msg = ( + f"Converting {Timestamp.max.strftime('%Y-%m-%d %H:%M:%S')} " + f"overflows past {Timestamp.max}" + ) + tokyo = Timestamp.max.tz_localize(pytz.timezone("Asia/Tokyo")) + assert tokyo._value < Timestamp.max._value + tokyo.tz_convert("US/Pacific") # tz_convert doesn't change value + with pytest.raises(OutOfBoundsDatetime, match=msg): + Timestamp.max.tz_localize(pytz.timezone("US/Pacific")) + + @pytest.mark.parametrize( + "tz", + [zoneinfo.ZoneInfo("US/Central"), "dateutil/US/Central", "pytz/US/Central"], + ) + def test_tz_localize_ambiguous_bool(self, unit, tz): + # make sure that we are correctly accepting bool values as ambiguous + # GH#14402 + if isinstance(tz, str) and tz.startswith("pytz/"): + pytz = pytest.importorskip("pytz") + tz = pytz.timezone(tz.removeprefix("pytz/")) + ts = Timestamp("2015-11-01 01:00:03").as_unit(unit) + expected0 = Timestamp("2015-11-01 01:00:03-0500", tz=tz) + expected1 = Timestamp("2015-11-01 01:00:03-0600", tz=tz) + + msg = "Cannot infer dst time from 2015-11-01 01:00:03" + with pytest.raises(ValueError, match=msg): + ts.tz_localize(tz) + + result = ts.tz_localize(tz, ambiguous=True) + assert result == expected0 + assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value + + result = ts.tz_localize(tz, ambiguous=False) + assert result == expected1 + assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value + + @pytest.mark.parametrize("year", ["2014", "2200"]) + def test_tz_localize_ambiguous(self, year): + ts = Timestamp(f"{year}-11-02 01:00").as_unit("s") + ts_dst = ts.tz_localize("US/Eastern", ambiguous=True) + ts_no_dst = ts.tz_localize("US/Eastern", ambiguous=False) + + assert ts_no_dst._value - ts_dst._value == 3600 + msg = re.escape( + "'ambiguous' parameter must be one of: " + "True, False, 'NaT', 'raise' (default)" + ) + with pytest.raises(ValueError, match=msg): + ts.tz_localize("US/Eastern", ambiguous="infer") + + def test_tz_localize_invalid(self): + # GH#8025 + msg = "Cannot localize tz-aware Timestamp, use tz_convert for conversions" + with pytest.raises(TypeError, match=msg): + Timestamp("2011-01-01", tz="US/Eastern").tz_localize("Asia/Tokyo") + + msg = "Cannot convert tz-naive Timestamp, use tz_localize to localize" + with pytest.raises(TypeError, match=msg): + Timestamp("2011-01-01").tz_convert("Asia/Tokyo") + + @pytest.mark.parametrize("year", ["2015", "2201"]) + @pytest.mark.parametrize( + "stamp, tz", + [ + ("2015-03-08 02:00", "US/Eastern"), + ("2015-03-08 02:30", "US/Pacific"), + ("2015-03-29 02:00", "Europe/Paris"), + ("2015-03-29 02:30", "Europe/Belgrade"), + ], + ) + def test_tz_localize_nonexistent(self, stamp, tz, year): + # GH#13057 + stamp = stamp.replace("2015", year) + ts = Timestamp(stamp) + with pytest.raises(ValueError, match=stamp): + ts.tz_localize(tz) + # GH 22644 + with pytest.raises(ValueError, match=stamp): + ts.tz_localize(tz, nonexistent="raise") + assert ts.tz_localize(tz, nonexistent="NaT") is NaT + + @pytest.mark.parametrize( + "stamp, tz, forward_expected, backward_expected", + [ + ( + "2015-03-29 02:00:00", + "Europe/Warsaw", + "2015-03-29 03:00:00", + "2015-03-29 01:59:59.999999", + ), # utc+1 -> utc+2 + ( + "2023-03-12 02:00:00", + "America/Los_Angeles", + "2023-03-12 03:00:00", + "2023-03-12 01:59:59.999999", + ), # utc-8 -> utc-7 + ( + "2023-03-26 01:00:00", + "Europe/London", + "2023-03-26 02:00:00", + "2023-03-26 00:59:59.999999", + ), # utc+0 -> utc+1 + ( + "2023-03-26 00:00:00", + "Atlantic/Azores", + "2023-03-26 01:00:00", + "2023-03-25 23:59:59.999999", + ), # utc-1 -> utc+0 + ], + ) + def test_tz_localize_nonexistent_shift( + self, stamp, tz, forward_expected, backward_expected + ): + ts = Timestamp(stamp) + forward_ts = ts.tz_localize(tz, nonexistent="shift_forward") + assert forward_ts == Timestamp(forward_expected, tz=tz) + backward_ts = ts.tz_localize(tz, nonexistent="shift_backward") + assert backward_ts == Timestamp(backward_expected, tz=tz) + + def test_tz_localize_ambiguous_raise(self): + # GH#13057 + ts = Timestamp("2015-11-1 01:00") + msg = "Cannot infer dst time from 2015-11-01 01:00:00," + with pytest.raises(ValueError, match=msg): + ts.tz_localize("US/Pacific", ambiguous="raise") + + def test_tz_localize_nonexistent_invalid_arg(self, warsaw): + # GH 22644 + tz = warsaw + ts = Timestamp("2015-03-29 02:00:00") + msg = ( + "The nonexistent argument must be one of 'raise', 'NaT', " + "'shift_forward', 'shift_backward' or a timedelta object" + ) + with pytest.raises(ValueError, match=msg): + ts.tz_localize(tz, nonexistent="foo") + + @pytest.mark.parametrize( + "stamp", + [ + "2014-02-01 09:00", + "2014-07-08 09:00", + "2014-11-01 17:00", + "2014-11-05 00:00", + ], + ) + def test_tz_localize_roundtrip(self, stamp, tz_aware_fixture): + tz = tz_aware_fixture + ts = Timestamp(stamp) + localized = ts.tz_localize(tz) + assert localized == Timestamp(stamp, tz=tz) + + msg = "Cannot localize tz-aware Timestamp" + with pytest.raises(TypeError, match=msg): + localized.tz_localize(tz) + + reset = localized.tz_localize(None) + assert reset == ts + assert reset.tzinfo is None + + def test_tz_localize_ambiguous_compat(self): + # validate that pytz and dateutil are compat for dst + # when the transition happens + pytz = pytest.importorskip("pytz") + naive = Timestamp("2013-10-27 01:00:00") + assert naive.unit == "us" + + pytz_zone = pytz.timezone("Europe/London") + dateutil_zone = "dateutil/Europe/London" + result_pytz = naive.tz_localize(pytz_zone, ambiguous=False) + result_dateutil = naive.tz_localize(dateutil_zone, ambiguous=False) + assert result_pytz._value == result_dateutil._value + assert result_pytz._value == 1_382_835_600 * 10**6 + + # fixed ambiguous behavior + # see gh-14621, GH#45087 + assert result_pytz.to_pydatetime().tzname() == "GMT" + assert result_dateutil.to_pydatetime().tzname() == "GMT" + assert str(result_pytz) == str(result_dateutil) + + # 1 hour difference + result_pytz = naive.tz_localize(pytz_zone, ambiguous=True) + result_dateutil = naive.tz_localize(dateutil_zone, ambiguous=True) + assert result_pytz._value == result_dateutil._value + assert result_pytz._value == 1_382_832_000 * 10**6 + + # see gh-14621 + assert str(result_pytz) == str(result_dateutil) + assert ( + result_pytz.to_pydatetime().tzname() + == result_dateutil.to_pydatetime().tzname() + ) + + @pytest.mark.parametrize( + "tz", + [ + "pytz/US/Eastern", + gettz("US/Eastern"), + zoneinfo.ZoneInfo("US/Eastern"), + "dateutil/US/Eastern", + ], + ) + def test_timestamp_tz_localize(self, tz): + if isinstance(tz, str) and tz.startswith("pytz/"): + pytz = pytest.importorskip("pytz") + tz = pytz.timezone(tz.removeprefix("pytz/")) + stamp = Timestamp("3/11/2012 04:00") + + result = stamp.tz_localize(tz) + expected = Timestamp("3/11/2012 04:00", tz=tz) + assert result.hour == expected.hour + assert result == expected + + @pytest.mark.parametrize("year", ["2018", "2204"]) + @pytest.mark.parametrize( + "start_ts, tz, end_ts, shift", + [ + ["2018-03-25 02:20:00", "Europe/Warsaw", "2018-03-25 03:00:00", "forward"], + [ + "2018-03-25 02:20:00", + "Europe/Warsaw", + "2018-03-25 01:59:59.999999999", + "backward", + ], + [ + "2018-03-25 02:20:00", + "Europe/Warsaw", + "2018-03-25 03:20:00", + timedelta(hours=1), + ], + [ + "2018-03-25 02:20:00", + "Europe/Warsaw", + "2018-03-25 01:20:00", + timedelta(hours=-1), + ], + ["2018-03-11 02:33:00", "US/Pacific", "2018-03-11 03:00:00", "forward"], + [ + "2018-03-11 02:33:00", + "US/Pacific", + "2018-03-11 01:59:59.999999999", + "backward", + ], + [ + "2018-03-11 02:33:00", + "US/Pacific", + "2018-03-11 03:33:00", + timedelta(hours=1), + ], + [ + "2018-03-11 02:33:00", + "US/Pacific", + "2018-03-11 01:33:00", + timedelta(hours=-1), + ], + ], + ) + @pytest.mark.parametrize("tz_type", ["", "dateutil/"]) + def test_timestamp_tz_localize_nonexistent_shift( + self, start_ts, tz, end_ts, shift, year, tz_type, unit + ): + # GH 8917, 24466 + if year == "2204" and tz_type == "dateutil/": + pytest.skip("ZoneInfo required for year >= 2200") + start_ts = start_ts.replace("2018", year) + end_ts = end_ts.replace("2018", year) + tz = tz_type + tz + if isinstance(shift, str): + shift = "shift_" + shift + ts = Timestamp(start_ts).as_unit(unit) + result = ts.tz_localize(tz, nonexistent=shift) + expected = Timestamp(end_ts).tz_localize(tz) + + if unit == "us": + assert result == expected.replace(nanosecond=0) + elif unit == "ms": + micros = expected.microsecond - expected.microsecond % 1000 + assert result == expected.replace(microsecond=micros, nanosecond=0) + elif unit == "s": + assert result == expected.replace(microsecond=0, nanosecond=0) + else: + assert result == expected + assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value + + @pytest.mark.parametrize("year", ["2015", "2201"]) + @pytest.mark.parametrize("offset", [-1, 1]) + def test_timestamp_tz_localize_nonexistent_shift_invalid( + self, offset, year, warsaw + ): + # GH 8917, 24466 + tz = warsaw + _distant_date_only_for_zoneinfo(year, tz) + ts = Timestamp(f"{year}-03-29 02:20:00") + msg = "The provided timedelta will relocalize on a nonexistent time" + with pytest.raises(ValueError, match=msg): + ts.tz_localize(tz, nonexistent=timedelta(seconds=offset)) + + @pytest.mark.parametrize("year", ["2015", "2201"]) + def test_timestamp_tz_localize_nonexistent_NaT(self, year, warsaw, unit): + # GH 8917 + tz = warsaw + _distant_date_only_for_zoneinfo(year, tz) + ts = Timestamp(f"{year}-03-29 02:20:00").as_unit(unit) + result = ts.tz_localize(tz, nonexistent="NaT") + assert result is NaT + + @pytest.mark.parametrize("year", ["2015", "2201"]) + def test_timestamp_tz_localize_nonexistent_raise(self, year, warsaw, unit): + # GH 8917 + tz = warsaw + _distant_date_only_for_zoneinfo(year, tz) + ts = Timestamp(f"{year}-03-29 02:20:00").as_unit(unit) + msg = f"{year}-03-29 02:20:00" + with pytest.raises(ValueError, match=msg): + ts.tz_localize(tz, nonexistent="raise") + msg = ( + "The nonexistent argument must be one of 'raise', 'NaT', " + "'shift_forward', 'shift_backward' or a timedelta object" + ) + with pytest.raises(ValueError, match=msg): + ts.tz_localize(tz, nonexistent="foo") diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_arithmetic.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_arithmetic.py new file mode 100644 index 0000000000000000000000000000000000000000..d65d425620c84477ca1b69cf73156c9183cecced --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_arithmetic.py @@ -0,0 +1,346 @@ +from datetime import ( + datetime, + timedelta, + timezone, +) + +from dateutil.tz import gettz +import numpy as np +import pytest + +from pandas._libs.tslibs import ( + OutOfBoundsDatetime, + OutOfBoundsTimedelta, + Timedelta, + Timestamp, + offsets, + to_offset, +) + +import pandas._testing as tm + + +class TestTimestampArithmetic: + def test_overflow_offset(self): + # no overflow expected + + stamp = Timestamp("2000/1/1") + offset_no_overflow = to_offset("D") * 100 + + expected = Timestamp("2000/04/10") + assert stamp + offset_no_overflow == expected + + assert offset_no_overflow + stamp == expected + + expected = Timestamp("1999/09/23") + assert stamp - offset_no_overflow == expected + + def test_overflow_offset_raises(self): + # xref https://github.com/statsmodels/statsmodels/issues/3374 + # ends up multiplying really large numbers which overflow + + stamp = Timestamp("2017-01-13 00:00:00").as_unit("ns") + offset_overflow = 20169940 * offsets.Day(1) + lmsg2 = r"Cannot cast -?20169940 days \+?00:00:00 to unit='ns' without overflow" + + with pytest.raises(OutOfBoundsTimedelta, match=lmsg2): + stamp + offset_overflow + + with pytest.raises(OutOfBoundsTimedelta, match=lmsg2): + offset_overflow + stamp + + with pytest.raises(OutOfBoundsTimedelta, match=lmsg2): + stamp - offset_overflow + + # xref https://github.com/pandas-dev/pandas/issues/14080 + # used to crash, so check for proper overflow exception + + stamp = Timestamp("2000/1/1").as_unit("ns") + offset_overflow = to_offset("D") * 100**5 + + lmsg3 = ( + r"Cannot cast -?10000000000 days \+?00:00:00 to unit='ns' without overflow" + ) + with pytest.raises(OutOfBoundsTimedelta, match=lmsg3): + stamp + offset_overflow + + with pytest.raises(OutOfBoundsTimedelta, match=lmsg3): + offset_overflow + stamp + + with pytest.raises(OutOfBoundsTimedelta, match=lmsg3): + stamp - offset_overflow + + def test_overflow_timestamp_raises(self): + # https://github.com/pandas-dev/pandas/issues/31774 + msg = "Result is too large" + a = Timestamp("2101-01-01 00:00:00").as_unit("ns") + b = Timestamp("1688-01-01 00:00:00").as_unit("ns") + + with pytest.raises(OutOfBoundsDatetime, match=msg): + a - b + + # but we're OK for timestamp and datetime.datetime + assert (a - b.to_pydatetime()) == (a.to_pydatetime() - b) + + def test_delta_preserve_nanos(self): + val = Timestamp(1337299200000000123) + result = val + timedelta(1) + assert result.nanosecond == val.nanosecond + + def test_rsub_dtscalars(self, tz_naive_fixture): + # In particular, check that datetime64 - Timestamp works GH#28286 + td = Timedelta(1235345642000) + ts = Timestamp("2021-01-01", tz=tz_naive_fixture) + other = ts + td + + assert other - ts == td + assert other.to_pydatetime() - ts == td + if tz_naive_fixture is None: + assert other.to_datetime64() - ts == td + else: + msg = "Cannot subtract tz-naive and tz-aware datetime-like objects" + with pytest.raises(TypeError, match=msg): + other.to_datetime64() - ts + + def test_timestamp_sub_datetime(self): + dt = datetime(2013, 10, 12) + ts = Timestamp(datetime(2013, 10, 13)) + assert (ts - dt).days == 1 + assert (dt - ts).days == -1 + + def test_subtract_tzaware_datetime(self): + t1 = Timestamp("2020-10-22T22:00:00+00:00") + t2 = datetime(2020, 10, 22, 22, tzinfo=timezone.utc) + + result = t1 - t2 + + assert isinstance(result, Timedelta) + assert result == Timedelta("0 days") + + def test_subtract_timestamp_from_different_timezone(self): + t1 = Timestamp("20130101").tz_localize("US/Eastern") + t2 = Timestamp("20130101").tz_localize("CET") + + result = t1 - t2 + + assert isinstance(result, Timedelta) + assert result == Timedelta("0 days 06:00:00") + + def test_subtracting_involving_datetime_with_different_tz(self): + t1 = datetime(2013, 1, 1, tzinfo=timezone(timedelta(hours=-5))) + t2 = Timestamp("20130101").tz_localize("CET") + + result = t1 - t2 + + assert isinstance(result, Timedelta) + assert result == Timedelta("0 days 06:00:00") + + result = t2 - t1 + assert isinstance(result, Timedelta) + assert result == Timedelta("-1 days +18:00:00") + + def test_subtracting_different_timezones(self, tz_aware_fixture): + t_raw = Timestamp("20130101") + t_UTC = t_raw.tz_localize("UTC") + t_diff = t_UTC.tz_convert(tz_aware_fixture) + Timedelta("0 days 05:00:00") + + result = t_diff - t_UTC + + assert isinstance(result, Timedelta) + assert result == Timedelta("0 days 05:00:00") + + def test_addition_subtraction_types(self): + # Assert on the types resulting from Timestamp +/- various date/time + # objects + dt = datetime(2014, 3, 4) + td = timedelta(seconds=1) + ts = Timestamp(dt) + + msg = "Addition/subtraction of integers" + with pytest.raises(TypeError, match=msg): + # GH#22535 add/sub with integers is deprecated + ts + 1 + with pytest.raises(TypeError, match=msg): + ts - 1 + + # Timestamp + datetime not supported, though subtraction is supported + # and yields timedelta more tests in tseries/base/tests/test_base.py + assert type(ts - dt) == Timedelta + assert type(ts + td) == Timestamp + assert type(ts - td) == Timestamp + + # Timestamp +/- datetime64 not supported, so not tested (could possibly + # assert error raised?) + td64 = np.timedelta64(1, "D") + assert type(ts + td64) == Timestamp + assert type(ts - td64) == Timestamp + + @pytest.mark.parametrize( + "td", [Timedelta(hours=3), np.timedelta64(3, "h"), timedelta(hours=3)] + ) + def test_radd_tdscalar(self, td, fixed_now_ts): + # GH#24775 timedelta64+Timestamp should not raise + ts = fixed_now_ts + assert td + ts == ts + td + + @pytest.mark.parametrize( + "other,expected_difference", + [ + (np.timedelta64(-123, "ns"), -123), + (np.timedelta64(1234567898, "ns"), 1234567898), + (np.timedelta64(-123, "us"), -123000), + (np.timedelta64(-123, "ms"), -123000000), + ], + ) + def test_timestamp_add_timedelta64_unit(self, other, expected_difference): + now = datetime.now(timezone.utc) + ts = Timestamp(now).as_unit("ns") + result = ts + other + valdiff = result._value - ts._value + assert valdiff == expected_difference + + ts2 = Timestamp(now) + assert ts2 + other == result + + @pytest.mark.parametrize( + "ts", + [ + Timestamp("1776-07-04"), + Timestamp("1776-07-04", tz="UTC"), + ], + ) + @pytest.mark.parametrize( + "other", + [ + 1, + np.int64(1), + np.array([1, 2], dtype=np.int32), + np.array([3, 4], dtype=np.uint64), + ], + ) + def test_add_int_with_freq(self, ts, other): + msg = "Addition/subtraction of integers and integer-arrays" + with pytest.raises(TypeError, match=msg): + ts + other + with pytest.raises(TypeError, match=msg): + other + ts + + with pytest.raises(TypeError, match=msg): + ts - other + + msg = "unsupported operand type" + with pytest.raises(TypeError, match=msg): + other - ts + + @pytest.mark.parametrize("shape", [(6,), (2, 3)]) + def test_addsub_m8ndarray(self, shape): + # GH#33296 + ts = Timestamp("2020-04-04 15:45").as_unit("ns") + other = np.arange(6).astype("m8[h]").reshape(shape) + + result = ts + other + + ex_stamps = [ts + Timedelta(hours=n) for n in range(6)] + expected = np.array([x.asm8 for x in ex_stamps], dtype="M8[ns]").reshape(shape) + tm.assert_numpy_array_equal(result, expected) + + result = other + ts + tm.assert_numpy_array_equal(result, expected) + + result = ts - other + ex_stamps = [ts - Timedelta(hours=n) for n in range(6)] + expected = np.array([x.asm8 for x in ex_stamps], dtype="M8[ns]").reshape(shape) + tm.assert_numpy_array_equal(result, expected) + + msg = r"unsupported operand type\(s\) for -: 'numpy.ndarray' and 'Timestamp'" + with pytest.raises(TypeError, match=msg): + other - ts + + @pytest.mark.parametrize("shape", [(6,), (2, 3)]) + def test_addsub_m8ndarray_tzaware(self, shape): + # GH#33296 + ts = Timestamp("2020-04-04 15:45", tz="US/Pacific") + + other = np.arange(6).astype("m8[h]").reshape(shape) + + result = ts + other + + ex_stamps = [ts + Timedelta(hours=n) for n in range(6)] + expected = np.array(ex_stamps).reshape(shape) + tm.assert_numpy_array_equal(result, expected) + + result = other + ts + tm.assert_numpy_array_equal(result, expected) + + result = ts - other + ex_stamps = [ts - Timedelta(hours=n) for n in range(6)] + expected = np.array(ex_stamps).reshape(shape) + tm.assert_numpy_array_equal(result, expected) + + msg = r"unsupported operand type\(s\) for -: 'numpy.ndarray' and 'Timestamp'" + with pytest.raises(TypeError, match=msg): + other - ts + + def test_subtract_different_utc_objects(self, utc_fixture, utc_fixture2): + # GH 32619 + dt = datetime(2021, 1, 1) + ts1 = Timestamp(dt, tz=utc_fixture) + ts2 = Timestamp(dt, tz=utc_fixture2) + result = ts1 - ts2 + expected = Timedelta(0) + assert result == expected + + @pytest.mark.parametrize( + "tz", + [ + "pytz/US/Eastern", + gettz("US/Eastern"), + "US/Eastern", + "dateutil/US/Eastern", + ], + ) + def test_timestamp_add_timedelta_push_over_dst_boundary(self, tz): + # GH#1389 + if isinstance(tz, str) and tz.startswith("pytz/"): + pytz = pytest.importorskip("pytz") + tz = pytz.timezone(tz.removeprefix("pytz/")) + # 4 hours before DST transition + stamp = Timestamp("3/10/2012 22:00", tz=tz) + + result = stamp + timedelta(hours=6) + + # spring forward, + "7" hours + expected = Timestamp("3/11/2012 05:00", tz=tz) + + assert result == expected + + def test_timestamp_dst_transition(self): + # GH 60084 + dt_str = "2023-11-05 01:00-08:00" + tz_str = "America/Los_Angeles" + + ts1 = Timestamp(dt_str, tz=tz_str) + ts2 = ts1 + Timedelta(hours=0) + + assert ts1 == ts2 + assert hash(ts1) == hash(ts2) + + +class SubDatetime(datetime): + pass + + +@pytest.mark.parametrize( + "lh,rh", + [ + (SubDatetime(2000, 1, 1), Timedelta(hours=1)), + (Timedelta(hours=1), SubDatetime(2000, 1, 1)), + ], +) +def test_dt_subclass_add_timedelta(lh, rh): + # GH#25851 + # ensure that subclassed datetime works for + # Timedelta operations + result = lh + rh + expected = SubDatetime(2000, 1, 1, 1) + assert result == expected diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_comparisons.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_comparisons.py new file mode 100644 index 0000000000000000000000000000000000000000..b2590c43e1ece984506bf70b2e778f79b5e828de --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_comparisons.py @@ -0,0 +1,313 @@ +from datetime import ( + datetime, + timedelta, +) +import operator + +import numpy as np +import pytest + +from pandas import Timestamp +import pandas._testing as tm + + +class TestTimestampComparison: + def test_compare_non_nano_dt64(self): + # don't raise when converting dt64 to Timestamp in __richcmp__ + dt = np.datetime64("1066-10-14") + ts = Timestamp(dt) + + assert dt == ts + + def test_comparison_dt64_ndarray(self): + ts = Timestamp("2021-01-01") + ts2 = Timestamp("2019-04-05") + arr = np.array([[ts.asm8, ts2.asm8]], dtype="M8[ns]") + + result = ts == arr + expected = np.array([[True, False]], dtype=bool) + tm.assert_numpy_array_equal(result, expected) + + result = arr == ts + tm.assert_numpy_array_equal(result, expected) + + result = ts != arr + tm.assert_numpy_array_equal(result, ~expected) + + result = arr != ts + tm.assert_numpy_array_equal(result, ~expected) + + result = ts2 < arr + tm.assert_numpy_array_equal(result, expected) + + result = arr < ts2 + tm.assert_numpy_array_equal(result, np.array([[False, False]], dtype=bool)) + + result = ts2 <= arr + tm.assert_numpy_array_equal(result, np.array([[True, True]], dtype=bool)) + + result = arr <= ts2 + tm.assert_numpy_array_equal(result, ~expected) + + result = ts >= arr + tm.assert_numpy_array_equal(result, np.array([[True, True]], dtype=bool)) + + result = arr >= ts + tm.assert_numpy_array_equal(result, np.array([[True, False]], dtype=bool)) + + @pytest.mark.parametrize("reverse", [True, False]) + def test_comparison_dt64_ndarray_tzaware(self, reverse, comparison_op): + ts = Timestamp("2021-01-01 00:00:00.00000", tz="UTC") + arr = np.array([ts.asm8, ts.asm8], dtype="M8[ns]") + + left, right = ts, arr + if reverse: + left, right = arr, ts + + if comparison_op is operator.eq: + expected = np.array([False, False], dtype=bool) + result = comparison_op(left, right) + tm.assert_numpy_array_equal(result, expected) + elif comparison_op is operator.ne: + expected = np.array([True, True], dtype=bool) + result = comparison_op(left, right) + tm.assert_numpy_array_equal(result, expected) + else: + msg = "Cannot compare tz-naive and tz-aware timestamps" + with pytest.raises(TypeError, match=msg): + comparison_op(left, right) + + def test_comparison_object_array(self): + # GH#15183 + ts = Timestamp("2011-01-03 00:00:00-0500", tz="US/Eastern") + other = Timestamp("2011-01-01 00:00:00-0500", tz="US/Eastern") + naive = Timestamp("2011-01-01 00:00:00") + + arr = np.array([other, ts], dtype=object) + res = arr == ts + expected = np.array([False, True], dtype=bool) + assert (res == expected).all() + + # 2D case + arr = np.array([[other, ts], [ts, other]], dtype=object) + res = arr != ts + expected = np.array([[True, False], [False, True]], dtype=bool) + assert res.shape == expected.shape + assert (res == expected).all() + + # tzaware mismatch + arr = np.array([naive], dtype=object) + msg = "Cannot compare tz-naive and tz-aware timestamps" + with pytest.raises(TypeError, match=msg): + arr < ts + + def test_comparison(self): + # 5-18-2012 00:00:00.000 + stamp = 1337299200000000000 + + val = Timestamp(stamp) + + assert val == val + assert not val != val + assert not val < val + assert val <= val + assert not val > val + assert val >= val + + other = datetime(2012, 5, 18) + assert val == other + assert not val != other + assert not val < other + assert val <= other + assert not val > other + assert val >= other + + other = Timestamp(stamp + 100) + + assert val != other + assert val != other + assert val < other + assert val <= other + assert other > val + assert other >= val + + def test_compare_invalid(self): + # GH#8058 + val = Timestamp("20130101 12:01:02") + assert not val == "foo" + assert not val == 10.0 + assert not val == 1 + assert not val == [] + assert not val == {"foo": 1} + assert not val == np.float64(1) + assert not val == np.int64(1) + + assert val != "foo" + assert val != 10.0 + assert val != 1 + assert val != [] + assert val != {"foo": 1} + assert val != np.float64(1) + assert val != np.int64(1) + + @pytest.mark.parametrize("tz", [None, "US/Pacific"]) + def test_compare_date(self, tz): + # GH#36131 comparing Timestamp with date object is deprecated + ts = Timestamp("2021-01-01 00:00:00.00000", tz=tz) + dt = ts.to_pydatetime().date() + # in 2.0 we disallow comparing pydate objects with Timestamps, + # following the stdlib datetime behavior. + + msg = "Cannot compare Timestamp with datetime.date" + for left, right in [(ts, dt), (dt, ts)]: + assert not left == right + assert left != right + + with pytest.raises(TypeError, match=msg): + left < right + with pytest.raises(TypeError, match=msg): + left <= right + with pytest.raises(TypeError, match=msg): + left > right + with pytest.raises(TypeError, match=msg): + left >= right + + def test_cant_compare_tz_naive_w_aware(self, utc_fixture): + # see GH#1404 + a = Timestamp("3/12/2012") + b = Timestamp("3/12/2012", tz=utc_fixture) + + msg = "Cannot compare tz-naive and tz-aware timestamps" + assert not a == b + assert a != b + with pytest.raises(TypeError, match=msg): + a < b + with pytest.raises(TypeError, match=msg): + a <= b + with pytest.raises(TypeError, match=msg): + a > b + with pytest.raises(TypeError, match=msg): + a >= b + + assert not b == a + assert b != a + with pytest.raises(TypeError, match=msg): + b < a + with pytest.raises(TypeError, match=msg): + b <= a + with pytest.raises(TypeError, match=msg): + b > a + with pytest.raises(TypeError, match=msg): + b >= a + + assert not a == b.to_pydatetime() + assert not a.to_pydatetime() == b + + def test_timestamp_compare_scalars(self): + # case where ndim == 0 + lhs = np.datetime64(datetime(2013, 12, 6)) + rhs = Timestamp("now") + nat = Timestamp("nat") + + ops = {"gt": "lt", "lt": "gt", "ge": "le", "le": "ge", "eq": "eq", "ne": "ne"} + + for left, right in ops.items(): + left_f = getattr(operator, left) + right_f = getattr(operator, right) + expected = left_f(lhs, rhs) + + result = right_f(rhs, lhs) + assert result == expected + + expected = left_f(rhs, nat) + result = right_f(nat, rhs) + assert result == expected + + def test_timestamp_compare_with_early_datetime(self): + # e.g. datetime.min + stamp = Timestamp("2012-01-01") + + assert not stamp == datetime.min + assert not stamp == datetime(1600, 1, 1) + assert not stamp == datetime(2700, 1, 1) + assert stamp != datetime.min + assert stamp != datetime(1600, 1, 1) + assert stamp != datetime(2700, 1, 1) + assert stamp > datetime(1600, 1, 1) + assert stamp >= datetime(1600, 1, 1) + assert stamp < datetime(2700, 1, 1) + assert stamp <= datetime(2700, 1, 1) + + other = Timestamp.min.to_pydatetime(warn=False) + assert other - timedelta(microseconds=1) < Timestamp.min + + def test_timestamp_compare_oob_dt64(self): + us = np.timedelta64(1, "us") + other = np.datetime64(Timestamp.min).astype("M8[us]") + + # This may change if the implementation bound is dropped to match + # DatetimeArray/DatetimeIndex GH#24124 + assert Timestamp.min > other + # Note: numpy gets the reversed comparison wrong + + other = np.datetime64(Timestamp.max).astype("M8[us]") + assert Timestamp.max > other # not actually OOB + assert other < Timestamp.max + + assert Timestamp.max < other + us + # Note: numpy gets the reversed comparison wrong + + # GH-42794 + other = datetime(9999, 9, 9) + assert Timestamp.min < other + assert other > Timestamp.min + assert Timestamp.max < other + assert other > Timestamp.max + + other = datetime(1, 1, 1) + assert Timestamp.max > other + assert other < Timestamp.max + assert Timestamp.min > other + assert other < Timestamp.min + + def test_compare_zerodim_array(self, fixed_now_ts): + # GH#26916 + ts = fixed_now_ts + dt64 = np.datetime64("2016-01-01", "ns") + arr = np.array(dt64) + assert arr.ndim == 0 + + result = arr < ts + assert result is np.bool_(True) + result = arr > ts + assert result is np.bool_(False) + + +def test_rich_comparison_with_unsupported_type(): + # Comparisons with unsupported objects should return NotImplemented + # (it previously raised TypeError, see #24011) + + class Inf: + def __lt__(self, o): + return False + + def __le__(self, o): + return isinstance(o, Inf) + + def __gt__(self, o): + return not isinstance(o, Inf) + + def __ge__(self, o): + return True + + def __eq__(self, other) -> bool: + return isinstance(other, Inf) + + inf = Inf() + timestamp = Timestamp("2018-11-30") + + for left, right in [(inf, timestamp), (timestamp, inf)]: + assert left > right or left < right + assert left >= right or left <= right + assert not left == right + assert left != right diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_constructors.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_constructors.py new file mode 100644 index 0000000000000000000000000000000000000000..39898981512deec6c95ca3928542b2497c510ab2 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_constructors.py @@ -0,0 +1,1107 @@ +import calendar +from datetime import ( + date, + datetime, + timedelta, + timezone, +) +import zoneinfo + +import dateutil.tz +from dateutil.tz import ( + gettz, + tzoffset, + tzutc, +) +import numpy as np +import pytest + +from pandas._libs.tslibs.dtypes import NpyDatetimeUnit +from pandas.compat import PY314 +from pandas.errors import ( + OutOfBoundsDatetime, + Pandas4Warning, +) + +from pandas import ( + NA, + NaT, + Period, + Timedelta, + Timestamp, +) +import pandas._testing as tm + + +class TestTimestampConstructorUnitKeyword: + @pytest.mark.parametrize("typ", [int, float]) + def test_constructor_int_float_with_YM_unit(self, typ): + # GH#47266 avoid the conversions in cast_from_unit + val = typ(150) + + ts = Timestamp(val, unit="Y") + expected = Timestamp("2120-01-01") + assert ts == expected + + ts = Timestamp(val, unit="M") + expected = Timestamp("1982-07-01") + assert ts == expected + + @pytest.mark.parametrize("typ", [int, float]) + def test_construct_from_int_float_with_unit_out_of_bound_raises(self, typ): + # GH#50870 make sure we get an OutOfBoundsDatetime instead of OverflowError + val = typ(150000000000000) + + msg = f"cannot convert input {int(val)} with the unit 'D'" + with pytest.raises(OutOfBoundsDatetime, match=msg): + Timestamp(val, unit="D") + + def test_constructor_float_not_round_with_YM_unit_raises(self): + # GH#47267 avoid the conversions in cast_from-unit + + msg = "Conversion of non-round float with unit=[MY] is ambiguous" + with pytest.raises(ValueError, match=msg): + Timestamp(150.5, unit="Y") + + with pytest.raises(ValueError, match=msg): + Timestamp(150.5, unit="M") + + @pytest.mark.parametrize( + "value, check_kwargs", + [ + [946688461000000000, {}], + [946688461000000000 / 1000, {"unit": "us"}], + [946688461000000000 / 1_000_000, {"unit": "ms"}], + [946688461000000000 / 1_000_000_000, {"unit": "s"}], + [10957, {"unit": "D", "h": 0}], + [ + (946688461000000000 + 500000) / 1000000000, + {"unit": "s", "us": 499, "ns": 964}, + ], + [ + (946688461000000000 + 500000000) / 1000000000, + {"unit": "s", "us": 500000}, + ], + [(946688461000000000 + 500000) / 1000000, {"unit": "ms", "us": 500}], + [(946688461000000000 + 500000) / 1000, {"unit": "us", "us": 500}], + [(946688461000000000 + 500000000) / 1000000, {"unit": "ms", "us": 500000}], + [946688461000000000 / 1000.0 + 5, {"unit": "us", "us": 5}], + [946688461000000000 / 1000.0 + 5000, {"unit": "us", "us": 5000}], + [946688461000000000 / 1000000.0 + 0.5, {"unit": "ms", "us": 500}], + [946688461000000000 / 1000000.0 + 0.005, {"unit": "ms", "us": 5, "ns": 5}], + [946688461000000000 / 1000000000.0 + 0.5, {"unit": "s", "us": 500000}], + [10957 + 0.5, {"unit": "D", "h": 12}], + ], + ) + def test_construct_with_unit(self, value, check_kwargs): + def check(value, unit=None, h=1, s=1, us=0, ns=0): + stamp = Timestamp(value, unit=unit) + assert stamp.year == 2000 + assert stamp.month == 1 + assert stamp.day == 1 + assert stamp.hour == h + if unit != "D": + assert stamp.minute == 1 + assert stamp.second == s + assert stamp.microsecond == us + else: + assert stamp.minute == 0 + assert stamp.second == 0 + assert stamp.microsecond == 0 + assert stamp.nanosecond == ns + + check(value, **check_kwargs) + + +class TestTimestampConstructorFoldKeyword: + def test_timestamp_constructor_invalid_fold_raise(self): + # Test for GH#25057 + # Valid fold values are only [None, 0, 1] + msg = "Valid values for the fold argument are None, 0, or 1." + with pytest.raises(ValueError, match=msg): + Timestamp(123, fold=2) + + def test_timestamp_constructor_pytz_fold_raise(self): + # Test for GH#25057 + # pytz doesn't support fold. Check that we raise + # if fold is passed with pytz + pytz = pytest.importorskip("pytz") + msg = "pytz timezones do not support fold. Please use dateutil timezones." + tz = pytz.timezone("Europe/London") + with pytest.raises(ValueError, match=msg): + Timestamp(datetime(2019, 10, 27, 0, 30, 0, 0), tz=tz, fold=0) + + @pytest.mark.parametrize("fold", [0, 1]) + @pytest.mark.parametrize( + "ts_input", + [ + 1572136200000000000, + 1572136200000000000.0, + np.datetime64(1572136200000000000, "ns"), + "2019-10-27 01:30:00+01:00", + datetime(2019, 10, 27, 0, 30, 0, 0, tzinfo=timezone.utc), + ], + ) + def test_timestamp_constructor_fold_conflict(self, ts_input, fold): + # Test for GH#25057 + # Check that we raise on fold conflict + msg = ( + "Cannot pass fold with possibly unambiguous input: int, float, " + "numpy.datetime64, str, or timezone-aware datetime-like. " + "Pass naive datetime-like or build Timestamp from components." + ) + with pytest.raises(ValueError, match=msg): + Timestamp(ts_input=ts_input, fold=fold) + + @pytest.mark.parametrize("tz", ["dateutil/Europe/London", None]) + @pytest.mark.parametrize("fold", [0, 1]) + def test_timestamp_constructor_retain_fold(self, tz, fold): + # Test for GH#25057 + # Check that we retain fold + ts = Timestamp(year=2019, month=10, day=27, hour=1, minute=30, tz=tz, fold=fold) + result = ts.fold + expected = fold + assert result == expected + + @pytest.mark.parametrize( + "tz", + [ + "dateutil/Europe/London", + zoneinfo.ZoneInfo("Europe/London"), + ], + ) + @pytest.mark.parametrize( + "ts_input,fold_out", + [ + (1572136200000000000, 0), + (1572139800000000000, 1), + ("2019-10-27 01:30:00+01:00", 0), + ("2019-10-27 01:30:00+00:00", 1), + (datetime(2019, 10, 27, 1, 30, 0, 0, fold=0), 0), + (datetime(2019, 10, 27, 1, 30, 0, 0, fold=1), 1), + ], + ) + def test_timestamp_constructor_infer_fold_from_value(self, tz, ts_input, fold_out): + # Test for GH#25057 + # Check that we infer fold correctly based on timestamps since utc + # or strings + ts = Timestamp(ts_input, tz=tz) + result = ts.fold + expected = fold_out + assert result == expected + + @pytest.mark.parametrize("tz", ["dateutil/Europe/London"]) + @pytest.mark.parametrize( + "fold,value_out", + [ + (0, 1572136200000000), + (1, 1572139800000000), + ], + ) + def test_timestamp_constructor_adjust_value_for_fold(self, tz, fold, value_out): + # Test for GH#25057 + # Check that we adjust value for fold correctly + # based on timestamps since utc + ts_input = datetime(2019, 10, 27, 1, 30) + ts = Timestamp(ts_input, tz=tz, fold=fold) + result = ts._value + expected = value_out + assert result == expected + + +class TestTimestampConstructorPositionalAndKeywordSupport: + def test_constructor_positional(self): + # see GH#10758 + msg = "'NoneType' object cannot be interpreted as an integer" + with pytest.raises(TypeError, match=msg): + Timestamp(2000, 1) + + msg = "month must be in 1..12" + with pytest.raises(ValueError, match=msg): + Timestamp(2000, 0, 1) + with pytest.raises(ValueError, match=msg): + Timestamp(2000, 13, 1) + + if PY314: + msg = "must be in range 1..31 for month 1 in year 2000" + else: + msg = "day is out of range for month" + with pytest.raises(ValueError, match=msg): + Timestamp(2000, 1, 0) + with pytest.raises(ValueError, match=msg): + Timestamp(2000, 1, 32) + + # see gh-11630 + assert repr(Timestamp(2015, 11, 12)) == repr(Timestamp("20151112")) + assert repr(Timestamp(2015, 11, 12, 1, 2, 3, 999999)) == repr( + Timestamp("2015-11-12 01:02:03.999999") + ) + + def test_constructor_keyword(self): + # GH#10758 + msg = "function missing required argument 'day'|Required argument 'day'" + with pytest.raises(TypeError, match=msg): + Timestamp(year=2000, month=1) + + msg = "month must be in 1..12" + with pytest.raises(ValueError, match=msg): + Timestamp(year=2000, month=0, day=1) + with pytest.raises(ValueError, match=msg): + Timestamp(year=2000, month=13, day=1) + + if PY314: + msg = "must be in range 1..31 for month 1 in year 2000" + else: + msg = "day is out of range for month" + with pytest.raises(ValueError, match=msg): + Timestamp(year=2000, month=1, day=0) + with pytest.raises(ValueError, match=msg): + Timestamp(year=2000, month=1, day=32) + + assert repr(Timestamp(year=2015, month=11, day=12)) == repr( + Timestamp("20151112") + ) + + assert repr( + Timestamp( + year=2015, + month=11, + day=12, + hour=1, + minute=2, + second=3, + microsecond=999999, + ) + ) == repr(Timestamp("2015-11-12 01:02:03.999999")) + + @pytest.mark.parametrize( + "arg", + [ + "year", + "month", + "day", + "hour", + "minute", + "second", + "microsecond", + "nanosecond", + ], + ) + def test_invalid_date_kwarg_with_string_input(self, arg): + kwarg = {arg: 1} + msg = "Cannot pass a date attribute keyword argument" + with pytest.raises(ValueError, match=msg): + Timestamp("2010-10-10 12:59:59.999999999", **kwarg) + + @pytest.mark.parametrize("kwargs", [{}, {"year": 2020}, {"year": 2020, "month": 1}]) + def test_constructor_missing_keyword(self, kwargs): + # GH#31200 + + # The exact error message of datetime() depends on its version + msg1 = r"function missing required argument '(year|month|day)' \(pos [123]\)" + msg2 = r"Required argument '(year|month|day)' \(pos [123]\) not found" + msg = "|".join([msg1, msg2]) + + with pytest.raises(TypeError, match=msg): + Timestamp(**kwargs) + + def test_constructor_positional_with_tzinfo(self): + # GH#31929 + ts = Timestamp(2020, 12, 31, tzinfo=timezone.utc) + expected = Timestamp("2020-12-31", tzinfo=timezone.utc) + assert ts == expected + + @pytest.mark.parametrize("kwd", ["nanosecond", "microsecond", "second", "minute"]) + def test_constructor_positional_keyword_mixed_with_tzinfo(self, kwd, request): + # TODO: if we passed microsecond with a keyword we would mess up + # xref GH#45307 + if kwd != "nanosecond": + # nanosecond is keyword-only as of 2.0, others are not + mark = pytest.mark.xfail(reason="GH#45307") + request.applymarker(mark) + + kwargs = {kwd: 4} + ts = Timestamp(2020, 12, 31, tzinfo=timezone.utc, **kwargs) + + td_kwargs = {kwd + "s": 4} + td = Timedelta(**td_kwargs) + expected = Timestamp("2020-12-31", tz=timezone.utc) + td + assert ts == expected + + +class TestTimestampClassMethodConstructors: + # Timestamp constructors other than __new__ + + def test_utcnow_deprecated(self): + # GH#56680 + msg = "Timestamp.utcnow is deprecated" + with tm.assert_produces_warning(Pandas4Warning, match=msg): + Timestamp.utcnow() + + def test_utcfromtimestamp_deprecated(self): + # GH#56680 + msg = "Timestamp.utcfromtimestamp is deprecated" + with tm.assert_produces_warning(Pandas4Warning, match=msg): + Timestamp.utcfromtimestamp(43) + + def test_constructor_strptime(self): + # GH#25016 + # Test support for Timestamp.strptime + fmt = "%Y%m%d-%H%M%S-%f%z" + ts = "20190129-235348-000001+0000" + msg = r"Timestamp.strptime\(\) is not implemented" + with pytest.raises(NotImplementedError, match=msg): + Timestamp.strptime(ts, fmt) + + def test_constructor_fromisocalendar(self): + # GH#30395 + expected_timestamp = Timestamp("2000-01-03 00:00:00") + expected_stdlib = datetime.fromisocalendar(2000, 1, 1) + result = Timestamp.fromisocalendar(2000, 1, 1) + assert result == expected_timestamp + assert result == expected_stdlib + assert isinstance(result, Timestamp) + + def test_constructor_fromordinal(self): + base = datetime(2000, 1, 1) + + ts = Timestamp.fromordinal(base.toordinal()) + assert base == ts + assert base.toordinal() == ts.toordinal() + + ts = Timestamp.fromordinal(base.toordinal(), tz="US/Eastern") + assert Timestamp("2000-01-01", tz="US/Eastern") == ts + assert base.toordinal() == ts.toordinal() + + # GH#3042 + dt = datetime(2011, 4, 16, 0, 0) + ts = Timestamp.fromordinal(dt.toordinal()) + assert ts.to_pydatetime() == dt + + # with a tzinfo + stamp = Timestamp("2011-4-16", tz="US/Eastern") + dt_tz = stamp.to_pydatetime() + ts = Timestamp.fromordinal(dt_tz.toordinal(), tz="US/Eastern") + assert ts.to_pydatetime() == dt_tz + + def test_now(self): + # GH#9000 + ts_from_string = Timestamp("now") + ts_from_method = Timestamp.now() + ts_datetime = datetime.now() + + ts_from_string_tz = Timestamp("now", tz="US/Eastern") + ts_from_method_tz = Timestamp.now(tz="US/Eastern") + + # Check that the delta between the times is less than 1s (arbitrarily + # small) + delta = Timedelta(seconds=1) + assert abs(ts_from_method - ts_from_string) < delta + assert abs(ts_datetime - ts_from_method) < delta + assert abs(ts_from_method_tz - ts_from_string_tz) < delta + assert ( + abs( + ts_from_string_tz.tz_localize(None) + - ts_from_method_tz.tz_localize(None) + ) + < delta + ) + + def test_today(self): + ts_from_string = Timestamp("today") + ts_from_method = Timestamp.today() + ts_datetime = datetime.today() + + ts_from_string_tz = Timestamp("today", tz="US/Eastern") + ts_from_method_tz = Timestamp.today(tz="US/Eastern") + + # Check that the delta between the times is less than 1s (arbitrarily + # small) + delta = Timedelta(seconds=1) + assert abs(ts_from_method - ts_from_string) < delta + assert abs(ts_datetime - ts_from_method) < delta + assert abs(ts_from_method_tz - ts_from_string_tz) < delta + assert ( + abs( + ts_from_string_tz.tz_localize(None) + - ts_from_method_tz.tz_localize(None) + ) + < delta + ) + + +class TestTimestampResolutionInference: + def test_construct_from_time_unit(self): + # GH#54097 only passing a time component, no date + ts = Timestamp("01:01:01.111") + assert ts.unit == "us" + + def test_constructor_str_infer_reso(self): + # non-iso8601 path + + # _parse_delimited_date path + ts = Timestamp("01/30/2023") + assert ts.unit == "us" + + # _parse_dateabbr_string path + ts = Timestamp("2015Q1") + assert ts.unit == "us" + + # dateutil_parse path + ts = Timestamp("2016-01-01 1:30:01 PM") + assert ts.unit == "us" + + ts = Timestamp("2016 June 3 15:25:01.345") + assert ts.unit == "us" + + ts = Timestamp("300-01-01") + assert ts.unit == "us" + + ts = Timestamp("300 June 1:30:01.300") + assert ts.unit == "us" + + # dateutil path -> don't drop trailing zeros + ts = Timestamp("01-01-2013T00:00:00.000000000+0000") + assert ts.unit == "ns" + + ts = Timestamp("2016/01/02 03:04:05.001000 UTC") + assert ts.unit == "us" + + # higher-than-nanosecond -> we drop the trailing bits + ts = Timestamp("01-01-2013T00:00:00.000000002100+0000") + assert ts == Timestamp("01-01-2013T00:00:00.000000002+0000") + assert ts.unit == "ns" + + # GH#56208 minute reso through the ISO8601 path with tz offset + ts = Timestamp("2020-01-01 00:00+00:00") + assert ts.unit == "us" + + ts = Timestamp("2020-01-01 00+00:00") + assert ts.unit == "us" + + @pytest.mark.parametrize("method", ["now", "today"]) + def test_now_today_unit(self, method): + # GH#55879 + ts_from_method = getattr(Timestamp, method)() + ts_from_string = Timestamp(method) + assert ts_from_method.unit == ts_from_string.unit == "us" + + +class TestTimestampConstructors: + def test_disallow_dt64_with_weird_unit(self): + # GH#25611 + dt64 = np.datetime64(1, "500m") + msg = "np.datetime64 objects with units containing a multiplier" + with pytest.raises(ValueError, match=msg): + Timestamp(dt64) + + def test_weekday_but_no_day_raises(self): + # GH#52659 + msg = "Parsing datetimes with weekday but no day information is not supported" + with pytest.raises(ValueError, match=msg): + Timestamp("2023 Sept Thu") + + def test_construct_from_string_invalid_raises(self): + # dateutil (weirdly) parses "200622-12-31" as + # datetime(2022, 6, 20, 12, 0, tzinfo=tzoffset(None, -111600) + # which besides being mis-parsed, is a tzoffset that will cause + # str(ts) to raise ValueError. Ensure we raise in the constructor + # instead. + # see test_to_datetime_malformed_raise for analogous to_datetime test + with pytest.raises(ValueError, match="gives an invalid tzoffset"): + Timestamp("200622-12-31") + + def test_constructor_from_iso8601_str_with_offset_reso(self): + # GH#49737 + ts = Timestamp("2016-01-01 04:05:06-01:00") + assert ts.unit == "us" + + ts = Timestamp("2016-01-01 04:05:06.000-01:00") + assert ts.unit == "us" + + ts = Timestamp("2016-01-01 04:05:06.000000-01:00") + assert ts.unit == "us" + + ts = Timestamp("2016-01-01 04:05:06.000000001-01:00") + assert ts.unit == "ns" + + def test_constructor_from_date_second_reso(self): + # GH#49034 constructing from a pydate object gets lowest supported + # reso, i.e. seconds + obj = date(2012, 9, 1) + ts = Timestamp(obj) + assert ts.unit == "s" + + def test_constructor_datetime64_with_tz(self): + # GH#42288, GH#24559 + dt = np.datetime64("1970-01-01 05:00:00") + tzstr = "UTC+05:00" + + # pre-2.0 this interpreted dt as a UTC time. in 2.0 this is treated + # as a wall-time, consistent with DatetimeIndex behavior + ts = Timestamp(dt, tz=tzstr) + + alt = Timestamp(dt).tz_localize(tzstr) + assert ts == alt + assert ts.hour == 5 + + def test_constructor(self): + base_str = "2014-07-01 09:00" + base_dt = datetime(2014, 7, 1, 9) + base_expected = 1_404_205_200_000_000_000 + + # confirm base representation is correct + assert calendar.timegm(base_dt.timetuple()) * 1_000_000_000 == base_expected + + tests = [ + (base_str, base_dt, base_expected), + ( + "2014-07-01 10:00", + datetime(2014, 7, 1, 10), + base_expected + 3600 * 1_000_000_000, + ), + ( + "2014-07-01 09:00:00.000008000", + datetime(2014, 7, 1, 9, 0, 0, 8), + base_expected + 8000, + ), + ( + "2014-07-01 09:00:00.000000005", + Timestamp("2014-07-01 09:00:00.000000005"), + base_expected + 5, + ), + ] + + timezones = [ + (None, 0), + ("UTC", 0), + (timezone.utc, 0), + ("Asia/Tokyo", 9), + ("US/Eastern", -4), + ("dateutil/US/Pacific", -7), + (timezone(timedelta(hours=-3)), -3), + (dateutil.tz.tzoffset(None, 18000), 5), + ] + + for date_str, date_obj, expected in tests: + for result in [Timestamp(date_str), Timestamp(date_obj)]: + result = result.as_unit("ns") # test originally written before non-nano + # only with timestring + assert result.as_unit("ns")._value == expected + + # re-creation shouldn't affect to internal value + result = Timestamp(result) + assert result.as_unit("ns")._value == expected + + # with timezone + for tz, offset in timezones: + for result in [Timestamp(date_str, tz=tz), Timestamp(date_obj, tz=tz)]: + result = result.as_unit( + "ns" + ) # test originally written before non-nano + expected_tz = expected - offset * 3600 * 1_000_000_000 + assert result.as_unit("ns")._value == expected_tz + + # should preserve tz + result = Timestamp(result) + assert result.as_unit("ns")._value == expected_tz + + # should convert to UTC + if tz is not None: + result = Timestamp(result).tz_convert("UTC") + else: + result = Timestamp(result, tz="UTC") + expected_utc = expected - offset * 3600 * 1_000_000_000 + assert result.as_unit("ns")._value == expected_utc + + def test_constructor_with_stringoffset(self): + # GH 7833 + base_str = "2014-07-01 11:00:00+02:00" + base_dt = datetime(2014, 7, 1, 9) + base_expected = 1_404_205_200_000_000_000 + + # confirm base representation is correct + assert calendar.timegm(base_dt.timetuple()) * 1_000_000_000 == base_expected + + tests = [ + (base_str, base_expected), + ("2014-07-01 12:00:00+02:00", base_expected + 3600 * 1_000_000_000), + ("2014-07-01 11:00:00.000008000+02:00", base_expected + 8000), + ("2014-07-01 11:00:00.000000005+02:00", base_expected + 5), + ] + + timezones = [ + ("UTC", 0), + (timezone.utc, 0), + ("Asia/Tokyo", 9), + ("US/Eastern", -4), + ("dateutil/US/Pacific", -7), + (timezone(timedelta(hours=-3)), -3), + (dateutil.tz.tzoffset(None, 18000), 5), + ] + + for date_str, expected in tests: + for result in [Timestamp(date_str)]: + # only with timestring + assert result.as_unit("ns")._value == expected + + # re-creation shouldn't affect to internal value + result = Timestamp(result) + assert result.as_unit("ns")._value == expected + + # with timezone + for tz, offset in timezones: + result = Timestamp(date_str, tz=tz) + expected_tz = expected + assert result.as_unit("ns")._value == expected_tz + + # should preserve tz + result = Timestamp(result) + assert result.as_unit("ns")._value == expected_tz + + # should convert to UTC + result = Timestamp(result).tz_convert("UTC") + expected_utc = expected + assert result.as_unit("ns")._value == expected_utc + + # This should be 2013-11-01 05:00 in UTC + # converted to Chicago tz + result = Timestamp("2013-11-01 00:00:00-0500", tz="America/Chicago") + assert result._value == Timestamp("2013-11-01 05:00")._value + expected = "Timestamp('2013-11-01 00:00:00-0500', tz='America/Chicago')" + assert repr(result) == expected + assert result == eval(repr(result)) + + # This should be 2013-11-01 05:00 in UTC + # converted to Tokyo tz (+09:00) + result = Timestamp("2013-11-01 00:00:00-0500", tz="Asia/Tokyo") + assert result._value == Timestamp("2013-11-01 05:00")._value + expected = "Timestamp('2013-11-01 14:00:00+0900', tz='Asia/Tokyo')" + assert repr(result) == expected + assert result == eval(repr(result)) + + # GH11708 + # This should be 2015-11-18 10:00 in UTC + # converted to Asia/Katmandu + result = Timestamp("2015-11-18 15:45:00+05:45", tz="Asia/Katmandu") + assert result._value == Timestamp("2015-11-18 10:00")._value + expected = "Timestamp('2015-11-18 15:45:00+0545', tz='Asia/Katmandu')" + assert repr(result) == expected + assert result == eval(repr(result)) + + # This should be 2015-11-18 10:00 in UTC + # converted to Asia/Kolkata + result = Timestamp("2015-11-18 15:30:00+05:30", tz="Asia/Kolkata") + assert result._value == Timestamp("2015-11-18 10:00")._value + expected = "Timestamp('2015-11-18 15:30:00+0530', tz='Asia/Kolkata')" + assert repr(result) == expected + assert result == eval(repr(result)) + + def test_constructor_invalid(self): + msg = "Cannot convert input" + with pytest.raises(TypeError, match=msg): + Timestamp(slice(2)) + msg = "Cannot convert Period" + with pytest.raises(ValueError, match=msg): + Timestamp(Period("1000-01-01")) + + def test_constructor_invalid_tz(self): + # GH#17690 + msg = ( + "Argument 'tzinfo' has incorrect type " + r"\(expected datetime.tzinfo, got str\)" + ) + with pytest.raises(TypeError, match=msg): + Timestamp("2017-10-22", tzinfo="US/Eastern") + + msg = "at most one of" + with pytest.raises(ValueError, match=msg): + Timestamp("2017-10-22", tzinfo=timezone.utc, tz="UTC") + + msg = "Cannot pass a date attribute keyword argument when passing a date string" + with pytest.raises(ValueError, match=msg): + # GH#5168 + # case where user tries to pass tz as an arg, not kwarg, gets + # interpreted as `year` + Timestamp("2012-01-01", "US/Pacific") + + def test_constructor_tz_or_tzinfo(self): + # GH#17943, GH#17690, GH#5168 + stamps = [ + Timestamp(year=2017, month=10, day=22, tz="UTC"), + Timestamp(year=2017, month=10, day=22, tzinfo=timezone.utc), + Timestamp(year=2017, month=10, day=22, tz=timezone.utc), + Timestamp(datetime(2017, 10, 22), tzinfo=timezone.utc), + Timestamp(datetime(2017, 10, 22), tz="UTC"), + Timestamp(datetime(2017, 10, 22), tz=timezone.utc), + ] + assert all(ts == stamps[0] for ts in stamps) + + @pytest.mark.parametrize( + "result", + [ + Timestamp(datetime(2000, 1, 2, 3, 4, 5, 6), nanosecond=1), + Timestamp( + year=2000, + month=1, + day=2, + hour=3, + minute=4, + second=5, + microsecond=6, + nanosecond=1, + ), + Timestamp( + year=2000, + month=1, + day=2, + hour=3, + minute=4, + second=5, + microsecond=6, + nanosecond=1, + tz="UTC", + ), + Timestamp(2000, 1, 2, 3, 4, 5, 6, None, nanosecond=1), + Timestamp(2000, 1, 2, 3, 4, 5, 6, tz=timezone.utc, nanosecond=1), + ], + ) + def test_constructor_nanosecond(self, result): + # GH 18898 + # As of 2.0 (GH 49416), nanosecond should not be accepted positionally + expected = Timestamp(datetime(2000, 1, 2, 3, 4, 5, 6), tz=result.tz) + expected = expected + Timedelta(nanoseconds=1) + assert result == expected + + @pytest.mark.parametrize("z", ["Z0", "Z00"]) + def test_constructor_invalid_Z0_isostring(self, z): + # GH 8910 + msg = f"Unknown datetime string format, unable to parse: 2014-11-02 01:00{z}" + with pytest.raises(ValueError, match=msg): + Timestamp(f"2014-11-02 01:00{z}") + + def test_out_of_bounds_integer_value(self): + # GH#26651 check that we raise OutOfBoundsDatetime, not OverflowError + msg = str(Timestamp.max._value * 2) + with pytest.raises(OutOfBoundsDatetime, match=msg): + Timestamp(Timestamp.max._value * 2) + msg = str(Timestamp.min._value * 2) + with pytest.raises(OutOfBoundsDatetime, match=msg): + Timestamp(Timestamp.min._value * 2) + + def test_out_of_bounds_value(self): + one_us = np.timedelta64(1, "ns").astype("timedelta64[us]") + + # By definition we can't go out of bounds in [ns], so we + # convert the datetime64s to [us] so we can go out of bounds + min_ts_us = np.datetime64(Timestamp.min).astype("M8[us]") + one_us + max_ts_us = np.datetime64(Timestamp.max).astype("M8[us]") + + # No error for the min/max datetimes + Timestamp(min_ts_us) + Timestamp(max_ts_us) + + # We used to raise on these before supporting non-nano + us_val = NpyDatetimeUnit.NPY_FR_us.value + assert Timestamp(min_ts_us - one_us)._creso == us_val + assert Timestamp(max_ts_us + one_us)._creso == us_val + + # https://github.com/numpy/numpy/issues/22346 for why + # we can't use the same construction as above with minute resolution + + # too_low, too_high are the _just_ outside the range of M8[s] + too_low = np.datetime64("-292277022657-01-27T08:29", "m") + too_high = np.datetime64("292277026596-12-04T15:31", "m") + + msg = "Out of bounds" + # One us less than the minimum is an error + with pytest.raises(ValueError, match=msg): + Timestamp(too_low) + + # One us more than the maximum is an error + with pytest.raises(ValueError, match=msg): + Timestamp(too_high) + + def test_out_of_bounds_string(self): + msg = "Cannot cast .* to unit='ns' without overflow" + with pytest.raises(ValueError, match=msg): + Timestamp("1676-01-01").as_unit("ns") + with pytest.raises(ValueError, match=msg): + Timestamp("2263-01-01").as_unit("ns") + + ts = Timestamp("2263-01-01") + assert ts.unit == "us" + + ts = Timestamp("1676-01-01") + assert ts.unit == "us" + + def test_barely_out_of_bounds(self): + # GH#19529 + # GH#19382 close enough to bounds that dropping nanos would result + # in an in-bounds datetime + msg = "Out of bounds nanosecond timestamp: 2262-04-11 23:47:16" + with pytest.raises(OutOfBoundsDatetime, match=msg): + Timestamp("2262-04-11 23:47:16.854775808") + + def test_bounds_with_different_units(self): + out_of_bounds_dates = ("1677-09-21", "2262-04-12") + + time_units = ("D", "h", "m", "s", "ms", "us") + + for date_string in out_of_bounds_dates: + for unit in time_units: + dt64 = np.datetime64(date_string, unit) + ts = Timestamp(dt64) + if unit in ["s", "ms", "us"]: + # We can preserve the input unit + assert ts._value == dt64.view("i8") + else: + # we chose the closest unit that we _do_ support + assert ts._creso == NpyDatetimeUnit.NPY_FR_s.value + + # With more extreme cases, we can't even fit inside second resolution + info = np.iinfo(np.int64) + msg = "Out of bounds second timestamp:" + for value in [info.min + 1, info.max]: + for unit in ["D", "h", "m"]: + dt64 = np.datetime64(value, unit) + with pytest.raises(OutOfBoundsDatetime, match=msg): + Timestamp(dt64) + + in_bounds_dates = ("1677-09-23", "2262-04-11") + + for date_string in in_bounds_dates: + for unit in time_units: + dt64 = np.datetime64(date_string, unit) + Timestamp(dt64) + + @pytest.mark.parametrize("arg", ["001-01-01", "0001-01-01"]) + def test_out_of_bounds_string_consistency(self, arg): + # GH 15829 + msg = "Cannot cast 0001-01-01 00:00:00 to unit='ns' without overflow" + with pytest.raises(OutOfBoundsDatetime, match=msg): + Timestamp(arg).as_unit("ns") + + ts = Timestamp(arg) + assert ts.unit == "us" + assert ts.year == ts.month == ts.day == 1 + + def test_min_valid(self): + # Ensure that Timestamp.min is a valid Timestamp + Timestamp(Timestamp.min) + + def test_max_valid(self): + # Ensure that Timestamp.max is a valid Timestamp + Timestamp(Timestamp.max) + + @pytest.mark.parametrize("offset", ["+0300", "+0200"]) + def test_construct_timestamp_near_dst(self, offset): + # GH 20854 + expected = Timestamp(f"2016-10-30 03:00:00{offset}", tz="Europe/Helsinki") + result = Timestamp(expected).tz_convert("Europe/Helsinki") + assert result == expected + + @pytest.mark.parametrize( + "arg", ["2013/01/01 00:00:00+09:00", "2013-01-01 00:00:00+09:00"] + ) + def test_construct_with_different_string_format(self, arg): + # GH 12064 + result = Timestamp(arg) + expected = Timestamp(datetime(2013, 1, 1), tz=timezone(timedelta(hours=9))) + assert result == expected + + @pytest.mark.parametrize("box", [datetime, Timestamp]) + def test_raise_tz_and_tzinfo_in_datetime_input(self, box): + # GH 23579 + kwargs = {"year": 2018, "month": 1, "day": 1, "tzinfo": timezone.utc} + msg = "Cannot pass a datetime or Timestamp" + with pytest.raises(ValueError, match=msg): + Timestamp(box(**kwargs), tz="US/Pacific") + msg = "Cannot pass a datetime or Timestamp" + with pytest.raises(ValueError, match=msg): + Timestamp(box(**kwargs), tzinfo=zoneinfo.ZoneInfo("US/Pacific")) + + def test_dont_convert_dateutil_utc_to_default_utc(self): + result = Timestamp(datetime(2018, 1, 1), tz=tzutc()) + expected = Timestamp(datetime(2018, 1, 1)).tz_localize(tzutc()) + assert result == expected + + def test_constructor_subclassed_datetime(self): + # GH 25851 + # ensure that subclassed datetime works for + # Timestamp creation + class SubDatetime(datetime): + pass + + data = SubDatetime(2000, 1, 1) + result = Timestamp(data) + expected = Timestamp(2000, 1, 1) + assert result == expected + + def test_timestamp_constructor_tz_utc(self): + utc_stamp = Timestamp("3/11/2012 05:00", tz="utc") + assert utc_stamp.tzinfo is timezone.utc + assert utc_stamp.hour == 5 + + utc_stamp = Timestamp("3/11/2012 05:00").tz_localize("utc") + assert utc_stamp.hour == 5 + + def test_timestamp_to_datetime_tzoffset(self): + tzinfo = tzoffset(None, 7200) + expected = Timestamp("3/11/2012 04:00", tz=tzinfo) + result = Timestamp(expected.to_pydatetime()) + assert expected == result + + def test_timestamp_constructor_near_dst_boundary(self): + # GH#11481 & GH#15777 + # Naive string timestamps were being localized incorrectly + # with tz_convert_from_utc_single instead of tz_localize_to_utc + + for tz in ["Europe/Brussels", "Europe/Prague"]: + result = Timestamp("2015-10-25 01:00", tz=tz) + expected = Timestamp("2015-10-25 01:00").tz_localize(tz) + assert result == expected + + msg = "Cannot infer dst time from 2015-10-25 02:00:00" + with pytest.raises(ValueError, match=msg): + Timestamp("2015-10-25 02:00", tz=tz) + + result = Timestamp("2017-03-26 01:00", tz="Europe/Paris") + expected = Timestamp("2017-03-26 01:00").tz_localize("Europe/Paris") + assert result == expected + + msg = "2017-03-26 02:00" + with pytest.raises(ValueError, match=msg): + Timestamp("2017-03-26 02:00", tz="Europe/Paris") + + # GH#11708 + naive = Timestamp("2015-11-18 10:00:00") + result = naive.tz_localize("UTC").tz_convert("Asia/Kolkata") + expected = Timestamp("2015-11-18 15:30:00+0530", tz="Asia/Kolkata") + assert result == expected + + # GH#15823 + result = Timestamp("2017-03-26 00:00", tz="Europe/Paris") + expected = Timestamp("2017-03-26 00:00:00+0100", tz="Europe/Paris") + assert result == expected + + result = Timestamp("2017-03-26 01:00", tz="Europe/Paris") + expected = Timestamp("2017-03-26 01:00:00+0100", tz="Europe/Paris") + assert result == expected + + msg = "2017-03-26 02:00" + with pytest.raises(ValueError, match=msg): + Timestamp("2017-03-26 02:00", tz="Europe/Paris") + + result = Timestamp("2017-03-26 02:00:00+0100", tz="Europe/Paris") + naive = Timestamp(result.as_unit("ns")._value) + expected = naive.tz_localize("UTC").tz_convert("Europe/Paris") + assert result == expected + + result = Timestamp("2017-03-26 03:00", tz="Europe/Paris") + expected = Timestamp("2017-03-26 03:00:00+0200", tz="Europe/Paris") + assert result == expected + + @pytest.mark.parametrize( + "tz", + [ + "pytz/US/Eastern", + gettz("US/Eastern"), + "US/Eastern", + "dateutil/US/Eastern", + ], + ) + def test_timestamp_constructed_by_date_and_tz(self, tz): + # GH#2993, Timestamp cannot be constructed by datetime.date + # and tz correctly + if isinstance(tz, str) and tz.startswith("pytz/"): + pytz = pytest.importorskip("pytz") + tz = pytz.timezone(tz.removeprefix("pytz/")) + result = Timestamp(date(2012, 3, 11), tz=tz) + + expected = Timestamp("3/11/2012", tz=tz) + assert result.hour == expected.hour + assert result == expected + + def test_explicit_tz_none(self): + # GH#48688 + msg = "Passed data is timezone-aware, incompatible with 'tz=None'" + with pytest.raises(ValueError, match=msg): + Timestamp(datetime(2022, 1, 1, tzinfo=timezone.utc), tz=None) + + with pytest.raises(ValueError, match=msg): + Timestamp("2022-01-01 00:00:00", tzinfo=timezone.utc, tz=None) + + with pytest.raises(ValueError, match=msg): + Timestamp("2022-01-01 00:00:00-0400", tz=None) + + +def test_constructor_ambiguous_dst(): + # GH 24329 + # Make sure that calling Timestamp constructor + # on Timestamp created from ambiguous time + # doesn't change Timestamp.value + ts = Timestamp(1382835600000000000, tz="dateutil/Europe/London") + expected = ts._value + result = Timestamp(ts)._value + assert result == expected + + +@pytest.mark.parametrize("epoch", [1552211999999999872, 1552211999999999999]) +def test_constructor_before_dst_switch(epoch): + # GH 31043 + # Make sure that calling Timestamp constructor + # on time just before DST switch doesn't lead to + # nonexistent time or value change + ts = Timestamp(epoch, tz="dateutil/America/Los_Angeles") + result = ts.tz.dst(ts) + expected = timedelta(seconds=0) + assert Timestamp(ts)._value == epoch + assert result == expected + + +def test_timestamp_constructor_identity(): + # Test for #30543 + expected = Timestamp("2017-01-01T12") + result = Timestamp(expected) + assert result is expected + + +@pytest.mark.parametrize("nano", [-1, 1000]) +def test_timestamp_nano_range(nano): + # GH 48255 + with pytest.raises(ValueError, match="nanosecond must be in 0..999"): + Timestamp(year=2022, month=1, day=1, nanosecond=nano) + + +def test_non_nano_value(): + # https://github.com/pandas-dev/pandas/issues/49076 + msg = "The 'unit' keyword is only used when" + with tm.assert_produces_warning(UserWarning, match=msg): + result = Timestamp("1800-01-01", unit="s").value + # `.value` shows nanoseconds, even though unit is 's' + assert result == -5364662400000000000 + + # out-of-nanoseconds-bounds `.value` raises informative message + msg = ( + r"Cannot convert Timestamp to nanoseconds without overflow. " + r"Use `.asm8.view\('i8'\)` to cast represent Timestamp in its " + r"own unit \(here, us\).$" + ) + ts = Timestamp("0300-01-01") + assert ts.unit == "us" + with pytest.raises(OverflowError, match=msg): + ts.value + # check that the suggested workaround actually works + result = ts.asm8.view("i8") + assert result == -52_700_112_000 * 10**6 + + +@pytest.mark.parametrize( + "na_value", [None, np.nan, np.datetime64("NaT", "ns"), NaT, NA] +) +def test_timestamp_constructor_na_value(na_value): + # GH45481 + result = Timestamp(na_value) + expected = NaT + assert result is expected diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_formats.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_formats.py new file mode 100644 index 0000000000000000000000000000000000000000..7b20f0a17556d69408fc371b4d01839adafd480e --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_formats.py @@ -0,0 +1,206 @@ +from datetime import ( + datetime, + timezone, +) +import pprint + +import dateutil.tz +import pytest + +from pandas.compat import WASM + +from pandas import Timestamp + +ts_no_ns = Timestamp( + year=2019, + month=5, + day=18, + hour=15, + minute=17, + second=8, + microsecond=132263, +) +ts_no_ns_year1 = Timestamp( + year=1, + month=5, + day=18, + hour=15, + minute=17, + second=8, + microsecond=132263, +) +ts_ns = Timestamp( + year=2019, + month=5, + day=18, + hour=15, + minute=17, + second=8, + microsecond=132263, + nanosecond=123, +) +ts_ns_tz = Timestamp( + year=2019, + month=5, + day=18, + hour=15, + minute=17, + second=8, + microsecond=132263, + nanosecond=123, + tz="UTC", +) +ts_no_us = Timestamp( + year=2019, + month=5, + day=18, + hour=15, + minute=17, + second=8, + microsecond=0, + nanosecond=123, +) + + +@pytest.mark.parametrize( + "ts, timespec, expected_iso", + [ + (ts_no_ns, "auto", "2019-05-18T15:17:08.132263"), + (ts_no_ns, "seconds", "2019-05-18T15:17:08"), + (ts_no_ns, "nanoseconds", "2019-05-18T15:17:08.132263000"), + (ts_no_ns_year1, "seconds", "0001-05-18T15:17:08"), + (ts_no_ns_year1, "nanoseconds", "0001-05-18T15:17:08.132263000"), + (ts_ns, "auto", "2019-05-18T15:17:08.132263123"), + (ts_ns, "hours", "2019-05-18T15"), + (ts_ns, "minutes", "2019-05-18T15:17"), + (ts_ns, "seconds", "2019-05-18T15:17:08"), + (ts_ns, "milliseconds", "2019-05-18T15:17:08.132"), + (ts_ns, "microseconds", "2019-05-18T15:17:08.132263"), + (ts_ns, "nanoseconds", "2019-05-18T15:17:08.132263123"), + (ts_ns_tz, "auto", "2019-05-18T15:17:08.132263123+00:00"), + (ts_ns_tz, "hours", "2019-05-18T15+00:00"), + (ts_ns_tz, "minutes", "2019-05-18T15:17+00:00"), + (ts_ns_tz, "seconds", "2019-05-18T15:17:08+00:00"), + (ts_ns_tz, "milliseconds", "2019-05-18T15:17:08.132+00:00"), + (ts_ns_tz, "microseconds", "2019-05-18T15:17:08.132263+00:00"), + (ts_ns_tz, "nanoseconds", "2019-05-18T15:17:08.132263123+00:00"), + (ts_no_us, "auto", "2019-05-18T15:17:08.000000123"), + ], +) +def test_isoformat(ts, timespec, expected_iso): + assert ts.isoformat(timespec=timespec) == expected_iso + + +class TestTimestampRendering: + @pytest.mark.parametrize( + "tz", ["UTC", "Asia/Tokyo", "US/Eastern", "dateutil/America/Los_Angeles"] + ) + @pytest.mark.parametrize("freq", ["D", "M", "S", "N"]) + @pytest.mark.parametrize( + "date", ["2014-03-07", "2014-01-01 09:00", "2014-01-01 00:00:00.000000001"] + ) + @pytest.mark.skipif(WASM, reason="tzset is not available on WASM") + def test_repr(self, date, freq, tz): + # avoid to match with timezone name + freq_repr = f"'{freq}'" + if tz.startswith("dateutil"): + tz_repr = tz.replace("dateutil", "") + else: + tz_repr = tz + + date_only = Timestamp(date) + assert date in repr(date_only) + assert tz_repr not in repr(date_only) + assert freq_repr not in repr(date_only) + assert date_only == eval(repr(date_only)) + + date_tz = Timestamp(date, tz=tz) + assert date in repr(date_tz) + assert tz_repr in repr(date_tz) + assert freq_repr not in repr(date_tz) + assert date_tz == eval(repr(date_tz)) + + def test_repr_utcoffset(self): + # This can cause the tz field to be populated, but it's redundant to + # include this information in the date-string. + date_with_utc_offset = Timestamp("2014-03-13 00:00:00-0400") + assert "2014-03-13 00:00:00-0400" in repr(date_with_utc_offset) + assert "tzoffset" not in repr(date_with_utc_offset) + assert "UTC-04:00" in repr(date_with_utc_offset) + expr = repr(date_with_utc_offset) + assert date_with_utc_offset == eval(expr) + + def test_timestamp_repr_pre1900(self): + # pre-1900 + stamp = Timestamp("1850-01-01", tz="US/Eastern") + repr(stamp) + + iso8601 = "1850-01-01 01:23:45.012345" + stamp = Timestamp(iso8601, tz="US/Eastern") + result = repr(stamp) + assert iso8601 in result + + def test_pprint(self): + # GH#12622 + nested_obj = {"foo": 1, "bar": [{"w": {"a": Timestamp("2011-01-01")}}] * 10} + result = pprint.pformat(nested_obj, width=50) + expected = r"""{'bar': [{'w': {'a': Timestamp('2011-01-01 00:00:00')}}, + {'w': {'a': Timestamp('2011-01-01 00:00:00')}}, + {'w': {'a': Timestamp('2011-01-01 00:00:00')}}, + {'w': {'a': Timestamp('2011-01-01 00:00:00')}}, + {'w': {'a': Timestamp('2011-01-01 00:00:00')}}, + {'w': {'a': Timestamp('2011-01-01 00:00:00')}}, + {'w': {'a': Timestamp('2011-01-01 00:00:00')}}, + {'w': {'a': Timestamp('2011-01-01 00:00:00')}}, + {'w': {'a': Timestamp('2011-01-01 00:00:00')}}, + {'w': {'a': Timestamp('2011-01-01 00:00:00')}}], + 'foo': 1}""" + assert result == expected + + def test_to_timestamp_repr_is_code(self): + zs = [ + Timestamp("99-04-17 00:00:00", tz="UTC"), + Timestamp("2001-04-17 00:00:00", tz="UTC"), + Timestamp("2001-04-17 00:00:00", tz="America/Los_Angeles"), + Timestamp("2001-04-17 00:00:00", tz=None), + ] + for z in zs: + assert eval(repr(z)) == z + + def test_repr_matches_pydatetime_no_tz(self): + dt_date = datetime(2013, 1, 2) + assert str(dt_date) == str(Timestamp(dt_date)) + + dt_datetime = datetime(2013, 1, 2, 12, 1, 3) + assert str(dt_datetime) == str(Timestamp(dt_datetime)) + + dt_datetime_us = datetime(2013, 1, 2, 12, 1, 3, 45) + assert str(dt_datetime_us) == str(Timestamp(dt_datetime_us)) + + ts_nanos_only = Timestamp(200) + assert str(ts_nanos_only) == "1970-01-01 00:00:00.000000200" + + ts_nanos_micros = Timestamp(1200) + assert str(ts_nanos_micros) == "1970-01-01 00:00:00.000001200" + + def test_repr_matches_pydatetime_tz_stdlib(self): + dt_date = datetime(2013, 1, 2, tzinfo=timezone.utc) + assert str(dt_date) == str(Timestamp(dt_date)) + + dt_datetime = datetime(2013, 1, 2, 12, 1, 3, tzinfo=timezone.utc) + assert str(dt_datetime) == str(Timestamp(dt_datetime)) + + dt_datetime_us = datetime(2013, 1, 2, 12, 1, 3, 45, tzinfo=timezone.utc) + assert str(dt_datetime_us) == str(Timestamp(dt_datetime_us)) + + def test_repr_matches_pydatetime_tz_dateutil(self): + utc = dateutil.tz.tzutc() + + dt_date = datetime(2013, 1, 2, tzinfo=utc) + assert str(dt_date) == str(Timestamp(dt_date)) + + dt_datetime = datetime(2013, 1, 2, 12, 1, 3, tzinfo=utc) + assert str(dt_datetime) == str(Timestamp(dt_datetime)) + + dt_datetime_us = datetime(2013, 1, 2, 12, 1, 3, 45, tzinfo=utc) + assert str(dt_datetime_us) == str(Timestamp(dt_datetime_us)) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_timestamp.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_timestamp.py new file mode 100644 index 0000000000000000000000000000000000000000..3e11bbe0118eac02fcb9978abf6497b78316daae --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_timestamp.py @@ -0,0 +1,938 @@ +"""test the scalar Timestamp""" + +import calendar +from datetime import ( + datetime, + timedelta, + timezone, +) +import locale +import time +import unicodedata +import zoneinfo + +from dateutil.tz import ( + tzlocal, + tzutc, +) +from hypothesis import ( + given, + strategies as st, +) +import numpy as np +import pytest + +from pandas._libs.tslibs.dtypes import NpyDatetimeUnit +from pandas._libs.tslibs.timezones import ( + dateutil_gettz as gettz, + get_timezone, + maybe_get_tz, + tz_compare, +) +from pandas.compat import IS64 +from pandas.compat.numpy import ( + is_numpy_dev, + np_version_gt2_5, +) +from pandas.errors import Pandas4Warning + +from pandas import ( + NaT, + Timedelta, + Timestamp, +) +import pandas._testing as tm + +from pandas.tseries import offsets +from pandas.tseries.frequencies import to_offset + + +class TestTimestampProperties: + def test_properties_business(self): + freq = to_offset("B") + + ts = Timestamp("2017-10-01") + assert ts.dayofweek == 6 + assert ts.day_of_week == 6 + assert ts.is_month_start # not a weekday + assert not freq.is_month_start(ts) + assert freq.is_month_start(ts + Timedelta(days=1)) + assert not freq.is_quarter_start(ts) + assert freq.is_quarter_start(ts + Timedelta(days=1)) + + ts = Timestamp("2017-09-30") + assert ts.dayofweek == 5 + assert ts.day_of_week == 5 + assert ts.is_month_end + assert not freq.is_month_end(ts) + assert freq.is_month_end(ts - Timedelta(days=1)) + assert ts.is_quarter_end + assert not freq.is_quarter_end(ts) + assert freq.is_quarter_end(ts - Timedelta(days=1)) + + @pytest.mark.parametrize( + "attr, expected", + [ + ["year", 2014], + ["month", 12], + ["day", 31], + ["hour", 23], + ["minute", 59], + ["second", 0], + ["microsecond", 0], + ["nanosecond", 0], + ["dayofweek", 2], + ["day_of_week", 2], + ["quarter", 4], + ["dayofyear", 365], + ["day_of_year", 365], + ["week", 1], + ["daysinmonth", 31], + ], + ) + @pytest.mark.parametrize("tz", [None, "US/Eastern"]) + def test_fields(self, attr, expected, tz): + # GH 10050 + # GH 13303 + ts = Timestamp("2014-12-31 23:59:00", tz=tz) + result = getattr(ts, attr) + # that we are int like + assert isinstance(result, int) + assert result == expected + + @pytest.mark.parametrize("tz", [None, "US/Eastern"]) + def test_millisecond_raises(self, tz): + ts = Timestamp("2014-12-31 23:59:00", tz=tz) + msg = "'Timestamp' object has no attribute 'millisecond'" + with pytest.raises(AttributeError, match=msg): + ts.millisecond + + @pytest.mark.parametrize( + "start", ["is_month_start", "is_quarter_start", "is_year_start"] + ) + @pytest.mark.parametrize("tz", [None, "US/Eastern"]) + def test_is_start(self, start, tz): + ts = Timestamp("2014-01-01 00:00:00", tz=tz) + assert getattr(ts, start) + + @pytest.mark.parametrize("end", ["is_month_end", "is_year_end", "is_quarter_end"]) + @pytest.mark.parametrize("tz", [None, "US/Eastern"]) + def test_is_end(self, end, tz): + ts = Timestamp("2014-12-31 23:59:59", tz=tz) + assert getattr(ts, end) + + # GH 12806 + @pytest.mark.parametrize("tz", [None, "EST"]) + @pytest.mark.parametrize( + "time_locale", + [None, *tm.get_locales()], + ) + def test_names(self, tz, time_locale): + # GH 17354 + # Test .day_name(), .month_name + data = Timestamp("2017-08-28 23:00:00", tz=tz) + if time_locale is None: + expected_day = "Monday" + expected_month = "August" + else: + with tm.set_locale(time_locale, locale.LC_TIME): + expected_day = calendar.day_name[0].capitalize() + expected_month = calendar.month_name[8].capitalize() + + result_day = data.day_name(time_locale) + result_month = data.month_name(time_locale) + + # Work around https://github.com/pandas-dev/pandas/issues/22342 + # different normalizations + expected_day = unicodedata.normalize("NFD", expected_day) + expected_month = unicodedata.normalize("NFD", expected_month) + + result_day = unicodedata.normalize("NFD", result_day) + result_month = unicodedata.normalize("NFD", result_month) + + assert result_day == expected_day + assert result_month == expected_month + + # Test NaT + nan_ts = Timestamp(NaT) + assert np.isnan(nan_ts.day_name(time_locale)) + assert np.isnan(nan_ts.month_name(time_locale)) + + def test_is_leap_year(self, tz_naive_fixture): + tz = tz_naive_fixture + if not IS64 and tz == tzlocal(): + # https://github.com/dateutil/dateutil/issues/197 + pytest.skip( + "tzlocal() on a 32 bit platform causes internal overflow errors" + ) + # GH 13727 + dt = Timestamp("2000-01-01 00:00:00", tz=tz) + assert dt.is_leap_year + assert isinstance(dt.is_leap_year, bool) + + dt = Timestamp("1999-01-01 00:00:00", tz=tz) + assert not dt.is_leap_year + + dt = Timestamp("2004-01-01 00:00:00", tz=tz) + assert dt.is_leap_year + + dt = Timestamp("2100-01-01 00:00:00", tz=tz) + assert not dt.is_leap_year + + def test_woy_boundary(self): + # make sure weeks at year boundaries are correct + d = datetime(2013, 12, 31) + result = Timestamp(d).week + expected = 1 # ISO standard + assert result == expected + + d = datetime(2008, 12, 28) + result = Timestamp(d).week + expected = 52 # ISO standard + assert result == expected + + d = datetime(2009, 12, 31) + result = Timestamp(d).week + expected = 53 # ISO standard + assert result == expected + + d = datetime(2010, 1, 1) + result = Timestamp(d).week + expected = 53 # ISO standard + assert result == expected + + d = datetime(2010, 1, 3) + result = Timestamp(d).week + expected = 53 # ISO standard + assert result == expected + + result = np.array( + [ + Timestamp(datetime(*args)).week + for args in [(2000, 1, 1), (2000, 1, 2), (2005, 1, 1), (2005, 1, 2)] + ] + ) + assert (result == [52, 52, 53, 53]).all() + + def test_resolution(self): + # GH#21336, GH#21365 + dt = Timestamp("2100-01-01 00:00:00.000000000") + assert dt.resolution == Timedelta(nanoseconds=1) + + # Check that the attribute is available on the class, mirroring + # the stdlib datetime behavior + assert Timestamp.resolution == Timedelta(nanoseconds=1) + + assert dt.as_unit("us").resolution == Timedelta(microseconds=1) + assert dt.as_unit("ms").resolution == Timedelta(milliseconds=1) + assert dt.as_unit("s").resolution == Timedelta(seconds=1) + + @pytest.mark.parametrize( + "date_string, expected", + [ + ("0000-2-29", 1), + ("0000-3-1", 2), + ("1582-10-14", 3), + ("-0040-1-1", 4), + ("2023-06-18", 6), + ], + ) + def test_dow_historic(self, date_string, expected): + # GH 53738 + ts = Timestamp(date_string) + dow = ts.weekday() + assert dow == expected + + @pytest.mark.slow + @given( + ts=st.datetimes(), + sign=st.sampled_from(["-", ""]), + ) + def test_dow_parametric(self, ts, sign): + # GH 53738 + ts = ( + f"{sign}{str(ts.year).zfill(4)}" + f"-{str(ts.month).zfill(2)}" + f"-{str(ts.day).zfill(2)}" + ) + result = Timestamp(ts).weekday() + expected = ( + (np.datetime64(ts) - np.datetime64("1970-01-01")).astype("int64") - 4 + ) % 7 + assert result == expected + + +class TestTimestamp: + @pytest.mark.parametrize("tz", [None, zoneinfo.ZoneInfo("US/Pacific")]) + def test_disallow_setting_tz(self, tz): + # GH#3746 + ts = Timestamp("2010") + msg = "Cannot directly set timezone" + with pytest.raises(AttributeError, match=msg): + ts.tz = tz + + def test_default_to_stdlib_utc(self): + msg = "Timestamp.utcnow is deprecated" + with tm.assert_produces_warning(Pandas4Warning, match=msg): + assert Timestamp.utcnow().tz is timezone.utc + assert Timestamp.now("UTC").tz is timezone.utc + assert Timestamp("2016-01-01", tz="UTC").tz is timezone.utc + + def test_tz(self): + tstr = "2014-02-01 09:00" + ts = Timestamp(tstr) + local = ts.tz_localize("Asia/Tokyo") + assert local.hour == 9 + assert local == Timestamp(tstr, tz="Asia/Tokyo") + conv = local.tz_convert("US/Eastern") + assert conv == Timestamp("2014-01-31 19:00", tz="US/Eastern") + assert conv.hour == 19 + + # preserves nanosecond + ts = Timestamp(tstr) + offsets.Nano(5) + local = ts.tz_localize("Asia/Tokyo") + assert local.hour == 9 + assert local.nanosecond == 5 + conv = local.tz_convert("US/Eastern") + assert conv.nanosecond == 5 + assert conv.hour == 19 + + def test_utc_z_designator(self): + assert get_timezone(Timestamp("2014-11-02 01:00Z").tzinfo) is timezone.utc + + def test_asm8(self): + ns = [Timestamp.min._value, Timestamp.max._value, 1000] + + for n in ns: + assert ( + Timestamp(n).asm8.view("i8") == np.datetime64(n, "ns").view("i8") == n + ) + + assert Timestamp("nat").asm8.view("i8") == np.datetime64("nat", "ns").view("i8") + + def test_class_ops(self): + def compare(x, y): + assert int((Timestamp(x)._value - Timestamp(y)._value) / 1e9) == 0 + + compare(Timestamp.now(), datetime.now()) + compare(Timestamp.now("UTC"), datetime.now(timezone.utc)) + compare(Timestamp.now("UTC"), datetime.now(tzutc())) + msg = "Timestamp.utcnow is deprecated" + with tm.assert_produces_warning(Pandas4Warning, match=msg): + compare(Timestamp.utcnow(), datetime.now(timezone.utc)) + compare(Timestamp.today(), datetime.today()) + current_time = calendar.timegm(datetime.now().utctimetuple()) + + msg = "Timestamp.utcfromtimestamp is deprecated" + with tm.assert_produces_warning(Pandas4Warning, match=msg): + ts_utc = Timestamp.utcfromtimestamp(current_time) + assert ts_utc.timestamp() == current_time + compare( + Timestamp.fromtimestamp(current_time), datetime.fromtimestamp(current_time) + ) + compare( + # Support tz kwarg in Timestamp.fromtimestamp + Timestamp.fromtimestamp(current_time, "UTC"), + datetime.fromtimestamp(current_time, timezone.utc), + ) + compare( + # Support tz kwarg in Timestamp.fromtimestamp + Timestamp.fromtimestamp(current_time, tz="UTC"), + datetime.fromtimestamp(current_time, timezone.utc), + ) + + date_component = datetime.now(timezone.utc) + time_component = (date_component + timedelta(minutes=10)).time() + compare( + Timestamp.combine(date_component, time_component), + datetime.combine(date_component, time_component), + ) + + def test_basics_nanos(self): + val = np.int64(946_684_800_000_000_000).view("M8[ns]") + stamp = Timestamp(val.view("i8") + 500) + assert stamp.year == 2000 + assert stamp.month == 1 + assert stamp.microsecond == 0 + assert stamp.nanosecond == 500 + + # GH 14415 + val = np.iinfo(np.int64).min + 80_000_000_000_000 + stamp = Timestamp(val) + assert stamp.year == 1677 + assert stamp.month == 9 + assert stamp.day == 21 + assert stamp.microsecond == 145224 + assert stamp.nanosecond == 192 + + def test_roundtrip(self): + # test value to string and back conversions + # further test accessors + base = Timestamp("20140101 00:00:00").as_unit("ns") + + result = Timestamp(base._value + Timedelta("5ms").value) + assert result == Timestamp(f"{base}.005000") + assert result.microsecond == 5000 + + result = Timestamp(base._value + Timedelta("5us").value) + assert result == Timestamp(f"{base}.000005") + assert result.microsecond == 5 + + result = Timestamp(base._value + Timedelta("5ns")._value) + assert result == Timestamp(f"{base}.000000005") + assert result.nanosecond == 5 + assert result.microsecond == 0 + + result = Timestamp(base._value + Timedelta("6ms 5us").value) + assert result == Timestamp(f"{base}.006005") + assert result.microsecond == 5 + 6 * 1000 + + result = Timestamp(base._value + Timedelta("200ms 5us").value) + assert result == Timestamp(f"{base}.200005") + assert result.microsecond == 5 + 200 * 1000 + + def test_hash_equivalent(self): + d = {datetime(2011, 1, 1): 5} + stamp = Timestamp(datetime(2011, 1, 1)) + assert d[stamp] == 5 + + @pytest.mark.parametrize( + "timezone, year, month, day, hour", + [["America/Chicago", 2013, 11, 3, 1], ["America/Santiago", 2021, 4, 3, 23]], + ) + def test_hash_timestamp_with_fold(self, timezone, year, month, day, hour): + # see gh-33931 + test_timezone = gettz(timezone) + transition_1 = Timestamp( + year=year, + month=month, + day=day, + hour=hour, + minute=0, + fold=0, + tzinfo=test_timezone, + ) + transition_2 = Timestamp( + year=year, + month=month, + day=day, + hour=hour, + minute=0, + fold=1, + tzinfo=test_timezone, + ) + assert hash(transition_1) == hash(transition_2) + + +class TestTimestampNsOperations: + def test_nanosecond_string_parsing(self): + ts = Timestamp("2013-05-01 07:15:45.123456789") + # GH 7878 + expected_repr = "2013-05-01 07:15:45.123456789" + expected_value = 1_367_392_545_123_456_789 + assert ts._value == expected_value + assert expected_repr in repr(ts) + + ts = Timestamp("2013-05-01 07:15:45.123456789+09:00", tz="Asia/Tokyo") + assert ts._value == expected_value - 9 * 3600 * 1_000_000_000 + assert expected_repr in repr(ts) + + ts = Timestamp("2013-05-01 07:15:45.123456789", tz="UTC") + assert ts._value == expected_value + assert expected_repr in repr(ts) + + ts = Timestamp("2013-05-01 07:15:45.123456789", tz="US/Eastern") + assert ts._value == expected_value + 4 * 3600 * 1_000_000_000 + assert expected_repr in repr(ts) + + # GH 10041 + ts = Timestamp("20130501T071545.123456789") + assert ts._value == expected_value + assert expected_repr in repr(ts) + + def test_nanosecond_timestamp(self): + # GH 7610 + expected = 1_293_840_000_000_000_005 + t = Timestamp("2011-01-01") + offsets.Nano(5) + assert repr(t) == "Timestamp('2011-01-01 00:00:00.000000005')" + assert t._value == expected + assert t.nanosecond == 5 + + t = Timestamp(t) + assert repr(t) == "Timestamp('2011-01-01 00:00:00.000000005')" + assert t._value == expected + assert t.nanosecond == 5 + + t = Timestamp("2011-01-01 00:00:00.000000005") + assert repr(t) == "Timestamp('2011-01-01 00:00:00.000000005')" + assert t._value == expected + assert t.nanosecond == 5 + + expected = 1_293_840_000_000_000_010 + t = t + offsets.Nano(5) + assert repr(t) == "Timestamp('2011-01-01 00:00:00.000000010')" + assert t._value == expected + assert t.nanosecond == 10 + + t = Timestamp(t) + assert repr(t) == "Timestamp('2011-01-01 00:00:00.000000010')" + assert t._value == expected + assert t.nanosecond == 10 + + t = Timestamp("2011-01-01 00:00:00.000000010") + assert repr(t) == "Timestamp('2011-01-01 00:00:00.000000010')" + assert t._value == expected + assert t.nanosecond == 10 + + +class TestTimestampConversion: + def test_conversion(self): + # GH#9255 + ts = Timestamp("2000-01-01").as_unit("ns") + + result = ts.to_pydatetime() + expected = datetime(2000, 1, 1) + assert result == expected + assert type(result) == type(expected) + + result = ts.to_datetime64() + expected = np.datetime64(ts._value, "ns") + assert result == expected + assert type(result) == type(expected) + assert result.dtype == expected.dtype + + def test_to_period_tz_warning(self): + # GH#21333 make sure a warning is issued when timezone + # info is lost + ts = Timestamp("2009-04-15 16:17:18", tz="US/Eastern") + with tm.assert_produces_warning(UserWarning, match="drop timezone information"): + ts.to_period("D") + + def test_to_numpy_alias(self): + # GH 24653: alias .to_numpy() for scalars + ts = Timestamp(datetime.now()) + assert ts.to_datetime64() == ts.to_numpy() + + # GH#44460 + msg = "dtype and copy arguments are ignored" + with pytest.raises(ValueError, match=msg): + ts.to_numpy("M8[s]") + with pytest.raises(ValueError, match=msg): + ts.to_numpy(copy=True) + + +class TestNonNano: + @pytest.fixture(params=["s", "ms", "us"]) + def reso(self, request): + return request.param + + @pytest.fixture + def dt64(self, reso): + # cases that are in-bounds for nanosecond, so we can compare against + # the existing implementation. + return np.datetime64("2016-01-01", reso) + + @pytest.fixture + def ts(self, dt64): + return Timestamp._from_dt64(dt64) + + @pytest.fixture + def ts_tz(self, ts, tz_aware_fixture): + tz = maybe_get_tz(tz_aware_fixture) + return Timestamp._from_value_and_reso(ts._value, ts._creso, tz) + + def test_non_nano_construction(self, dt64, ts, reso): + assert ts._value == dt64.view("i8") + + if reso == "s": + assert ts._creso == NpyDatetimeUnit.NPY_FR_s.value + elif reso == "ms": + assert ts._creso == NpyDatetimeUnit.NPY_FR_ms.value + elif reso == "us": + assert ts._creso == NpyDatetimeUnit.NPY_FR_us.value + + def test_non_nano_fields(self, dt64, ts): + alt = Timestamp(dt64) + + assert ts.year == alt.year + assert ts.month == alt.month + assert ts.day == alt.day + assert ts.hour == ts.minute == ts.second == ts.microsecond == 0 + assert ts.nanosecond == 0 + + assert ts.to_julian_date() == alt.to_julian_date() + assert ts.weekday() == alt.weekday() + assert ts.isoweekday() == alt.isoweekday() + + def test_start_end_fields(self, ts): + assert ts.is_year_start + assert ts.is_quarter_start + assert ts.is_month_start + assert not ts.is_year_end + assert not ts.is_month_end + assert not ts.is_month_end + + # 2016-01-01 is a Friday, so is year/quarter/month start with this freq + assert ts.is_year_start + assert ts.is_quarter_start + assert ts.is_month_start + assert not ts.is_year_end + assert not ts.is_month_end + assert not ts.is_month_end + + def test_day_name(self, dt64, ts): + alt = Timestamp(dt64) + assert ts.day_name() == alt.day_name() + + def test_month_name(self, dt64, ts): + alt = Timestamp(dt64) + assert ts.month_name() == alt.month_name() + + def test_tz_convert(self, ts): + ts = Timestamp._from_value_and_reso(ts._value, ts._creso, timezone.utc) + + tz = zoneinfo.ZoneInfo("US/Pacific") + result = ts.tz_convert(tz) + + assert isinstance(result, Timestamp) + assert result._creso == ts._creso + assert tz_compare(result.tz, tz) + + def test_repr(self, dt64, ts): + alt = Timestamp(dt64) + + assert str(ts) == str(alt) + assert repr(ts) == repr(alt) + + def test_comparison(self, dt64, ts): + alt = Timestamp(dt64) + + assert ts == dt64 + assert dt64 == ts + assert ts == alt + assert alt == ts + + assert not ts != dt64 + assert not dt64 != ts + assert not ts != alt + assert not alt != ts + + assert not ts < dt64 + assert not dt64 < ts + assert not ts < alt + assert not alt < ts + + assert not ts > dt64 + assert not dt64 > ts + assert not ts > alt + assert not alt > ts + + assert ts >= dt64 + assert dt64 >= ts + assert ts >= alt + assert alt >= ts + + assert ts <= dt64 + assert dt64 <= ts + assert ts <= alt + assert alt <= ts + + def test_cmp_cross_reso(self): + # numpy gets this wrong because of silent overflow + dt64 = np.datetime64(9223372800, "s") # won't fit in M8[ns] + ts = Timestamp._from_dt64(dt64) + + # subtracting 3600*24 gives a datetime64 that _can_ fit inside the + # nanosecond implementation bounds. + other = Timestamp(dt64 - np.timedelta64(3600 * 24, "s")).as_unit("ns") + assert other < ts + if not is_numpy_dev and not np_version_gt2_5: + assert other.asm8 > ts.asm8 # <- numpy gets this wrong + assert ts > other + if not is_numpy_dev and not np_version_gt2_5: + assert ts.asm8 < other.asm8 # <- numpy gets this wrong + assert not other == ts + assert ts != other + + @pytest.mark.xfail(reason="Dispatches to np.datetime64 which is wrong") + def test_cmp_cross_reso_reversed_dt64(self): + dt64 = np.datetime64(106752, "D") # won't fit in M8[ns] + ts = Timestamp._from_dt64(dt64) + other = Timestamp(dt64 - 1) + + assert other.asm8 < ts + + def test_pickle(self, ts, tz_aware_fixture, temp_file): + tz = tz_aware_fixture + tz = maybe_get_tz(tz) + ts = Timestamp._from_value_and_reso(ts._value, ts._creso, tz) + rt = tm.round_trip_pickle(ts, temp_file) + assert rt._creso == ts._creso + assert rt == ts + + def test_normalize(self, dt64, ts): + alt = Timestamp(dt64) + result = ts.normalize() + assert result._creso == ts._creso + assert result == alt.normalize() + + def test_asm8(self, dt64, ts): + rt = ts.asm8 + assert rt == dt64 + assert rt.dtype == dt64.dtype + + def test_to_numpy(self, dt64, ts): + res = ts.to_numpy() + assert res == dt64 + assert res.dtype == dt64.dtype + + def test_to_datetime64(self, dt64, ts): + res = ts.to_datetime64() + assert res == dt64 + assert res.dtype == dt64.dtype + + def test_timestamp(self, dt64, ts): + alt = Timestamp(dt64) + assert ts.timestamp() == alt.timestamp() + + def test_to_period(self, dt64, ts): + alt = Timestamp(dt64) + assert ts.to_period("D") == alt.to_period("D") + + @pytest.mark.parametrize( + "td", [timedelta(days=4), Timedelta(days=4), np.timedelta64(4, "D")] + ) + def test_addsub_timedeltalike_non_nano(self, dt64, ts, td): + exp_reso = max(ts._creso, Timedelta(td)._creso) + + result = ts - td + expected = Timestamp(dt64) - td + assert isinstance(result, Timestamp) + assert result._creso == exp_reso + assert result == expected + + result = ts + td + expected = Timestamp(dt64) + td + assert isinstance(result, Timestamp) + assert result._creso == exp_reso + assert result == expected + + result = td + ts + expected = td + Timestamp(dt64) + assert isinstance(result, Timestamp) + assert result._creso == exp_reso + assert result == expected + + def test_addsub_offset(self, ts_tz): + # specifically non-Tick offset + off = offsets.YearEnd(1) + result = ts_tz + off + + assert isinstance(result, Timestamp) + assert result._creso == ts_tz._creso + if ts_tz.month == 12 and ts_tz.day == 31: + assert result.year == ts_tz.year + 1 + else: + assert result.year == ts_tz.year + assert result.day == 31 + assert result.month == 12 + assert tz_compare(result.tz, ts_tz.tz) + + result = ts_tz - off + + assert isinstance(result, Timestamp) + assert result._creso == ts_tz._creso + assert result.year == ts_tz.year - 1 + assert result.day == 31 + assert result.month == 12 + assert tz_compare(result.tz, ts_tz.tz) + + def test_sub_datetimelike_mismatched_reso(self, ts_tz): + # case with non-lossy rounding + ts = ts_tz + + # choose a unit for `other` that doesn't match ts_tz's; + # this construction ensures we get cases with other._creso < ts._creso + # and cases with other._creso > ts._creso + unit = { + NpyDatetimeUnit.NPY_FR_us.value: "ms", + NpyDatetimeUnit.NPY_FR_ms.value: "s", + NpyDatetimeUnit.NPY_FR_s.value: "us", + }[ts._creso] + other = ts.as_unit(unit) + assert other._creso != ts._creso + + result = ts - other + assert isinstance(result, Timedelta) + assert result._value == 0 + assert result._creso == max(ts._creso, other._creso) + + result = other - ts + assert isinstance(result, Timedelta) + assert result._value == 0 + assert result._creso == max(ts._creso, other._creso) + + if ts._creso < other._creso: + # Case where rounding is lossy + other2 = other + Timedelta._from_value_and_reso(1, other._creso) + exp = ts.as_unit(other.unit) - other2 + + res = ts - other2 + assert res == exp + assert res._creso == max(ts._creso, other._creso) + + res = other2 - ts + assert res == -exp + assert res._creso == max(ts._creso, other._creso) + else: + ts2 = ts + Timedelta._from_value_and_reso(1, ts._creso) + exp = ts2 - other.as_unit(ts2.unit) + + res = ts2 - other + assert res == exp + assert res._creso == max(ts._creso, other._creso) + res = other - ts2 + assert res == -exp + assert res._creso == max(ts._creso, other._creso) + + def test_sub_timedeltalike_mismatched_reso(self, ts_tz): + # case with non-lossy rounding + ts = ts_tz + + # choose a unit for `other` that doesn't match ts_tz's; + # this construction ensures we get cases with other._creso < ts._creso + # and cases with other._creso > ts._creso + unit = { + NpyDatetimeUnit.NPY_FR_us.value: "ms", + NpyDatetimeUnit.NPY_FR_ms.value: "s", + NpyDatetimeUnit.NPY_FR_s.value: "us", + }[ts._creso] + other = Timedelta(0).as_unit(unit) + assert other._creso != ts._creso + + result = ts + other + assert isinstance(result, Timestamp) + assert result == ts + assert result._creso == max(ts._creso, other._creso) + + result = other + ts + assert isinstance(result, Timestamp) + assert result == ts + assert result._creso == max(ts._creso, other._creso) + + if ts._creso < other._creso: + # Case where rounding is lossy + other2 = other + Timedelta._from_value_and_reso(1, other._creso) + exp = ts.as_unit(other.unit) + other2 + res = ts + other2 + assert res == exp + assert res._creso == max(ts._creso, other._creso) + res = other2 + ts + assert res == exp + assert res._creso == max(ts._creso, other._creso) + else: + ts2 = ts + Timedelta._from_value_and_reso(1, ts._creso) + exp = ts2 + other.as_unit(ts2.unit) + + res = ts2 + other + assert res == exp + assert res._creso == max(ts._creso, other._creso) + res = other + ts2 + assert res == exp + assert res._creso == max(ts._creso, other._creso) + + def test_addition_doesnt_downcast_reso(self): + # https://github.com/pandas-dev/pandas/pull/48748#pullrequestreview-1122635413 + ts = Timestamp(year=2022, month=1, day=1, microsecond=999999).as_unit("us") + td = Timedelta(microseconds=1).as_unit("us") + res = ts + td + assert res._creso == ts._creso + + def test_sub_timedelta64_mismatched_reso(self, ts_tz): + ts = ts_tz + + res = ts + np.timedelta64(1, "ns") + exp = ts.as_unit("ns") + np.timedelta64(1, "ns") + assert exp == res + assert exp._creso == NpyDatetimeUnit.NPY_FR_ns.value + + def test_min(self, ts): + assert ts.min <= ts + assert ts.min._creso == ts._creso + assert ts.min._value == NaT._value + 1 + + def test_max(self, ts): + assert ts.max >= ts + assert ts.max._creso == ts._creso + assert ts.max._value == np.iinfo(np.int64).max + + def test_resolution(self, ts): + expected = Timedelta._from_value_and_reso(1, ts._creso) + result = ts.resolution + assert result == expected + assert result._creso == expected._creso + + def test_out_of_ns_bounds(self): + # https://github.com/pandas-dev/pandas/issues/51060 + result = Timestamp(-52700112000, unit="s") + assert result == Timestamp("0300-01-01") + assert result.to_numpy() == np.datetime64("0300-01-01T00:00:00", "s") + + +def test_timestamp_class_min_max_resolution(): + # when accessed on the class (as opposed to an instance), we default + # to nanoseconds + assert Timestamp.min == Timestamp(NaT._value + 1) + assert Timestamp.min._creso == NpyDatetimeUnit.NPY_FR_ns.value + + assert Timestamp.max == Timestamp(np.iinfo(np.int64).max) + assert Timestamp.max._creso == NpyDatetimeUnit.NPY_FR_ns.value + + assert Timestamp.resolution == Timedelta(1) + assert Timestamp.resolution._creso == NpyDatetimeUnit.NPY_FR_ns.value + + +def test_delimited_date(): + # https://github.com/pandas-dev/pandas/issues/50231 + with tm.assert_produces_warning(None): + result = Timestamp("13-01-2000") + expected = Timestamp(2000, 1, 13) + assert result == expected + + +def test_utctimetuple(): + # GH 32174 + ts = Timestamp("2000-01-01", tz="UTC") + result = ts.utctimetuple() + expected = time.struct_time((2000, 1, 1, 0, 0, 0, 5, 1, 0)) + assert result == expected + + +def test_negative_dates(): + # https://github.com/pandas-dev/pandas/issues/50787 + ts = Timestamp("-2000-01-01") + msg = ( + " not yet supported on Timestamps which are outside the range of " + "Python's standard library. For now, please call the components you need " + r"\(such as `.year` and `.month`\) and construct your string from there.$" + ) + func = "^strftime" + with pytest.raises(NotImplementedError, match=func + msg): + ts.strftime("%Y") + + msg = ( + " not yet supported on Timestamps which " + "are outside the range of Python's standard library. " + ) + func = "^date" + with pytest.raises(NotImplementedError, match=func + msg): + ts.date() + func = "^isocalendar" + with pytest.raises(NotImplementedError, match=func + msg): + ts.isocalendar() + func = "^timetuple" + with pytest.raises(NotImplementedError, match=func + msg): + ts.timetuple() + func = "^toordinal" + with pytest.raises(NotImplementedError, match=func + msg): + ts.toordinal() diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_timezones.py b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_timezones.py new file mode 100644 index 0000000000000000000000000000000000000000..8f2ee3ef45075226a844360d78c6c8530a1c56a4 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/scalar/timestamp/test_timezones.py @@ -0,0 +1,25 @@ +""" +Tests for Timestamp timezone-related methods +""" + +from datetime import datetime + +from pandas._libs.tslibs import timezones + +from pandas import Timestamp + + +class TestTimestampTZOperations: + # ------------------------------------------------------------------ + + def test_timestamp_timetz_equivalent_with_datetime_tz(self, tz_naive_fixture): + # GH21358 + tz = timezones.maybe_get_tz(tz_naive_fixture) + + stamp = Timestamp("2018-06-04 10:20:30", tz=tz) + _datetime = datetime(2018, 6, 4, hour=10, minute=20, second=30, tzinfo=tz) + + result = stamp.timetz() + expected = _datetime.timetz() + + assert result == expected diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/__init__.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/__init__.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_cat_accessor.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_cat_accessor.py new file mode 100644 index 0000000000000000000000000000000000000000..b8bbf770132c4456052a1c345577af537dbe5065 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_cat_accessor.py @@ -0,0 +1,260 @@ +import numpy as np +import pytest + +from pandas.errors import Pandas4Warning + +from pandas import ( + Categorical, + DataFrame, + Index, + Series, + Timestamp, + date_range, + period_range, + timedelta_range, +) +import pandas._testing as tm +from pandas.core.arrays.categorical import CategoricalAccessor +from pandas.core.indexes.accessors import Properties + + +class TestCatAccessor: + @pytest.mark.parametrize( + "method", + [ + lambda x: x.cat.set_categories([1, 2, 3]), + lambda x: x.cat.reorder_categories([2, 3, 1], ordered=True), + lambda x: x.cat.rename_categories([1, 2, 3]), + lambda x: x.cat.remove_unused_categories(), + lambda x: x.cat.remove_categories([2]), + lambda x: x.cat.add_categories([4]), + lambda x: x.cat.as_ordered(), + lambda x: x.cat.as_unordered(), + ], + ) + def test_getname_categorical_accessor(self, method): + # GH#17509 + ser = Series([1, 2, 3], name="A").astype("category") + expected = "A" + result = method(ser).name + assert result == expected + + def test_cat_accessor(self): + ser = Series(Categorical(["a", "b", np.nan, "a"])) + tm.assert_index_equal(ser.cat.categories, Index(["a", "b"])) + assert not ser.cat.ordered + + exp = Categorical(["a", "b", np.nan, "a"], categories=["b", "a"]) + + res = ser.cat.set_categories(["b", "a"]) + tm.assert_categorical_equal(res.values, exp) + + ser[:] = "a" + ser = ser.cat.remove_unused_categories() + tm.assert_index_equal(ser.cat.categories, Index(["a"])) + + def test_cat_accessor_api(self): + # GH#9322 + + assert Series.cat is CategoricalAccessor + ser = Series(list("aabbcde")).astype("category") + assert isinstance(ser.cat, CategoricalAccessor) + + invalid = Series([1]) + with pytest.raises(AttributeError, match="only use .cat accessor"): + invalid.cat + assert not hasattr(invalid, "cat") + + def test_cat_accessor_no_new_attributes(self): + # https://github.com/pandas-dev/pandas/issues/10673 + cat = Series(list("aabbcde")).astype("category") + with pytest.raises(AttributeError, match="You cannot add any new attribute"): + cat.cat.xlabel = "a" + + def test_categorical_delegations(self): + # invalid accessor + msg = r"Can only use \.cat accessor with a 'category' dtype" + with pytest.raises(AttributeError, match=msg): + Series([1, 2, 3]).cat + with pytest.raises(AttributeError, match=msg): + Series([1, 2, 3]).cat() + with pytest.raises(AttributeError, match=msg): + Series(["a", "b", "c"]).cat + with pytest.raises(AttributeError, match=msg): + Series(np.arange(5.0)).cat + with pytest.raises(AttributeError, match=msg): + Series([Timestamp("20130101")]).cat + + # Series should delegate calls to '.categories', '.codes', '.ordered' + # and the methods '.set_categories()' 'drop_unused_categories()' to the + # categorical + ser = Series(Categorical(["a", "b", "c", "a"], ordered=True)) + exp_categories = Index(["a", "b", "c"]) + tm.assert_index_equal(ser.cat.categories, exp_categories) + ser = ser.cat.rename_categories([1, 2, 3]) + exp_categories = Index([1, 2, 3]) + tm.assert_index_equal(ser.cat.categories, exp_categories) + + exp_codes = Series([0, 1, 2, 0], dtype="int8") + tm.assert_series_equal(ser.cat.codes, exp_codes) + + assert ser.cat.ordered + ser = ser.cat.as_unordered() + assert not ser.cat.ordered + + ser = ser.cat.as_ordered() + assert ser.cat.ordered + + # reorder + ser = Series(Categorical(["a", "b", "c", "a"], ordered=True)) + exp_categories = Index(["c", "b", "a"]) + exp_values = np.array(["a", "b", "c", "a"], dtype=np.object_) + ser = ser.cat.set_categories(["c", "b", "a"]) + tm.assert_index_equal(ser.cat.categories, exp_categories) + tm.assert_numpy_array_equal(ser.values.__array__(), exp_values) + tm.assert_numpy_array_equal(ser.__array__(), exp_values) + + # remove unused categories + ser = Series(Categorical(["a", "b", "b", "a"], categories=["a", "b", "c"])) + exp_categories = Index(["a", "b"]) + exp_values = np.array(["a", "b", "b", "a"], dtype=np.object_) + ser = ser.cat.remove_unused_categories() + tm.assert_index_equal(ser.cat.categories, exp_categories) + tm.assert_numpy_array_equal(ser.values.__array__(), exp_values) + tm.assert_numpy_array_equal(ser.__array__(), exp_values) + + # This method is likely to be confused, so test that it raises an error + # on wrong inputs: + msg = "'Series' object has no attribute 'set_categories'" + with pytest.raises(AttributeError, match=msg): + ser.set_categories([4, 3, 2, 1]) + + # right: ser.cat.set_categories([4,3,2,1]) + + # GH#18862 (let Series.cat.rename_categories take callables) + ser = Series(Categorical(["a", "b", "c", "a"], ordered=True)) + result = ser.cat.rename_categories(lambda x: x.upper()) + expected = Series( + Categorical(["A", "B", "C", "A"], categories=["A", "B", "C"], ordered=True) + ) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "idx", + [ + date_range("1/1/2015", periods=5, unit="ns"), + date_range("1/1/2015", periods=5, tz="MET", unit="ns"), + period_range("1/1/2015", freq="D", periods=5), + timedelta_range("1 days", "10 days"), + ], + ) + def test_dt_accessor_api_for_categorical(self, idx): + # https://github.com/pandas-dev/pandas/issues/10661 + + ser = Series(idx) + cat = ser.astype("category") + + # only testing field (like .day) + # and bool (is_month_start) + attr_names = type(ser._values)._datetimelike_ops + + assert isinstance(cat.dt, Properties) + + special_func_defs = [ + ("strftime", ("%Y-%m-%d",), {}), + ("round", ("D",), {}), + ("floor", ("D",), {}), + ("ceil", ("D",), {}), + ("asfreq", ("D",), {}), + ("as_unit", ("s"), {}), + ] + if idx.dtype == "M8[ns]": + # exclude dt64tz since that is already localized and would raise + tup = ("tz_localize", ("UTC",), {}) + special_func_defs.append(tup) + elif idx.dtype.kind == "M": + # exclude dt64 since that is not localized so would raise + tup = ("tz_convert", ("EST",), {}) + special_func_defs.append(tup) + + _special_func_names = [f[0] for f in special_func_defs] + + _ignore_names = ["components", "tz_localize", "tz_convert"] + + func_names = [ + fname + for fname in dir(ser.dt) + if not ( + fname.startswith("_") + or fname in attr_names + or fname in _special_func_names + or fname in _ignore_names + ) + ] + + func_defs = [(fname, (), {}) for fname in func_names] + func_defs.extend( + f_def for f_def in special_func_defs if f_def[0] in dir(ser.dt) + ) + + for func, args, kwargs in func_defs: + warn_cls = [] + if func == "to_period" and getattr(idx, "tz", None) is not None: + # dropping TZ + warn_cls.append(UserWarning) + elif func == "to_pytimedelta": + # GH 57463 + warn_cls.append(Pandas4Warning) + if warn_cls: + warn_cls = tuple(warn_cls) + else: + warn_cls = None + with tm.assert_produces_warning(warn_cls): + res = getattr(cat.dt, func)(*args, **kwargs) + exp = getattr(ser.dt, func)(*args, **kwargs) + + tm.assert_equal(res, exp) + + for attr in attr_names: + res = getattr(cat.dt, attr) + exp = getattr(ser.dt, attr) + + tm.assert_equal(res, exp) + + def test_dt_accessor_api_for_categorical_invalid(self): + invalid = Series([1, 2, 3]).astype("category") + msg = "Can only use .dt accessor with datetimelike" + + with pytest.raises(AttributeError, match=msg): + invalid.dt + assert not hasattr(invalid, "str") + + def test_set_categories_setitem(self): + # GH#43334 + + df = DataFrame({"Survived": [1, 0, 1], "Sex": [0, 1, 1]}, dtype="category") + + df["Survived"] = df["Survived"].cat.rename_categories(["No", "Yes"]) + df["Sex"] = df["Sex"].cat.rename_categories(["female", "male"]) + + # values should not be coerced to NaN + assert list(df["Sex"]) == ["female", "male", "male"] + assert list(df["Survived"]) == ["Yes", "No", "Yes"] + + df["Sex"] = Categorical(df["Sex"], categories=["female", "male"], ordered=False) + df["Survived"] = Categorical( + df["Survived"], categories=["No", "Yes"], ordered=False + ) + + # values should not be coerced to NaN + assert list(df["Sex"]) == ["female", "male", "male"] + assert list(df["Survived"]) == ["Yes", "No", "Yes"] + + def test_categorical_of_booleans_is_boolean(self): + # https://github.com/pandas-dev/pandas/issues/46313 + df = DataFrame( + {"int_cat": [1, 2, 3], "bool_cat": [True, False, False]}, dtype="category" + ) + value = df["bool_cat"].cat.categories.dtype + expected = np.dtype(np.bool_) + assert value is expected diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_dt_accessor.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_dt_accessor.py new file mode 100644 index 0000000000000000000000000000000000000000..04e8d6a5dba938fdabcc0f47436e8dfdea537b01 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_dt_accessor.py @@ -0,0 +1,832 @@ +import calendar +from datetime import ( + date, + datetime, + time, +) +import locale +import unicodedata + +import numpy as np +import pytest + +from pandas._libs.tslibs.timezones import maybe_get_tz +from pandas.errors import Pandas4Warning + +from pandas.core.dtypes.common import ( + is_integer_dtype, + is_list_like, +) + +import pandas as pd +from pandas import ( + DataFrame, + DatetimeIndex, + Index, + Period, + PeriodIndex, + Series, + StringDtype, + TimedeltaIndex, + date_range, + period_range, + timedelta_range, +) +import pandas._testing as tm +from pandas.core.arrays import ( + DatetimeArray, + PeriodArray, + TimedeltaArray, +) + +ok_for_period = PeriodArray._datetimelike_ops +ok_for_period_methods = ["strftime", "to_timestamp", "asfreq"] +ok_for_dt = DatetimeArray._datetimelike_ops +ok_for_dt_methods = [ + "to_period", + "to_pydatetime", + "tz_localize", + "tz_convert", + "normalize", + "strftime", + "round", + "floor", + "ceil", + "day_name", + "month_name", + "isocalendar", + "as_unit", +] +ok_for_td = TimedeltaArray._datetimelike_ops +ok_for_td_methods = [ + "components", + "to_pytimedelta", + "total_seconds", + "round", + "floor", + "ceil", + "as_unit", +] + + +def get_dir(ser): + # check limited display api + results = [r for r in ser.dt.__dir__() if not r.startswith("_")] + return sorted(set(results)) + + +class TestSeriesDatetimeValues: + def _compare(self, ser, name): + # GH 7207, 11128 + # test .dt namespace accessor + + def get_expected(ser, prop): + result = getattr(Index(ser._values), prop) + if isinstance(result, np.ndarray): + if is_integer_dtype(result): + result = result.astype("int64") + elif not is_list_like(result) or isinstance(result, DataFrame): + return result + return Series(result, index=ser.index, name=ser.name) + + left = getattr(ser.dt, name) + right = get_expected(ser, name) + if not (is_list_like(left) and is_list_like(right)): + assert left == right + elif isinstance(left, DataFrame): + tm.assert_frame_equal(left, right) + else: + tm.assert_series_equal(left, right) + + @pytest.mark.parametrize("freq", ["D", "s", "ms"]) + def test_dt_namespace_accessor_datetime64(self, freq): + # GH#7207, GH#11128 + # test .dt namespace accessor + + # datetimeindex + dti = date_range("20130101", periods=5, freq=freq) + ser = Series(dti, name="xxx") + + for prop in ok_for_dt: + # we test freq below + if prop != "freq": + self._compare(ser, prop) + + for prop in ok_for_dt_methods: + getattr(ser.dt, prop) + + result = ser.dt.to_pydatetime() + assert isinstance(result, Series) + assert result.dtype == object + + result = ser.dt.tz_localize("US/Eastern") + exp_values = DatetimeIndex(ser.values).tz_localize("US/Eastern") + expected = Series(exp_values, index=ser.index, name="xxx") + tm.assert_series_equal(result, expected) + + tz_result = result.dt.tz + assert str(tz_result) == "US/Eastern" + freq_result = ser.dt.freq + assert freq_result == DatetimeIndex(ser.values, freq="infer").freq + + # let's localize, then convert + result = ser.dt.tz_localize("UTC").dt.tz_convert("US/Eastern") + exp_values = ( + DatetimeIndex(ser.values).tz_localize("UTC").tz_convert("US/Eastern") + ) + expected = Series(exp_values, index=ser.index, name="xxx") + tm.assert_series_equal(result, expected) + + def test_dt_namespace_accessor_datetime64tz(self): + # GH#7207, GH#11128 + # test .dt namespace accessor + + # datetimeindex with tz + dti = date_range("20130101", periods=5, tz="US/Eastern") + ser = Series(dti, name="xxx") + for prop in ok_for_dt: + # we test freq below + if prop != "freq": + self._compare(ser, prop) + + for prop in ok_for_dt_methods: + getattr(ser.dt, prop) + + result = ser.dt.to_pydatetime() + assert isinstance(result, Series) + assert result.dtype == object + + result = ser.dt.tz_convert("CET") + expected = Series(ser._values.tz_convert("CET"), index=ser.index, name="xxx") + tm.assert_series_equal(result, expected) + + tz_result = result.dt.tz + assert str(tz_result) == "CET" + freq_result = ser.dt.freq + assert freq_result == DatetimeIndex(ser.values, freq="infer").freq + + def test_dt_namespace_accessor_timedelta(self): + # GH#7207, GH#11128 + # test .dt namespace accessor + + # timedelta index + cases = [ + Series( + timedelta_range("1 day", periods=5), index=list("abcde"), name="xxx" + ), + Series(timedelta_range("1 day 01:23:45", periods=5, freq="s"), name="xxx"), + Series( + timedelta_range("2 days 01:23:45.012345", periods=5, freq="ms"), + name="xxx", + ), + ] + for ser in cases: + for prop in ok_for_td: + # we test freq below + if prop != "freq": + self._compare(ser, prop) + + for prop in ok_for_td_methods: + getattr(ser.dt, prop) + + result = ser.dt.components + assert isinstance(result, DataFrame) + tm.assert_index_equal(result.index, ser.index) + + msg = "The behavior of TimedeltaProperties.to_pytimedelta is deprecated" + with tm.assert_produces_warning(Pandas4Warning, match=msg): + result = ser.dt.to_pytimedelta() + assert isinstance(result, np.ndarray) + assert result.dtype == object + + result = ser.dt.total_seconds() + assert isinstance(result, Series) + assert result.dtype == "float64" + + freq_result = ser.dt.freq + assert freq_result == TimedeltaIndex(ser.values, freq="infer").freq + + def test_dt_namespace_accessor_period(self): + # GH#7207, GH#11128 + # test .dt namespace accessor + + # periodindex + pi = period_range("20130101", periods=5, freq="D") + ser = Series(pi, name="xxx") + + for prop in ok_for_period: + # we test freq below + if prop != "freq": + self._compare(ser, prop) + + for prop in ok_for_period_methods: + getattr(ser.dt, prop) + + freq_result = ser.dt.freq + assert freq_result == PeriodIndex(ser.values).freq + + def test_dt_namespace_accessor_index_and_values(self): + # both + index = date_range("20130101", periods=3, freq="D") + dti = date_range("20140204", periods=3, freq="s") + ser = Series(dti, index=index, name="xxx") + exp = Series( + np.array([2014, 2014, 2014], dtype="int32"), index=index, name="xxx" + ) + tm.assert_series_equal(ser.dt.year, exp) + + exp = Series(np.array([2, 2, 2], dtype="int32"), index=index, name="xxx") + tm.assert_series_equal(ser.dt.month, exp) + + exp = Series(np.array([0, 1, 2], dtype="int32"), index=index, name="xxx") + tm.assert_series_equal(ser.dt.second, exp) + + exp = Series([ser.iloc[0]] * 3, index=index, name="xxx") + tm.assert_series_equal(ser.dt.normalize(), exp) + + def test_dt_accessor_limited_display_api(self): + # tznaive + ser = Series(date_range("20130101", periods=5, freq="D"), name="xxx") + results = get_dir(ser) + tm.assert_almost_equal(results, sorted(set(ok_for_dt + ok_for_dt_methods))) + + # tzaware + ser = Series(date_range("2015-01-01", "2016-01-01", freq="min"), name="xxx") + ser = ser.dt.tz_localize("UTC").dt.tz_convert("America/Chicago") + results = get_dir(ser) + tm.assert_almost_equal(results, sorted(set(ok_for_dt + ok_for_dt_methods))) + + # Period + idx = period_range("20130101", periods=5, freq="D", name="xxx") + ser = Series(idx) + results = get_dir(ser) + tm.assert_almost_equal( + results, sorted(set(ok_for_period + ok_for_period_methods)) + ) + + def test_dt_accessor_ambiguous_freq_conversions(self): + # GH#11295 + # ambiguous time error on the conversions + ser = Series(date_range("2015-01-01", "2016-01-01", freq="min"), name="xxx") + ser = ser.dt.tz_localize("UTC").dt.tz_convert("America/Chicago") + + exp_values = date_range( + "2015-01-01", "2016-01-01", freq="min", tz="UTC" + ).tz_convert("America/Chicago") + # freq not preserved by tz_localize above + exp_values = exp_values._with_freq(None) + expected = Series(exp_values, name="xxx") + tm.assert_series_equal(ser, expected) + + def test_dt_accessor_not_writeable(self): + # no setting allowed + ser = Series(date_range("20130101", periods=5, freq="D"), name="xxx") + with pytest.raises(ValueError, match="modifications"): + ser.dt.hour = 5 + + # trying to set a copy + with tm.raises_chained_assignment_error(): + ser.dt.hour[0] = 5 + + @pytest.mark.parametrize( + "method, dates", + [ + ["round", ["2012-01-02", "2012-01-02", "2012-01-01"]], + ["floor", ["2012-01-01", "2012-01-01", "2012-01-01"]], + ["ceil", ["2012-01-02", "2012-01-02", "2012-01-02"]], + ], + ) + def test_dt_round(self, method, dates): + # round + ser = Series( + pd.to_datetime( + ["2012-01-01 13:00:00", "2012-01-01 12:01:00", "2012-01-01 08:00:00"] + ), + name="xxx", + ) + result = getattr(ser.dt, method)("D") + expected = Series(pd.to_datetime(dates), name="xxx") + tm.assert_series_equal(result, expected) + + def test_dt_round_tz(self): + ser = Series( + pd.to_datetime( + ["2012-01-01 13:00:00", "2012-01-01 12:01:00", "2012-01-01 08:00:00"] + ), + name="xxx", + ) + result = ser.dt.tz_localize("UTC").dt.tz_convert("US/Eastern").dt.round("D") + + exp_values = pd.to_datetime( + ["2012-01-01", "2012-01-01", "2012-01-01"] + ).tz_localize("US/Eastern") + expected = Series(exp_values, name="xxx") + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("method", ["ceil", "round", "floor"]) + def test_dt_round_tz_ambiguous(self, method): + # GH 18946 round near "fall back" DST + df1 = DataFrame( + [ + pd.to_datetime("2017-10-29 02:00:00+02:00", utc=True), + pd.to_datetime("2017-10-29 02:00:00+01:00", utc=True), + pd.to_datetime("2017-10-29 03:00:00+01:00", utc=True), + ], + columns=["date"], + ) + df1["date"] = df1["date"].dt.tz_convert("Europe/Madrid") + # infer + result = getattr(df1.date.dt, method)("h", ambiguous="infer") + expected = df1["date"] + tm.assert_series_equal(result, expected) + + # bool-array + result = getattr(df1.date.dt, method)("h", ambiguous=[True, False, False]) + tm.assert_series_equal(result, expected) + + # NaT + result = getattr(df1.date.dt, method)("h", ambiguous="NaT") + expected = df1["date"].copy() + expected.iloc[0:2] = pd.NaT + tm.assert_series_equal(result, expected) + + # raise + with tm.external_error_raised(ValueError): + getattr(df1.date.dt, method)("h", ambiguous="raise") + + @pytest.mark.parametrize( + "method, ts_str, freq", + [ + ["ceil", "2018-03-11 01:59:00-0600", "5min"], + ["round", "2018-03-11 01:59:00-0600", "5min"], + ["floor", "2018-03-11 03:01:00-0500", "2h"], + ], + ) + def test_dt_round_tz_nonexistent(self, method, ts_str, freq): + # GH 23324 round near "spring forward" DST + ser = Series([pd.Timestamp(ts_str, tz="America/Chicago").as_unit("s")]) + result = getattr(ser.dt, method)(freq, nonexistent="shift_forward") + expected = Series( + [pd.Timestamp("2018-03-11 03:00:00", tz="America/Chicago").as_unit("s")] + ) + tm.assert_series_equal(result, expected) + + result = getattr(ser.dt, method)(freq, nonexistent="NaT") + expected = Series([pd.NaT]).dt.tz_localize(result.dt.tz) + tm.assert_series_equal(result, expected) + + with pytest.raises(ValueError, match="2018-03-11 02:00:00"): + getattr(ser.dt, method)(freq, nonexistent="raise") + + @pytest.mark.parametrize("freq", ["ns", "us", "1000us"]) + def test_dt_round_nonnano_higher_resolution_no_op(self, freq): + # GH 52761 + ser = Series( + ["2020-05-31 08:00:00", "2000-12-31 04:00:05", "1800-03-14 07:30:20"], + dtype="datetime64[ms]", + ) + expected = ser.copy() + result = ser.dt.round(freq) + tm.assert_series_equal(result, expected) + + assert not np.shares_memory(ser.array._ndarray, result.array._ndarray) + + def test_dt_namespace_accessor_categorical(self): + # GH 19468 + dti = DatetimeIndex(["20171111", "20181212"]).repeat(2) + ser = Series(pd.Categorical(dti), name="foo") + result = ser.dt.year + expected = Series([2017, 2017, 2018, 2018], dtype="int32", name="foo") + tm.assert_series_equal(result, expected) + + def test_dt_tz_localize_categorical(self, tz_aware_fixture): + # GH 27952 + tz = tz_aware_fixture + datetimes = Series( + ["2019-01-01", "2019-01-01", "2019-01-02"], dtype="datetime64[ns]" + ) + categorical = datetimes.astype("category") + result = categorical.dt.tz_localize(tz) + expected = datetimes.dt.tz_localize(tz) + tm.assert_series_equal(result, expected) + + def test_dt_tz_convert_categorical(self, tz_aware_fixture): + # GH 27952 + tz = tz_aware_fixture + datetimes = Series( + ["2019-01-01", "2019-01-01", "2019-01-02"], dtype="datetime64[ns, MET]" + ) + categorical = datetimes.astype("category") + result = categorical.dt.tz_convert(tz) + expected = datetimes.dt.tz_convert(tz) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("accessor", ["year", "month", "day"]) + def test_dt_other_accessors_categorical(self, accessor): + # GH 27952 + datetimes = Series( + ["2018-01-01", "2018-01-01", "2019-01-02"], dtype="datetime64[ns]" + ) + categorical = datetimes.astype("category") + result = getattr(categorical.dt, accessor) + expected = getattr(datetimes.dt, accessor) + tm.assert_series_equal(result, expected) + + def test_dt_accessor_no_new_attributes(self): + # https://github.com/pandas-dev/pandas/issues/10673 + ser = Series(date_range("20130101", periods=5, freq="D")) + with pytest.raises(AttributeError, match="You cannot add any new attribute"): + ser.dt.xlabel = "a" + + @pytest.mark.parametrize( + "time_locale", + [None, *tm.get_locales()], + ) + def test_dt_accessor_datetime_name_accessors(self, time_locale): + # Test Monday -> Sunday and January -> December, in that sequence + if time_locale is None: + # If the time_locale is None, day-name and month_name should + # return the english attributes + expected_days = [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", + ] + expected_months = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ] + else: + with tm.set_locale(time_locale, locale.LC_TIME): + expected_days = calendar.day_name[:] + expected_months = calendar.month_name[1:] + + ser = Series(date_range(freq="D", start=datetime(1998, 1, 1), periods=365)) + english_days = [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", + ] + for day, name, eng_name in zip(range(4, 11), expected_days, english_days): + name = name.capitalize() + assert ser.dt.day_name(locale=time_locale)[day] == name + assert ser.dt.day_name(locale=None)[day] == eng_name + ser = pd.concat([ser, Series([pd.NaT])]) + assert np.isnan(ser.dt.day_name(locale=time_locale).iloc[-1]) + + ser = Series(date_range(freq="ME", start="2012", end="2013")) + result = ser.dt.month_name(locale=time_locale) + expected = Series([month.capitalize() for month in expected_months]) + + # work around https://github.com/pandas-dev/pandas/issues/22342 + result = result.str.normalize("NFD") + expected = expected.str.normalize("NFD") + + tm.assert_series_equal(result, expected) + + for s_date, expected in zip(ser, expected_months): + result = s_date.month_name(locale=time_locale) + expected = expected.capitalize() + + result = unicodedata.normalize("NFD", result) + expected = unicodedata.normalize("NFD", expected) + + assert result == expected + + ser = pd.concat([ser, Series([pd.NaT])]) + assert np.isnan(ser.dt.month_name(locale=time_locale).iloc[-1]) + + def test_strftime(self): + # GH 10086 + ser = Series(date_range("20130101", periods=5)) + result = ser.dt.strftime("%Y/%m/%d") + expected = Series( + ["2013/01/01", "2013/01/02", "2013/01/03", "2013/01/04", "2013/01/05"] + ) + tm.assert_series_equal(result, expected) + + ser = Series(date_range("2015-02-03 11:22:33.4567", periods=5)) + result = ser.dt.strftime("%Y/%m/%d %H-%M-%S") + expected = Series( + [ + "2015/02/03 11-22-33", + "2015/02/04 11-22-33", + "2015/02/05 11-22-33", + "2015/02/06 11-22-33", + "2015/02/07 11-22-33", + ] + ) + tm.assert_series_equal(result, expected) + + ser = Series(period_range("20130101", periods=5)) + result = ser.dt.strftime("%Y/%m/%d") + expected = Series( + ["2013/01/01", "2013/01/02", "2013/01/03", "2013/01/04", "2013/01/05"] + ) + tm.assert_series_equal(result, expected) + + ser = Series(period_range("2015-02-03 11:22:33.4567", periods=5, freq="s")) + result = ser.dt.strftime("%Y/%m/%d %H-%M-%S") + expected = Series( + [ + "2015/02/03 11-22-33", + "2015/02/03 11-22-34", + "2015/02/03 11-22-35", + "2015/02/03 11-22-36", + "2015/02/03 11-22-37", + ] + ) + tm.assert_series_equal(result, expected) + + def test_strftime_dt64_days(self): + ser = Series(date_range("20130101", periods=5)) + ser.iloc[0] = pd.NaT + result = ser.dt.strftime("%Y/%m/%d") + expected = Series( + [np.nan, "2013/01/02", "2013/01/03", "2013/01/04", "2013/01/05"] + ) + tm.assert_series_equal(result, expected) + + datetime_index = date_range("20150301", periods=5) + result = datetime_index.strftime("%Y/%m/%d") + + expected = Index( + ["2015/03/01", "2015/03/02", "2015/03/03", "2015/03/04", "2015/03/05"], + ) + # dtype may be S10 or U10 depending on python version + tm.assert_index_equal(result, expected) + + def test_strftime_period_days(self, using_infer_string): + period_index = period_range("20150301", periods=5) + result = period_index.strftime("%Y/%m/%d") + expected = Index( + ["2015/03/01", "2015/03/02", "2015/03/03", "2015/03/04", "2015/03/05"], + dtype="=U10", + ) + if using_infer_string: + expected = expected.astype(StringDtype(na_value=np.nan)) + tm.assert_index_equal(result, expected) + + def test_strftime_dt64_microsecond_resolution(self): + ser = Series([datetime(2013, 1, 1, 2, 32, 59), datetime(2013, 1, 2, 14, 32, 1)]) + result = ser.dt.strftime("%Y-%m-%d %H:%M:%S") + expected = Series(["2013-01-01 02:32:59", "2013-01-02 14:32:01"]) + tm.assert_series_equal(result, expected) + + def test_strftime_period_hours(self): + ser = Series(period_range("20130101", periods=4, freq="h")) + result = ser.dt.strftime("%Y/%m/%d %H:%M:%S") + expected = Series( + [ + "2013/01/01 00:00:00", + "2013/01/01 01:00:00", + "2013/01/01 02:00:00", + "2013/01/01 03:00:00", + ] + ) + tm.assert_series_equal(result, expected) + + def test_strftime_period_minutes(self): + ser = Series(period_range("20130101", periods=4, freq="ms")) + result = ser.dt.strftime("%Y/%m/%d %H:%M:%S.%l") + expected = Series( + [ + "2013/01/01 00:00:00.000", + "2013/01/01 00:00:00.001", + "2013/01/01 00:00:00.002", + "2013/01/01 00:00:00.003", + ] + ) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "data", + [ + DatetimeIndex(["2019-01-01", pd.NaT]), + PeriodIndex(["2019-01-01", pd.NaT], dtype="period[D]"), + ], + ) + def test_strftime_nat(self, data): + # GH 29578 + ser = Series(data) + result = ser.dt.strftime("%Y-%m-%d") + expected = Series(["2019-01-01", np.nan]) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "data", [DatetimeIndex([pd.NaT]), PeriodIndex([pd.NaT], dtype="period[D]")] + ) + def test_strftime_all_nat(self, data): + # https://github.com/pandas-dev/pandas/issues/45858 + ser = Series(data) + with tm.assert_produces_warning(None): + result = ser.dt.strftime("%Y-%m-%d") + expected = Series([np.nan], dtype="str") + tm.assert_series_equal(result, expected) + + def test_valid_dt_with_missing_values(self): + # GH 8689 + ser = Series(date_range("20130101", periods=5, freq="D")) + ser.iloc[2] = pd.NaT + + for attr in ["microsecond", "nanosecond", "second", "minute", "hour", "day"]: + expected = getattr(ser.dt, attr).copy() + expected.iloc[2] = np.nan + result = getattr(ser.dt, attr) + tm.assert_series_equal(result, expected) + + result = ser.dt.date + expected = Series( + [ + date(2013, 1, 1), + date(2013, 1, 2), + pd.NaT, + date(2013, 1, 4), + date(2013, 1, 5), + ], + dtype="object", + ) + tm.assert_series_equal(result, expected) + + result = ser.dt.time + expected = Series([time(0), time(0), pd.NaT, time(0), time(0)], dtype="object") + tm.assert_series_equal(result, expected) + + def test_dt_accessor_api(self): + # GH 9322 + from pandas.core.indexes.accessors import ( + CombinedDatetimelikeProperties, + DatetimeProperties, + ) + + assert Series.dt is CombinedDatetimelikeProperties + + ser = Series(date_range("2000-01-01", periods=3)) + assert isinstance(ser.dt, DatetimeProperties) + + @pytest.mark.parametrize( + "data", + [ + np.arange(5), + list("abcde"), + np.random.default_rng(2).standard_normal(5), + ], + ) + def test_dt_accessor_invalid(self, data): + # GH#9322 check that series with incorrect dtypes don't have attr + ser = Series(data) + with pytest.raises(AttributeError, match="only use .dt accessor"): + ser.dt + assert not hasattr(ser, "dt") + + def test_dt_accessor_updates_on_inplace(self): + ser = Series(date_range("2018-01-01", periods=10)) + ser[2] = None + result = ser.fillna(pd.Timestamp("2018-01-01"), inplace=True) + assert result is ser + result = ser.dt.date + assert result[0] == result[2] + + def test_date_tz(self): + # GH11757 + rng = DatetimeIndex( + ["2014-04-04 23:56", "2014-07-18 21:24", "2015-11-22 22:14"], + tz="US/Eastern", + ) + ser = Series(rng) + expected = Series([date(2014, 4, 4), date(2014, 7, 18), date(2015, 11, 22)]) + tm.assert_series_equal(ser.dt.date, expected) + tm.assert_series_equal(ser.apply(lambda x: x.date()), expected) + + def test_dt_timetz_accessor(self, tz_naive_fixture): + # GH21358 + tz = maybe_get_tz(tz_naive_fixture) + + dtindex = DatetimeIndex( + ["2014-04-04 23:56", "2014-07-18 21:24", "2015-11-22 22:14"], tz=tz + ) + ser = Series(dtindex) + expected = Series( + [time(23, 56, tzinfo=tz), time(21, 24, tzinfo=tz), time(22, 14, tzinfo=tz)] + ) + result = ser.dt.timetz + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "input_series, expected_output", + [ + [["2020-01-01"], [[2020, 1, 3]]], + [[pd.NaT], [[None, None, None]]], + [["2019-12-31", "2019-12-29"], [[2020, 1, 2], [2019, 52, 7]]], + [["2010-01-01", pd.NaT], [[2009, 53, 5], [None, None, None]]], + # see GH#36032 + [["2016-01-08", "2016-01-04"], [[2016, 1, 5], [2016, 1, 1]]], + [["2016-01-07", "2016-01-01"], [[2016, 1, 4], [2015, 53, 5]]], + ], + ) + def test_isocalendar(self, input_series, expected_output): + result = pd.to_datetime(Series(input_series)).dt.isocalendar() + expected_frame = DataFrame( + expected_output, columns=["year", "week", "day"], dtype="UInt32" + ) + tm.assert_frame_equal(result, expected_frame) + + def test_hour_index(self): + dt_series = Series( + date_range(start="2021-01-01", periods=5, freq="h"), + index=[2, 6, 7, 8, 11], + dtype="category", + ) + result = dt_series.dt.hour + expected = Series( + [0, 1, 2, 3, 4], + dtype="int32", + index=[2, 6, 7, 8, 11], + ) + tm.assert_series_equal(result, expected) + + +class TestSeriesPeriodValuesDtAccessor: + @pytest.mark.parametrize( + "input_vals", + [ + [Period("2016-01", freq="M"), Period("2016-02", freq="M")], + [Period("2016-01-01", freq="D"), Period("2016-01-02", freq="D")], + [ + Period("2016-01-01 00:00:00", freq="h"), + Period("2016-01-01 01:00:00", freq="h"), + ], + [ + Period("2016-01-01 00:00:00", freq="M"), + Period("2016-01-01 00:01:00", freq="M"), + ], + [ + Period("2016-01-01 00:00:00", freq="s"), + Period("2016-01-01 00:00:01", freq="s"), + ], + ], + ) + def test_end_time_timevalues(self, input_vals): + # GH#17157 + # Check that the time part of the Period is adjusted by end_time + # when using the dt accessor on a Series + dtype = pd.PeriodDtype(input_vals[0].freq) + input_vals = PeriodArray._from_sequence(np.asarray(input_vals), dtype=dtype) + + ser = Series(input_vals) + result = ser.dt.end_time + expected = ser.apply(lambda x: x.end_time) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("input_vals", [("2001"), ("NaT")]) + def test_to_period(self, input_vals): + # GH#21205 + expected = Series([input_vals], dtype="Period[D]") + result = Series([input_vals], dtype="datetime64[ns]").dt.to_period("D") + tm.assert_series_equal(result, expected) + + +def test_normalize_pre_epoch_dates(): + # GH: 36294 + ser = pd.to_datetime(Series(["1969-01-01 09:00:00", "2016-01-01 09:00:00"])) + result = ser.dt.normalize() + expected = pd.to_datetime(Series(["1969-01-01", "2016-01-01"])) + tm.assert_series_equal(result, expected) + + +def test_day_attribute_non_nano_beyond_int32(): + # GH 52386 + data = np.array( + [ + 136457654736252, + 134736784364431, + 245345345545332, + 223432411, + 2343241, + 3634548734, + 23234, + ], + dtype="timedelta64[s]", + ) + ser = Series(data) + result = ser.dt.days + expected = Series([1579371003, 1559453522, 2839645203, 2586, 27, 42066, 0]) + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_list_accessor.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_list_accessor.py new file mode 100644 index 0000000000000000000000000000000000000000..9ff01ebf2fc98ecf5d6c875b40fd96b80b25f5c2 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_list_accessor.py @@ -0,0 +1,146 @@ +import re + +import pytest + +from pandas import ( + ArrowDtype, + Series, +) +import pandas._testing as tm + +pa = pytest.importorskip("pyarrow") + + +@pytest.mark.parametrize( + "list_dtype", + ( + pa.list_(pa.int64()), + pa.list_(pa.int64(), list_size=3), + pa.large_list(pa.int64()), + ), +) +def test_list_getitem(list_dtype): + ser = Series( + [[1, 2, 3], [4, None, 5], None], + dtype=ArrowDtype(list_dtype), + name="a", + ) + actual = ser.list[1] + expected = Series([2, None, None], dtype="int64[pyarrow]", name="a") + tm.assert_series_equal(actual, expected) + + +def test_list_getitem_index(): + # GH 58425 + ser = Series( + [[1, 2, 3], [4, None, 5], None], + dtype=ArrowDtype(pa.list_(pa.int64())), + index=[1, 3, 7], + name="a", + ) + actual = ser.list[1] + expected = Series( + [2, None, None], + dtype="int64[pyarrow]", + index=[1, 3, 7], + name="a", + ) + tm.assert_series_equal(actual, expected) + + +def test_list_getitem_slice(): + ser = Series( + [[1, 2, 3], [4, None, 5], None], + dtype=ArrowDtype(pa.list_(pa.int64())), + index=[1, 3, 7], + name="a", + ) + actual = ser.list[1:None:None] + expected = Series( + [[2, 3], [None, 5], None], + dtype=ArrowDtype(pa.list_(pa.int64())), + index=[1, 3, 7], + name="a", + ) + tm.assert_series_equal(actual, expected) + + +def test_list_len(): + ser = Series( + [[1, 2, 3], [4, None], None], + dtype=ArrowDtype(pa.list_(pa.int64())), + name="a", + ) + actual = ser.list.len() + expected = Series([3, 2, None], dtype=ArrowDtype(pa.int32()), name="a") + tm.assert_series_equal(actual, expected) + + +def test_list_flatten(): + ser = Series( + [[1, 2, 3], None, [4, None], [], [7, 8]], + dtype=ArrowDtype(pa.list_(pa.int64())), + name="a", + ) + actual = ser.list.flatten() + expected = Series( + [1, 2, 3, 4, None, 7, 8], + dtype=ArrowDtype(pa.int64()), + index=[0, 0, 0, 2, 2, 4, 4], + name="a", + ) + tm.assert_series_equal(actual, expected) + + +def test_list_getitem_slice_invalid(): + ser = Series( + [[1, 2, 3], [4, None, 5], None], + dtype=ArrowDtype(pa.list_(pa.int64())), + ) + with tm.external_error_raised(pa.ArrowInvalid): + ser.list[1:None:0] + + +def test_list_accessor_non_list_dtype(): + ser = Series( + [1, 2, 4], + dtype=ArrowDtype(pa.int64()), + ) + with pytest.raises( + AttributeError, + match=re.escape( + "Can only use the '.list' accessor with 'list[pyarrow]' dtype, " + "not int64[pyarrow]." + ), + ): + ser.list[1:None:0] + + +@pytest.mark.parametrize( + "list_dtype", + ( + pa.list_(pa.int64()), + pa.list_(pa.int64(), list_size=3), + pa.large_list(pa.int64()), + ), +) +def test_list_getitem_invalid_index(list_dtype): + ser = Series( + [[1, 2, 3], [4, None, 5], None], + dtype=ArrowDtype(list_dtype), + ) + with tm.external_error_raised(pa.ArrowInvalid): + ser.list[-1] + with tm.external_error_raised(pa.ArrowInvalid): + ser.list[5] + with pytest.raises(ValueError, match="key must be an int or slice, got str"): + ser.list["abc"] + + +def test_list_accessor_not_iterable(): + ser = Series( + [[1, 2, 3], [4, None], None], + dtype=ArrowDtype(pa.list_(pa.int64())), + ) + with pytest.raises(TypeError, match="'ListAccessor' object is not iterable"): + iter(ser.list) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_sparse_accessor.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_sparse_accessor.py new file mode 100644 index 0000000000000000000000000000000000000000..118095b5dcdbc4f63ba511feb954673485f34427 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_sparse_accessor.py @@ -0,0 +1,9 @@ +from pandas import Series + + +class TestSparseAccessor: + def test_sparse_accessor_updates_on_inplace(self): + ser = Series([1, 1, 2, 3], dtype="Sparse[int]") + return_value = ser.drop([0, 1], inplace=True) + assert return_value is None + assert ser.sparse.density == 1.0 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_str_accessor.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_str_accessor.py new file mode 100644 index 0000000000000000000000000000000000000000..ff530459b78fbc74da55c556fa58ba051573c713 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_str_accessor.py @@ -0,0 +1,26 @@ +import pytest + +from pandas import Series +import pandas._testing as tm + + +class TestStrAccessor: + def test_str_attribute(self): + # GH#9068 + methods = ["strip", "rstrip", "lstrip"] + ser = Series([" jack", "jill ", " jesse ", "frank"]) + for method in methods: + expected = Series([getattr(str, method)(x) for x in ser.values]) + tm.assert_series_equal(getattr(Series.str, method)(ser.str), expected) + + # str accessor only valid with string values + ser = Series(range(5)) + msg = "Can only use .str accessor with string values, not integer" + with pytest.raises(AttributeError, match=msg): + ser.str.repeat(2) + + def test_str_accessor_updates_on_inplace(self): + ser = Series(list("abc")) + return_value = ser.drop([0], inplace=True) + assert return_value is None + assert len(ser.str.lower()) == 2 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_struct_accessor.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_struct_accessor.py new file mode 100644 index 0000000000000000000000000000000000000000..f0bf81621610a3290d68b62745828139614bf268 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/accessors/test_struct_accessor.py @@ -0,0 +1,188 @@ +import re + +import pytest + +from pandas import ( + ArrowDtype, + DataFrame, + Index, + Series, +) +import pandas._testing as tm + +pa = pytest.importorskip("pyarrow") +pc = pytest.importorskip("pyarrow.compute") + + +def test_struct_accessor_dtypes(): + ser = Series( + [], + dtype=ArrowDtype( + pa.struct( + [ + ("int_col", pa.int64()), + ("string_col", pa.string()), + ( + "struct_col", + pa.struct( + [ + ("int_col", pa.int64()), + ("float_col", pa.float64()), + ] + ), + ), + ] + ) + ), + ) + actual = ser.struct.dtypes + expected = Series( + [ + ArrowDtype(pa.int64()), + ArrowDtype(pa.string()), + ArrowDtype( + pa.struct( + [ + ("int_col", pa.int64()), + ("float_col", pa.float64()), + ] + ) + ), + ], + index=Index(["int_col", "string_col", "struct_col"]), + ) + tm.assert_series_equal(actual, expected) + + +def test_struct_accessor_field(): + index = Index([-100, 42, 123]) + ser = Series( + [ + {"rice": 1.0, "maize": -1, "wheat": "a"}, + {"rice": 2.0, "maize": 0, "wheat": "b"}, + {"rice": 3.0, "maize": 1, "wheat": "c"}, + ], + dtype=ArrowDtype( + pa.struct( + [ + ("rice", pa.float64()), + ("maize", pa.int64()), + ("wheat", pa.string()), + ] + ) + ), + index=index, + ) + by_name = ser.struct.field("maize") + by_name_expected = Series( + [-1, 0, 1], + dtype=ArrowDtype(pa.int64()), + index=index, + name="maize", + ) + tm.assert_series_equal(by_name, by_name_expected) + + by_index = ser.struct.field(2) + by_index_expected = Series( + ["a", "b", "c"], + dtype=ArrowDtype(pa.string()), + index=index, + name="wheat", + ) + tm.assert_series_equal(by_index, by_index_expected) + + +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, str,"): + ser.struct.field(1.1) + + +def test_struct_accessor_explode(): + index = Index([-100, 42, 123]) + ser = Series( + [ + {"painted": 1, "snapping": {"sea": "green"}}, + {"painted": 2, "snapping": {"sea": "leatherback"}}, + {"painted": 3, "snapping": {"sea": "hawksbill"}}, + ], + dtype=ArrowDtype( + pa.struct( + [ + ("painted", pa.int64()), + ("snapping", pa.struct([("sea", pa.string())])), + ] + ) + ), + index=index, + ) + actual = ser.struct.explode() + expected = DataFrame( + { + "painted": Series([1, 2, 3], index=index, dtype=ArrowDtype(pa.int64())), + "snapping": Series( + [{"sea": "green"}, {"sea": "leatherback"}, {"sea": "hawksbill"}], + index=index, + dtype=ArrowDtype(pa.struct([("sea", pa.string())])), + ), + }, + ) + tm.assert_frame_equal(actual, expected) + + +@pytest.mark.parametrize( + "invalid", + [ + pytest.param(Series([1, 2, 3], dtype="int64"), id="int64"), + pytest.param( + Series(["a", "b", "c"], dtype="string[pyarrow]"), id="string-pyarrow" + ), + ], +) +def test_struct_accessor_api_for_invalid(invalid): + with pytest.raises( + AttributeError, + match=re.escape( + "Can only use the '.struct' accessor with 'struct[pyarrow]' dtype, " + f"not {invalid.dtype}." + ), + ): + 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 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/__init__.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_datetime.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_datetime.py new file mode 100644 index 0000000000000000000000000000000000000000..9d0ae973d6c662fc53aada8e8dd383af393eb7a8 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_datetime.py @@ -0,0 +1,493 @@ +""" +Also test support for datetime64[ns] in Series / DataFrame +""" + +from datetime import ( + datetime, + timedelta, +) +import re + +from dateutil.tz import ( + gettz, + tzutc, +) +import numpy as np +import pytest + +from pandas._libs import index as libindex + +import pandas as pd +from pandas import ( + DataFrame, + Series, + Timestamp, + date_range, + period_range, +) +import pandas._testing as tm + + +def test_fancy_getitem(): + dti = date_range( + freq="WOM-1FRI", start=datetime(2005, 1, 1), end=datetime(2010, 1, 1) + ) + + s = Series(np.arange(len(dti)), index=dti) + + assert s["1/2/2009"] == 48 + assert s["2009-1-2"] == 48 + assert s[datetime(2009, 1, 2)] == 48 + assert s[Timestamp(datetime(2009, 1, 2))] == 48 + with pytest.raises(KeyError, match=r"^'2009-1-3'$"): + s["2009-1-3"] + tm.assert_series_equal( + s["3/6/2009":"2009-06-05"], s[datetime(2009, 3, 6) : datetime(2009, 6, 5)] + ) + + +def test_fancy_setitem(): + dti = date_range( + freq="WOM-1FRI", start=datetime(2005, 1, 1), end=datetime(2010, 1, 1) + ) + + s = Series(np.arange(len(dti)), index=dti) + + s["1/2/2009"] = -2 + assert s.iloc[48] == -2 + s["1/2/2009":"2009-06-05"] = -3 + assert (s[48:54] == -3).all() + + +@pytest.mark.parametrize("tz_source", ["pytz", "dateutil"]) +def test_getitem_setitem_datetime_tz(tz_source): + if tz_source == "pytz": + pytz = pytest.importorskip(tz_source) + tzget = pytz.timezone + else: + # handle special case for utc in dateutil + tzget = lambda x: tzutc() if x == "UTC" else gettz(x) + + N = 50 + # testing with timezone, GH #2785 + rng = date_range("1/1/1990", periods=N, freq="h", tz=tzget("US/Eastern")) + ts = Series(np.random.default_rng(2).standard_normal(N), index=rng) + + # also test Timestamp tz handling, GH #2789 + result = ts.copy() + result["1990-01-01 09:00:00+00:00"] = 0 + result["1990-01-01 09:00:00+00:00"] = ts.iloc[4] + tm.assert_series_equal(result, ts) + + result = ts.copy() + result["1990-01-01 03:00:00-06:00"] = 0 + result["1990-01-01 03:00:00-06:00"] = ts.iloc[4] + tm.assert_series_equal(result, ts) + + # repeat with datetimes + result = ts.copy() + result[datetime(1990, 1, 1, 9, tzinfo=tzget("UTC"))] = 0 + result[datetime(1990, 1, 1, 9, tzinfo=tzget("UTC"))] = ts.iloc[4] + tm.assert_series_equal(result, ts) + + result = ts.copy() + dt = Timestamp(1990, 1, 1, 3).tz_localize(tzget("US/Central")) + dt = dt.to_pydatetime() + result[dt] = 0 + result[dt] = ts.iloc[4] + tm.assert_series_equal(result, ts) + + +def test_getitem_setitem_datetimeindex(): + N = 50 + # testing with timezone, GH #2785 + rng = date_range("1/1/1990", periods=N, freq="h", tz="US/Eastern", unit="ns") + ts = Series(np.random.default_rng(2).standard_normal(N), index=rng) + + result = ts["1990-01-01 04:00:00"] + expected = ts.iloc[4] + assert result == expected + + result = ts.copy() + result["1990-01-01 04:00:00"] = 0 + result["1990-01-01 04:00:00"] = ts.iloc[4] + tm.assert_series_equal(result, ts) + + result = ts["1990-01-01 04:00:00":"1990-01-01 07:00:00"] + expected = ts[4:8] + tm.assert_series_equal(result, expected) + + result = ts.copy() + result["1990-01-01 04:00:00":"1990-01-01 07:00:00"] = 0 + result["1990-01-01 04:00:00":"1990-01-01 07:00:00"] = ts[4:8] + tm.assert_series_equal(result, ts) + + lb = "1990-01-01 04:00:00" + rb = "1990-01-01 07:00:00" + # GH#18435 strings get a pass from tzawareness compat + result = ts[(ts.index >= lb) & (ts.index <= rb)] + expected = ts[4:8] + tm.assert_series_equal(result, expected) + + lb = "1990-01-01 04:00:00-0500" + rb = "1990-01-01 07:00:00-0500" + result = ts[(ts.index >= lb) & (ts.index <= rb)] + expected = ts[4:8] + tm.assert_series_equal(result, expected) + + # But we do not give datetimes a pass on tzawareness compat + msg = "Cannot compare tz-naive and tz-aware datetime-like objects" + naive = datetime(1990, 1, 1, 4) + for key in [naive, Timestamp(naive), np.datetime64(naive, "ns")]: + with pytest.raises(KeyError, match=re.escape(repr(key))): + # GH#36148 as of 2.0 we require tzawareness-compat + ts[key] + + result = ts.copy() + # GH#36148 as of 2.0 we do not ignore tzawareness mismatch in indexing, + # so setting it as a new key casts to object rather than matching + # rng[4] + result[naive] = ts.iloc[4] + assert result.index.dtype == object + tm.assert_index_equal(result.index[:-1], rng.astype(object)) + assert result.index[-1] == naive + + msg = "Cannot compare tz-naive and tz-aware datetime-like objects" + with pytest.raises(TypeError, match=msg): + # GH#36148 require tzawareness compat as of 2.0 + ts[naive : datetime(1990, 1, 1, 7)] + + result = ts.copy() + with pytest.raises(TypeError, match=msg): + # GH#36148 require tzawareness compat as of 2.0 + result[naive : datetime(1990, 1, 1, 7)] = 0 + with pytest.raises(TypeError, match=msg): + # GH#36148 require tzawareness compat as of 2.0 + result[naive : datetime(1990, 1, 1, 7)] = 99 + # the __setitems__ here failed, so result should still match ts + tm.assert_series_equal(result, ts) + + lb = naive + rb = datetime(1990, 1, 1, 7) + msg = r"Invalid comparison between dtype=datetime64\[ns, US/Eastern\] and datetime" + with pytest.raises(TypeError, match=msg): + # tznaive vs tzaware comparison is invalid + # see GH#18376, GH#18162 + ts[(ts.index >= lb) & (ts.index <= rb)] + + lb = Timestamp(naive).tz_localize(rng.tzinfo) + rb = Timestamp(datetime(1990, 1, 1, 7)).tz_localize(rng.tzinfo) + result = ts[(ts.index >= lb) & (ts.index <= rb)] + expected = ts[4:8] + tm.assert_series_equal(result, expected) + + result = ts[ts.index[4]] + expected = ts.iloc[4] + assert result == expected + + result = ts[ts.index[4:8]] + expected = ts[4:8] + tm.assert_series_equal(result, expected) + + result = ts.copy() + result[ts.index[4:8]] = 0 + result.iloc[4:8] = ts.iloc[4:8] + tm.assert_series_equal(result, ts) + + # also test partial date slicing + result = ts["1990-01-02"] + expected = ts[24:48] + tm.assert_series_equal(result, expected) + + result = ts.copy() + result["1990-01-02"] = 0 + result["1990-01-02"] = ts[24:48] + tm.assert_series_equal(result, ts) + + +def test_getitem_setitem_periodindex(): + N = 50 + rng = period_range("1/1/1990", periods=N, freq="h") + ts = Series(np.random.default_rng(2).standard_normal(N), index=rng) + + result = ts["1990-01-01 04"] + expected = ts.iloc[4] + assert result == expected + + result = ts.copy() + result["1990-01-01 04"] = 0 + result["1990-01-01 04"] = ts.iloc[4] + tm.assert_series_equal(result, ts) + + result = ts["1990-01-01 04":"1990-01-01 07"] + expected = ts[4:8] + tm.assert_series_equal(result, expected) + + result = ts.copy() + result["1990-01-01 04":"1990-01-01 07"] = 0 + result["1990-01-01 04":"1990-01-01 07"] = ts[4:8] + tm.assert_series_equal(result, ts) + + lb = "1990-01-01 04" + rb = "1990-01-01 07" + result = ts[(ts.index >= lb) & (ts.index <= rb)] + expected = ts[4:8] + tm.assert_series_equal(result, expected) + + # GH 2782 + result = ts[ts.index[4]] + expected = ts.iloc[4] + assert result == expected + + result = ts[ts.index[4:8]] + expected = ts[4:8] + tm.assert_series_equal(result, expected) + + result = ts.copy() + result[ts.index[4:8]] = 0 + result.iloc[4:8] = ts.iloc[4:8] + tm.assert_series_equal(result, ts) + + +def test_datetime_indexing(): + index = date_range("1/1/2000", "1/7/2000") + index = index.repeat(3) + + s = Series(len(index), index=index) + stamp = Timestamp("1/8/2000") + + with pytest.raises(KeyError, match=re.escape(repr(stamp))): + s[stamp] + s[stamp] = 0 + assert s[stamp] == 0 + + # not monotonic + s = Series(len(index), index=index) + s = s[::-1] + + with pytest.raises(KeyError, match=re.escape(repr(stamp))): + s[stamp] + s[stamp] = 0 + assert s[stamp] == 0 + + +# test duplicates in time series + + +def test_indexing_with_duplicate_datetimeindex( + rand_series_with_duplicate_datetimeindex, +): + ts = rand_series_with_duplicate_datetimeindex + + uniques = ts.index.unique() + for date in uniques: + result = ts[date] + + mask = ts.index == date + total = (ts.index == date).sum() + expected = ts[mask] + if total > 1: + tm.assert_series_equal(result, expected) + else: + tm.assert_almost_equal(result, expected.iloc[0]) + + cp = ts.copy() + cp[date] = 0 + expected = Series(np.where(mask, 0, ts), index=ts.index) + tm.assert_series_equal(cp, expected) + + key = datetime(2000, 1, 6) + with pytest.raises(KeyError, match=re.escape(repr(key))): + ts[key] + + # new index + ts[datetime(2000, 1, 6)] = 0 + assert ts[datetime(2000, 1, 6)] == 0 + + +def test_loc_getitem_over_size_cutoff(monkeypatch): + # #1821 + + monkeypatch.setattr(libindex, "_SIZE_CUTOFF", 1000) + + # create large list of non periodic datetime + dates = [] + sec = timedelta(seconds=1) + half_sec = timedelta(microseconds=500000) + d = datetime(2011, 12, 5, 20, 30) + n = 1100 + for i in range(n): + dates.append(d) + dates.append(d + sec) + dates.append(d + sec + half_sec) + dates.append(d + sec + sec + half_sec) + d += 3 * sec + + # duplicate some values in the list + duplicate_positions = np.random.default_rng(2).integers(0, len(dates) - 1, 20) + for p in duplicate_positions: + dates[p + 1] = dates[p] + + df = DataFrame( + np.random.default_rng(2).standard_normal((len(dates), 4)), + index=dates, + columns=list("ABCD"), + ) + + pos = n * 3 + timestamp = df.index[pos] + assert timestamp in df.index + + # it works! + df.loc[timestamp] + assert len(df.loc[[timestamp]]) > 0 + + +def test_indexing_over_size_cutoff_period_index(monkeypatch): + # GH 27136 + + monkeypatch.setattr(libindex, "_SIZE_CUTOFF", 1000) + + n = 1100 + idx = period_range("1/1/2000", freq="min", periods=n) + assert idx._engine.over_size_threshold + + s = Series(np.random.default_rng(2).standard_normal(len(idx)), index=idx) + + pos = n - 1 + timestamp = idx[pos] + assert timestamp in s.index + + # it works! + s[timestamp] + assert len(s.loc[[timestamp]]) > 0 + + +def test_indexing_unordered(): + # GH 2437 + rng = date_range(start="2011-01-01", end="2011-01-15") + ts = Series(np.random.default_rng(2).random(len(rng)), index=rng) + ts2 = pd.concat([ts[0:4], ts[-4:], ts[4:-4]]) + + for t in ts.index: + expected = ts[t] + result = ts2[t] + assert expected == result + + # GH 3448 (ranges) + def compare(slobj): + result = ts2[slobj].copy() + result = result.sort_index() + expected = ts[slobj] + expected.index = expected.index._with_freq(None) + tm.assert_series_equal(result, expected) + + for key in [ + slice("2011-01-01", "2011-01-15"), + slice("2010-12-30", "2011-01-15"), + slice("2011-01-01", "2011-01-16"), + # partial ranges + slice("2011-01-01", "2011-01-6"), + slice("2011-01-06", "2011-01-8"), + slice("2011-01-06", "2011-01-12"), + ]: + with pytest.raises( + KeyError, match="Value based partial slicing on non-monotonic" + ): + compare(key) + + # single values + result = ts2["2011"].sort_index() + expected = ts["2011"] + expected.index = expected.index._with_freq(None) + tm.assert_series_equal(result, expected) + + +def test_indexing_unordered2(): + # diff freq + rng = date_range(datetime(2005, 1, 1), periods=20, freq="ME") + ts = Series(np.arange(len(rng)), index=rng) + ts = ts.take(np.random.default_rng(2).permutation(20)) + + result = ts["2005"] + for t in result.index: + assert t.year == 2005 + + +def test_indexing(): + idx = date_range("2001-1-1", periods=20, freq="ME") + ts = Series(np.random.default_rng(2).random(len(idx)), index=idx) + + # getting + + # GH 3070, make sure semantics work on Series/Frame + result = ts["2001"] + tm.assert_series_equal(result, ts.iloc[:12]) + + df = DataFrame({"A": ts}) + + # GH#36179 pre-2.0 df["2001"] operated as slicing on rows. in 2.0 it behaves + # like any other key, so raises + with pytest.raises(KeyError, match="2001"): + df["2001"] + + # setting + ts = Series(np.random.default_rng(2).random(len(idx)), index=idx) + expected = ts.copy() + expected.iloc[:12] = 1 + ts["2001"] = 1 + tm.assert_series_equal(ts, expected) + + expected = df.copy() + expected.iloc[:12, 0] = 1 + df.loc["2001", "A"] = 1 + tm.assert_frame_equal(df, expected) + + +def test_getitem_str_month_with_datetimeindex(): + # GH3546 (not including times on the last day) + idx = date_range(start="2013-05-31 00:00", end="2013-05-31 23:00", freq="h") + ts = Series(range(len(idx)), index=idx) + expected = ts["2013-05"] + tm.assert_series_equal(expected, ts) + + idx = date_range(start="2013-05-31 00:00", end="2013-05-31 23:59", freq="s") + ts = Series(range(len(idx)), index=idx) + expected = ts["2013-05"] + tm.assert_series_equal(expected, ts) + + +def test_getitem_str_year_with_datetimeindex(): + idx = [ + Timestamp("2013-05-31 00:00"), + Timestamp(datetime(2013, 5, 31, 23, 59, 59, 999999)), + ] + ts = Series(range(len(idx)), index=idx) + expected = ts["2013"] + tm.assert_series_equal(expected, ts) + + +def test_getitem_str_second_with_datetimeindex(): + # GH14826, indexing with a seconds resolution string / datetime object + df = DataFrame( + np.random.default_rng(2).random((5, 5)), + columns=["open", "high", "low", "close", "volume"], + index=date_range("2012-01-02 18:01:00", periods=5, tz="US/Central", freq="s"), + ) + + # this is a single date, so will raise + with pytest.raises(KeyError, match=r"^'2012-01-02 18:01:02'$"): + df["2012-01-02 18:01:02"] + + msg = r"Timestamp\('2012-01-02 18:01:02-0600', tz='US/Central'\)" + with pytest.raises(KeyError, match=msg): + df[df.index[2]] + + +def test_compare_datetime_with_all_none(): + # GH#54870 + ser = Series(["2020-01-01", "2020-01-02"], dtype="datetime64[ns]") + ser2 = Series([None, None]) + result = ser > ser2 + expected = Series([False, False]) + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_delitem.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_delitem.py new file mode 100644 index 0000000000000000000000000000000000000000..7440ef2692c47fbce129f06276a8e8761a23cf2b --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_delitem.py @@ -0,0 +1,69 @@ +import pytest + +from pandas import ( + Index, + Series, + date_range, +) +import pandas._testing as tm + + +class TestSeriesDelItem: + def test_delitem(self): + # GH#5542 + # should delete the item inplace + s = Series(range(5)) + del s[0] + + expected = Series(range(1, 5), index=range(1, 5)) + tm.assert_series_equal(s, expected) + + del s[1] + expected = Series(range(2, 5), index=range(2, 5)) + tm.assert_series_equal(s, expected) + + # only 1 left, del, add, del + s = Series(1) + del s[0] + tm.assert_series_equal(s, Series(dtype="int64", index=Index([], dtype="int64"))) + s[0] = 1 + tm.assert_series_equal(s, Series(1)) + del s[0] + tm.assert_series_equal(s, Series(dtype="int64", index=Index([], dtype="int64"))) + + def test_delitem_object_index(self): + # Index(dtype=object) + s = Series(1, index=Index(["a"], dtype="str")) + del s["a"] + tm.assert_series_equal(s, Series(dtype="int64", index=Index([], dtype="str"))) + s["a"] = 1 + tm.assert_series_equal(s, Series(1, index=Index(["a"], dtype="str"))) + del s["a"] + tm.assert_series_equal(s, Series(dtype="int64", index=Index([], dtype="str"))) + + def test_delitem_missing_key(self): + # empty + s = Series(dtype=object) + + with pytest.raises(KeyError, match=r"^0$"): + del s[0] + + def test_delitem_extension_dtype(self): + # GH#40386 + # DatetimeTZDtype + dti = date_range("2016-01-01", periods=3, tz="US/Pacific") + ser = Series(dti) + + expected = ser[[0, 2]] + del ser[1] + assert ser.dtype == dti.dtype + tm.assert_series_equal(ser, expected) + + # PeriodDtype + pi = dti.tz_localize(None).to_period("D") + ser = Series(pi) + + expected = ser[:2] + del ser[2] + assert ser.dtype == pi.dtype + tm.assert_series_equal(ser, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_get.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_get.py new file mode 100644 index 0000000000000000000000000000000000000000..5ff92ca89efba514e1b0a795f1faa66934248132 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_get.py @@ -0,0 +1,228 @@ +import numpy as np +import pytest + +import pandas as pd +from pandas import ( + DatetimeIndex, + Index, + Series, + date_range, +) +import pandas._testing as tm + + +def test_get(): + # GH 6383 + s = Series( + np.array( + [ + 43, + 48, + 60, + 48, + 50, + 51, + 50, + 45, + 57, + 48, + 56, + 45, + 51, + 39, + 55, + 43, + 54, + 52, + 51, + 54, + ] + ) + ) + + result = s.get(25, 0) + expected = 0 + assert result == expected + + s = Series( + np.array( + [ + 43, + 48, + 60, + 48, + 50, + 51, + 50, + 45, + 57, + 48, + 56, + 45, + 51, + 39, + 55, + 43, + 54, + 52, + 51, + 54, + ] + ), + index=Index( + [ + 25.0, + 36.0, + 49.0, + 64.0, + 81.0, + 100.0, + 121.0, + 144.0, + 169.0, + 196.0, + 1225.0, + 1296.0, + 1369.0, + 1444.0, + 1521.0, + 1600.0, + 1681.0, + 1764.0, + 1849.0, + 1936.0, + ], + dtype=np.float64, + ), + ) + + result = s.get(25, 0) + expected = 43 + assert result == expected + + # GH 7407 + # with a boolean accessor + df = pd.DataFrame({"i": [0] * 3, "b": [False] * 3}) + vc = df.i.value_counts() + result = vc.get(99, default="Missing") + assert result == "Missing" + + vc = df.b.value_counts() + result = vc.get(False, default="Missing") + assert result == 3 + + result = vc.get(True, default="Missing") + assert result == "Missing" + + +def test_get_nan(float_numpy_dtype): + # GH 8569 + s = Index(range(10), dtype=float_numpy_dtype).to_series() + assert s.get(np.nan) is None + assert s.get(np.nan, default="Missing") == "Missing" + + +def test_get_nan_multiple(float_numpy_dtype): + # GH 8569 + # ensure that fixing "test_get_nan" above hasn't broken get + # with multiple elements + s = Index(range(10), dtype=float_numpy_dtype).to_series() + + idx = [2, 30] + assert s.get(idx) is None + + idx = [2, np.nan] + assert s.get(idx) is None + + # GH 17295 - all missing keys + idx = [20, 30] + assert s.get(idx) is None + + idx = [np.nan, np.nan] + assert s.get(idx) is None + + +def test_get_with_default(): + # GH#7725 + d0 = ["a", "b", "c", "d"] + d1 = np.arange(4, dtype="int64") + + for data, index in ((d0, d1), (d1, d0)): + s = Series(data, index=index) + for i, d in zip(index, data): + assert s.get(i) == d + assert s.get(i, d) == d + assert s.get(i, "z") == d + + assert s.get("e", "z") == "z" + assert s.get("e", "e") == "e" + + assert s.get(10, "z") == "z" + assert s.get(10, 10) == 10 + + +@pytest.mark.parametrize( + "arr", + [ + np.random.default_rng(2).standard_normal(10), + DatetimeIndex(date_range("2020-01-01", periods=10), name="a").tz_localize( + tz="US/Eastern" + ), + ], +) +def test_get_with_ea(arr): + # GH#21260 + ser = Series(arr, index=[2 * i for i in range(len(arr))]) + assert ser.get(4) == ser.iloc[2] + + result = ser.get([4, 6]) + expected = ser.iloc[[2, 3]] + tm.assert_series_equal(result, expected) + + result = ser.get(slice(2)) + expected = ser.iloc[[0, 1]] + tm.assert_series_equal(result, expected) + + assert ser.get(-1) is None + assert ser.get(ser.index.max() + 1) is None + + ser = Series(arr[:6], index=list("abcdef")) + assert ser.get("c") == ser.iloc[2] + + result = ser.get(slice("b", "d")) + expected = ser.iloc[[1, 2, 3]] + tm.assert_series_equal(result, expected) + + result = ser.get("Z") + assert result is None + + # As of 3.0, ints are treated as labels + assert ser.get(4) is None + assert ser.get(-1) is None + assert ser.get(len(ser)) is None + + # GH#21257 + ser = Series(arr) + ser2 = ser[::2] + assert ser2.get(1) is None + + +def test_getitem_get(string_series, object_series): + for obj in [string_series, object_series]: + idx = obj.index[5] + + assert obj[idx] == obj.get(idx) + assert obj[idx] == obj.iloc[5] + + # As of 3.0, ints are treated as labels + assert string_series.get(-1) is None + assert string_series.iloc[5] == string_series.get(string_series.index[5]) + + +def test_get_none(): + # GH#5652 + s1 = Series(dtype=object) + s2 = Series(dtype=object, index=list("abc")) + for s in [s1, s2]: + result = s.get(None) + assert result is None diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_getitem.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_getitem.py new file mode 100644 index 0000000000000000000000000000000000000000..37d6c9b42e0037c98f4df2c7931b6f65957660fc --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_getitem.py @@ -0,0 +1,721 @@ +""" +Series.__getitem__ test classes are organized by the type of key passed. +""" + +from datetime import ( + date, + datetime, + time, +) + +import numpy as np +import pytest + +from pandas._libs.tslibs import ( + conversion, + timezones, +) +from pandas.compat.numpy import np_version_gt2 +from pandas.errors import Pandas4Warning + +from pandas.core.dtypes.common import is_scalar + +import pandas as pd +from pandas import ( + Categorical, + DataFrame, + DatetimeIndex, + Index, + Series, + Timestamp, + date_range, + period_range, + timedelta_range, +) +import pandas._testing as tm +from pandas.core.indexing import IndexingError + +from pandas.tseries.offsets import BDay + + +class TestSeriesGetitemScalars: + def test_getitem_object_index_float_string(self): + # GH#17286 + ser = Series([1] * 4, index=Index(["a", "b", "c", 1.0])) + assert ser["a"] == 1 + assert ser[1.0] == 1 + + def test_getitem_float_keys_tuple_values(self): + # see GH#13509 + + # unique Index + ser = Series([(1, 1), (2, 2), (3, 3)], index=[0.0, 0.1, 0.2], name="foo") + result = ser[0.0] + assert result == (1, 1) + + # non-unique Index + expected = Series([(1, 1), (2, 2)], index=[0.0, 0.0], name="foo") + ser = Series([(1, 1), (2, 2), (3, 3)], index=[0.0, 0.0, 0.2], name="foo") + + result = ser[0.0] + tm.assert_series_equal(result, expected) + + def test_getitem_unrecognized_scalar(self): + # GH#32684 a scalar key that is not recognized by lib.is_scalar + + # a series that might be produced via `frame.dtypes` + ser = Series([1, 2], index=[np.dtype("O"), np.dtype("i8")]) + + key = ser.index[1] + + result = ser[key] + assert result == 2 + + def test_getitem_negative_out_of_bounds(self): + ser = Series(["a"] * 10, index=["a"] * 10) + + with pytest.raises(KeyError, match="^-11$"): + ser[-11] + + def test_getitem_out_of_bounds_indexerror(self, datetime_series): + # don't segfault, GH#495 + N = len(datetime_series) + with pytest.raises(KeyError, match=str(N)): + datetime_series[N] + + def test_getitem_out_of_bounds_empty_rangeindex_keyerror(self): + # GH#917 + # With a RangeIndex, an int key gives a KeyError + ser = Series([], dtype=object) + with pytest.raises(KeyError, match="-1"): + ser[-1] + + def test_getitem_keyerror_with_integer_index(self, any_int_numpy_dtype): + dtype = any_int_numpy_dtype + ser = Series( + np.random.default_rng(2).standard_normal(6), + index=Index([0, 0, 1, 1, 2, 2], dtype=dtype), + ) + + with pytest.raises(KeyError, match=r"^5$"): + ser[5] + + with pytest.raises(KeyError, match=r"^'c'$"): + ser["c"] + + # not monotonic + ser = Series( + np.random.default_rng(2).standard_normal(6), index=[2, 2, 0, 0, 1, 1] + ) + + with pytest.raises(KeyError, match=r"^5$"): + ser[5] + + with pytest.raises(KeyError, match=r"^'c'$"): + ser["c"] + + def test_getitem_int64(self, datetime_series): + if np_version_gt2: + msg = r"^np.int64\(5\)$" + else: + msg = "^5$" + idx = np.int64(5) + with pytest.raises(KeyError, match=msg): + datetime_series[idx] + + def test_getitem_full_range(self): + # github.com/pandas-dev/pandas/commit/4f433773141d2eb384325714a2776bcc5b2e20f7 + ser = Series(range(5), index=list(range(5))) + result = ser[list(range(5))] + tm.assert_series_equal(result, ser) + + # ------------------------------------------------------------------ + # Series with DatetimeIndex + + @pytest.mark.parametrize("tzstr", ["Europe/Berlin", "dateutil/Europe/Berlin"]) + def test_getitem_pydatetime_tz(self, tzstr): + tz = timezones.maybe_get_tz(tzstr) + + index = date_range( + start="2012-12-24 16:00", end="2012-12-24 18:00", freq="h", tz=tzstr + ) + ts = Series(index=index, data=index.hour) + time_pandas = Timestamp("2012-12-24 17:00", tz=tzstr) + + dt = datetime(2012, 12, 24, 17, 0) + time_datetime = conversion.localize_pydatetime(dt, tz) + assert ts[time_pandas] == ts[time_datetime] + + @pytest.mark.parametrize("tz", ["US/Eastern", "dateutil/US/Eastern"]) + def test_string_index_alias_tz_aware(self, tz): + rng = date_range("1/1/2000", periods=10, tz=tz) + ser = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng) + + result = ser["1/3/2000"] + tm.assert_almost_equal(result, ser.iloc[2]) + + def test_getitem_time_object(self): + rng = date_range("1/1/2000", "1/5/2000", freq="5min") + ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng) + + mask = (rng.hour == 9) & (rng.minute == 30) + result = ts[time(9, 30)] + expected = ts[mask] + result.index = result.index._with_freq(None) + tm.assert_series_equal(result, expected) + + # ------------------------------------------------------------------ + # Series with CategoricalIndex + + def test_getitem_scalar_categorical_index(self): + cats = Categorical([Timestamp("12-31-1999"), Timestamp("12-31-2000")]) + + ser = Series([1, 2], index=cats) + + expected = ser.iloc[0] + result = ser[cats[0]] + assert result == expected + + def test_getitem_numeric_categorical_listlike_matches_scalar(self): + # GH#15470 + ser = Series(["a", "b", "c"], index=pd.CategoricalIndex([2, 1, 0])) + + # 0 is treated as a label + assert ser[0] == "c" + + # the listlike analogue should also be treated as labels + res = ser[[0]] + expected = ser.iloc[-1:] + tm.assert_series_equal(res, expected) + + res2 = ser[[0, 1, 2]] + tm.assert_series_equal(res2, ser.iloc[::-1]) + + def test_getitem_integer_categorical_not_positional(self): + # GH#14865 + ser = Series(["a", "b", "c"], index=Index([1, 2, 3], dtype="category")) + assert ser.get(3) == "c" + assert ser[3] == "c" + + def test_getitem_str_with_timedeltaindex(self): + rng = timedelta_range("1 day 10:11:12", freq="h", periods=500) + ser = Series(np.arange(len(rng)), index=rng) + + key = "6 days, 23:11:12" + indexer = rng.get_loc(key) + assert indexer == 133 + + result = ser[key] + assert result == ser.iloc[133] + + msg = r"^Timedelta\('50 days 00:00:00'\)$" + with pytest.raises(KeyError, match=msg): + rng.get_loc("50 days") + with pytest.raises(KeyError, match=msg): + ser["50 days"] + + def test_getitem_bool_index_positional(self): + # GH#48653 + ser = Series({True: 1, False: 0}) + with pytest.raises(KeyError, match="^0$"): + ser[0] + + +class TestSeriesGetitemSlices: + def test_getitem_partial_str_slice_with_datetimeindex(self): + # GH#34860 + arr = date_range("1/1/2008", "1/1/2009") + ser = arr.to_series() + result = ser["2008"] + + rng = date_range(start="2008-01-01", end="2008-12-31") + expected = Series(rng, index=rng) + + tm.assert_series_equal(result, expected) + + def test_getitem_slice_strings_with_datetimeindex(self): + idx = DatetimeIndex( + ["1/1/2000", "1/2/2000", "1/2/2000", "1/3/2000", "1/4/2000"] + ) + + ts = Series(np.random.default_rng(2).standard_normal(len(idx)), index=idx) + + result = ts["1/2/2000":] + expected = ts[1:] + tm.assert_series_equal(result, expected) + + result = ts["1/2/2000":"1/3/2000"] + expected = ts[1:4] + tm.assert_series_equal(result, expected) + + def test_getitem_partial_str_slice_with_timedeltaindex(self): + rng = timedelta_range("1 day 10:11:12", freq="h", periods=500) + ser = Series(np.arange(len(rng)), index=rng) + + result = ser["5 day":"6 day"] + expected = ser.iloc[86:134] + tm.assert_series_equal(result, expected) + + result = ser["5 day":] + expected = ser.iloc[86:] + tm.assert_series_equal(result, expected) + + result = ser[:"6 day"] + expected = ser.iloc[:134] + tm.assert_series_equal(result, expected) + + def test_getitem_partial_str_slice_high_reso_with_timedeltaindex(self): + # higher reso + rng = timedelta_range("1 day 10:11:12", freq="us", periods=2000) + ser = Series(np.arange(len(rng)), index=rng) + + result = ser["1 day 10:11:12":] + expected = ser.iloc[0:] + tm.assert_series_equal(result, expected) + + result = ser["1 day 10:11:12.001":] + expected = ser.iloc[1000:] + tm.assert_series_equal(result, expected) + + result = ser["1 days, 10:11:12.001001"] + assert result == ser.iloc[1001] + + def test_getitem_slice_2d(self, datetime_series): + # GH#30588 multi-dimensional indexing deprecated + with pytest.raises(ValueError, match="Multi-dimensional indexing"): + datetime_series[:, np.newaxis] + + def test_getitem_median_slice_bug(self): + index = date_range("20090415", "20090519", freq="2B") + ser = Series(np.random.default_rng(2).standard_normal(13), index=index) + + indexer = [slice(6, 7, None)] + msg = "Indexing with a single-item list" + with pytest.raises(ValueError, match=msg): + # GH#31299 + ser[indexer] + # but we're OK with a single-element tuple + result = ser[(indexer[0],)] + expected = ser[indexer[0]] + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "slc, positions", + [ + [slice(date(2018, 1, 1), None), [0, 1, 2]], + [slice(date(2019, 1, 2), None), [2]], + [slice(date(2020, 1, 1), None), []], + [slice(None, date(2020, 1, 1)), [0, 1, 2]], + [slice(None, date(2019, 1, 1)), [0]], + ], + ) + def test_getitem_slice_date(self, slc, positions): + # https://github.com/pandas-dev/pandas/issues/31501 + ser = Series( + [0, 1, 2], + DatetimeIndex(["2019-01-01", "2019-01-01T06:00:00", "2019-01-02"]), + ) + msg = "Slicing with a datetime.date object is deprecated" + with tm.assert_produces_warning(Pandas4Warning, match=msg): + result = ser[slc] + expected = ser.take(positions) + tm.assert_series_equal(result, expected) + + def test_getitem_slice_float_raises(self, datetime_series): + msg = ( + "cannot do slice indexing on DatetimeIndex with these indexers " + r"\[{key}\] of type float" + ) + with pytest.raises(TypeError, match=msg.format(key=r"4\.0")): + datetime_series[4.0:10.0] + + with pytest.raises(TypeError, match=msg.format(key=r"4\.5")): + datetime_series[4.5:10.0] + + def test_getitem_slice_bug(self): + ser = Series(range(10), index=list(range(10))) + result = ser[-12:] + tm.assert_series_equal(result, ser) + + result = ser[-7:] + tm.assert_series_equal(result, ser[3:]) + + result = ser[:-12] + tm.assert_series_equal(result, ser[:0]) + + def test_getitem_slice_integers(self): + ser = Series( + np.random.default_rng(2).standard_normal(8), + index=[2, 4, 6, 8, 10, 12, 14, 16], + ) + + result = ser[:4] + expected = Series(ser.values[:4], index=[2, 4, 6, 8]) + tm.assert_series_equal(result, expected) + + +class TestSeriesGetitemListLike: + @pytest.mark.parametrize("box", [list, np.array, Index, Series]) + def test_getitem_no_matches(self, box): + # GH#33462 we expect the same behavior for list/ndarray/Index/Series + ser = Series(["A", "B"]) + + key = Series(["C"], dtype=object) + key = box(key) + + msg = r"None of \[Index\(\['C'\], dtype='object|str'\)\] are in the \[index\]" + with pytest.raises(KeyError, match=msg): + ser[key] + + def test_getitem_intlist_intindex_periodvalues(self): + ser = Series(period_range("2000-01-01", periods=10, freq="D")) + + result = ser[[2, 4]] + exp = Series( + [pd.Period("2000-01-03", freq="D"), pd.Period("2000-01-05", freq="D")], + index=[2, 4], + dtype="Period[D]", + ) + tm.assert_series_equal(result, exp) + assert result.dtype == "Period[D]" + + @pytest.mark.parametrize("box", [list, np.array, Index]) + def test_getitem_intlist_intervalindex_non_int(self, box): + # GH#33404 fall back to positional since ints are unambiguous; + # changed in 3.0 to never fallback + dti = date_range("2000-01-03", periods=3)._with_freq(None) + ii = pd.IntervalIndex.from_breaks(dti) + ser = Series(range(len(ii)), index=ii) + + key = box([0]) + msg = r"None of \[Index\(\[0\], dtype='int(32|64)'\)\] are in the \[index\]" + with pytest.raises(KeyError, match=msg): + ser[key] + + @pytest.mark.parametrize("box", [list, np.array, Index]) + @pytest.mark.parametrize("dtype", [np.int64, np.float64, np.uint64]) + def test_getitem_intlist_multiindex_numeric_level(self, dtype, box): + # GH#33404 do _not_ fall back to positional since ints are ambiguous + idx = Index(range(4)).astype(dtype) + dti = date_range("2000-01-03", periods=3) + mi = pd.MultiIndex.from_product([idx, dti]) + ser = Series(range(len(mi))[::-1], index=mi) + + key = box([5]) + with pytest.raises(KeyError, match="5"): + ser[key] + + def test_getitem_uint_array_key(self, any_unsigned_int_numpy_dtype): + # GH #37218 + ser = Series([1, 2, 3]) + key = np.array([4], dtype=any_unsigned_int_numpy_dtype) + + with pytest.raises(KeyError, match="4"): + ser[key] + with pytest.raises(KeyError, match="4"): + ser.loc[key] + + +class TestGetitemBooleanMask: + def test_getitem_boolean(self, string_series): + ser = string_series + mask = ser > ser.median() + + # passing list is OK + result = ser[list(mask)] + expected = ser[mask] + tm.assert_series_equal(result, expected) + tm.assert_index_equal(result.index, ser.index[mask]) + + def test_getitem_boolean_empty(self): + ser = Series([], dtype=np.int64) + ser.index.name = "index_name" + ser = ser[ser.isna()] + assert ser.index.name == "index_name" + assert ser.dtype == np.int64 + + # GH#5877 + # indexing with empty series + ser = Series(["A", "B"], dtype=object) + expected = Series(dtype=object, index=Index([], dtype="int64")) + result = ser[Series([], dtype=object)] + tm.assert_series_equal(result, expected) + + # invalid because of the boolean indexer + # that's empty or not-aligned + msg = ( + r"Unalignable boolean Series provided as indexer \(index of " + r"the boolean Series and of the indexed object do not match" + ) + with pytest.raises(IndexingError, match=msg): + ser[Series([], dtype=bool)] + + with pytest.raises(IndexingError, match=msg): + ser[Series([True], dtype=bool)] + + def test_getitem_boolean_object(self, string_series): + # using column from DataFrame + + ser = string_series + mask = ser > ser.median() + omask = mask.astype(object) + + # getitem + result = ser[omask] + expected = ser[mask] + tm.assert_series_equal(result, expected) + + # setitem + s2 = ser.copy() + cop = ser.copy() + cop[omask] = 5 + s2[mask] = 5 + tm.assert_series_equal(cop, s2) + + # nans raise exception + omask[5:10] = np.nan + msg = "Cannot mask with non-boolean array containing NA / NaN values" + with pytest.raises(ValueError, match=msg): + ser[omask] + with pytest.raises(ValueError, match=msg): + ser[omask] = 5 + + def test_getitem_boolean_dt64_copies(self): + # GH#36210 + dti = date_range("2016-01-01", periods=4, tz="US/Pacific") + key = np.array([True, True, False, False]) + + ser = Series(dti._data) + + res = ser[key] + assert res._values._ndarray.base is None + + # compare with numeric case for reference + ser2 = Series(range(4)) + res2 = ser2[key] + assert res2._values.base is None + + def test_getitem_boolean_corner(self, datetime_series): + ts = datetime_series + mask_shifted = ts.shift(1, freq=BDay()) > ts.median() + + msg = ( + r"Unalignable boolean Series provided as indexer \(index of " + r"the boolean Series and of the indexed object do not match" + ) + with pytest.raises(IndexingError, match=msg): + ts[mask_shifted] + + with pytest.raises(IndexingError, match=msg): + ts.loc[mask_shifted] + + def test_getitem_boolean_different_order(self, string_series): + ordered = string_series.sort_values() + + sel = string_series[ordered > 0] + exp = string_series[string_series > 0] + tm.assert_series_equal(sel, exp) + + def test_getitem_boolean_contiguous_preserve_freq(self): + rng = date_range("1/1/2000", "3/1/2000", freq="B") + + mask = np.zeros(len(rng), dtype=bool) + mask[10:20] = True + + masked = rng[mask] + expected = rng[10:20] + assert expected.freq == rng.freq + tm.assert_index_equal(masked, expected) + + mask[22] = True + masked = rng[mask] + assert masked.freq is None + + +class TestGetitemCallable: + def test_getitem_callable(self): + # GH#12533 + ser = Series(4, index=list("ABCD")) + result = ser[lambda x: "A"] + assert result == ser.loc["A"] + + result = ser[lambda x: ["A", "B"]] + expected = ser.loc[["A", "B"]] + tm.assert_series_equal(result, expected) + + result = ser[lambda x: [True, False, True, True]] + expected = ser.iloc[[0, 2, 3]] + tm.assert_series_equal(result, expected) + + +def test_getitem_generator(string_series): + gen = (x > 0 for x in string_series) + result = string_series[gen] + result2 = string_series[iter(string_series > 0)] + expected = string_series[string_series > 0] + tm.assert_series_equal(result, expected) + tm.assert_series_equal(result2, expected) + + +@pytest.mark.parametrize( + "data", + [ + [0, 1], + date_range("2012-01-01", periods=2), + date_range("2012-01-01", periods=2, tz="CET"), + ], +) +def test_getitem_ndim_deprecated(data): + series = Series(data) + with pytest.raises(ValueError, match="Multi-dimensional indexing"): + series[:, None] + + +def test_getitem_multilevel_scalar_slice_not_implemented( + multiindex_year_month_day_dataframe_random_data, +): + # not implementing this for now + df = multiindex_year_month_day_dataframe_random_data + ser = df["A"] + + msg = r"\(2000, slice\(3, 4, None\)\)" + with pytest.raises(TypeError, match=msg): + ser[2000, 3:4] + + +def test_getitem_dataframe_raises(): + rng = list(range(10)) + ser = Series(10, index=rng) + df = DataFrame(rng, index=rng) + msg = ( + "Indexing a Series with DataFrame is not supported, " + "use the appropriate DataFrame column" + ) + with pytest.raises(TypeError, match=msg): + ser[df > 5] + + +def test_getitem_assignment_series_alignment(): + # https://github.com/pandas-dev/pandas/issues/37427 + # with getitem, when assigning with a Series, it is not first aligned + ser = Series(range(10)) + idx = np.array([2, 4, 9]) + ser[idx] = Series([10, 11, 12]) + expected = Series([0, 1, 10, 3, 11, 5, 6, 7, 8, 12]) + tm.assert_series_equal(ser, expected) + + +def test_getitem_duplicate_index_mistyped_key_raises_keyerror(): + # GH#29189 float_index.get_loc(None) should raise KeyError, not TypeError + ser = Series([2, 5, 6, 8], index=[2.0, 4.0, 4.0, 5.0]) + with pytest.raises(KeyError, match="None"): + ser[None] + + with pytest.raises(KeyError, match="None"): + ser.index.get_loc(None) + + with pytest.raises(KeyError, match="None"): + ser.index._engine.get_loc(None) + + +def test_getitem_1tuple_slice_without_multiindex(): + ser = Series(range(5)) + key = (slice(3),) + + result = ser[key] + expected = ser[key[0]] + tm.assert_series_equal(result, expected) + + +def test_getitem_preserve_name(datetime_series): + result = datetime_series[datetime_series > 0] + assert result.name == datetime_series.name + + result = datetime_series[5:10] + assert result.name == datetime_series.name + + +def test_getitem_with_integer_labels(): + # integer indexes, be careful + ser = Series( + np.random.default_rng(2).standard_normal(10), index=list(range(0, 20, 2)) + ) + inds = [0, 2, 5, 7, 8] + arr_inds = np.array([0, 2, 5, 7, 8]) + with pytest.raises(KeyError, match="not in index"): + ser[inds] + + with pytest.raises(KeyError, match="not in index"): + ser[arr_inds] + + +def test_getitem_missing(datetime_series): + # missing + d = datetime_series.index[0] - BDay() + msg = r"Timestamp\('1999-12-31 00:00:00'\)" + with pytest.raises(KeyError, match=msg): + datetime_series[d] + + +def test_getitem_fancy(string_series, object_series): + msg = r"None of \[Index\(\[1, 2, 3\], dtype='int(32|64)'\)\] are in the \[index\]" + with pytest.raises(KeyError, match=msg): + string_series[[1, 2, 3]] + with pytest.raises(KeyError, match=msg): + object_series[[1, 2, 3]] + + +def test_getitem_box_float64(datetime_series): + with pytest.raises(KeyError, match="^5$"): + datetime_series[5] + + +def test_getitem_unordered_dup(): + obj = Series(range(5), index=["c", "a", "a", "b", "b"]) + assert is_scalar(obj["c"]) + assert obj["c"] == 0 + + +def test_getitem_dups(): + ser = Series(range(5), index=["A", "A", "B", "C", "C"], dtype=np.int64) + expected = Series([3, 4], index=["C", "C"], dtype=np.int64) + result = ser["C"] + tm.assert_series_equal(result, expected) + + +def test_getitem_categorical_str(): + # GH#31765 + ser = Series(range(5), index=Categorical(["a", "b", "c", "a", "b"])) + result = ser["a"] + expected = ser.iloc[[0, 3]] + tm.assert_series_equal(result, expected) + + +def test_slice_can_reorder_not_uniquely_indexed(): + ser = Series(1, index=["a", "a", "b", "b", "c"]) + ser[::-1] # it works! + + +@pytest.mark.parametrize("index_vals", ["aabcd", "aadcb"]) +def test_duplicated_index_getitem_positional_indexer(index_vals): + # GH 11747; changed in 3.0 integers are treated as always-labels + s = Series(range(5), index=list(index_vals)) + + with pytest.raises(KeyError, match="^3$"): + s[3] + + +class TestGetitemDeprecatedIndexers: + @pytest.mark.parametrize("key", [{1}, {1: 1}]) + def test_getitem_dict_and_set_deprecated(self, key): + # GH#42825 enforced in 2.0 + ser = Series([1, 2, 3]) + with pytest.raises(TypeError, match="as an indexer is not supported"): + ser[key] + + @pytest.mark.parametrize("key", [{1}, {1: 1}]) + def test_setitem_dict_and_set_disallowed(self, key): + # GH#42825 enforced in 2.0 + ser = Series([1, 2, 3]) + with pytest.raises(TypeError, match="as an indexer is not supported"): + ser[key] = 1 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_indexing.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_indexing.py new file mode 100644 index 0000000000000000000000000000000000000000..673b08b80b6a94fbb1b825658ff443c8458671cd --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_indexing.py @@ -0,0 +1,510 @@ +"""test get/set & misc""" + +from datetime import timedelta +import re + +import numpy as np +import pytest + +from pandas.errors import IndexingError + +from pandas import ( + NA, + DataFrame, + Index, + IndexSlice, + MultiIndex, + NaT, + Series, + Timedelta, + Timestamp, + concat, + date_range, + isna, + period_range, + timedelta_range, +) +import pandas._testing as tm + + +def test_basic_indexing(): + s = Series( + np.random.default_rng(2).standard_normal(5), index=["a", "b", "a", "a", "b"] + ) + + with pytest.raises(KeyError, match="^5$"): + s[5] + + with pytest.raises(KeyError, match=r"^'c'$"): + s["c"] + + s = s.sort_index() + + with pytest.raises(KeyError, match="^5$"): + s[5] + + +def test_getitem_numeric_should_not_fallback_to_positional(any_numeric_dtype): + # GH51053 + dtype = any_numeric_dtype + idx = Index([1, 0, 1], dtype=dtype) + ser = Series(range(3), index=idx) + result = ser[1] + expected = Series([0, 2], index=Index([1, 1], dtype=dtype)) + tm.assert_series_equal(result, expected, check_exact=True) + + +def test_setitem_numeric_should_not_fallback_to_positional(any_numeric_dtype): + # GH51053 + dtype = any_numeric_dtype + idx = Index([1, 0, 1], dtype=dtype) + ser = Series(range(3), index=idx) + ser[1] = 10 + expected = Series([10, 1, 10], index=idx) + tm.assert_series_equal(ser, expected, check_exact=True) + + +def test_basic_getitem_with_labels(datetime_series): + indices = datetime_series.index[[5, 10, 15]] + + result = datetime_series[indices] + expected = datetime_series.reindex(indices) + tm.assert_series_equal(result, expected) + + result = datetime_series[indices[0] : indices[2]] + expected = datetime_series.loc[indices[0] : indices[2]] + tm.assert_series_equal(result, expected) + + +def test_basic_getitem_dt64tz_values(): + # GH12089 + # with tz for values + ser = Series( + date_range("2011-01-01", periods=3, tz="US/Eastern"), index=["a", "b", "c"] + ) + expected = Timestamp("2011-01-01", tz="US/Eastern") + result = ser.loc["a"] + assert result == expected + result = ser.iloc[0] + assert result == expected + result = ser["a"] + assert result == expected + + +def test_getitem_setitem_ellipsis(): + s = Series(np.random.default_rng(2).standard_normal(10)) + + result = s[...] + tm.assert_series_equal(result, s) + + +@pytest.mark.parametrize( + "result_1, duplicate_item, expected_1", + [ + [ + {1: 12, 2: [1, 2, 2, 3]}, + {1: 313}, + Series({1: 12}, dtype=object), + ], + [ + {1: [1, 2, 3], 2: [1, 2, 2, 3]}, + {1: [1, 2, 3]}, + Series({1: [1, 2, 3]}), + ], + ], +) +def test_getitem_with_duplicates_indices(result_1, duplicate_item, expected_1): + # GH 17610 + result_1 = Series(result_1) + duplicate_item = Series(duplicate_item) + result = result_1._append_internal(duplicate_item) + expected = expected_1._append_internal(duplicate_item) + tm.assert_series_equal(result[1], expected) + assert result[2] == result_1[2] + + +def test_getitem_setitem_integers(): + # caused bug without test + s = Series([1, 2, 3], ["a", "b", "c"]) + + assert s.iloc[0] == s["a"] + s.iloc[0] = 5 + tm.assert_almost_equal(s["a"], 5) + + +def test_series_box_timestamp(): + rng = date_range("20090415", "20090519", freq="B") + ser = Series(rng) + assert isinstance(ser[0], Timestamp) + assert isinstance(ser.at[1], Timestamp) + assert isinstance(ser.iat[2], Timestamp) + assert isinstance(ser.loc[3], Timestamp) + assert isinstance(ser.iloc[4], Timestamp) + + ser = Series(rng, index=rng) + assert isinstance(ser[rng[0]], Timestamp) + assert isinstance(ser.at[rng[1]], Timestamp) + assert isinstance(ser.iat[2], Timestamp) + assert isinstance(ser.loc[rng[3]], Timestamp) + assert isinstance(ser.iloc[4], Timestamp) + + +def test_series_box_timedelta(): + rng = timedelta_range("1 day 1 s", periods=5, freq="h") + ser = Series(rng) + assert isinstance(ser[0], Timedelta) + assert isinstance(ser.at[1], Timedelta) + assert isinstance(ser.iat[2], Timedelta) + assert isinstance(ser.loc[3], Timedelta) + assert isinstance(ser.iloc[4], Timedelta) + + +def test_getitem_ambiguous_keyerror(indexer_sl): + ser = Series(range(10), index=list(range(0, 20, 2))) + with pytest.raises(KeyError, match=r"^1$"): + indexer_sl(ser)[1] + + +def test_getitem_dups_with_missing(indexer_sl): + # breaks reindex, so need to use .loc internally + # GH 4246 + ser = Series([1, 2, 3, 4], ["foo", "bar", "foo", "bah"]) + with pytest.raises(KeyError, match=re.escape("['bam'] not in index")): + indexer_sl(ser)[["foo", "bar", "bah", "bam"]] + + +def test_setitem_ambiguous_keyerror(indexer_sl): + s = Series(range(10), index=list(range(0, 20, 2))) + + # equivalent of an append + s2 = s.copy() + indexer_sl(s2)[1] = 5 + expected = concat([s, Series([5], index=[1])]) + tm.assert_series_equal(s2, expected) + + +def test_setitem(datetime_series): + datetime_series[datetime_series.index[5]] = np.nan + datetime_series.iloc[[1, 2, 17]] = np.nan + datetime_series.iloc[6] = np.nan + assert np.isnan(datetime_series.iloc[6]) + assert np.isnan(datetime_series.iloc[2]) + datetime_series[np.isnan(datetime_series)] = 5 + assert not np.isnan(datetime_series.iloc[2]) + + +def test_setslice(datetime_series): + sl = datetime_series[5:20] + assert len(sl) == len(sl.index) + assert sl.index.is_unique is True + + +def test_basic_getitem_setitem_corner(datetime_series): + # invalid tuples, e.g. td.ts[:, None] vs. td.ts[:, 2] + msg = "key of type tuple not found and not a MultiIndex" + with pytest.raises(KeyError, match=msg): + datetime_series[:, 2] + with pytest.raises(KeyError, match=msg): + datetime_series[:, 2] = 2 + + # weird lists. [slice(0, 5)] raises but not two slices + msg = "Indexing with a single-item list" + with pytest.raises(ValueError, match=msg): + # GH#31299 + datetime_series[[slice(None, 5)]] + + # but we're OK with a single-element tuple + result = datetime_series[(slice(None, 5),)] + expected = datetime_series[:5] + tm.assert_series_equal(result, expected) + + # OK + msg = r"unhashable type(: 'slice')?" + with pytest.raises(TypeError, match=msg): + datetime_series[[5, [None, None]]] + with pytest.raises(TypeError, match=msg): + datetime_series[[5, [None, None]]] = 2 + + +def test_slice(string_series, object_series): + original = string_series.copy() + numSlice = string_series[10:20] + numSliceEnd = string_series[-10:] + objSlice = object_series[10:20] + + assert string_series.index[9] not in numSlice.index + assert object_series.index[9] not in objSlice.index + + assert len(numSlice) == len(numSlice.index) + assert string_series[numSlice.index[0]] == numSlice[numSlice.index[0]] + + assert numSlice.index[1] == string_series.index[11] + tm.assert_numpy_array_equal(np.array(numSliceEnd), np.array(string_series)[-10:]) + + # Test return view. + sl = string_series[10:20] + sl[:] = 0 + + # Doesn't modify parent (CoW) + tm.assert_series_equal(string_series, original) + + +def test_timedelta_assignment(): + # GH 8209 + s = Series([], dtype=object) + s.loc["B"] = timedelta(1) + expected = Series(Timedelta("1 days"), dtype="timedelta64[us]", index=["B"]) + tm.assert_series_equal(s, expected) + + s = s.reindex(s.index.insert(0, "A")) + expected = Series( + [np.nan, Timedelta("1 days")], dtype="timedelta64[us]", index=["A", "B"] + ) + tm.assert_series_equal(s, expected) + + s.loc["A"] = timedelta(1) + expected = Series(Timedelta("1 days"), dtype="timedelta64[us]", index=["A", "B"]) + tm.assert_series_equal(s, expected) + + +def test_underlying_data_conversion(): + # GH 4080 + df = DataFrame({c: [1, 2, 3] for c in ["a", "b", "c"]}) + return_value = df.set_index(["a", "b", "c"], inplace=True) + assert return_value is None + s = Series([1], index=[(2, 2, 2)]) + df["val"] = 0 + df_original = df.copy() + df + + with tm.raises_chained_assignment_error(): + df["val"].update(s) + expected = df_original + tm.assert_frame_equal(df, expected) + + +def test_preserve_refs(datetime_series): + seq = datetime_series.iloc[[5, 10, 15]] + seq.iloc[1] = np.nan + assert not np.isnan(datetime_series.iloc[10]) + + +def test_multilevel_preserve_name(lexsorted_two_level_string_multiindex, indexer_sl): + index = lexsorted_two_level_string_multiindex + ser = Series( + np.random.default_rng(2).standard_normal(len(index)), index=index, name="sth" + ) + + result = indexer_sl(ser)["foo"] + assert result.name == ser.name + + +# miscellaneous methods + + +@pytest.mark.parametrize( + "index", + [ + date_range("2014-01-01", periods=20, freq="MS"), + period_range("2014-01", periods=20, freq="M"), + timedelta_range("0", periods=20, freq="h"), + ], +) +def test_slice_with_negative_step(index): + keystr1 = str(index[9]) + keystr2 = str(index[13]) + + ser = Series(np.arange(20), index) + SLC = IndexSlice + + for key in [keystr1, index[9]]: + tm.assert_indexing_slices_equivalent(ser, SLC[key::-1], SLC[9::-1]) + tm.assert_indexing_slices_equivalent(ser, SLC[:key:-1], SLC[:8:-1]) + + for key2 in [keystr2, index[13]]: + tm.assert_indexing_slices_equivalent(ser, SLC[key2:key:-1], SLC[13:8:-1]) + tm.assert_indexing_slices_equivalent(ser, SLC[key:key2:-1], SLC[0:0:-1]) + + +def test_tuple_index(): + # GH 35534 - Selecting values when a Series has an Index of tuples + s = Series([1, 2], index=[("a",), ("b",)]) + assert s[("a",)] == 1 + assert s[("b",)] == 2 + s[("b",)] = 3 + assert s[("b",)] == 3 + + +def test_frozenset_index(): + # GH35747 - Selecting values when a Series has an Index of frozenset + idx0, idx1 = frozenset("a"), frozenset("b") + s = Series([1, 2], index=[idx0, idx1]) + assert s[idx0] == 1 + assert s[idx1] == 2 + s[idx1] = 3 + assert s[idx1] == 3 + + +def test_loc_setitem_all_false_indexer(): + # GH#45778 + ser = Series([1, 2], index=["a", "b"]) + expected = ser.copy() + rhs = Series([6, 7], index=["a", "b"]) + ser.loc[ser > 100] = rhs + tm.assert_series_equal(ser, expected) + + +def test_loc_boolean_indexer_non_matching_index(): + # GH#46551 + ser = Series([1]) + result = ser.loc[Series([NA, False], dtype="boolean")] + expected = Series([], dtype="int64") + tm.assert_series_equal(result, expected) + + +def test_loc_boolean_indexer_miss_matching_index(): + # GH#46551 + ser = Series([1]) + indexer = Series([NA, False], dtype="boolean", index=[1, 2]) + with pytest.raises(IndexingError, match="Unalignable"): + ser.loc[indexer] + + +def test_loc_setitem_nested_data_enlargement(): + # GH#48614 + df = DataFrame({"a": [1]}) + ser = Series({"label": df}) + ser.loc["new_label"] = df + expected = Series({"label": df, "new_label": df}) + tm.assert_series_equal(ser, expected) + + +def test_loc_ea_numeric_index_oob_slice_end(): + # GH#50161 + ser = Series(1, index=Index([0, 1, 2], dtype="Int64")) + result = ser.loc[2:3] + expected = Series(1, index=Index([2], dtype="Int64")) + tm.assert_series_equal(result, expected) + + +def test_getitem_bool_int_key(): + # GH#48653 + ser = Series({True: 1, False: 0}) + with pytest.raises(KeyError, match="0"): + ser.loc[0] + + +@pytest.mark.parametrize("val", [{}, {"b": "x"}]) +@pytest.mark.parametrize("indexer", [[], [False, False], slice(0, -1), np.array([])]) +def test_setitem_empty_indexer(indexer, val): + # GH#45981 + df = DataFrame({"a": [1, 2], **val}) + expected = df.copy() + df.loc[indexer] = 1.5 + tm.assert_frame_equal(df, expected) + + +def test_loc_non_monotonic_index_with_a_missing_label(): + msg = "Cannot get left slice bound for non-monotonic index with a missing label 4" + ser = Series([3, 6, 7, 6], index=[3, 8, 7, 6]) + with pytest.raises(KeyError, match=msg): + ser.loc[4:7] + + +class TestDeprecatedIndexers: + @pytest.mark.parametrize("key", [{1}, {1: 1}]) + def test_getitem_dict_and_set_deprecated(self, key): + # GH#42825 enforced in 2.0 + ser = Series([1, 2]) + with pytest.raises(TypeError, match="as an indexer is not supported"): + ser.loc[key] + + @pytest.mark.parametrize("key", [{1}, {1: 1}, ({1}, 2), ({1: 1}, 2)]) + def test_getitem_dict_and_set_deprecated_multiindex(self, key): + # GH#42825 enforced in 2.0 + ser = Series([1, 2], index=MultiIndex.from_tuples([(1, 2), (3, 4)])) + with pytest.raises(TypeError, match="as an indexer is not supported"): + ser.loc[key] + + @pytest.mark.parametrize("key", [{1}, {1: 1}]) + def test_setitem_dict_and_set_disallowed(self, key): + # GH#42825 enforced in 2.0 + ser = Series([1, 2]) + with pytest.raises(TypeError, match="as an indexer is not supported"): + ser.loc[key] = 1 + + @pytest.mark.parametrize("key", [{1}, {1: 1}, ({1}, 2), ({1: 1}, 2)]) + def test_setitem_dict_and_set_disallowed_multiindex(self, key): + # GH#42825 enforced in 2.0 + ser = Series([1, 2], index=MultiIndex.from_tuples([(1, 2), (3, 4)])) + with pytest.raises(TypeError, match="as an indexer is not supported"): + ser.loc[key] = 1 + + +class TestSetitemValidation: + # This is adapted from pandas/tests/arrays/masked/test_indexing.py + def _check_setitem_invalid(self, ser, invalid, indexer): + orig_ser = ser.copy() + + with pytest.raises(TypeError, match="Invalid value"): + ser[indexer] = invalid + ser = orig_ser.copy() + + with pytest.raises(TypeError, match="Invalid value"): + ser.iloc[indexer] = invalid + ser = orig_ser.copy() + + with pytest.raises(TypeError, match="Invalid value"): + ser.loc[indexer] = invalid + ser = orig_ser.copy() + + with pytest.raises(TypeError, match="Invalid value"): + ser[:] = invalid + + def _check_setitem_valid(self, ser, value, indexer): + orig_ser = ser.copy() + + ser[indexer] = value + ser = orig_ser.copy() + + ser.iloc[indexer] = value + ser = orig_ser.copy() + + ser.loc[indexer] = value + ser = orig_ser.copy() + + ser[:] = value + + _invalid_scalars = [ + 1 + 2j, + "True", + "1", + "1.0", + NaT, + np.datetime64("NaT", "ns"), + np.timedelta64("NaT", "ns"), + ] + _indexers = [0, [0], slice(0, 1), [True, False, False], slice(None, None, None)] + + @pytest.mark.parametrize( + "invalid", [*_invalid_scalars, 1, 1.0, np.int64(1), np.float64(1)] + ) + @pytest.mark.parametrize("indexer", _indexers) + def test_setitem_validation_scalar_bool(self, invalid, indexer): + ser = Series([True, False, False], dtype="bool") + self._check_setitem_invalid(ser, invalid, indexer) + + @pytest.mark.parametrize("invalid", [*_invalid_scalars, True, 1.5, np.float64(1.5)]) + @pytest.mark.parametrize("indexer", _indexers) + def test_setitem_validation_scalar_int(self, invalid, any_int_numpy_dtype, indexer): + ser = Series([1, 2, 3], dtype=any_int_numpy_dtype) + if isna(invalid) and invalid is not NaT and not np.isnat(invalid): + self._check_setitem_valid(ser, invalid, indexer) + else: + self._check_setitem_invalid(ser, invalid, indexer) + + @pytest.mark.parametrize("invalid", [*_invalid_scalars, True]) + @pytest.mark.parametrize("indexer", _indexers) + def test_setitem_validation_scalar_float(self, invalid, float_numpy_dtype, indexer): + ser = Series([1, 2, None], dtype=float_numpy_dtype) + self._check_setitem_invalid(ser, invalid, indexer) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_mask.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_mask.py new file mode 100644 index 0000000000000000000000000000000000000000..3c21cd0d5ca648dcf0f1ac412dd232221c031c6f --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_mask.py @@ -0,0 +1,69 @@ +import numpy as np +import pytest + +from pandas import Series +import pandas._testing as tm + + +def test_mask(): + # compare with tested results in test_where + s = Series(np.random.default_rng(2).standard_normal(5)) + cond = s > 0 + + rs = s.where(~cond, np.nan) + tm.assert_series_equal(rs, s.mask(cond)) + + rs = s.where(~cond) + rs2 = s.mask(cond) + tm.assert_series_equal(rs, rs2) + + rs = s.where(~cond, -s) + rs2 = s.mask(cond, -s) + tm.assert_series_equal(rs, rs2) + + cond = Series([True, False, False, True, False], index=s.index) + s2 = -(s.abs()) + rs = s2.where(~cond[:3]) + rs2 = s2.mask(cond[:3]) + tm.assert_series_equal(rs, rs2) + + rs = s2.where(~cond[:3], -s2) + rs2 = s2.mask(cond[:3], -s2) + tm.assert_series_equal(rs, rs2) + + msg = "Array conditional must be same shape as self" + with pytest.raises(ValueError, match=msg): + s.mask(1) + with pytest.raises(ValueError, match=msg): + s.mask(cond[:3].values, -s) + + +def test_mask_casts(): + # dtype changes + ser = Series([1, 2, 3, 4]) + result = ser.mask(ser > 2, np.nan) + expected = Series([1, 2, np.nan, np.nan]) + tm.assert_series_equal(result, expected) + + +def test_mask_casts2(): + # see gh-21891 + ser = Series([1, 2]) + res = ser.mask([True, False]) + + exp = Series([np.nan, 2]) + tm.assert_series_equal(res, exp) + + +def test_mask_inplace(): + s = Series(np.random.default_rng(2).standard_normal(5)) + cond = s > 0 + + rs = s.copy() + rs.mask(cond, inplace=True) + tm.assert_series_equal(rs.dropna(), s[~cond]) + tm.assert_series_equal(rs, s.mask(cond)) + + rs = s.copy() + rs.mask(cond, -s, inplace=True) + tm.assert_series_equal(rs, s.mask(cond, -s)) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_set_value.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_set_value.py new file mode 100644 index 0000000000000000000000000000000000000000..3bf89da53d9230b0d5990ad2e4e72c37e225b5b9 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_set_value.py @@ -0,0 +1,49 @@ +from datetime import datetime + +import numpy as np + +from pandas import ( + DatetimeIndex, + Index, + Series, +) +import pandas._testing as tm + + +def test_series_set_value(using_infer_string): + # GH#1561, GH#51363 as of 3.0 we do not do inference in Index.insert + + dates = [datetime(2001, 1, 1), datetime(2001, 1, 2)] + if using_infer_string: + index = DatetimeIndex(dates) + else: + index = Index(dates, dtype=object) + + s = Series(dtype=object) + s._set_value(dates[0], 1.0) + s._set_value(dates[1], np.nan) + + expected = Series([1.0, np.nan], index=index) + + tm.assert_series_equal(s, expected) + + +def test_set_value_dt64(datetime_series): + idx = datetime_series.index[10] + res = datetime_series._set_value(idx, 0) + assert res is None + assert datetime_series[idx] == 0 + + +def test_set_value_str_index(string_series): + # equiv + ser = string_series.copy() + res = ser._set_value("foobar", 0) + assert res is None + assert ser.index[-1] == "foobar" + assert ser["foobar"] == 0 + + ser2 = string_series.copy() + ser2.loc["foobar"] = 0 + assert ser2.index[-1] == "foobar" + assert ser2["foobar"] == 0 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_setitem.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_setitem.py new file mode 100644 index 0000000000000000000000000000000000000000..322ea76b0d920f0d90a134dfa4632717e4e5a4cb --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_setitem.py @@ -0,0 +1,1843 @@ +import contextlib +from datetime import ( + date, + datetime, +) +from decimal import Decimal + +import numpy as np +import pytest + +from pandas.compat.numpy import np_version_gt2 +from pandas.errors import IndexingError + +from pandas.core.dtypes.common import is_list_like + +from pandas import ( + NA, + Categorical, + DataFrame, + DatetimeIndex, + Index, + Interval, + IntervalIndex, + MultiIndex, + NaT, + Period, + Series, + StringDtype, + Timedelta, + Timestamp, + array, + concat, + date_range, + interval_range, + period_range, + timedelta_range, +) +import pandas._testing as tm + +from pandas.tseries.offsets import BDay + + +class TestSetitemDT64Values: + def test_setitem_none_nan(self): + series = Series(date_range("1/1/2000", periods=10, unit="ns")) + series[3] = None + assert series[3] is NaT + + series[3:5] = None + assert series[4] is NaT + + series[5] = np.nan + assert series[5] is NaT + + series[5:7] = np.nan + assert series[6] is NaT + + def test_setitem_multiindex_empty_slice(self): + # https://github.com/pandas-dev/pandas/issues/35878 + idx = MultiIndex.from_tuples([("a", 1), ("b", 2)]) + result = Series([1, 2], index=idx) + expected = result.copy() + result.loc[[]] = 0 + tm.assert_series_equal(result, expected) + + def test_setitem_with_string_index(self): + # GH#23451 + # Set object dtype to avoid upcast when setting date.today() + ser = Series([1, 2, 3], index=["Date", "b", "other"], dtype=object) + ser["Date"] = date.today() + assert ser.Date == date.today() + assert ser["Date"] == date.today() + + def test_setitem_tuple_with_datetimetz_values(self): + # GH#20441 + arr = date_range("2017", periods=4, tz="US/Eastern", unit="ns") + index = [(0, 1), (0, 2), (0, 3), (0, 4)] + result = Series(arr, index=index) + expected = result.copy() + result[(0, 1)] = np.nan + expected.iloc[0] = np.nan + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("tz", ["US/Eastern", "UTC", "Asia/Tokyo"]) + def test_setitem_with_tz(self, tz, indexer_sli): + orig = Series(date_range("2016-01-01", freq="h", periods=3, tz=tz, unit="ns")) + assert orig.dtype == f"datetime64[ns, {tz}]" + + exp = Series( + [ + Timestamp("2016-01-01 00:00", tz=tz), + Timestamp("2011-01-01 00:00", tz=tz), + Timestamp("2016-01-01 02:00", tz=tz), + ], + dtype=orig.dtype, + ) + + # scalar + ser = orig.copy() + indexer_sli(ser)[1] = Timestamp("2011-01-01", tz=tz) + tm.assert_series_equal(ser, exp) + + # vector + vals = Series( + [Timestamp("2011-01-01", tz=tz), Timestamp("2012-01-01", tz=tz)], + index=[1, 2], + dtype=orig.dtype, + ) + assert vals.dtype == f"datetime64[ns, {tz}]" + + exp = Series( + [ + Timestamp("2016-01-01 00:00", tz=tz), + Timestamp("2011-01-01 00:00", tz=tz), + Timestamp("2012-01-01 00:00", tz=tz), + ], + dtype=orig.dtype, + ) + + ser = orig.copy() + indexer_sli(ser)[[1, 2]] = vals + tm.assert_series_equal(ser, exp) + + def test_setitem_with_tz_dst(self, indexer_sli): + # GH#14146 trouble setting values near DST boundary + tz = "US/Eastern" + orig = Series(date_range("2016-11-06", freq="h", periods=3, tz=tz, unit="ns")) + assert orig.dtype == f"datetime64[ns, {tz}]" + + exp = Series( + [ + Timestamp("2016-11-06 00:00-04:00", tz=tz), + Timestamp("2011-01-01 00:00-05:00", tz=tz), + Timestamp("2016-11-06 01:00-05:00", tz=tz), + ], + dtype=orig.dtype, + ) + + # scalar + ser = orig.copy() + indexer_sli(ser)[1] = Timestamp("2011-01-01", tz=tz) + tm.assert_series_equal(ser, exp) + + # vector + vals = Series( + [Timestamp("2011-01-01", tz=tz), Timestamp("2012-01-01", tz=tz)], + index=[1, 2], + dtype=orig.dtype, + ) + assert vals.dtype == f"datetime64[ns, {tz}]" + + exp = Series( + [ + Timestamp("2016-11-06 00:00", tz=tz), + Timestamp("2011-01-01 00:00", tz=tz), + Timestamp("2012-01-01 00:00", tz=tz), + ], + dtype=orig.dtype, + ) + + ser = orig.copy() + indexer_sli(ser)[[1, 2]] = vals + tm.assert_series_equal(ser, exp) + + def test_object_series_setitem_dt64array_exact_match(self): + # make sure the dt64 isn't cast by numpy to integers + # https://github.com/numpy/numpy/issues/12550 + + ser = Series({"X": np.nan}, dtype=object) + + indexer = [True] + + # "exact_match" -> size of array being set matches size of ser + value = np.array([4], dtype="M8[ns]") + + ser.iloc[indexer] = value + + expected = Series([value[0]], index=["X"], dtype=object) + assert all(isinstance(x, np.datetime64) for x in expected.values) + + tm.assert_series_equal(ser, expected) + + +class TestSetitemScalarIndexer: + def test_setitem_negative_out_of_bounds(self): + # As of 3.0, int keys are treated as labels, so this becomes + # setitem-with-expansion + ser = Series(["a"] * 10, index=["a"] * 10) + ser[-11] = "foo" + exp = Series(["a"] * 10 + ["foo"], index=["a"] * 10 + [-11]) + tm.assert_series_equal(ser, exp) + + @pytest.mark.parametrize("indexer", [tm.loc, tm.at]) + @pytest.mark.parametrize("ser_index", [0, 1]) + def test_setitem_series_object_dtype(self, indexer, ser_index): + # GH#38303 + ser = Series([0, 0], dtype="object") + idxr = indexer(ser) + idxr[0] = Series([42], index=[ser_index]) + expected = Series([Series([42], index=[ser_index]), 0], dtype="object") + tm.assert_series_equal(ser, expected) + + @pytest.mark.parametrize("index, exp_value", [(0, 42), (1, np.nan)]) + def test_setitem_series(self, index, exp_value): + # GH#38303 + ser = Series([0, 0]) + ser.loc[0] = Series([42], index=[index]) + expected = Series([exp_value, 0]) + tm.assert_series_equal(ser, expected) + + +class TestSetitemSlices: + def test_setitem_slice_float_raises(self, datetime_series): + msg = ( + "cannot do slice indexing on DatetimeIndex with these indexers " + r"\[{key}\] of type float" + ) + with pytest.raises(TypeError, match=msg.format(key=r"4\.0")): + datetime_series[4.0:10.0] = 0 + + with pytest.raises(TypeError, match=msg.format(key=r"4\.5")): + datetime_series[4.5:10.0] = 0 + + def test_setitem_slice(self): + ser = Series(range(10), index=list(range(10))) + ser[-12:] = 0 + assert (ser == 0).all() + + ser[:-12] = 5 + assert (ser == 0).all() + + def test_setitem_slice_integers(self): + ser = Series( + np.random.default_rng(2).standard_normal(8), + index=[2, 4, 6, 8, 10, 12, 14, 16], + ) + + ser[:4] = 0 + assert (ser[:4] == 0).all() + assert not (ser[4:] == 0).any() + + def test_setitem_slicestep(self): + # caught this bug when writing tests + series = Series( + np.arange(20, dtype=np.float64), index=np.arange(20, dtype=np.int64) + ) + + series[::2] = 0 + assert (series[::2] == 0).all() + + def test_setitem_multiindex_slice(self, indexer_sli): + # GH 8856 + mi = MultiIndex.from_product(([0, 1], list("abcde"))) + result = Series(np.arange(10, dtype=np.int64), mi) + indexer_sli(result)[::4] = 100 + expected = Series([100, 1, 2, 3, 100, 5, 6, 7, 100, 9], mi) + tm.assert_series_equal(result, expected) + + +class TestSetitemBooleanMask: + def test_setitem_mask_cast(self): + # GH#2746 + # need to upcast + ser = Series([1, 2], index=[1, 2], dtype="int64") + ser[[True, False]] = Series([0], index=[1], dtype="int64") + expected = Series([0, 2], index=[1, 2], dtype="int64") + + tm.assert_series_equal(ser, expected) + + def test_setitem_mask_align_and_promote(self): + # GH#8387: test that changing types does not break alignment + ts = Series( + np.random.default_rng(2).standard_normal(100), index=np.arange(100, 0, -1) + ).round(5) + mask = ts > 0 + left = ts.copy() + right = ts[mask].copy().map(str) + with pytest.raises(TypeError, match="Invalid value"): + left[mask] = right + + def test_setitem_mask_promote_strs(self): + ser = Series([0, 1, 2, 0]) + mask = ser > 0 + ser2 = ser[mask].map(str) + with pytest.raises(TypeError, match="Invalid value"): + ser[mask] = ser2 + + def test_setitem_mask_promote(self): + ser = Series([0, "foo", "bar", 0]) + mask = Series([False, True, True, False]) + ser2 = ser[mask] + ser[mask] = ser2 + + expected = Series([0, "foo", "bar", 0]) + tm.assert_series_equal(ser, expected) + + def test_setitem_boolean(self, string_series): + mask = string_series > string_series.median() + + # similar indexed series + result = string_series.copy() + result[mask] = string_series * 2 + expected = string_series * 2 + tm.assert_series_equal(result[mask], expected[mask]) + + # needs alignment + result = string_series.copy() + result[mask] = (string_series * 2)[0:5] + expected = (string_series * 2)[0:5].reindex_like(string_series) + expected[-mask] = string_series[mask] + tm.assert_series_equal(result[mask], expected[mask]) + + def test_setitem_boolean_corner(self, datetime_series): + ts = datetime_series + mask_shifted = ts.shift(1, freq=BDay()) > ts.median() + + msg = ( + r"Unalignable boolean Series provided as indexer \(index of " + r"the boolean Series and of the indexed object do not match" + ) + with pytest.raises(IndexingError, match=msg): + ts[mask_shifted] = 1 + + with pytest.raises(IndexingError, match=msg): + ts.loc[mask_shifted] = 1 + + def test_setitem_boolean_different_order(self, string_series): + ordered = string_series.sort_values() + + copy = string_series.copy() + copy[ordered > 0] = 0 + + expected = string_series.copy() + expected[expected > 0] = 0 + + tm.assert_series_equal(copy, expected) + + @pytest.mark.parametrize("func", [list, np.array, Series]) + def test_setitem_boolean_python_list(self, func): + # GH19406 + ser = Series([None, "b", None]) + mask = func([True, False, True]) + ser[mask] = ["a", "c"] + expected = Series(["a", "b", "c"]) + tm.assert_series_equal(ser, expected) + + def test_setitem_boolean_nullable_int_types(self, any_numeric_ea_dtype): + # GH: 26468 + ser = Series([5, 6, 7, 8], dtype=any_numeric_ea_dtype) + ser[ser > 6] = Series(range(4), dtype=any_numeric_ea_dtype) + expected = Series([5, 6, 2, 3], dtype=any_numeric_ea_dtype) + tm.assert_series_equal(ser, expected) + + ser = Series([5, 6, 7, 8], dtype=any_numeric_ea_dtype) + ser.loc[ser > 6] = Series(range(4), dtype=any_numeric_ea_dtype) + tm.assert_series_equal(ser, expected) + + ser = Series([5, 6, 7, 8], dtype=any_numeric_ea_dtype) + loc_ser = Series(range(4), dtype=any_numeric_ea_dtype) + ser.loc[ser > 6] = loc_ser.loc[loc_ser > 1] + tm.assert_series_equal(ser, expected) + + def test_setitem_with_bool_mask_and_values_matching_n_trues_in_length(self): + # GH#30567 + ser = Series([None] * 10) + mask = [False] * 3 + [True] * 5 + [False] * 2 + ser[mask] = range(5) + result = ser + expected = Series([None] * 3 + list(range(5)) + [None] * 2, dtype=object) + tm.assert_series_equal(result, expected) + + def test_setitem_nan_with_bool(self): + # GH 13034 + result = Series([True, False, True]) + with pytest.raises(TypeError, match="Invalid value"): + result[0] = np.nan + + def test_setitem_mask_smallint_upcast(self): + orig = Series([1, 2, 3], dtype="int8") + alt = np.array([999, 1000, 1001], dtype=np.int64) + + mask = np.array([True, False, True]) + + ser = orig.copy() + with pytest.raises(TypeError, match="Invalid value"): + ser[mask] = Series(alt) + + with pytest.raises(TypeError, match="Invalid value"): + ser.mask(mask, alt, inplace=True) + + res = ser.where(~mask, Series(alt)) + expected = Series([999, 2, 1001]) + tm.assert_series_equal(res, expected) + + def test_setitem_mask_smallint_no_upcast(self): + # like test_setitem_mask_smallint_upcast, but while we can't hold 'alt', + # we *can* hold alt[mask] without casting + orig = Series([1, 2, 3], dtype="uint8") + alt = Series([245, 1000, 246], dtype=np.int64) + + mask = np.array([True, False, True]) + + ser = orig.copy() + ser[mask] = alt + expected = Series([245, 2, 246], dtype="uint8") + tm.assert_series_equal(ser, expected) + + ser2 = orig.copy() + ser2.mask(mask, alt, inplace=True) + tm.assert_series_equal(ser2, expected) + + # TODO: ser.where(~mask, alt) unnecessarily upcasts to int64 + ser3 = orig.copy() + res = ser3.where(~mask, alt) + tm.assert_series_equal(res, expected, check_dtype=False) + + +class TestSetitemViewCopySemantics: + def test_setitem_invalidates_datetime_index_freq(self): + # GH#24096 altering a datetime64tz Series inplace invalidates the + # `freq` attribute on the underlying DatetimeIndex + + dti = date_range("20130101", periods=3, tz="US/Eastern") + ts = dti[1] + ser = Series(dti) + assert ser._values is not dti + assert ser._values._ndarray.base is dti._data._ndarray.base + assert dti.freq == "D" + ser.iloc[1] = NaT + assert ser._values.freq is None + + # check that the DatetimeIndex was not altered in place + assert ser._values is not dti + assert ser._values._ndarray.base is not dti._data._ndarray.base + assert dti[1] == ts + assert dti.freq == "D" + + def test_dt64tz_setitem_does_not_mutate_dti(self): + # GH#21907, GH#24096 + dti = date_range("2016-01-01", periods=10, tz="US/Pacific") + ts = dti[0] + ser = Series(dti) + assert ser._values is not dti + assert ser._values._ndarray.base is dti._data._ndarray.base + assert ser._mgr.blocks[0].values._ndarray.base is dti._data._ndarray.base + + assert ser._mgr.blocks[0].values is not dti + + ser[::3] = NaT + assert ser[0] is NaT + assert dti[0] == ts + + +class TestSetitemCallable: + def test_setitem_callable_key(self): + # GH#12533 + ser = Series([1, 2, 3, 4], index=list("ABCD")) + ser[lambda x: "A"] = -1 + + expected = Series([-1, 2, 3, 4], index=list("ABCD")) + tm.assert_series_equal(ser, expected) + + def test_setitem_callable_other(self): + # GH#13299 + inc = lambda x: x + 1 + + # set object dtype to avoid upcast when setting inc + ser = Series([1, 2, -1, 4], dtype=object) + ser[ser < 0] = inc + + expected = Series([1, 2, inc, 4]) + tm.assert_series_equal(ser, expected) + + +class TestSetitemWithExpansion: + def test_setitem_empty_series(self, using_infer_string): + # GH#10193 + key = Timestamp("2012-01-01") + series = Series(dtype=object) + series[key] = 47 + expected = Series( + 47, index=[key] if using_infer_string else Index([key], dtype=object) + ) + tm.assert_series_equal(series, expected) + + def test_setitem_empty_series_datetimeindex_preserves_freq(self): + # GH#33573 our index should retain its freq + dti = DatetimeIndex([], freq="D", dtype="M8[ns]") + series = Series([], index=dti, dtype=object) + key = Timestamp("2012-01-01") + series[key] = 47 + expected = Series(47, DatetimeIndex([key], freq="D").as_unit("ns")) + tm.assert_series_equal(series, expected) + assert series.index.freq == expected.index.freq + + def test_setitem_empty_series_timestamp_preserves_dtype(self): + # GH 21881 + timestamp = Timestamp(1412526600000000000) + series = Series([timestamp], index=["timestamp"], dtype=object) + expected = series["timestamp"] + + series = Series([], dtype=object) + series["anything"] = 300.0 + series["timestamp"] = timestamp + result = series["timestamp"] + assert result == expected + + @pytest.mark.parametrize( + "td", + [ + Timedelta("9 days"), + Timedelta("9 days").to_timedelta64(), + Timedelta("9 days").to_pytimedelta(), + ], + ) + def test_append_timedelta_does_not_cast(self, td, using_infer_string, request): + # GH#22717 inserting a Timedelta should _not_ cast to int64 + if using_infer_string and not isinstance(td, Timedelta): + # TODO: GH#56010 + request.applymarker(pytest.mark.xfail(reason="inferred as string")) + + expected = Series(["x", td], index=[0, "td"], dtype=object) + + ser = Series(["x"]) + ser["td"] = td + tm.assert_series_equal(ser, expected) + assert isinstance(ser["td"], Timedelta) + + ser = Series(["x"]) + ser.loc["td"] = Timedelta("9 days") + tm.assert_series_equal(ser, expected) + assert isinstance(ser["td"], Timedelta) + + def test_setitem_with_expansion_type_promotion(self): + # GH#12599 + ser = Series(dtype=object) + ser["a"] = Timestamp("2016-01-01") + ser["b"] = 3.0 + ser["c"] = "foo" + expected = Series([Timestamp("2016-01-01"), 3.0, "foo"], index=["a", "b", "c"]) + tm.assert_series_equal(ser, expected) + + def test_setitem_not_contained(self, string_series): + # set item that's not contained + ser = string_series.copy() + assert "foobar" not in ser.index + ser["foobar"] = 1 + + app = Series([1], index=["foobar"], name="series") + expected = concat([string_series, app]) + tm.assert_series_equal(ser, expected) + + def test_setitem_keep_precision(self, any_numeric_ea_dtype): + # GH#32346 + ser = Series([1, 2], dtype=any_numeric_ea_dtype) + ser[2] = 10 + expected = Series([1, 2, 10], dtype=any_numeric_ea_dtype) + tm.assert_series_equal(ser, expected) + + @pytest.mark.parametrize( + "na, target_na, dtype, target_dtype, indexer, raises", + [ + (NA, NA, "Int64", "Int64", 1, False), + (NA, NA, "Int64", "Int64", 2, False), + (NA, np.nan, "int64", "float64", 1, False), + (NA, np.nan, "int64", "float64", 2, False), + (NaT, NaT, "int64", "object", 1, True), + (NaT, NaT, "int64", "object", 2, False), + (np.nan, NA, "Int64", "Int64", 1, False), + (np.nan, NA, "Int64", "Int64", 2, False), + (np.nan, NA, "Float64", "Float64", 1, False), + (np.nan, NA, "Float64", "Float64", 2, False), + (np.nan, np.nan, "int64", "float64", 1, False), + (np.nan, np.nan, "int64", "float64", 2, False), + ], + ) + def test_setitem_enlarge_with_na( + self, na, target_na, dtype, target_dtype, indexer, raises + ): + # GH#32346 + ser = Series([1, 2], dtype=dtype) + if raises: + with pytest.raises(TypeError, match="Invalid value"): + ser[indexer] = na + else: + ser[indexer] = na + expected_values = [1, target_na] if indexer == 1 else [1, 2, target_na] + expected = Series(expected_values, dtype=target_dtype) + tm.assert_series_equal(ser, expected) + + def test_setitem_enlargement_object_none(self, nulls_fixture, using_infer_string): + # GH#48665 + ser = Series(["a", "b"]) + ser[3] = nulls_fixture + dtype = ( + "str" + if using_infer_string and not isinstance(nulls_fixture, Decimal) + else object + ) + expected = Series(["a", "b", nulls_fixture], index=[0, 1, 3], dtype=dtype) + tm.assert_series_equal(ser, expected) + if using_infer_string: + ser[3] is np.nan + else: + assert ser[3] is nulls_fixture + + +def test_setitem_scalar_into_readonly_backing_data(): + # GH#14359: test that you cannot mutate a read only buffer + + array = np.zeros(5) + array.flags.writeable = False # make the array immutable + series = Series(array, copy=False) + + for n in series.index: + msg = "assignment destination is read-only" + with pytest.raises(ValueError, match=msg): + series[n] = 1 + + assert array[n] == 0 + + +def test_setitem_slice_into_readonly_backing_data(): + # GH#14359: test that you cannot mutate a read only buffer + + array = np.zeros(5) + array.flags.writeable = False # make the array immutable + series = Series(array, copy=False) + + msg = "assignment destination is read-only" + with pytest.raises(ValueError, match=msg): + series[1:3] = 1 + + assert not array.any() + + +def test_setitem_categorical_assigning_ops(): + orig = Series(Categorical(["b", "b"], categories=["a", "b"])) + ser = orig.copy() + ser[:] = "a" + exp = Series(Categorical(["a", "a"], categories=["a", "b"])) + tm.assert_series_equal(ser, exp) + + ser = orig.copy() + ser[1] = "a" + exp = Series(Categorical(["b", "a"], categories=["a", "b"])) + tm.assert_series_equal(ser, exp) + + ser = orig.copy() + ser[ser.index > 0] = "a" + exp = Series(Categorical(["b", "a"], categories=["a", "b"])) + tm.assert_series_equal(ser, exp) + + ser = orig.copy() + ser[[False, True]] = "a" + exp = Series(Categorical(["b", "a"], categories=["a", "b"])) + tm.assert_series_equal(ser, exp) + + ser = orig.copy() + ser.index = ["x", "y"] + ser["y"] = "a" + exp = Series(Categorical(["b", "a"], categories=["a", "b"]), index=["x", "y"]) + tm.assert_series_equal(ser, exp) + + +def test_setitem_nan_into_categorical(): + # ensure that one can set something to np.nan + ser = Series(Categorical([1, 2, 3])) + exp = Series(Categorical([1, np.nan, 3], categories=[1, 2, 3])) + ser[1] = np.nan + tm.assert_series_equal(ser, exp) + + +class TestSetitemCasting: + @pytest.mark.parametrize("unique", [True, False]) + @pytest.mark.parametrize("val", [3, 3.0, "3"], ids=type) + def test_setitem_non_bool_into_bool(self, val, indexer_sli, unique): + # dont cast these 3-like values to bool + ser = Series([True, False]) + if not unique: + ser.index = [1, 1] + + with pytest.raises(TypeError, match="Invalid value"): + indexer_sli(ser)[1] = val + + def test_setitem_boolean_array_into_npbool(self): + # GH#45462 + ser = Series([True, False, True]) + values = ser._values + arr = array([True, False, None]) + + ser[:2] = arr[:2] # no NAs -> can set inplace + assert ser._values is values + + with pytest.raises(TypeError, match="Invalid value"): + ser[1:] = arr[1:] # has an NA -> cast to boolean dtype + + +class SetitemCastingEquivalents: + """ + Check each of several methods that _should_ be equivalent to `obj[key] = val` + + We assume that + - obj.index is the default Index(range(len(obj))) + - the setitem does not expand the obj + """ + + @pytest.fixture + def is_inplace(self, obj, expected): + """ + Whether we expect the setting to be in-place or not. + """ + return expected.dtype == obj.dtype + + def check_indexer(self, obj, key, expected, val, indexer, is_inplace): + orig = obj + obj = obj.copy() + arr = obj._values + + indexer(obj)[key] = val + tm.assert_series_equal(obj, expected) + + self._check_inplace(is_inplace, orig, arr, obj) + + def _check_inplace(self, is_inplace, orig, arr, obj): + if is_inplace is None: + # We are not (yet) checking whether setting is inplace or not + pass + elif is_inplace: + if arr.dtype.kind in ["m", "M"]: + # We may not have the same DTA/TDA, but will have the same + # underlying data + assert arr._ndarray is obj._values._ndarray + else: + assert obj._values is arr + else: + # otherwise original array should be unchanged + tm.assert_equal(arr, orig._values) + + def test_int_key(self, obj, key, expected, raises, val, indexer_sli, is_inplace): + if not isinstance(key, int): + pytest.skip("Not relevant for int key") + if raises: + ctx = pytest.raises(TypeError, match="Invalid value") + else: + ctx = contextlib.nullcontext() + + with ctx: + self.check_indexer(obj, key, expected, val, indexer_sli, is_inplace) + + if indexer_sli is tm.loc: + with ctx: + self.check_indexer(obj, key, expected, val, tm.at, is_inplace) + elif indexer_sli is tm.iloc: + with ctx: + self.check_indexer(obj, key, expected, val, tm.iat, is_inplace) + + rng = range(key, key + 1) + with ctx: + self.check_indexer(obj, rng, expected, val, indexer_sli, is_inplace) + + if indexer_sli is not tm.loc: + # Note: no .loc because that handles slice edges differently + slc = slice(key, key + 1) + with ctx: + self.check_indexer(obj, slc, expected, val, indexer_sli, is_inplace) + + ilkey = [key] + with ctx: + self.check_indexer(obj, ilkey, expected, val, indexer_sli, is_inplace) + + indkey = np.array(ilkey) + with ctx: + self.check_indexer(obj, indkey, expected, val, indexer_sli, is_inplace) + + genkey = (x for x in [key]) + with ctx: + self.check_indexer(obj, genkey, expected, val, indexer_sli, is_inplace) + + def test_slice_key(self, obj, key, expected, raises, val, indexer_sli, is_inplace): + if not isinstance(key, slice): + pytest.skip("Not relevant for slice key") + if raises: + ctx = pytest.raises(TypeError, match="Invalid value") + else: + ctx = contextlib.nullcontext() + + if indexer_sli is not tm.loc: + # Note: no .loc because that handles slice edges differently + with ctx: + self.check_indexer(obj, key, expected, val, indexer_sli, is_inplace) + + ilkey = list(range(len(obj)))[key] + with ctx: + self.check_indexer(obj, ilkey, expected, val, indexer_sli, is_inplace) + + indkey = np.array(ilkey) + with ctx: + self.check_indexer(obj, indkey, expected, val, indexer_sli, is_inplace) + + genkey = (x for x in indkey) + with ctx: + self.check_indexer(obj, genkey, expected, val, indexer_sli, is_inplace) + + def test_mask_key(self, obj, key, expected, raises, val, indexer_sli): + # setitem with boolean mask + mask = np.zeros(obj.shape, dtype=bool) + mask[key] = True + + obj = obj.copy() + + if is_list_like(val) and len(val) < mask.sum(): + msg = "boolean index did not match indexed array along dimension" + with pytest.raises(IndexError, match=msg): + indexer_sli(obj)[mask] = val + return + + if raises: + with pytest.raises(TypeError, match="Invalid value"): + indexer_sli(obj)[mask] = val + else: + indexer_sli(obj)[mask] = val + + def test_series_where(self, obj, key, expected, raises, val, is_inplace): + mask = np.zeros(obj.shape, dtype=bool) + mask[key] = True + + if is_list_like(val) and len(val) < len(obj): + # Series.where is not valid here + msg = "operands could not be broadcast together with shapes" + with pytest.raises(ValueError, match=msg): + obj.where(~mask, val) + return + + orig = obj + obj = obj.copy() + arr = obj._values + + res = obj.where(~mask, val) + + if val is NA and res.dtype == object: + expected = expected.fillna(NA) + elif val is None and res.dtype == object: + assert expected.dtype == object + expected = expected.copy() + expected[expected.isna()] = None + tm.assert_series_equal(res, expected) + + self._check_inplace(is_inplace, orig, arr, obj) + + def test_index_where(self, obj, key, expected, raises, val): + mask = np.zeros(obj.shape, dtype=bool) + mask[key] = True + + res = Index(obj).where(~mask, val) + expected_idx = Index(expected, dtype=expected.dtype) + tm.assert_index_equal(res, expected_idx) + + def test_index_putmask(self, obj, key, expected, raises, val): + mask = np.zeros(obj.shape, dtype=bool) + mask[key] = True + + res = Index(obj).putmask(mask, val) + tm.assert_index_equal(res, Index(expected, dtype=expected.dtype)) + + +@pytest.mark.parametrize( + "obj,expected,key,raises", + [ + pytest.param( + # GH#45568 setting a valid NA value into IntervalDtype[int] should + # cast to IntervalDtype[float] + Series(interval_range(1, 5)), + Series( + [Interval(1, 2), np.nan, Interval(3, 4), Interval(4, 5)], + dtype="interval[float64]", + ), + 1, + True, + id="interval_int_na_value", + ), + pytest.param( + # these induce dtype changes + Series([2, 3, 4, 5, 6, 7, 8, 9, 10]), + Series([np.nan, 3, np.nan, 5, np.nan, 7, np.nan, 9, np.nan]), + slice(None, None, 2), + False, + id="int_series_slice_key_step", + ), + pytest.param( + Series([True, True, False, False]), + Series([np.nan, True, np.nan, False], dtype=object), + slice(None, None, 2), + True, + id="bool_series_slice_key_step", + ), + pytest.param( + # these induce dtype changes + Series(np.arange(10)), + Series([np.nan, np.nan, np.nan, np.nan, np.nan, 5, 6, 7, 8, 9]), + slice(None, 5), + False, + id="int_series_slice_key", + ), + pytest.param( + # changes dtype GH#4463 + Series([1, 2, 3]), + Series([np.nan, 2, 3]), + 0, + False, + id="int_series_int_key", + ), + pytest.param( + # changes dtype GH#4463 + Series([False]), + Series([np.nan], dtype=object), + # TODO: maybe go to float64 since we are changing the _whole_ Series? + 0, + True, + id="bool_series_int_key_change_all", + ), + pytest.param( + # changes dtype GH#4463 + Series([False, True]), + Series([np.nan, True], dtype=object), + 0, + True, + id="bool_series_int_key", + ), + ], +) +class TestSetitemCastingEquivalents(SetitemCastingEquivalents): + @pytest.fixture(params=[np.nan, np.float64("NaN"), None, NA]) + def val(self, request): + """ + NA values that should generally be valid_na for *all* dtypes. + + Include both python float NaN and np.float64; only np.float64 has a + `dtype` attribute. + """ + return request.param + + +class TestSetitemTimedelta64IntoNumeric(SetitemCastingEquivalents): + # timedelta64 should not be treated as integers when setting into + # numeric Series + + @pytest.fixture + def val(self): + td = np.timedelta64(4, "ns") + return td + # TODO: could also try np.full((1,), td) + + @pytest.fixture(params=[complex, int, float]) + def dtype(self, request): + return request.param + + @pytest.fixture + def obj(self, dtype): + arr = np.arange(5).astype(dtype) + ser = Series(arr) + return ser + + @pytest.fixture + def expected(self, dtype): + arr = np.arange(5).astype(dtype) + ser = Series(arr) + ser = ser.astype(object) + ser.iloc[0] = np.timedelta64(4, "ns") + return ser + + @pytest.fixture + def key(self): + return 0 + + @pytest.fixture + def raises(self): + return True + + +class TestSetitemDT64IntoInt(SetitemCastingEquivalents): + # GH#39619 dont cast dt64 to int when doing this setitem + + @pytest.fixture(params=["M8[ns]", "m8[ns]"]) + def dtype(self, request): + return request.param + + @pytest.fixture + def scalar(self, dtype): + val = np.datetime64("2021-01-18 13:25:00", "ns") + if dtype == "m8[ns]": + val = val - val + return val + + @pytest.fixture + def expected(self, scalar): + expected = Series([scalar, scalar, 3], dtype=object) + assert isinstance(expected[0], type(scalar)) + return expected + + @pytest.fixture + def obj(self): + return Series([1, 2, 3]) + + @pytest.fixture + def key(self): + return slice(None, -1) + + @pytest.fixture(params=[None, list, np.array]) + def val(self, scalar, request): + box = request.param + if box is None: + return scalar + return box([scalar, scalar]) + + @pytest.fixture + def raises(self): + return True + + +class TestSetitemNAPeriodDtype(SetitemCastingEquivalents): + # Setting compatible NA values into Series with PeriodDtype + + @pytest.fixture + def expected(self, key): + exp = Series(period_range("2000-01-01", periods=10, freq="D")) + exp._values.view("i8")[key] = NaT._value + assert exp[key] is NaT or all(x is NaT for x in exp[key]) + return exp + + @pytest.fixture + def obj(self): + return Series(period_range("2000-01-01", periods=10, freq="D")) + + @pytest.fixture(params=[3, slice(3, 5)]) + def key(self, request): + return request.param + + @pytest.fixture(params=[None, np.nan]) + def val(self, request): + return request.param + + @pytest.fixture + def raises(self): + return False + + +class TestSetitemNADatetimeLikeDtype(SetitemCastingEquivalents): + # some nat-like values should be cast to datetime64/timedelta64 when + # inserting into a datetime64/timedelta64 series. Others should coerce + # to object and retain their dtypes. + # GH#18586 for td64 and boolean mask case + + @pytest.fixture( + params=["m8[ns]", "M8[ns]", "datetime64[ns, UTC]", "datetime64[ns, US/Central]"] + ) + def dtype(self, request): + return request.param + + @pytest.fixture + def obj(self, dtype): + i8vals = date_range("2016-01-01", periods=3).asi8 + idx = Index(i8vals, dtype=dtype) + assert idx.dtype == dtype + return Series(idx) + + @pytest.fixture( + params=[ + None, + np.nan, + NaT, + np.timedelta64("NaT", "ns"), + np.datetime64("NaT", "ns"), + ] + ) + def val(self, request): + return request.param + + @pytest.fixture + def is_inplace(self, val, obj): + # td64 -> cast to object iff val is datetime64("NaT") + # dt64 -> cast to object iff val is timedelta64("NaT") + # dt64tz -> cast to object with anything _but_ NaT + return val is NaT or val is None or val is np.nan or obj.dtype == val.dtype + + @pytest.fixture + def expected(self, obj, val, is_inplace): + dtype = obj.dtype if is_inplace else object + expected = Series([val, *list(obj[1:])], dtype=dtype) + return expected + + @pytest.fixture + def key(self): + return 0 + + @pytest.fixture + def raises(self, is_inplace): + return False if is_inplace else True + + +class TestSetitemMismatchedTZCastsToObject(SetitemCastingEquivalents): + # GH#24024 + @pytest.fixture + def obj(self): + return Series(date_range("2000", periods=2, tz="US/Central")) + + @pytest.fixture + def val(self): + return Timestamp("2000", tz="US/Eastern") + + @pytest.fixture + def key(self): + return 0 + + @pytest.fixture + def expected(self, obj, val): + # pre-2.0 this would cast to object, in 2.0 we cast the val to + # the target tz + expected = Series( + [ + val.tz_convert("US/Central"), + Timestamp("2000-01-02 00:00:00-06:00", tz="US/Central"), + ], + dtype=obj.dtype, + ) + return expected + + @pytest.fixture + def raises(self): + return False + + +@pytest.mark.parametrize( + "obj,expected", + [ + # For numeric series, we should coerce to NaN. + (Series([1, 2, 3]), Series([np.nan, 2, 3])), + (Series([1.0, 2.0, 3.0]), Series([np.nan, 2.0, 3.0])), + # For datetime series, we should coerce to NaT. + ( + Series([datetime(2000, 1, 1), datetime(2000, 1, 2), datetime(2000, 1, 3)]), + Series([NaT, datetime(2000, 1, 2), datetime(2000, 1, 3)]), + ), + # For objects, we should preserve the None value. + (Series(["foo", "bar", "baz"]), Series([None, "bar", "baz"])), + ], +) +class TestSeriesNoneCoercion(SetitemCastingEquivalents): + @pytest.fixture + def key(self): + return 0 + + @pytest.fixture + def val(self): + return None + + @pytest.fixture + def raises(self): + return False + + +class TestSetitemFloatIntervalWithIntIntervalValues(SetitemCastingEquivalents): + # GH#44201 Cast to shared IntervalDtype rather than object + + def test_setitem_example(self): + # Just a case here to make obvious what this test class is aimed at + idx = IntervalIndex.from_breaks(range(4)) + obj = Series(idx) + val = Interval(0.5, 1.5) + + with pytest.raises(TypeError, match="Invalid value"): + obj[0] = val + + @pytest.fixture + def obj(self): + """ + Fixture to create a Series [(0, 1], (1, 2], (2, 3]] + """ + idx = IntervalIndex.from_breaks(range(4)) + return Series(idx) + + @pytest.fixture + def val(self): + """ + Fixture to get an interval (0.5, 1.5] + """ + return Interval(0.5, 1.5) + + @pytest.fixture + def key(self): + """ + Fixture to get a key 0 + """ + return 0 + + @pytest.fixture + def expected(self, obj, val): + """ + Fixture to get a Series [(0.5, 1.5], (1.0, 2.0], (2.0, 3.0]] + """ + data = [val, *list(obj[1:])] + idx = IntervalIndex(data, dtype="Interval[float64]") + return Series(idx) + + @pytest.fixture + def raises(self): + """ + Fixture to enable raising pytest exceptions + """ + return True + + +class TestSetitemRangeIntoIntegerSeries(SetitemCastingEquivalents): + # GH#44261 Setting a range with sufficiently-small integers into + # small-itemsize integer dtypes should not need to upcast + + @pytest.fixture + def obj(self, any_int_numpy_dtype): + dtype = np.dtype(any_int_numpy_dtype) + ser = Series(range(5), dtype=dtype) + return ser + + @pytest.fixture + def val(self): + return range(2, 4) + + @pytest.fixture + def key(self): + return slice(0, 2) + + @pytest.fixture + def expected(self, any_int_numpy_dtype): + dtype = np.dtype(any_int_numpy_dtype) + exp = Series([2, 3, 2, 3, 4], dtype=dtype) + return exp + + @pytest.fixture + def raises(self): + return False + + +@pytest.mark.parametrize( + "val, raises", + [ + (np.array([2.0, 3.0]), False), + (np.array([2.5, 3.5]), True), + ( + np.array([2**65, 2**65 + 1], dtype=np.float64), + True, + ), # all ints, but can't cast + ], +) +class TestSetitemFloatNDarrayIntoIntegerSeries(SetitemCastingEquivalents): + @pytest.fixture + def obj(self): + return Series(range(5), dtype=np.int64) + + @pytest.fixture + def key(self): + return slice(0, 2) + + @pytest.fixture + def expected(self, val): + if val[0] == 2: + # NB: this condition is based on currently-hardcoded "val" cases + dtype = np.int64 + else: + dtype = np.float64 + res_values = np.array(range(5), dtype=dtype) + res_values[:2] = val + return Series(res_values) + + +@pytest.mark.parametrize("val", [512, np.int16(512)]) +class TestSetitemIntoIntegerSeriesNeedsUpcast(SetitemCastingEquivalents): + @pytest.fixture + def obj(self): + return Series([1, 2, 3], dtype=np.int8) + + @pytest.fixture + def key(self): + return 1 + + @pytest.fixture + def expected(self): + return Series([1, 512, 3], dtype=np.int16) + + @pytest.fixture + def raises(self): + return True + + +@pytest.mark.parametrize("val", [2**33 + 1.0, 2**33 + 1.1, 2**62]) +class TestSmallIntegerSetitemUpcast(SetitemCastingEquivalents): + # https://github.com/pandas-dev/pandas/issues/39584#issuecomment-941212124 + @pytest.fixture + def obj(self): + return Series([1, 2, 3], dtype="i4") + + @pytest.fixture + def key(self): + return 0 + + @pytest.fixture + def expected(self, val): + if val % 1 != 0: + dtype = "f8" + else: + dtype = "i8" + return Series([val, 2, 3], dtype=dtype) + + @pytest.fixture + def raises(self): + return True + + +class CoercionTest(SetitemCastingEquivalents): + # Tests ported from tests.indexing.test_coercion + + @pytest.fixture + def key(self): + return 1 + + @pytest.fixture + def expected(self, obj, key, val, exp_dtype): + vals = list(obj) + vals[key] = val + return Series(vals, dtype=exp_dtype) + + +@pytest.mark.parametrize( + "val,exp_dtype,raises", + [(np.int32(1), np.int8, None), (np.int16(2**9), np.int16, True)], +) +class TestCoercionInt8(CoercionTest): + # previously test_setitem_series_int8 in tests.indexing.test_coercion + @pytest.fixture + def obj(self): + return Series([1, 2, 3, 4], dtype=np.int8) + + +@pytest.mark.parametrize("val", [1, 1.1, 1 + 1j, True]) +@pytest.mark.parametrize("exp_dtype", [object]) +class TestCoercionObject(CoercionTest): + # previously test_setitem_series_object in tests.indexing.test_coercion + @pytest.fixture + def obj(self): + return Series(["a", "b", "c", "d"], dtype=object) + + @pytest.fixture + def raises(self): + return False + + +@pytest.mark.parametrize( + "val,exp_dtype,raises", + [ + (1, object, True), + ("e", StringDtype(na_value=np.nan), False), + ], +) +class TestCoercionString(CoercionTest): + @pytest.fixture + def obj(self): + return Series(["a", "b", "c", "d"], dtype=StringDtype(na_value=np.nan)) + + +@pytest.mark.parametrize( + "val,exp_dtype,raises", + [ + (1, np.complex128, False), + (1.1, np.complex128, False), + (1 + 1j, np.complex128, False), + (True, object, True), + ], +) +class TestCoercionComplex(CoercionTest): + # previously test_setitem_series_complex128 in tests.indexing.test_coercion + @pytest.fixture + def obj(self): + return Series([1 + 1j, 2 + 2j, 3 + 3j, 4 + 4j]) + + +@pytest.mark.parametrize( + "val,exp_dtype,raises", + [ + (1, object, True), + ("3", object, True), + (3, object, True), + (1.1, object, True), + (1 + 1j, object, True), + (True, bool, False), + ], +) +class TestCoercionBool(CoercionTest): + # previously test_setitem_series_bool in tests.indexing.test_coercion + @pytest.fixture + def obj(self): + return Series([True, False, True, False], dtype=bool) + + +@pytest.mark.parametrize( + "val,exp_dtype,raises", + [ + (1, np.int64, False), + (1.1, np.float64, True), + (1 + 1j, np.complex128, True), + (True, object, True), + ], +) +class TestCoercionInt64(CoercionTest): + # previously test_setitem_series_int64 in tests.indexing.test_coercion + @pytest.fixture + def obj(self): + return Series([1, 2, 3, 4]) + + +@pytest.mark.parametrize( + "val,exp_dtype,raises", + [ + (1, np.float64, False), + (1.1, np.float64, False), + (1 + 1j, np.complex128, True), + (True, object, True), + ], +) +class TestCoercionFloat64(CoercionTest): + # previously test_setitem_series_float64 in tests.indexing.test_coercion + @pytest.fixture + def obj(self): + return Series([1.1, 2.2, 3.3, 4.4]) + + +@pytest.mark.parametrize( + "val,exp_dtype,raises", + [ + (1, np.float32, False), + pytest.param( + 1.1, + np.float32, + False, + marks=pytest.mark.xfail( + not np_version_gt2, + reason="np.float32(1.1) ends up as 1.100000023841858, so " + "np_can_hold_element raises and we cast to float64", + ), + ), + (1 + 1j, np.complex128, True), + (True, object, True), + (np.uint8(2), np.float32, False), + (np.uint32(2), np.float32, False), + # float32 cannot hold np.iinfo(np.uint32).max exactly + # (closest it can hold is 4294967300.0 which off by 5.0), so + # we cast to float64 + (np.uint32(np.iinfo(np.uint32).max), np.float64, True), + (np.uint64(2), np.float32, False), + (np.int64(2), np.float32, False), + ], +) +class TestCoercionFloat32(CoercionTest): + @pytest.fixture + def obj(self): + return Series([1.1, 2.2, 3.3, 4.4], dtype=np.float32) + + def test_slice_key(self, obj, key, expected, raises, val, indexer_sli, is_inplace): + super().test_slice_key(obj, key, expected, raises, val, indexer_sli, is_inplace) + + if isinstance(val, float): + # the xfail would xpass bc test_slice_key short-circuits + raise AssertionError("xfail not relevant for this test.") + + +@pytest.mark.parametrize( + "exp_dtype", + [ + "M8[ms]", + "M8[ms, UTC]", + "m8[ms]", + ], +) +class TestCoercionDatetime64HigherReso(CoercionTest): + @pytest.fixture + def obj(self, exp_dtype): + idx = date_range("2011-01-01", freq="D", periods=4, unit="s") + if exp_dtype == "m8[ms]": + idx = idx - Timestamp("1970-01-01").as_unit("s") + assert idx.dtype == "m8[s]" + elif exp_dtype == "M8[ms, UTC]": + idx = idx.tz_localize("UTC") + return Series(idx) + + @pytest.fixture + def val(self, exp_dtype): + ts = Timestamp("2011-01-02 03:04:05.678").as_unit("ms") + if exp_dtype == "m8[ms]": + return ts - Timestamp("1970-01-01").as_unit("s") + elif exp_dtype == "M8[ms, UTC]": + return ts.tz_localize("UTC") + return ts + + @pytest.fixture + def raises(self): + return True + + +@pytest.mark.parametrize( + "val,exp_dtype,raises", + [ + (Timestamp("2012-01-01"), "datetime64[ns]", False), + (1, object, True), + ("x", object, True), + ], +) +class TestCoercionDatetime64(CoercionTest): + # previously test_setitem_series_datetime64 in tests.indexing.test_coercion + + @pytest.fixture + def obj(self): + return Series(date_range("2011-01-01", freq="D", periods=4, unit="ns")) + + @pytest.fixture + def raises(self): + return False + + +@pytest.mark.parametrize( + "val,exp_dtype,raises", + [ + (Timestamp("2012-01-01", tz="US/Eastern"), "datetime64[ns, US/Eastern]", False), + # pre-2.0, a mis-matched tz would end up casting to object + (Timestamp("2012-01-01", tz="US/Pacific"), "datetime64[ns, US/Eastern]", False), + (Timestamp("2012-01-01"), object, True), + (1, object, True), + ], +) +class TestCoercionDatetime64TZ(CoercionTest): + # previously test_setitem_series_datetime64tz in tests.indexing.test_coercion + @pytest.fixture + def obj(self): + tz = "US/Eastern" + return Series(date_range("2011-01-01", freq="D", periods=4, tz=tz, unit="ns")) + + @pytest.fixture + def raises(self): + return False + + +@pytest.mark.parametrize( + "val,exp_dtype,raises", + [ + (Timedelta("12 day"), "timedelta64[us]", False), + (1, object, True), + ("x", object, True), + ], +) +class TestCoercionTimedelta64(CoercionTest): + # previously test_setitem_series_timedelta64 in tests.indexing.test_coercion + @pytest.fixture + def obj(self): + return Series(timedelta_range("1 day", periods=4)) + + @pytest.fixture + def raises(self): + return False + + +@pytest.mark.parametrize( + "val", ["foo", Period("2016", freq="Y"), Interval(1, 2, closed="both")] +) +@pytest.mark.parametrize("exp_dtype", [object]) +class TestPeriodIntervalCoercion(CoercionTest): + # GH#45768 + @pytest.fixture( + params=[ + period_range("2016-01-01", periods=3, freq="D"), + interval_range(1, 5), + ] + ) + def obj(self, request): + return Series(request.param) + + @pytest.fixture + def raises(self): + return True + + +def test_20643(): + # closed by GH#45121 + orig = Series([0, 1, 2], index=["a", "b", "c"]) + + ser = orig.copy() + with pytest.raises(TypeError, match="Invalid value"): + ser.at["b"] = 2.7 + + with pytest.raises(TypeError, match="Invalid value"): + ser.loc["b"] = 2.7 + + with pytest.raises(TypeError, match="Invalid value"): + ser["b"] = 2.7 + + ser = orig.copy() + with pytest.raises(TypeError, match="Invalid value"): + ser.iat[1] = 2.7 + + with pytest.raises(TypeError, match="Invalid value"): + ser.iloc[1] = 2.7 + + orig_df = orig.to_frame("A") + + df = orig_df.copy() + with pytest.raises(TypeError, match="Invalid value"): + df.at["b", "A"] = 2.7 + + with pytest.raises(TypeError, match="Invalid value"): + df.loc["b", "A"] = 2.7 + + with pytest.raises(TypeError, match="Invalid value"): + df.iloc[1, 0] = 2.7 + + with pytest.raises(TypeError, match="Invalid value"): + df.iat[1, 0] = 2.7 + + +def test_20643_comment(): + # https://github.com/pandas-dev/pandas/issues/20643#issuecomment-431244590 + # fixed sometime prior to GH#45121 + orig = Series([0, 1, 2], index=["a", "b", "c"]) + expected = Series([np.nan, 1, 2], index=["a", "b", "c"]) + + ser = orig.copy() + ser.iat[0] = None + tm.assert_series_equal(ser, expected) + + ser = orig.copy() + ser.iloc[0] = None + tm.assert_series_equal(ser, expected) + + +def test_15413(): + # fixed by GH#45121 + ser = Series([1, 2, 3]) + + with pytest.raises(TypeError, match="Invalid value"): + ser[ser == 2] += 0.5 + + with pytest.raises(TypeError, match="Invalid value"): + ser[1] += 0.5 + + with pytest.raises(TypeError, match="Invalid value"): + ser.loc[1] += 0.5 + + with pytest.raises(TypeError, match="Invalid value"): + ser.iloc[1] += 0.5 + + with pytest.raises(TypeError, match="Invalid value"): + ser.iat[1] += 0.5 + + with pytest.raises(TypeError, match="Invalid value"): + ser.at[1] += 0.5 + + +def test_32878_int_itemsize(): + # Fixed by GH#45121 + arr = np.arange(5).astype("i4") + ser = Series(arr) + val = np.int64(np.iinfo(np.int64).max) + with pytest.raises(TypeError, match="Invalid value"): + ser[0] = val + + +def test_32878_complex_itemsize(): + arr = np.arange(5).astype("c8") + ser = Series(arr) + val = np.finfo(np.float64).max + val = val.astype("c16") + + # GH#32878 used to coerce val to inf+0.000000e+00j + with pytest.raises(TypeError, match="Invalid value"): + ser[0] = val + + +def test_37692(indexer_al): + # GH#37692 + ser = Series([1, 2, 3], index=["a", "b", "c"]) + with pytest.raises(TypeError, match="Invalid value"): + indexer_al(ser)["b"] = "test" + + +def test_setitem_bool_int_float_consistency(indexer_sli): + # GH#21513 + # bool-with-int and bool-with-float both upcast to object + # int-with-float and float-with-int are both non-casting so long + # as the setitem can be done losslessly + for dtype in [np.float64, np.int64]: + ser = Series(0, index=range(3), dtype=dtype) + with pytest.raises(TypeError, match="Invalid value"): + indexer_sli(ser)[0] = True + + ser = Series(0, index=range(3), dtype=bool) + with pytest.raises(TypeError, match="Invalid value"): + ser[0] = dtype(1) + + # 1.0 can be held losslessly, so no casting + ser = Series(0, index=range(3), dtype=np.int64) + indexer_sli(ser)[0] = np.float64(1.0) + assert ser.dtype == np.int64 + + # 1 can be held losslessly, so no casting + ser = Series(0, index=range(3), dtype=np.float64) + indexer_sli(ser)[0] = np.int64(1) + + +def test_setitem_positional_with_casting(): + # GH#45070 case where in __setitem__ we get a KeyError, then when + # we fallback we *also* get a ValueError if we try to set inplace. + # As of 3.0 we always treat int keys as labels, so this becomes + # setitem-with-expansion + ser = Series([1, 2, 3], index=["a", "b", "c"]) + + ser[0] = "X" + expected = Series([1, 2, 3, "X"], index=["a", "b", "c", 0], dtype=object) + tm.assert_series_equal(ser, expected) + + +def test_setitem_positional_float_into_int_coerces(): + # Case where we hit a KeyError and then trying to set in-place incorrectly + # casts a float to an int; + # As of 3.0 we always treat int keys as labels, so this becomes + # setitem-with-expansion + ser = Series([1, 2, 3], index=["a", "b", "c"]) + + ser[0] = 1.5 + expected = Series([1, 2, 3, 1.5], index=["a", "b", "c", 0]) + tm.assert_series_equal(ser, expected) + + +def test_setitem_int_not_positional(): + # GH#42215 deprecated falling back to positional on __setitem__ with an + # int not contained in the index; enforced in 2.0 + ser = Series([1, 2, 3, 4], index=[1.1, 2.1, 3.0, 4.1]) + assert not ser.index._should_fallback_to_positional + # assert not ser.index.astype(object)._should_fallback_to_positional + + # 3.0 is in our index, so post-enforcement behavior is unchanged + ser[3] = 10 + expected = Series([1, 2, 10, 4], index=ser.index) + tm.assert_series_equal(ser, expected) + + # pre-enforcement `ser[5] = 5` raised IndexError + ser[5] = 5 + expected = Series([1, 2, 10, 4, 5], index=[1.1, 2.1, 3.0, 4.1, 5.0]) + tm.assert_series_equal(ser, expected) + + ii = IntervalIndex.from_breaks(range(10))[::2] + ser2 = Series(range(len(ii)), index=ii) + exp_index = ii.astype(object).append(Index([4])) + expected2 = Series([0, 1, 2, 3, 4, 9], index=exp_index) + # pre-enforcement `ser2[4] = 9` interpreted 4 as positional + ser2[4] = 9 + tm.assert_series_equal(ser2, expected2) + + mi = MultiIndex.from_product([ser.index, ["A", "B"]]) + ser3 = Series(range(len(mi)), index=mi) + expected3 = ser3.copy() + expected3.loc[4] = 99 + # pre-enforcement `ser3[4] = 99` interpreted 4 as positional + ser3[4] = 99 + tm.assert_series_equal(ser3, expected3) + + +def test_setitem_with_bool_indexer(): + # GH#42530 + + df = DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) + result = df.pop("b").copy() + result[[True, False, False]] = 9 + expected = Series(data=[9, 5, 6], name="b") + tm.assert_series_equal(result, expected) + + df.loc[[True, False, False], "a"] = 10 + expected = DataFrame({"a": [10, 2, 3]}) + tm.assert_frame_equal(df, expected) + + +@pytest.mark.parametrize("size", range(2, 6)) +@pytest.mark.parametrize( + "mask", [[True, False, False, False, False], [True, False], [False]] +) +@pytest.mark.parametrize( + "item", [2.0, np.nan, np.finfo(float).max, np.finfo(float).min] +) +@pytest.mark.parametrize("box", [np.array, list, tuple]) +def test_setitem_bool_indexer_dont_broadcast_length1_values(size, mask, item, box): + # GH#44265 + # see also tests.series.indexing.test_where.test_broadcast + + selection = np.resize(mask, size) + + data = np.arange(size, dtype=float) + + ser = Series(data) + + if selection.sum() != 1: + msg = ( + "cannot set using a list-like indexer with a different " + "length than the value" + ) + with pytest.raises(ValueError, match=msg): + # GH#44265 + ser[selection] = box([item]) + else: + # In this corner case setting is equivalent to setting with the unboxed + # item + ser[selection] = box([item]) + + expected = Series(np.arange(size, dtype=float)) + expected[selection] = item + tm.assert_series_equal(ser, expected) + + +def test_setitem_empty_mask_dont_upcast_dt64(): + dti = date_range("2016-01-01", periods=3) + ser = Series(dti) + orig = ser.copy() + mask = np.zeros(3, dtype=bool) + + ser[mask] = "foo" + assert ser.dtype == dti.dtype # no-op -> dont upcast + tm.assert_series_equal(ser, orig) + + ser.mask(mask, "foo", inplace=True) + assert ser.dtype == dti.dtype # no-op -> dont upcast + tm.assert_series_equal(ser, orig) + + +def test_setitem_bool_dtype_with_boolean_indexer(): + # GH 57338 + s1 = Series([True, True, True], dtype=bool) + s2 = Series([False, False, False], dtype=bool) + condition = [False, True, False] + s1[condition] = s2[condition] + expected = Series([True, False, True], dtype=bool) + tm.assert_series_equal(s1, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_take.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_take.py new file mode 100644 index 0000000000000000000000000000000000000000..43fbae89089665fb2bf589d35db1b3f3041dd018 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_take.py @@ -0,0 +1,50 @@ +import pytest + +import pandas as pd +from pandas import Series +import pandas._testing as tm + + +def test_take_validate_axis(): + # GH#51022 + ser = Series([-1, 5, 6, 2, 4]) + + msg = "No axis named foo for object type Series" + with pytest.raises(ValueError, match=msg): + ser.take([1, 2], axis="foo") + + +def test_take(): + ser = Series([-1, 5, 6, 2, 4]) + + actual = ser.take([1, 3, 4]) + expected = Series([5, 2, 4], index=[1, 3, 4]) + tm.assert_series_equal(actual, expected) + + actual = ser.take([-1, 3, 4]) + expected = Series([4, 2, 4], index=[4, 3, 4]) + tm.assert_series_equal(actual, expected) + + msg = "indices are out-of-bounds" + with pytest.raises(IndexError, match=msg): + ser.take([1, 10]) + with pytest.raises(IndexError, match=msg): + ser.take([2, 5]) + + +def test_take_categorical(): + # https://github.com/pandas-dev/pandas/issues/20664 + ser = Series(pd.Categorical(["a", "b", "c"])) + result = ser.take([-2, -2, 0]) + expected = Series( + pd.Categorical(["b", "b", "a"], categories=["a", "b", "c"]), index=[1, 1, 0] + ) + tm.assert_series_equal(result, expected) + + +def test_take_slice_raises(): + ser = Series([-1, 5, 6, 2, 4]) + + msg = "Series.take requires a sequence of integers, not slice" + with pytest.raises(TypeError, match=msg): + ser.take(slice(0, 3, 1)) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_where.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_where.py new file mode 100644 index 0000000000000000000000000000000000000000..307da36629fd3a5eada337e690f9b570d050d350 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_where.py @@ -0,0 +1,446 @@ +import numpy as np +import pytest + +from pandas.core.dtypes.common import is_integer + +import pandas as pd +from pandas import ( + Series, + Timestamp, + date_range, + isna, +) +import pandas._testing as tm + + +def test_where_unsafe_int(any_signed_int_numpy_dtype): + s = Series(np.arange(10), dtype=any_signed_int_numpy_dtype) + mask = s < 5 + + s[mask] = range(2, 7) + expected = Series( + list(range(2, 7)) + list(range(5, 10)), + dtype=any_signed_int_numpy_dtype, + ) + + tm.assert_series_equal(s, expected) + + +def test_where_unsafe_float(float_numpy_dtype): + s = Series(np.arange(10), dtype=float_numpy_dtype) + mask = s < 5 + + s[mask] = range(2, 7) + data = list(range(2, 7)) + list(range(5, 10)) + expected = Series(data, dtype=float_numpy_dtype) + + tm.assert_series_equal(s, expected) + + +@pytest.mark.parametrize( + "dtype,expected_dtype", + [ + (np.int8, np.float64), + (np.int16, np.float64), + (np.int32, np.float64), + (np.int64, np.float64), + (np.float32, np.float32), + (np.float64, np.float64), + ], +) +def test_where_unsafe_upcast(dtype, expected_dtype): + # see gh-9743 + s = Series(np.arange(10), dtype=dtype) + values = [2.5, 3.5, 4.5, 5.5, 6.5] + mask = s < 5 + if np.dtype(dtype).kind == np.dtype(expected_dtype).kind == "f": + s[mask] = values + expected = Series(values + list(range(5, 10)), dtype=expected_dtype) + tm.assert_series_equal(s, expected) + else: + with pytest.raises(TypeError, match="Invalid value"): + s[mask] = values + + +def test_where_unsafe(): + # see gh-9731 + s = Series(np.arange(10), dtype="int64") + values = [2.5, 3.5, 4.5, 5.5] + + mask = s > 5 + expected = Series(list(range(6)) + values, dtype="float64") + + with pytest.raises(TypeError, match="Invalid value"): + s[mask] = values + s = s.astype("float64") + s[mask] = values + + # see gh-3235 + s = Series(np.arange(10), dtype="int64") + mask = s < 5 + s[mask] = range(2, 7) + expected = Series(list(range(2, 7)) + list(range(5, 10)), dtype="int64") + tm.assert_series_equal(s, expected) + assert s.dtype == expected.dtype + + s = Series(np.arange(10), dtype="int64") + mask = s > 5 + s[mask] = [0] * 4 + expected = Series([0, 1, 2, 3, 4, 5] + [0] * 4, dtype="int64") + tm.assert_series_equal(s, expected) + + s = Series(np.arange(10)) + mask = s > 5 + + msg = "cannot set using a list-like indexer with a different length than the value" + with pytest.raises(ValueError, match=msg): + s[mask] = [5, 4, 3, 2, 1] + + with pytest.raises(ValueError, match=msg): + s[mask] = [0] * 5 + + # dtype changes + s = Series([1, 2, 3, 4]) + result = s.where(s > 2, np.nan) + expected = Series([np.nan, np.nan, 3, 4]) + tm.assert_series_equal(result, expected) + + # GH 4667 + # setting with None changes dtype + s = Series(range(10)).astype(float) + s[8] = None + result = s[8] + assert isna(result) + + s = Series(range(10)).astype(float) + s[s > 8] = None + result = s[isna(s)] + expected = Series(np.nan, index=[9]) + tm.assert_series_equal(result, expected) + + +def test_where(): + s = Series(np.random.default_rng(2).standard_normal(5)) + cond = s > 0 + + rs = s.where(cond).dropna() + rs2 = s[cond] + tm.assert_series_equal(rs, rs2) + + rs = s.where(cond, -s) + tm.assert_series_equal(rs, s.abs()) + + rs = s.where(cond) + assert s.shape == rs.shape + assert rs is not s + + # test alignment + cond = Series([True, False, False, True, False], index=s.index) + s2 = -(s.abs()) + + expected = s2[cond].reindex(s2.index[:3]).reindex(s2.index) + rs = s2.where(cond[:3]) + tm.assert_series_equal(rs, expected) + + expected = s2.abs() + expected.iloc[0] = s2[0] + rs = s2.where(cond[:3], -s2) + tm.assert_series_equal(rs, expected) + + +def test_where_error(): + s = Series(np.random.default_rng(2).standard_normal(5)) + cond = s > 0 + + msg = "Array conditional must be same shape as self" + with pytest.raises(ValueError, match=msg): + s.where(1) + with pytest.raises(ValueError, match=msg): + s.where(cond[:3].values, -s) + + # GH 2745 + s = Series([1, 2]) + s[[True, False]] = [0, 1] + expected = Series([0, 2]) + tm.assert_series_equal(s, expected) + + # failures + msg = "cannot set using a list-like indexer with a different length than the value" + with pytest.raises(ValueError, match=msg): + s[[True, False]] = [0, 2, 3] + + with pytest.raises(ValueError, match=msg): + s[[True, False]] = [] + + +@pytest.mark.parametrize("klass", [list, tuple, np.array, Series]) +def test_where_array_like(klass): + # see gh-15414 + s = Series([1, 2, 3]) + cond = [False, True, True] + expected = Series([np.nan, 2, 3]) + + result = s.where(klass(cond)) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "cond", + [ + [1, 0, 1], + Series([2, 5, 7]), + ["True", "False", "True"], + [Timestamp("2017-01-01"), pd.NaT, Timestamp("2017-01-02")], + ], +) +def test_where_invalid_input(cond): + # see gh-15414: only boolean arrays accepted + s = Series([1, 2, 3]) + msg = "Boolean array expected for the condition" + + with pytest.raises(TypeError, match=msg): + s.where(cond) + + msg = "Array conditional must be same shape as self" + with pytest.raises(ValueError, match=msg): + s.where([True]) + + +def test_where_ndframe_align(): + msg = "Array conditional must be same shape as self" + s = Series([1, 2, 3]) + + cond = [True] + with pytest.raises(ValueError, match=msg): + s.where(cond) + + expected = Series([1, np.nan, np.nan]) + + out = s.where(Series(cond)) + tm.assert_series_equal(out, expected) + + cond = np.array([False, True, False, True]) + with pytest.raises(ValueError, match=msg): + s.where(cond) + + expected = Series([np.nan, 2, np.nan]) + + out = s.where(Series(cond)) + tm.assert_series_equal(out, expected) + + +def test_where_setitem_invalid(): + # GH 2702 + # make sure correct exceptions are raised on invalid list assignment + + msg = lambda x: ( + f"cannot set using a {x} indexer with a different length than the value" + ) + # slice + s = Series(list("abc"), dtype=object) + + with pytest.raises(ValueError, match=msg("slice")): + s[0:3] = list(range(27)) + + s[0:3] = list(range(3)) + expected = Series([0, 1, 2]) + tm.assert_series_equal(s.astype(np.int64), expected) + + # slice with step + s = Series(list("abcdef"), dtype=object) + + with pytest.raises(ValueError, match=msg("slice")): + s[0:4:2] = list(range(27)) + + s = Series(list("abcdef"), dtype=object) + s[0:4:2] = list(range(2)) + expected = Series([0, "b", 1, "d", "e", "f"]) + tm.assert_series_equal(s, expected) + + # neg slices + s = Series(list("abcdef"), dtype=object) + + with pytest.raises(ValueError, match=msg("slice")): + s[:-1] = list(range(27)) + + s[-3:-1] = list(range(2)) + expected = Series(["a", "b", "c", 0, 1, "f"]) + tm.assert_series_equal(s, expected) + + # list + s = Series(list("abc"), dtype=object) + + with pytest.raises(ValueError, match=msg("list-like")): + s[[0, 1, 2]] = list(range(27)) + + s = Series(list("abc"), dtype=object) + + with pytest.raises(ValueError, match=msg("list-like")): + s[[0, 1, 2]] = list(range(2)) + + # scalar + s = Series(list("abc"), dtype=object) + s[0] = list(range(10)) + expected = Series([list(range(10)), "b", "c"]) + tm.assert_series_equal(s, expected) + + +@pytest.mark.parametrize("size", range(2, 6)) +@pytest.mark.parametrize( + "mask", [[True, False, False, False, False], [True, False], [False]] +) +@pytest.mark.parametrize( + "item", [2.0, np.nan, np.finfo(float).max, np.finfo(float).min] +) +@pytest.mark.parametrize("box", [np.array, list, tuple]) +def test_broadcast(size, mask, item, box): + # GH#8801, GH#4195 + selection = np.resize(mask, size) + + data = np.arange(size, dtype=float) + + # Construct the expected series by taking the source + # data or item based on the selection + expected = Series( + [item if use_item else data[i] for i, use_item in enumerate(selection)] + ) + + s = Series(data) + + s[selection] = item + tm.assert_series_equal(s, expected) + + s = Series(data) + result = s.where(~selection, box([item])) + tm.assert_series_equal(result, expected) + + s = Series(data) + result = s.mask(selection, box([item])) + tm.assert_series_equal(result, expected) + + +def test_where_inplace(): + s = Series(np.random.default_rng(2).standard_normal(5)) + cond = s > 0 + + rs = s.copy() + + result = rs.where(cond, inplace=True) + assert result is rs + tm.assert_series_equal(rs.dropna(), s[cond]) + tm.assert_series_equal(rs, s.where(cond)) + + rs = s.copy() + result = rs.where(cond, -s, inplace=True) + assert result is rs + tm.assert_series_equal(rs, s.where(cond, -s)) + + +def test_where_dups(): + # GH 4550 + # where crashes with dups in index + s1 = Series(list(range(3))) + s2 = Series(list(range(3))) + comb = pd.concat([s1, s2]) + result = comb.where(comb < 2) + expected = Series([0, 1, np.nan, 0, 1, np.nan], index=[0, 1, 2, 0, 1, 2]) + tm.assert_series_equal(result, expected) + + # GH 4548 + # inplace updating not working with dups + comb[comb < 1] = 5 + expected = Series([5, 1, 2, 5, 1, 2], index=[0, 1, 2, 0, 1, 2]) + tm.assert_series_equal(comb, expected) + + comb[comb < 2] += 10 + expected = Series([5, 11, 2, 5, 11, 2], index=[0, 1, 2, 0, 1, 2]) + tm.assert_series_equal(comb, expected) + + +def test_where_numeric_with_string(): + # GH 9280 + s = Series([1, 2, 3]) + w = s.where(s > 1, "X") + + assert not is_integer(w[0]) + assert is_integer(w[1]) + assert is_integer(w[2]) + assert isinstance(w[0], str) + assert w.dtype == "object" + + w = s.where(s > 1, ["X", "Y", "Z"]) + assert not is_integer(w[0]) + assert is_integer(w[1]) + assert is_integer(w[2]) + assert isinstance(w[0], str) + assert w.dtype == "object" + + w = s.where(s > 1, np.array(["X", "Y", "Z"])) + assert not is_integer(w[0]) + assert is_integer(w[1]) + assert is_integer(w[2]) + assert isinstance(w[0], str) + assert w.dtype == "object" + + +def test_where_datetimetz(): + # GH 15701 + timestamps = ["2016-12-31 12:00:04+00:00", "2016-12-31 12:00:04.010000+00:00"] + ser = Series([Timestamp(t) for t in timestamps], dtype="datetime64[ns, UTC]") + rs = ser.where(Series([False, True])) + expected = Series([pd.NaT, ser[1]], dtype="datetime64[ns, UTC]") + tm.assert_series_equal(rs, expected) + + +def test_where_sparse(): + # GH#17198 make sure we dont get an AttributeError for sp_index + ser = Series(pd.arrays.SparseArray([1, 2])) + result = ser.where(ser >= 2, 0) + expected = Series(pd.arrays.SparseArray([0, 2])) + tm.assert_series_equal(result, expected) + + +def test_where_empty_series_and_empty_cond_having_non_bool_dtypes(): + # https://github.com/pandas-dev/pandas/issues/34592 + ser = Series([], dtype=float) + result = ser.where([]) + tm.assert_series_equal(result, ser) + + +def test_where_categorical(frame_or_series): + # https://github.com/pandas-dev/pandas/issues/18888 + exp = frame_or_series( + pd.Categorical(["A", "A", "B", "B", np.nan], categories=["A", "B", "C"]), + dtype="category", + ) + df = frame_or_series(["A", "A", "B", "B", "C"], dtype="category") + res = df.where(df != "C") + tm.assert_equal(exp, res) + + +def test_where_datetimelike_categorical(tz_naive_fixture): + # GH#37682 + tz = tz_naive_fixture + + dr = date_range("2001-01-01", periods=3, tz=tz)._with_freq(None) + lvals = pd.DatetimeIndex([dr[0], dr[1], pd.NaT]) + rvals = pd.Categorical([dr[0], pd.NaT, dr[2]]) + + mask = np.array([True, True, False]) + + # DatetimeIndex.where + res = lvals.where(mask, rvals) + tm.assert_index_equal(res, dr) + + # DatetimeArray.where + res = lvals._data._where(mask, rvals) + tm.assert_datetime_array_equal(res, dr._data) + + # Series.where + res = Series(lvals).where(mask, rvals) + tm.assert_series_equal(res, Series(dr)) + + # DataFrame.where + res = pd.DataFrame(lvals).where(mask[:, None], pd.DataFrame(rvals)) + + tm.assert_frame_equal(res, pd.DataFrame(dr)) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_xs.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_xs.py new file mode 100644 index 0000000000000000000000000000000000000000..a67f3ec708f24a97dab544bb5cf859dc54659215 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/indexing/test_xs.py @@ -0,0 +1,82 @@ +import numpy as np +import pytest + +from pandas import ( + MultiIndex, + Series, + date_range, +) +import pandas._testing as tm + + +def test_xs_datetimelike_wrapping(): + # GH#31630 a case where we shouldn't wrap datetime64 in Timestamp + arr = date_range("2016-01-01", periods=3)._data._ndarray + + ser = Series(arr, dtype=object) + for i in range(len(ser)): + ser.iloc[i] = arr[i] + assert ser.dtype == object + assert isinstance(ser[0], np.datetime64) + + result = ser.xs(0) + assert isinstance(result, np.datetime64) + + +class TestXSWithMultiIndex: + def test_xs_level_series(self, multiindex_dataframe_random_data): + df = multiindex_dataframe_random_data + ser = df["A"] + expected = ser[:, "two"] + result = df.xs("two", level=1)["A"] + tm.assert_series_equal(result, expected) + + def test_series_getitem_multiindex_xs_by_label(self): + # GH#5684 + idx = MultiIndex.from_tuples( + [("a", "one"), ("a", "two"), ("b", "one"), ("b", "two")] + ) + ser = Series([1, 2, 3, 4], index=idx) + return_value = ser.index.set_names(["L1", "L2"], inplace=True) + assert return_value is None + expected = Series([1, 3], index=["a", "b"]) + return_value = expected.index.set_names(["L1"], inplace=True) + assert return_value is None + + result = ser.xs("one", level="L2") + tm.assert_series_equal(result, expected) + + def test_series_getitem_multiindex_xs(self): + # GH#6258 + dt = list(date_range("20130903", periods=3)) + idx = MultiIndex.from_product([list("AB"), dt]) + ser = Series([1, 3, 4, 1, 3, 4], index=idx) + expected = Series([1, 1], index=list("AB")) + + result = ser.xs("20130903", level=1) + tm.assert_series_equal(result, expected) + + def test_series_xs_droplevel_false(self): + # GH: 19056 + mi = MultiIndex.from_tuples( + [("a", "x"), ("a", "y"), ("b", "x")], names=["level1", "level2"] + ) + ser = Series([1, 1, 1], index=mi) + result = ser.xs("a", axis=0, drop_level=False) + expected = Series( + [1, 1], + index=MultiIndex.from_tuples( + [("a", "x"), ("a", "y")], names=["level1", "level2"] + ), + ) + tm.assert_series_equal(result, expected) + + def test_xs_key_as_list(self): + # GH#41760 + mi = MultiIndex.from_tuples([("a", "x")], names=["level1", "level2"]) + ser = Series([1], index=mi) + with pytest.raises(TypeError, match="list keys are not supported"): + ser.xs(["a", "x"], axis=0, drop_level=False) + + with pytest.raises(TypeError, match="list keys are not supported"): + ser.xs(["a"], axis=0, drop_level=False) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/__init__.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..bcb0d30f405e2079a1ff21d838f52cd5f659fd93 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/__init__.py @@ -0,0 +1,7 @@ +""" +Test files dedicated to individual (stand-alone) Series methods + +Ideally these files/tests should correspond 1-to-1 with tests.frame.methods + +These may also present opportunities for sharing/de-duplicating test code. +""" diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_add_prefix_suffix.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_add_prefix_suffix.py new file mode 100644 index 0000000000000000000000000000000000000000..289a56b98b7e123fb1c1edf5cc9ff41369d51122 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_add_prefix_suffix.py @@ -0,0 +1,41 @@ +import pytest + +from pandas import Index +import pandas._testing as tm + + +def test_add_prefix_suffix(string_series): + with_prefix = string_series.add_prefix("foo#") + expected = Index([f"foo#{c}" for c in string_series.index]) + tm.assert_index_equal(with_prefix.index, expected) + + with_suffix = string_series.add_suffix("#foo") + expected = Index([f"{c}#foo" for c in string_series.index]) + tm.assert_index_equal(with_suffix.index, expected) + + with_pct_prefix = string_series.add_prefix("%") + expected = Index([f"%{c}" for c in string_series.index]) + tm.assert_index_equal(with_pct_prefix.index, expected) + + with_pct_suffix = string_series.add_suffix("%") + expected = Index([f"{c}%" for c in string_series.index]) + tm.assert_index_equal(with_pct_suffix.index, expected) + + +def test_add_prefix_suffix_axis(string_series): + # GH 47819 + with_prefix = string_series.add_prefix("foo#", axis=0) + expected = Index([f"foo#{c}" for c in string_series.index]) + tm.assert_index_equal(with_prefix.index, expected) + + with_pct_suffix = string_series.add_suffix("#foo", axis=0) + expected = Index([f"{c}#foo" for c in string_series.index]) + tm.assert_index_equal(with_pct_suffix.index, expected) + + +def test_add_prefix_suffix_invalid_axis(string_series): + with pytest.raises(ValueError, match="No axis named 1 for object type Series"): + string_series.add_prefix("foo#", axis=1) + + with pytest.raises(ValueError, match="No axis named 1 for object type Series"): + string_series.add_suffix("foo#", axis=1) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_align.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_align.py new file mode 100644 index 0000000000000000000000000000000000000000..fe2d8f92fc5501364b71fa8652e13dc4bbf723f8 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_align.py @@ -0,0 +1,195 @@ +from datetime import timezone + +import numpy as np +import pytest + +import pandas as pd +from pandas import ( + Series, + date_range, + period_range, +) +import pandas._testing as tm + + +@pytest.mark.parametrize( + "first_slice,second_slice", + [ + [[2, None], [None, -5]], + [[None, 0], [None, -5]], + [[None, -5], [None, 0]], + [[None, 0], [None, 0]], + ], +) +@pytest.mark.parametrize("fill", [None, -1]) +def test_align(datetime_series, first_slice, second_slice, join_type, fill): + a = datetime_series[slice(*first_slice)] + b = datetime_series[slice(*second_slice)] + + aa, ab = a.align(b, join=join_type, fill_value=fill) + + join_index = a.index.join(b.index, how=join_type) + if fill is not None: + diff_a = aa.index.difference(join_index) + diff_b = ab.index.difference(join_index) + if len(diff_a) > 0: + assert (aa.reindex(diff_a) == fill).all() + if len(diff_b) > 0: + assert (ab.reindex(diff_b) == fill).all() + + ea = a.reindex(join_index) + eb = b.reindex(join_index) + + if fill is not None: + ea = ea.fillna(fill) + eb = eb.fillna(fill) + + tm.assert_series_equal(aa, ea) + tm.assert_series_equal(ab, eb) + assert aa.name == "ts" + assert ea.name == "ts" + assert ab.name == "ts" + assert eb.name == "ts" + + +def test_align_nocopy(datetime_series): + b = datetime_series[:5].copy() + + # do copy + a = datetime_series.copy() + ra, _ = a.align(b, join="left") + ra[:5] = 5 + assert not (a[:5] == 5).any() + + # do not copy + a = datetime_series.copy() + ra, _ = a.align(b, join="left") + ra[:5] = 5 + assert not (a[:5] == 5).any() + + # do copy + a = datetime_series.copy() + b = datetime_series[:5].copy() + _, rb = a.align(b, join="right") + rb[:3] = 5 + assert not (b[:3] == 5).any() + + # do not copy + a = datetime_series.copy() + b = datetime_series[:5].copy() + _, rb = a.align(b, join="right") + rb[:2] = 5 + assert not (b[:2] == 5).any() + + +def test_align_same_index(datetime_series): + a, b = datetime_series.align(datetime_series) + assert a.index is not datetime_series.index + assert b.index is not datetime_series.index + assert a.index.is_(datetime_series.index) + assert b.index.is_(datetime_series.index) + + +def test_align_multiindex(): + # GH 10665 + + midx = pd.MultiIndex.from_product( + [range(2), range(3), range(2)], names=("a", "b", "c") + ) + idx = pd.Index(range(2), name="b") + s1 = Series(np.arange(12, dtype="int64"), index=midx) + s2 = Series(np.arange(2, dtype="int64"), index=idx) + + # these must be the same results (but flipped) + res1l, res1r = s1.align(s2, join="left") + res2l, res2r = s2.align(s1, join="right") + + expl = s1 + tm.assert_series_equal(expl, res1l) + tm.assert_series_equal(expl, res2r) + expr = Series([0, 0, 1, 1, np.nan, np.nan] * 2, index=midx) + tm.assert_series_equal(expr, res1r) + tm.assert_series_equal(expr, res2l) + + res1l, res1r = s1.align(s2, join="right") + res2l, res2r = s2.align(s1, join="left") + + exp_idx = pd.MultiIndex.from_product( + [range(2), range(2), range(2)], names=("a", "b", "c") + ) + expl = Series([0, 1, 2, 3, 6, 7, 8, 9], index=exp_idx) + tm.assert_series_equal(expl, res1l) + tm.assert_series_equal(expl, res2r) + expr = Series([0, 0, 1, 1] * 2, index=exp_idx) + tm.assert_series_equal(expr, res1r) + tm.assert_series_equal(expr, res2l) + + +def test_align_dt64tzindex_mismatched_tzs(): + idx1 = date_range("2001", periods=5, freq="h", tz="US/Eastern") + ser = Series(np.random.default_rng(2).standard_normal(len(idx1)), index=idx1) + ser_central = ser.tz_convert("US/Central") + # different timezones convert to UTC + + new1, new2 = ser.align(ser_central) + assert new1.index.tz is timezone.utc + assert new2.index.tz is timezone.utc + + +def test_align_periodindex(join_type): + rng = period_range("1/1/2000", "1/1/2010", freq="Y") + ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng) + + # TODO: assert something? + ts.align(ts[::2], join=join_type) + + +def test_align_stringindex(any_string_dtype): + left = Series(range(3), index=pd.Index(["a", "b", "d"], dtype=any_string_dtype)) + right = Series(range(3), index=pd.Index(["a", "b", "c"], dtype=any_string_dtype)) + result_left, result_right = left.align(right) + + expected_idx = pd.Index(["a", "b", "c", "d"], dtype=any_string_dtype) + expected_left = Series([0, 1, np.nan, 2], index=expected_idx) + expected_right = Series([0, 1, 2, np.nan], index=expected_idx) + + tm.assert_series_equal(result_left, expected_left) + tm.assert_series_equal(result_right, expected_right) + + +def test_align_left_fewer_levels(): + # GH#45224 + left = Series([2], index=pd.MultiIndex.from_tuples([(1, 3)], names=["a", "c"])) + right = Series( + [1], index=pd.MultiIndex.from_tuples([(1, 2, 3)], names=["a", "b", "c"]) + ) + result_left, result_right = left.align(right) + + expected_right = Series( + [1], index=pd.MultiIndex.from_tuples([(1, 3, 2)], names=["a", "c", "b"]) + ) + expected_left = Series( + [2], index=pd.MultiIndex.from_tuples([(1, 3, 2)], names=["a", "c", "b"]) + ) + tm.assert_series_equal(result_left, expected_left) + tm.assert_series_equal(result_right, expected_right) + + +def test_align_left_different_named_levels(): + # GH#45224 + left = Series( + [2], index=pd.MultiIndex.from_tuples([(1, 4, 3)], names=["a", "d", "c"]) + ) + right = Series( + [1], index=pd.MultiIndex.from_tuples([(1, 2, 3)], names=["a", "b", "c"]) + ) + result_left, result_right = left.align(right) + + expected_left = Series( + [2], index=pd.MultiIndex.from_tuples([(1, 4, 3, 2)], names=["a", "d", "c", "b"]) + ) + expected_right = Series( + [1], index=pd.MultiIndex.from_tuples([(1, 4, 3, 2)], names=["a", "d", "c", "b"]) + ) + tm.assert_series_equal(result_left, expected_left) + tm.assert_series_equal(result_right, expected_right) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_argsort.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_argsort.py new file mode 100644 index 0000000000000000000000000000000000000000..09bc6a8300d7f995067138ec7972febc7984fcb9 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_argsort.py @@ -0,0 +1,76 @@ +import numpy as np +import pytest + +from pandas import ( + Series, + Timestamp, + isna, +) +import pandas._testing as tm + + +class TestSeriesArgsort: + def test_argsort_axis(self): + # GH#54257 + ser = Series(range(3)) + + msg = "No axis named 2 for object type Series" + with pytest.raises(ValueError, match=msg): + ser.argsort(axis=2) + + def test_argsort_numpy(self, datetime_series): + ser = datetime_series + res = np.argsort(ser).values + expected = np.argsort(np.array(ser)) + tm.assert_numpy_array_equal(res, expected) + + def test_argsort_numpy_missing(self): + data = [0.1, np.nan, 0.2, np.nan, 0.3] + ser = Series(data) + result = np.argsort(ser) + expected = np.argsort(np.array(data)) + + tm.assert_numpy_array_equal(result.values, expected) + + def test_argsort(self, datetime_series): + argsorted = datetime_series.argsort() + assert issubclass(argsorted.dtype.type, np.integer) + + def test_argsort_dt64(self, unit): + # GH#2967 (introduced bug in 0.11-dev I think) + ser = Series( + [Timestamp(f"201301{i:02d}") for i in range(1, 6)], dtype=f"M8[{unit}]" + ) + assert ser.dtype == f"datetime64[{unit}]" + shifted = ser.shift(-1) + assert shifted.dtype == f"datetime64[{unit}]" + assert isna(shifted[4]) + + result = ser.argsort() + expected = Series(range(5), dtype=np.intp) + tm.assert_series_equal(result, expected) + + result = shifted.argsort() + expected = Series([*list(range(4)), 4], dtype=np.intp) + tm.assert_series_equal(result, expected) + + def test_argsort_stable(self): + ser = Series(np.random.default_rng(2).integers(0, 100, size=10000)) + mindexer = ser.argsort(kind="mergesort") + qindexer = ser.argsort() + + mexpected = np.argsort(ser.values, kind="mergesort") + qexpected = np.argsort(ser.values, kind="quicksort") + + tm.assert_series_equal(mindexer.astype(np.intp), Series(mexpected)) + tm.assert_series_equal(qindexer.astype(np.intp), Series(qexpected)) + msg = ( + r"ndarray Expected type , " + r"found instead" + ) + with pytest.raises(AssertionError, match=msg): + tm.assert_numpy_array_equal(qindexer, mindexer) + + def test_argsort_preserve_name(self, datetime_series): + result = datetime_series.argsort() + assert result.name == datetime_series.name diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_asof.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_asof.py new file mode 100644 index 0000000000000000000000000000000000000000..2acc2921e5efc089b1dd4ed7aa9a6cebc1a3d0fe --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_asof.py @@ -0,0 +1,205 @@ +import numpy as np +import pytest + +from pandas._libs.tslibs import IncompatibleFrequency + +from pandas import ( + DatetimeIndex, + PeriodIndex, + Series, + Timestamp, + date_range, + isna, + notna, + offsets, + period_range, +) +import pandas._testing as tm + + +class TestSeriesAsof: + def test_asof_nanosecond_index_access(self): + ts = Timestamp("20130101").as_unit("ns")._value + dti = DatetimeIndex([ts + 50 + i for i in range(100)]) + ser = Series(np.random.default_rng(2).standard_normal(100), index=dti) + + first_value = ser.asof(ser.index[0]) + + # GH#46903 previously incorrectly was "day" + assert dti.resolution == "nanosecond" + + # this used to not work bc parsing was done by dateutil that didn't + # handle nanoseconds + assert first_value == ser["2013-01-01 00:00:00.000000050"] + + expected_ts = np.datetime64("2013-01-01 00:00:00.000000050", "ns") + assert first_value == ser[Timestamp(expected_ts)] + + def test_basic(self): + # array or list or dates + N = 50 + rng = date_range("1/1/1990", periods=N, freq="53s") + ts = Series(np.random.default_rng(2).standard_normal(N), index=rng) + ts.iloc[15:30] = np.nan + dates = date_range("1/1/1990", periods=N * 3, freq="25s") + + result = ts.asof(dates) + assert notna(result).all() + lb = ts.index[14] + ub = ts.index[30] + + result = ts.asof(list(dates)) + assert notna(result).all() + lb = ts.index[14] + ub = ts.index[30] + + mask = (result.index >= lb) & (result.index < ub) + rs = result[mask] + assert (rs == ts[lb]).all() + + val = result[result.index[result.index >= ub][0]] + assert ts[ub] == val + + def test_scalar(self): + N = 30 + rng = date_range("1/1/1990", periods=N, freq="53s") + # Explicit cast to float avoid implicit cast when setting nan + ts = Series(np.arange(N), index=rng, dtype="float") + ts.iloc[5:10] = np.nan + ts.iloc[15:20] = np.nan + + val1 = ts.asof(ts.index[7]) + val2 = ts.asof(ts.index[19]) + + assert val1 == ts.iloc[4] + assert val2 == ts.iloc[14] + + # accepts strings + val1 = ts.asof(str(ts.index[7])) + assert val1 == ts.iloc[4] + + # in there + result = ts.asof(ts.index[3]) + assert result == ts.iloc[3] + + # no as of value + d = ts.index[0] - offsets.BDay() + assert np.isnan(ts.asof(d)) + + def test_with_nan(self): + # basic asof test + rng = date_range("1/1/2000", "1/2/2000", freq="4h") + s = Series(np.arange(len(rng)), index=rng) + r = s.resample("2h").mean() + + result = r.asof(r.index) + expected = Series( + [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6.0], + index=date_range("1/1/2000", "1/2/2000", freq="2h"), + ) + tm.assert_series_equal(result, expected) + + r.iloc[3:5] = np.nan + result = r.asof(r.index) + expected = Series( + [0, 0, 1, 1, 1, 1, 3, 3, 4, 4, 5, 5, 6.0], + index=date_range("1/1/2000", "1/2/2000", freq="2h"), + ) + tm.assert_series_equal(result, expected) + + r.iloc[-3:] = np.nan + result = r.asof(r.index) + expected = Series( + [0, 0, 1, 1, 1, 1, 3, 3, 4, 4, 4, 4, 4.0], + index=date_range("1/1/2000", "1/2/2000", freq="2h"), + ) + tm.assert_series_equal(result, expected) + + def test_periodindex(self): + # array or list or dates + N = 50 + rng = period_range("1/1/1990", periods=N, freq="h") + ts = Series(np.random.default_rng(2).standard_normal(N), index=rng) + ts.iloc[15:30] = np.nan + dates = date_range("1/1/1990", periods=N * 3, freq="37min") + + result = ts.asof(dates) + assert notna(result).all() + lb = ts.index[14] + ub = ts.index[30] + + result = ts.asof(list(dates)) + assert notna(result).all() + lb = ts.index[14] + ub = ts.index[30] + + pix = PeriodIndex(result.index.values, freq="h") + mask = (pix >= lb) & (pix < ub) + rs = result[mask] + assert (rs == ts[lb]).all() + + ts.iloc[5:10] = np.nan + ts.iloc[15:20] = np.nan + + val1 = ts.asof(ts.index[7]) + val2 = ts.asof(ts.index[19]) + + assert val1 == ts.iloc[4] + assert val2 == ts.iloc[14] + + # accepts strings + val1 = ts.asof(str(ts.index[7])) + assert val1 == ts.iloc[4] + + # in there + assert ts.asof(ts.index[3]) == ts.iloc[3] + + # no as of value + d = ts.index[0].to_timestamp() - offsets.BDay() + assert isna(ts.asof(d)) + + # Mismatched freq + msg = "Input has different freq" + with pytest.raises(IncompatibleFrequency, match=msg): + ts.asof(rng.asfreq("D")) + + def test_errors(self): + s = Series( + [1, 2, 3], + index=[Timestamp("20130101"), Timestamp("20130103"), Timestamp("20130102")], + ) + + # non-monotonic + assert not s.index.is_monotonic_increasing + with pytest.raises(ValueError, match="requires a sorted index"): + s.asof(s.index[0]) + + # subset with Series + N = 10 + rng = date_range("1/1/1990", periods=N, freq="53s") + s = Series(np.random.default_rng(2).standard_normal(N), index=rng) + with pytest.raises(ValueError, match="not valid for Series"): + s.asof(s.index[0], subset="foo") + + def test_all_nans(self): + # GH 15713 + # series is all nans + + # testing non-default indexes + N = 50 + rng = date_range("1/1/1990", periods=N, freq="53s") + + dates = date_range("1/1/1990", periods=N * 3, freq="25s") + result = Series(np.nan, index=rng).asof(dates) + expected = Series(np.nan, index=dates) + tm.assert_series_equal(result, expected) + + # testing scalar input + date = date_range("1/1/1990", periods=N * 3, freq="25s")[0] + result = Series(np.nan, index=rng).asof(date) + assert isna(result) + + # test name is propagated + result = Series(np.nan, index=[1, 2, 3, 4], name="test").asof([4, 5]) + expected = Series(np.nan, index=[4, 5], name="test") + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_astype.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_astype.py new file mode 100644 index 0000000000000000000000000000000000000000..07869d40d85a519022d7c12a37a0afd279d01df5 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_astype.py @@ -0,0 +1,686 @@ +from datetime import ( + datetime, + timedelta, +) +from importlib import reload +import string +import sys + +import numpy as np +import pytest + +from pandas._libs.tslibs import iNaT +from pandas.errors import Pandas4Warning +import pandas.util._test_decorators as td + +from pandas import ( + NA, + Categorical, + CategoricalDtype, + DatetimeTZDtype, + Index, + Interval, + NaT, + Series, + Timedelta, + Timestamp, + cut, + date_range, + to_datetime, +) +import pandas._testing as tm + + +def rand_str(nchars: int) -> str: + """ + Generate one random byte string. + """ + RANDS_CHARS = np.array( + list(string.ascii_letters + string.digits), dtype=(np.str_, 1) + ) + return "".join(np.random.default_rng(2).choice(RANDS_CHARS, nchars)) + + +class TestAstypeAPI: + def test_astype_unitless_dt64_raises(self): + # GH#47844 + ser = Series(["1970-01-01", "1970-01-01", "1970-01-01"], dtype="datetime64[ns]") + df = ser.to_frame() + + msg = "Casting to unit-less dtype 'datetime64' is not supported" + with pytest.raises(TypeError, match=msg): + ser.astype(np.datetime64) + with pytest.raises(TypeError, match=msg): + df.astype(np.datetime64) + with pytest.raises(TypeError, match=msg): + ser.astype("datetime64") + with pytest.raises(TypeError, match=msg): + df.astype("datetime64") + + def test_arg_for_errors_in_astype(self): + # see GH#14878 + ser = Series([1, 2, 3]) + + msg = ( + r"Expected value of kwarg 'errors' to be one of \['raise', " + r"'ignore'\]\. Supplied value is 'False'" + ) + with pytest.raises(ValueError, match=msg): + ser.astype(np.float64, errors=False) + + ser.astype(np.int8, errors="raise") + + @pytest.mark.parametrize("dtype_class", [dict, Series]) + def test_astype_dict_like(self, dtype_class): + # see GH#7271 + ser = Series(range(0, 10, 2), name="abc") + + dt1 = dtype_class({"abc": str}) + result = ser.astype(dt1) + expected = Series(["0", "2", "4", "6", "8"], name="abc", dtype="str") + tm.assert_series_equal(result, expected) + + dt2 = dtype_class({"abc": "float64"}) + result = ser.astype(dt2) + expected = Series([0.0, 2.0, 4.0, 6.0, 8.0], dtype="float64", name="abc") + tm.assert_series_equal(result, expected) + + dt3 = dtype_class({"abc": str, "def": str}) + msg = ( + "Only the Series name can be used for the key in Series dtype " + r"mappings\." + ) + with pytest.raises(KeyError, match=msg): + ser.astype(dt3) + + dt4 = dtype_class({0: str}) + with pytest.raises(KeyError, match=msg): + ser.astype(dt4) + + # GH#16717 + # if dtypes provided is empty, it should error + if dtype_class is Series: + dt5 = dtype_class({}, dtype=object) + else: + dt5 = dtype_class({}) + + with pytest.raises(KeyError, match=msg): + ser.astype(dt5) + + +class TestAstype: + @pytest.mark.parametrize("tz", [None, "UTC", "US/Pacific"]) + def test_astype_object_to_dt64_non_nano(self, tz): + # GH#55756, GH#54620 + ts = Timestamp("2999-01-01") + dtype = "M8[us]" + if tz is not None: + dtype = f"M8[us, {tz}]" + vals = [ts, "2999-01-02 03:04:05.678910", 2500] + ser = Series(vals, dtype=object) + result = ser.astype(dtype) + + # The 2500 is interpreted as microseconds, consistent with what + # we would get if we created DatetimeIndexes from vals[:2] and vals[2:] + # and concated the results. + pointwise = [ + vals[0].tz_localize(tz), + Timestamp(vals[1], tz=tz), + to_datetime(vals[2], unit="us", utc=True).tz_convert(tz), + ] + exp_vals = [x.as_unit("us").asm8 for x in pointwise] + exp_arr = np.array(exp_vals, dtype="M8[us]") + expected = Series(exp_arr, dtype="M8[us]") + if tz is not None: + expected = expected.dt.tz_localize("UTC").dt.tz_convert(tz) + tm.assert_series_equal(result, expected) + + def test_astype_mixed_object_to_dt64tz(self): + # pre-2.0 this raised ValueError bc of tz mismatch + # xref GH#32581 + ts = Timestamp("2016-01-04 05:06:07", tz="US/Pacific") + ts2 = ts.tz_convert("Asia/Tokyo") + + ser = Series([ts, ts2], dtype=object) + res = ser.astype("datetime64[ns, Europe/Brussels]") + expected = Series( + [ts.tz_convert("Europe/Brussels"), ts2.tz_convert("Europe/Brussels")], + dtype="datetime64[ns, Europe/Brussels]", + ) + tm.assert_series_equal(res, expected) + + @pytest.mark.parametrize("dtype", np.typecodes["All"]) + def test_astype_empty_constructor_equality(self, dtype): + # see GH#15524 + + if dtype not in ( + "S", + "V", # poor support (if any) currently + "M", + "m", # Generic timestamps raise a ValueError. Already tested. + ): + init_empty = Series([], dtype=dtype) + as_type_empty = Series([]).astype(dtype) + tm.assert_series_equal(init_empty, as_type_empty) + + @pytest.mark.parametrize("dtype", [str, np.str_]) + @pytest.mark.parametrize( + "data", + [ + [string.digits * 10, rand_str(63), rand_str(64), rand_str(1000)], + [string.digits * 10, rand_str(63), rand_str(64), np.nan, 1.0], + ], + ) + def test_astype_str_map(self, dtype, data, using_infer_string): + # see GH#4405 + series = Series(data) + using_string_dtype = using_infer_string and dtype is str + result = series.astype(dtype) + if using_string_dtype: + expected = series.map(lambda val: str(val) if val is not np.nan else np.nan) + else: + expected = series.map(str) + if using_infer_string: + expected = expected.astype(object) + tm.assert_series_equal(result, expected) + + def test_astype_float_to_period(self): + result = Series([np.nan]).astype("period[D]") + expected = Series([NaT], dtype="period[D]") + tm.assert_series_equal(result, expected) + + def test_astype_no_pandas_dtype(self): + # https://github.com/pandas-dev/pandas/pull/24866 + ser = Series([1, 2], dtype="int64") + # Don't have NumpyEADtype in the public API, so we use `.array.dtype`, + # which is a NumpyEADtype. + result = ser.astype(ser.array.dtype) + tm.assert_series_equal(result, ser) + + @pytest.mark.parametrize("dtype", [np.datetime64, np.timedelta64]) + def test_astype_generic_timestamp_no_frequency(self, dtype, request): + # see GH#15524, GH#15987 + data = [1] + ser = Series(data) + + if np.dtype(dtype).name not in ["timedelta64", "datetime64"]: + mark = pytest.mark.xfail(reason="GH#33890 Is assigned ns unit") + request.applymarker(mark) + + msg = ( + rf"The '{dtype.__name__}' dtype has no unit\. " + rf"Please pass in '{dtype.__name__}\[ns\]' instead." + ) + with pytest.raises(ValueError, match=msg): + ser.astype(dtype) + + def test_astype_dt64_to_str(self): + # GH#10442 : testing astype(str) is correct for Series/DatetimeIndex + dti = date_range("2012-01-01", periods=3) + result = Series(dti).astype(str) + expected = Series(["2012-01-01", "2012-01-02", "2012-01-03"], dtype="str") + tm.assert_series_equal(result, expected) + + def test_astype_dt64tz_to_str(self): + # GH#10442 : testing astype(str) is correct for Series/DatetimeIndex + dti_tz = date_range("2012-01-01", periods=3, tz="US/Eastern") + result = Series(dti_tz).astype(str) + expected = Series( + [ + "2012-01-01 00:00:00-05:00", + "2012-01-02 00:00:00-05:00", + "2012-01-03 00:00:00-05:00", + ], + dtype="str", + ) + tm.assert_series_equal(result, expected) + + def test_astype_datetime(self, unit): + ser = Series(iNaT, dtype=f"M8[{unit}]", index=range(5)) + + ser = ser.astype("O") + assert ser.dtype == np.object_ + + ser = Series([datetime(2001, 1, 2, 0, 0)]) + + ser = ser.astype("O") + assert ser.dtype == np.object_ + + ser = Series( + [datetime(2001, 1, 2, 0, 0) for i in range(3)], dtype=f"M8[{unit}]" + ) + + ser[1] = np.nan + assert ser.dtype == f"M8[{unit}]" + + ser = ser.astype("O") + assert ser.dtype == np.object_ + + def test_astype_datetime64tz(self): + ser = Series(date_range("20130101", periods=3, tz="US/Eastern", unit="ns")) + + # astype + result = ser.astype(object) + expected = Series(ser.astype(object), dtype=object) + tm.assert_series_equal(result, expected) + + result = Series(ser.values).dt.tz_localize("UTC").dt.tz_convert(ser.dt.tz) + tm.assert_series_equal(result, ser) + + # astype - object, preserves on construction + result = Series(ser.astype(object)) + expected = ser.astype(object) + tm.assert_series_equal(result, expected) + + # astype - datetime64[ns, tz] + msg = "Cannot use .astype to convert from timezone-naive" + with pytest.raises(TypeError, match=msg): + # dt64->dt64tz astype deprecated + Series(ser.values).astype("datetime64[ns, US/Eastern]") + + with pytest.raises(TypeError, match=msg): + # dt64->dt64tz astype deprecated + Series(ser.values).astype(ser.dtype) + + result = ser.astype("datetime64[ns, CET]") + expected = Series( + date_range("20130101 06:00:00", periods=3, tz="CET", unit="ns") + ) + tm.assert_series_equal(result, expected) + + def test_astype_str_cast_dt64(self): + # see GH#9757 + ts = Series([Timestamp("2010-01-04 00:00:00")]) + res = ts.astype(str) + + expected = Series(["2010-01-04"], dtype="str") + tm.assert_series_equal(res, expected) + + ts = Series([Timestamp("2010-01-04 00:00:00", tz="US/Eastern")]) + res = ts.astype(str) + + expected = Series(["2010-01-04 00:00:00-05:00"], dtype="str") + tm.assert_series_equal(res, expected) + + def test_astype_str_cast_td64(self): + # see GH#9757 + + td = Series([Timedelta(1, unit="D")]) + ser = td.astype(str) + + expected = Series(["1 days"], dtype="str") + tm.assert_series_equal(ser, expected) + + def test_dt64_series_astype_object(self): + dt64ser = Series(date_range("20130101", periods=3)) + result = dt64ser.astype(object) + assert isinstance(result.iloc[0], datetime) + assert result.dtype == np.object_ + + def test_td64_series_astype_object(self): + tdser = Series(["59 Days", "59 Days", "NaT"], dtype="timedelta64[ns]") + result = tdser.astype(object) + assert isinstance(result.iloc[0], timedelta) + assert result.dtype == np.object_ + + @pytest.mark.parametrize( + "data, dtype", + [ + (["x", "y", "z"], "string[python]"), + pytest.param( + ["x", "y", "z"], + "string[pyarrow]", + marks=td.skip_if_no("pyarrow"), + ), + (["x", "y", "z"], "category"), + (3 * [Timestamp("2020-01-01", tz="UTC")], None), + (3 * [Interval(0, 1)], None), + ], + ) + @pytest.mark.parametrize("errors", ["raise", "ignore"]) + def test_astype_ignores_errors_for_extension_dtypes(self, data, dtype, errors): + # https://github.com/pandas-dev/pandas/issues/35471 + ser = Series(data, dtype=dtype) + if errors == "ignore": + expected = ser + result = ser.astype(float, errors="ignore") + tm.assert_series_equal(result, expected) + else: + msg = "(Cannot cast)|(could not convert)" + with pytest.raises((ValueError, TypeError), match=msg): + ser.astype(float, errors=errors) + + def test_astype_from_float_to_str(self, any_float_dtype): + # https://github.com/pandas-dev/pandas/issues/36451 + ser = Series([0.1], dtype=any_float_dtype) + result = ser.astype(str) + expected = Series(["0.1"], dtype="str") + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "value, string_value", + [ + (None, "None"), + (np.nan, "nan"), + (NA, ""), + ], + ) + def test_astype_to_str_preserves_na(self, value, string_value, using_infer_string): + # https://github.com/pandas-dev/pandas/issues/36904 + ser = Series(["a", "b", value], dtype=object) + result = ser.astype(str) + expected = Series( + ["a", "b", None if using_infer_string else string_value], dtype="str" + ) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("dtype", ["float32", "float64", "int64", "int32"]) + def test_astype(self, dtype): + ser = Series(np.random.default_rng(2).standard_normal(5), name="foo") + as_typed = ser.astype(dtype) + + assert as_typed.dtype == dtype + assert as_typed.name == ser.name + + @pytest.mark.parametrize("value", [np.nan, np.inf]) + def test_astype_cast_nan_inf_int(self, any_int_numpy_dtype, value): + # gh-14265: check NaN and inf raise error when converting to int + msg = "Cannot convert non-finite values \\(NA or inf\\) to integer" + ser = Series([value]) + + with pytest.raises(ValueError, match=msg): + ser.astype(any_int_numpy_dtype) + + def test_astype_cast_object_int_fail(self, any_int_numpy_dtype): + arr = Series(["car", "house", "tree", "1"]) + msg = r"invalid literal for int\(\) with base 10: 'car'" + with pytest.raises(ValueError, match=msg): + arr.astype(any_int_numpy_dtype) + + def test_astype_float_to_uint_negatives_raise( + self, float_numpy_dtype, any_unsigned_int_numpy_dtype + ): + # GH#45151 We don't cast negative numbers to nonsense values + # TODO: same for EA float/uint dtypes, signed integers? + arr = np.arange(5).astype(float_numpy_dtype) - 3 # includes negatives + ser = Series(arr) + + msg = "Cannot losslessly cast from .* to .*" + with pytest.raises(ValueError, match=msg): + ser.astype(any_unsigned_int_numpy_dtype) + + with pytest.raises(ValueError, match=msg): + ser.to_frame().astype(any_unsigned_int_numpy_dtype) + + with pytest.raises(ValueError, match=msg): + # We currently catch and re-raise in Index.astype + Index(ser).astype(any_unsigned_int_numpy_dtype) + + with pytest.raises(ValueError, match=msg): + ser.array.astype(any_unsigned_int_numpy_dtype) + + def test_astype_cast_object_int(self): + arr = Series(["1", "2", "3", "4"], dtype=object) + result = arr.astype(int) + + tm.assert_series_equal(result, Series(np.arange(1, 5))) + + def test_astype_unicode(self, using_infer_string): + # see GH#7758: A bit of magic is required to set + # default encoding to utf-8 + digits = string.digits + test_series = [ + Series([digits * 10, rand_str(63), rand_str(64), rand_str(1000)]), + Series(["データーサイエンス、お前はもう死んでいる"]), + ] + + former_encoding = None + + if sys.getdefaultencoding() == "utf-8": + # GH#45326 as of 2.0 Series.astype matches Index.astype by handling + # bytes with obj.decode() instead of str(obj) + item = "野菜食べないとやばい" + ser = Series([item.encode()]) + result = ser.astype(np.str_) + expected = Series([item], dtype=object) + tm.assert_series_equal(result, expected) + + for ser in test_series: + res = ser.astype(np.str_) + expec = ser.map(str) + if using_infer_string: + expec = expec.astype(object) + tm.assert_series_equal(res, expec) + + # Restore the former encoding + if former_encoding is not None and former_encoding != "utf-8": + reload(sys) + sys.setdefaultencoding(former_encoding) + + def test_astype_bytes(self): + # GH#39474 + result = Series(["foo", "bar", "baz"]).astype(bytes) + assert result.dtypes == np.dtype("S3") + + def test_astype_nan_to_bool(self): + # GH#43018 + ser = Series(np.nan, dtype="object") + result = ser.astype("bool") + expected = Series(True, dtype="bool") + tm.assert_series_equal(result, expected) + + def test_astype_ea_to_datetimetzdtype(self, any_numeric_ea_dtype): + # GH37553 + ser = Series([4, 0, 9], dtype=any_numeric_ea_dtype) + result = ser.astype(DatetimeTZDtype(tz="US/Pacific")) + + expected = Series( + { + 0: Timestamp("1969-12-31 16:00:00.000000004-08:00", tz="US/Pacific"), + 1: Timestamp("1969-12-31 16:00:00.000000000-08:00", tz="US/Pacific"), + 2: Timestamp("1969-12-31 16:00:00.000000009-08:00", tz="US/Pacific"), + } + ) + + tm.assert_series_equal(result, expected) + + def test_astype_retain_attrs(self, any_numpy_dtype): + # GH#44414 + ser = Series([0, 1, 2, 3]) + ser.attrs["Location"] = "Michigan" + + result = ser.astype(any_numpy_dtype).attrs + expected = ser.attrs + + tm.assert_dict_equal(expected, result) + + +class TestAstypeString: + @pytest.mark.parametrize( + "data, dtype", + [ + ([True, NA], "boolean"), + (["A", NA], "category"), + (["2020-10-10", "2020-10-10"], "datetime64[ns]"), + (["2020-10-10", "2020-10-10", NaT], "datetime64[ns]"), + ( + ["2012-01-01 00:00:00-05:00", NaT], + "datetime64[ns, US/Eastern]", + ), + ([1, None], "UInt16"), + (["1/1/2021", "2/1/2021"], "period[M]"), + (["1/1/2021", "2/1/2021", NaT], "period[M]"), + (["1 Day", "59 Days", NaT], "timedelta64[us]"), + # currently no way to parse IntervalArray from a list of strings + ], + ) + def test_astype_string_to_extension_dtype_roundtrip( + self, data, dtype, request, nullable_string_dtype + ): + if dtype == "boolean": + mark = pytest.mark.xfail( + reason="TODO StringArray.astype() with missing values #GH40566" + ) + request.applymarker(mark) + # GH-40351 + ser = Series(data, dtype=dtype) + + # Note: just passing .astype(dtype) fails for dtype="category" + # with bc ser.dtype.categories will be object dtype whereas + # result.dtype.categories will have string dtype + result = ser.astype(nullable_string_dtype).astype(ser.dtype) + tm.assert_series_equal(result, ser) + + +class TestAstypeCategorical: + def test_astype_categorical_to_other(self): + cat = Categorical([f"{i} - {i + 499}" for i in range(0, 10000, 500)]) + ser = Series(np.random.default_rng(2).integers(0, 10000, 100)).sort_values() + ser = cut(ser, range(0, 10500, 500), right=False, labels=cat) + + expected = ser + tm.assert_series_equal(ser.astype("category"), expected) + tm.assert_series_equal(ser.astype(CategoricalDtype()), expected) + msg = r"Cannot cast object|str dtype to float64" + with pytest.raises(ValueError, match=msg): + ser.astype("float64") + + cat = Series(Categorical(["a", "b", "b", "a", "a", "c", "c", "c"])) + exp = Series(["a", "b", "b", "a", "a", "c", "c", "c"], dtype="str") + tm.assert_series_equal(cat.astype("str"), exp) + s2 = Series(Categorical(["1", "2", "3", "4"])) + exp2 = Series([1, 2, 3, 4]).astype("int") + tm.assert_series_equal(s2.astype("int"), exp2) + + # object don't sort correctly, so just compare that we have the same + # values + def cmp(a, b): + tm.assert_almost_equal(np.sort(np.unique(a)), np.sort(np.unique(b))) + + expected = Series(np.array(ser.values), name="value_group") + cmp(ser.astype("object"), expected) + cmp(ser.astype(np.object_), expected) + + # array conversion + tm.assert_almost_equal(np.array(ser), np.array(ser.values)) + + tm.assert_series_equal(ser.astype("category"), ser) + tm.assert_series_equal(ser.astype(CategoricalDtype()), ser) + + roundtrip_expected = ser.cat.set_categories( + ser.cat.categories.sort_values() + ).cat.remove_unused_categories() + result = ser.astype("object").astype("category") + tm.assert_series_equal(result, roundtrip_expected) + result = ser.astype("object").astype(CategoricalDtype()) + tm.assert_series_equal(result, roundtrip_expected) + + def test_astype_categorical_invalid_conversions(self): + # invalid conversion (these are NOT a dtype) + cat = Categorical([f"{i} - {i + 499}" for i in range(0, 10000, 500)]) + ser = Series(np.random.default_rng(2).integers(0, 10000, 100)).sort_values() + ser = cut(ser, range(0, 10500, 500), right=False, labels=cat) + + msg = "dtype '' not understood" + with pytest.raises(TypeError, match=msg): + ser.astype(Categorical) + with pytest.raises(TypeError, match=msg): + ser.astype("object").astype(Categorical) + + def test_astype_categoricaldtype(self): + ser = Series(["a", "b", "a"]) + result = ser.astype(CategoricalDtype(["a", "b"], ordered=True)) + expected = Series(Categorical(["a", "b", "a"], ordered=True)) + tm.assert_series_equal(result, expected) + + result = ser.astype(CategoricalDtype(["a", "b"], ordered=False)) + expected = Series(Categorical(["a", "b", "a"], ordered=False)) + tm.assert_series_equal(result, expected) + + result = ser.astype(CategoricalDtype(["a", "b", "c"], ordered=False)) + expected = Series( + Categorical(["a", "b", "a"], categories=["a", "b", "c"], ordered=False) + ) + tm.assert_series_equal(result, expected) + tm.assert_index_equal(result.cat.categories, Index(["a", "b", "c"])) + + @pytest.mark.parametrize("name", [None, "foo"]) + @pytest.mark.parametrize("dtype_ordered", [True, False]) + @pytest.mark.parametrize("series_ordered", [True, False]) + def test_astype_categorical_to_categorical( + self, name, dtype_ordered, series_ordered + ): + # GH#10696, GH#18593 + s_data = list("abcaacbab") + s_dtype = CategoricalDtype(list("bac"), ordered=series_ordered) + ser = Series(s_data, dtype=s_dtype, name=name) + + # unspecified categories + dtype = CategoricalDtype(ordered=dtype_ordered) + result = ser.astype(dtype) + exp_dtype = CategoricalDtype(s_dtype.categories, dtype_ordered) + expected = Series(s_data, name=name, dtype=exp_dtype) + tm.assert_series_equal(result, expected) + + # different categories + dtype = CategoricalDtype(list("adc"), dtype_ordered) + msg = "Constructing a Categorical with a dtype and values containing" + with tm.assert_produces_warning(Pandas4Warning, match=msg): + result = ser.astype(dtype) + with tm.assert_produces_warning(Pandas4Warning, match=msg): + expected = Series(s_data, name=name, dtype=dtype) + tm.assert_series_equal(result, expected) + + if dtype_ordered is False: + # not specifying ordered, so only test once + expected = ser + result = ser.astype("category") + tm.assert_series_equal(result, expected) + + def test_astype_bool_missing_to_categorical(self): + # GH-19182 + ser = Series([True, False, np.nan]) + assert ser.dtypes == np.object_ + + result = ser.astype(CategoricalDtype(categories=[True, False])) + expected = Series(Categorical([True, False, np.nan], categories=[True, False])) + tm.assert_series_equal(result, expected) + + def test_astype_categories_raises(self): + # deprecated GH#17636, removed in GH#27141 + ser = Series(["a", "b", "a"]) + with pytest.raises(TypeError, match="got an unexpected"): + ser.astype("category", categories=["a", "b"], ordered=True) + + @pytest.mark.parametrize("items", [["a", "b", "c", "a"], [1, 2, 3, 1]]) + def test_astype_from_categorical(self, items): + ser = Series(items) + exp = Series(Categorical(items)) + res = ser.astype("category") + tm.assert_series_equal(res, exp) + + def test_astype_from_categorical_with_keywords(self): + # with keywords + lst = ["a", "b", "c", "a"] + ser = Series(lst) + exp = Series(Categorical(lst, ordered=True)) + res = ser.astype(CategoricalDtype(None, ordered=True)) + tm.assert_series_equal(res, exp) + + exp = Series(Categorical(lst, categories=list("abcdef"), ordered=True)) + res = ser.astype(CategoricalDtype(list("abcdef"), ordered=True)) + tm.assert_series_equal(res, exp) + + def test_astype_timedelta64_with_np_nan(self): + # GH45798 + result = Series([Timedelta(1), np.nan], dtype="timedelta64[ns]") + expected = Series([Timedelta(1), NaT], dtype="timedelta64[ns]") + tm.assert_series_equal(result, expected) + + @td.skip_if_no("pyarrow") + def test_astype_int_na_string(self): + # GH#57418 + ser = Series([12, NA], dtype="Int64[pyarrow]") + result = ser.astype("string[pyarrow]") + expected = Series(["12", NA], dtype="string[pyarrow]") + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_autocorr.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_autocorr.py new file mode 100644 index 0000000000000000000000000000000000000000..c1d768cf02f37baceb01b9ef39b91e419cef1e0f --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_autocorr.py @@ -0,0 +1,30 @@ +import numpy as np + + +class TestAutoCorr: + def test_autocorr(self, datetime_series): + # Just run the function + corr1 = datetime_series.autocorr() + + # Now run it with the lag parameter + corr2 = datetime_series.autocorr(lag=1) + + # corr() with lag needs Series of at least length 2 + if len(datetime_series) <= 2: + assert np.isnan(corr1) + assert np.isnan(corr2) + else: + assert corr1 == corr2 + + # Choose a random lag between 1 and length of Series - 2 + # and compare the result with the Series corr() function + n = 1 + np.random.default_rng(2).integers(max(1, len(datetime_series) - 2)) + corr1 = datetime_series.corr(datetime_series.shift(n)) + corr2 = datetime_series.autocorr(lag=n) + + # corr() with lag needs Series of at least length 2 + if len(datetime_series) <= 2: + assert np.isnan(corr1) + assert np.isnan(corr2) + else: + assert corr1 == corr2 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_between.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_between.py new file mode 100644 index 0000000000000000000000000000000000000000..f035767e2ce0e99fd3e03df7d6eb953cb0f7a22e --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_between.py @@ -0,0 +1,74 @@ +import numpy as np +import pytest + +from pandas import ( + Series, + bdate_range, + date_range, + period_range, +) +import pandas._testing as tm + + +class TestBetween: + def test_between(self): + series = Series(date_range("1/1/2000", periods=10)) + left, right = series[[2, 7]] + + result = series.between(left, right) + expected = (series >= left) & (series <= right) + tm.assert_series_equal(result, expected) + + def test_between_datetime_object_dtype(self): + ser = Series(bdate_range("1/1/2000", periods=20), dtype=object) + ser[::2] = np.nan + + result = ser[ser.between(ser[3], ser[17])] + expected = ser[3:18].dropna() + tm.assert_series_equal(result, expected) + + result = ser[ser.between(ser[3], ser[17], inclusive="neither")] + expected = ser[5:16].dropna() + tm.assert_series_equal(result, expected) + + def test_between_period_values(self): + ser = Series(period_range("2000-01-01", periods=10, freq="D")) + left, right = ser[[2, 7]] + result = ser.between(left, right) + expected = (ser >= left) & (ser <= right) + tm.assert_series_equal(result, expected) + + def test_between_inclusive_string(self): + # GH 40628 + series = Series(date_range("1/1/2000", periods=10)) + left, right = series[[2, 7]] + + result = series.between(left, right, inclusive="both") + expected = (series >= left) & (series <= right) + tm.assert_series_equal(result, expected) + + result = series.between(left, right, inclusive="left") + expected = (series >= left) & (series < right) + tm.assert_series_equal(result, expected) + + result = series.between(left, right, inclusive="right") + expected = (series > left) & (series <= right) + tm.assert_series_equal(result, expected) + + result = series.between(left, right, inclusive="neither") + expected = (series > left) & (series < right) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("inclusive", ["yes", True, False]) + def test_between_error_args(self, inclusive): + # GH 40628 + series = Series(date_range("1/1/2000", periods=10)) + left, right = series[[2, 7]] + + value_error_msg = ( + "Inclusive has to be either string of 'both','left', 'right', or 'neither'." + ) + + series = Series(date_range("1/1/2000", periods=10)) + with pytest.raises(ValueError, match=value_error_msg): + series.between(left, right, inclusive=inclusive) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_case_when.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_case_when.py new file mode 100644 index 0000000000000000000000000000000000000000..ff52cff4e1255725b5c81e08d521a74c44055375 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_case_when.py @@ -0,0 +1,210 @@ +import numpy as np +import pytest + +from pandas import ( + DataFrame, + Series, + array as pd_array, + col, + date_range, +) +import pandas._testing as tm + + +@pytest.fixture +def df(): + """ + base dataframe for testing + """ + return DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) + + +def test_case_when_caselist_is_not_a_list(df): + """ + Raise ValueError if caselist is not a list. + """ + msg = "The caselist argument should be a list; " + msg += "instead got.+" + with pytest.raises(TypeError, match=msg): # GH39154 + df["a"].case_when(caselist=()) + + +def test_case_when_no_caselist(df): + """ + Raise ValueError if no caselist is provided. + """ + msg = "provide at least one boolean condition, " + msg += "with a corresponding replacement." + with pytest.raises(ValueError, match=msg): # GH39154 + df["a"].case_when([]) + + +def test_case_when_odd_caselist(df): + """ + Raise ValueError if no of caselist is odd. + """ + msg = "Argument 0 must have length 2; " + msg += "a condition and replacement; instead got length 3." + + with pytest.raises(ValueError, match=msg): + df["a"].case_when([(df["a"].eq(1), 1, df.a.gt(1))]) + + +def test_case_when_raise_error_from_mask(df): + """ + Raise Error from within Series.mask + """ + msg = "Failed to apply condition0 and replacement0." + with pytest.raises(ValueError, match=msg): + df["a"].case_when([(df["a"].eq(1), [1, 2])]) + + +def test_case_when_single_condition(df): + """ + Test output on a single condition. + """ + result = Series([np.nan, np.nan, np.nan]).case_when([(df.a.eq(1), 1)]) + expected = Series([1, np.nan, np.nan]) + tm.assert_series_equal(result, expected) + + +def test_case_when_multiple_conditions(df): + """ + Test output when booleans are derived from a computation + """ + result = Series([np.nan, np.nan, np.nan]).case_when( + [(df.a.eq(1), 1), (Series([False, True, False]), 2)] + ) + expected = Series([1, 2, np.nan]) + tm.assert_series_equal(result, expected) + + +def test_case_when_multiple_conditions_replacement_list(df): + """ + Test output when replacement is a list + """ + result = Series([np.nan, np.nan, np.nan]).case_when( + [([True, False, False], 1), (df["a"].gt(1) & df["b"].eq(5), [1, 2, 3])] + ) + expected = Series([1, 2, np.nan]) + tm.assert_series_equal(result, expected) + + +def test_case_when_multiple_conditions_replacement_extension_dtype(df): + """ + Test output when replacement has an extension dtype + """ + result = Series([np.nan, np.nan, np.nan]).case_when( + [ + ([True, False, False], 1), + (df["a"].gt(1) & df["b"].eq(5), pd_array([1, 2, 3], dtype="Int64")), + ], + ) + expected = Series([1, 2, np.nan], dtype="Float64") + tm.assert_series_equal(result, expected) + + +def test_case_when_multiple_conditions_replacement_series(df): + """ + Test output when replacement is a Series + """ + result = Series([np.nan, np.nan, np.nan]).case_when( + [ + (np.array([True, False, False]), 1), + (df["a"].gt(1) & df["b"].eq(5), Series([1, 2, 3])), + ], + ) + expected = Series([1, 2, np.nan]) + tm.assert_series_equal(result, expected) + + +def test_case_when_non_range_index(): + """ + Test output if index is not RangeIndex + """ + rng = np.random.default_rng(seed=123) + dates = date_range("1/1/2000", periods=8) + df = DataFrame( + rng.standard_normal(size=(8, 4)), index=dates, columns=["A", "B", "C", "D"] + ) + result = Series(5, index=df.index, name="A").case_when([(df.A.gt(0), df.B)]) + expected = df.A.mask(df.A.gt(0), df.B).where(df.A.gt(0), 5) + tm.assert_series_equal(result, expected) + + +def test_case_when_callable(): + """ + Test output on a callable + """ + # https://numpy.org/doc/stable/reference/generated/numpy.piecewise.html + x = np.linspace(-2.5, 2.5, 6) + ser = Series(x) + result = ser.case_when( + caselist=[ + (lambda df: df < 0, lambda df: -df), + (lambda df: df >= 0, lambda df: df), + ] + ) + expected = np.piecewise(x, [x < 0, x >= 0], [lambda x: -x, lambda x: x]) + tm.assert_series_equal(result, Series(expected)) + + +def test_case_when_expression_condition(df): + # GH 64070 + result = df.assign(out=col("a").case_when([(col("a") > 1, 10), (col("a") <= 1, 5)])) + expected = df.assign(out=Series([5, 10, 10], index=df.index)) + tm.assert_frame_equal(result, expected) + + +def test_case_when_expression_replacement(df): + # GH 64070 + result = df.assign( + out=col("a").case_when( + [(df["a"] > 1, col("a") + 100), (df["a"] <= 1, col("a") + 1)] + ) + ) + expected = df.assign(out=Series([2, 102, 103], index=df.index)) + tm.assert_frame_equal(result, expected) + + +def test_case_when_expression_in_assign(): + # GH 64070 + df = DataFrame({"age": [65, 30], "name": ["Jason", "Anna"]}) + result = df.assign(elderly=col("age").case_when([(col("name").eq("Jason"), 1)])) + expected = df.assign(elderly=Series([1, 30], index=df.index)) + tm.assert_frame_equal(result, expected) + + +def test_case_when_expression_mixed_args_in_assign(): + # GH 64070 + df = DataFrame({"name": ["Jason", "Amy", "Bob"], "age": [42, 10, 5]}) + caselist = [ + (lambda s: s < 10, col("age") + 100), + (col("name").eq("Jason"), 1), + ] + result = df.assign(elderly=col("age").case_when(caselist=caselist)) + expected = df.assign(elderly=Series([1, 10, 105], index=df.index)) + tm.assert_frame_equal(result, expected) + + +def test_case_when_expression_math_condition_in_assign(): + # GH 64070 + df = DataFrame({"a": [-1, 1, 2], "b": [0, -2, 1]}) + caselist = [(col("a") + col("b") > 0, 99)] + result = df.assign(flag=col("a").case_when(caselist=caselist)) + expected = df.assign(flag=Series([-1, 1, 99], index=df.index)) + tm.assert_frame_equal(result, expected) + + +def test_case_when_expression_missing_values_in_assign(): + # GH 64070 + df = DataFrame({"age": [np.nan, 5.0, 20.0]}) + caselist = [ + (col("age").isna(), "missing"), + (col("age") > 10, "adult"), + ] + result = df.assign(group=col("age").case_when(caselist=caselist)) + expected = df.assign( + group=Series(["missing", 5.0, "adult"], index=df.index, dtype=object) + ) + tm.assert_frame_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_clip.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_clip.py new file mode 100644 index 0000000000000000000000000000000000000000..293f034ea322c91499e00fb87c3cc81ce451863c --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_clip.py @@ -0,0 +1,157 @@ +from datetime import datetime + +import numpy as np +import pytest + +from pandas.errors import OutOfBoundsDatetime + +import pandas as pd +from pandas import ( + Series, + Timestamp, + isna, + notna, +) +import pandas._testing as tm + + +class TestSeriesClip: + def test_clip(self, datetime_series): + val = datetime_series.median() + + assert datetime_series.clip(lower=val).min() == val + assert datetime_series.clip(upper=val).max() == val + + result = datetime_series.clip(-0.5, 0.5) + expected = np.clip(datetime_series, -0.5, 0.5) + tm.assert_series_equal(result, expected) + assert isinstance(expected, Series) + + def test_clip_types_and_nulls(self): + sers = [ + Series([np.nan, 1.0, 2.0, 3.0]), + Series([None, "a", "b", "c"]), + Series(pd.to_datetime([np.nan, 1, 2, 3], unit="D")), + ] + + for s in sers: + thresh = s[2] + lower = s.clip(lower=thresh) + upper = s.clip(upper=thresh) + assert lower[notna(lower)].min() == thresh + assert upper[notna(upper)].max() == thresh + assert list(isna(s)) == list(isna(lower)) + assert list(isna(s)) == list(isna(upper)) + + def test_series_clipping_with_na_values(self, any_numeric_ea_dtype): + # Ensure that clipping method can handle NA values with out failing + # GH#40581 + + ser = Series([pd.NA, 1.0, 3.0], dtype=any_numeric_ea_dtype) + s_clipped_upper = ser.clip(upper=2.0) + s_clipped_lower = ser.clip(lower=2.0) + + expected_upper = Series([pd.NA, 1.0, 2.0], dtype=any_numeric_ea_dtype) + expected_lower = Series([pd.NA, 2.0, 3.0], dtype=any_numeric_ea_dtype) + + tm.assert_series_equal(s_clipped_upper, expected_upper) + tm.assert_series_equal(s_clipped_lower, expected_lower) + + def test_clip_with_na_args(self): + """Should process np.nan argument as None""" + # GH#17276 + s = Series([1, 2, 3]) + + tm.assert_series_equal(s.clip(np.nan), Series([1, 2, 3])) + tm.assert_series_equal(s.clip(upper=np.nan, lower=np.nan), Series([1, 2, 3])) + + # GH#19992 + + res = s.clip(lower=[0, 4, np.nan]) + tm.assert_series_equal(res, Series([1, 4, 3.0])) + res = s.clip(upper=[1, np.nan, 1]) + tm.assert_series_equal(res, Series([1, 2, 1.0])) + + # GH#40420 + s = Series([1, 2, 3]) + result = s.clip(0, [np.nan, np.nan, np.nan]) + tm.assert_series_equal(s, result) + + def test_clip_against_series(self): + # GH#6966 + + s = Series([1.0, 1.0, 4.0]) + + lower = Series([1.0, 2.0, 3.0]) + upper = Series([1.5, 2.5, 3.5]) + + tm.assert_series_equal(s.clip(lower, upper), Series([1.0, 2.0, 3.5])) + tm.assert_series_equal(s.clip(1.5, upper), Series([1.5, 1.5, 3.5])) + + @pytest.mark.parametrize("inplace", [True, False]) + @pytest.mark.parametrize("upper", [[1, 2, 3], np.asarray([1, 2, 3])]) + def test_clip_against_list_like(self, inplace, upper): + # GH#15390 + original = Series([5, 6, 7]) + result = original.clip(upper=upper, inplace=inplace) + expected = Series([1, 2, 3]) + + if inplace: + assert result is original + tm.assert_series_equal(result, expected, check_exact=True) + + def test_clip_with_datetimes(self): + # GH#11838 + # naive and tz-aware datetimes + + t = Timestamp("2015-12-01 09:30:30") + s = Series([Timestamp("2015-12-01 09:30:00"), Timestamp("2015-12-01 09:31:00")]) + result = s.clip(upper=t) + expected = Series( + [Timestamp("2015-12-01 09:30:00"), Timestamp("2015-12-01 09:30:30")] + ) + tm.assert_series_equal(result, expected) + + t = Timestamp("2015-12-01 09:30:30", tz="US/Eastern") + s = Series( + [ + Timestamp("2015-12-01 09:30:00", tz="US/Eastern"), + Timestamp("2015-12-01 09:31:00", tz="US/Eastern"), + ] + ) + result = s.clip(upper=t) + expected = Series( + [ + Timestamp("2015-12-01 09:30:00", tz="US/Eastern"), + Timestamp("2015-12-01 09:30:30", tz="US/Eastern"), + ] + ) + tm.assert_series_equal(result, expected) + + def test_clip_with_timestamps_and_oob_datetimes_object(self): + # GH-42794 + ser = Series([datetime(1, 1, 1), datetime(9999, 9, 9)], dtype=object) + + result = ser.clip(lower=Timestamp.min, upper=Timestamp.max) + expected = Series([Timestamp.min, Timestamp.max], dtype=object) + + tm.assert_series_equal(result, expected) + + def test_clip_with_timestamps_and_oob_datetimes_non_nano(self): + # GH#56410 + dtype = "M8[us]" + ser = Series([datetime(1, 1, 1), datetime(9999, 9, 9)], dtype=dtype) + + msg = ( + r"Incompatible \(high-resolution\) value for dtype='datetime64\[us\]'. " + "Explicitly cast before operating" + ) + with pytest.raises(OutOfBoundsDatetime, match=msg): + ser.clip(lower=Timestamp.min, upper=Timestamp.max) + + lower = Timestamp.min.as_unit("us") + upper = Timestamp.max.as_unit("us") + result = ser.clip(lower=lower, upper=upper) + expected = Series([lower, upper], dtype=dtype) + + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_combine.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_combine.py new file mode 100644 index 0000000000000000000000000000000000000000..06b830d3db3e1929e82266f4d2c3082ad2ba1f12 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_combine.py @@ -0,0 +1,26 @@ +from pandas import ( + NA, + Series, +) +import pandas._testing as tm + + +class TestCombine: + def test_combine_scalar(self): + # GH#21248 + ser = Series([i * 10 for i in range(5)]) + result = ser.combine(3, lambda x, y: x + y) + expected = Series([i * 10 + 3 for i in range(5)]) + tm.assert_series_equal(result, expected) + + result = ser.combine(22, lambda x, y: min(x, y)) + expected = Series([min(i * 10, 22) for i in range(5)]) + tm.assert_series_equal(result, expected) + + def test_combine_series(self): + # GH#31899 + s1 = Series([91, NA, 94], dtype="Int8") + s2 = Series([91, NA, 11], dtype="Int8") + result = s1.combine(s2, lambda x, y: x + y) + expected = Series([-74, NA, 105], dtype="Int8") # dtype should be preserved + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_combine_first.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_combine_first.py new file mode 100644 index 0000000000000000000000000000000000000000..7de879681d74ff5108b7dcf8071cef60ddc50d68 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_combine_first.py @@ -0,0 +1,164 @@ +from datetime import datetime + +import numpy as np + +from pandas.errors import Pandas4Warning + +import pandas as pd +from pandas import ( + Period, + Series, + date_range, + period_range, + to_datetime, +) +import pandas._testing as tm + + +class TestCombineFirst: + def test_combine_first_period_datetime(self): + # GH#3367 + didx = date_range(start="1950-01-31", end="1950-07-31", freq="ME") + pidx = period_range(start=Period("1950-1"), end=Period("1950-7"), freq="M") + # check to be consistent with DatetimeIndex + for idx in [didx, pidx]: + a = Series([1, np.nan, np.nan, 4, 5, np.nan, 7], index=idx) + b = Series([9, 9, 9, 9, 9, 9, 9], index=idx) + + result = a.combine_first(b) + expected = Series([1, 9, 9, 4, 5, 9, 7], index=idx, dtype=np.float64) + tm.assert_series_equal(result, expected) + + def test_combine_first_name(self, datetime_series): + result = datetime_series.combine_first(datetime_series[:5]) + assert result.name == datetime_series.name + + def test_combine_first(self, using_infer_string): + values = np.arange(20, dtype=np.float64) + series = Series(values, index=np.arange(20, dtype=np.int64)) + + series_copy = series * 2 + series_copy[::2] = np.nan + + # nothing used from the input + combined = series.combine_first(series_copy) + + tm.assert_series_equal(combined, series) + + # Holes filled from input + combined = series_copy.combine_first(series) + assert np.isfinite(combined).all() + + tm.assert_series_equal(combined[::2], series[::2]) + tm.assert_series_equal(combined[1::2], series_copy[1::2]) + + # mixed types + index = pd.Index([str(i) for i in range(20)]) + floats = Series(np.random.default_rng(2).standard_normal(20), index=index) + strings = Series([str(i) for i in range(10)], index=index[::2], dtype=object) + + combined = strings.combine_first(floats) + + tm.assert_series_equal(strings, combined.loc[index[::2]]) + tm.assert_series_equal(floats[1::2].astype(object), combined.loc[index[1::2]]) + + # corner case + ser = Series([1.0, 2, 3], index=[0, 1, 2]) + empty = Series([], index=[], dtype=object) + result = ser.combine_first(empty) + if not using_infer_string: + ser.index = ser.index.astype("O") + tm.assert_series_equal(result, ser.astype(object)) + + def test_combine_first_dt64(self, unit): + s0 = to_datetime(Series(["2010", np.nan])).dt.as_unit(unit) + s1 = to_datetime(Series([np.nan, "2011"])).dt.as_unit(unit) + rs = s0.combine_first(s1) + xp = to_datetime(Series(["2010", "2011"])).dt.as_unit(unit) + tm.assert_series_equal(rs, xp) + + def test_combine_first_dt64_casting_deprecation(self, unit): + # GH#62931 + s0 = to_datetime(Series(["2010", np.nan])).dt.as_unit(unit) + s1 = Series([np.nan, "2011"]) + + msg = "Silently casting non-datetime 'other' to datetime" + with tm.assert_produces_warning(Pandas4Warning, match=msg): + rs = s0.combine_first(s1) + + xp = Series([datetime(2010, 1, 1), "2011"], dtype=f"datetime64[{unit}]") + if unit in ["s", "ms"]: + # TODO: should _cast_pointwise_result attempt to preserve unit? + xp = xp.dt.as_unit("us") + tm.assert_series_equal(rs, xp) + + def test_combine_first_dt_tz_values(self, tz_naive_fixture): + ser1 = Series( + pd.DatetimeIndex(["20150101", "20150102", "20150103"], tz=tz_naive_fixture), + name="ser1", + ) + ser2 = Series( + pd.DatetimeIndex(["20160514", "20160515", "20160516"], tz=tz_naive_fixture), + index=[2, 3, 4], + name="ser2", + ) + result = ser1.combine_first(ser2) + exp_vals = pd.DatetimeIndex( + ["20150101", "20150102", "20150103", "20160515", "20160516"], + tz=tz_naive_fixture, + ) + exp = Series(exp_vals, name="ser1") + tm.assert_series_equal(exp, result) + + def test_combine_first_timezone_series_with_empty_series(self): + # GH 41800 + time_index = date_range( + datetime(2021, 1, 1, 1), + datetime(2021, 1, 1, 10), + freq="h", + tz="Europe/Rome", + ) + s1 = Series(range(10), index=time_index) + s2 = Series(index=time_index) + result = s1.combine_first(s2) + tm.assert_series_equal(result, s1.astype(np.float64)) + + def test_combine_first_preserves_dtype(self): + # GH51764 + s1 = Series([1666880195890293744, 1666880195890293837]) + s2 = Series([1, 2, 3]) + result = s1.combine_first(s2) + expected = Series([1666880195890293744, 1666880195890293837, 3]) + tm.assert_series_equal(result, expected) + + def test_combine_mixed_timezone(self): + # GH 26283 + uniform_tz = Series({pd.Timestamp("2019-05-01", tz="UTC"): 1.0}) + multi_tz = Series( + { + pd.Timestamp("2019-05-01 01:00:00+0100", tz="Europe/London"): 2.0, + pd.Timestamp("2019-05-02", tz="UTC"): 3.0, + } + ) + + result = uniform_tz.combine_first(multi_tz) + expected = Series( + [1.0, 3.0], + index=pd.Index( + [ + pd.Timestamp("2019-05-01 00:00:00+00:00", tz="UTC"), + pd.Timestamp("2019-05-02 00:00:00+00:00", tz="UTC"), + ], + dtype="object", + ), + ) + tm.assert_series_equal(result, expected) + + def test_combine_first_none_not_nan(self): + # GH#58977 + s1 = Series([None, None, None], index=["a", "b", "c"]) + s2 = Series([None, None, None], index=["b", "c", "d"]) + + result = s1.combine_first(s2) + expected = Series([None] * 4, index=["a", "b", "c", "d"]) + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_compare.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_compare.py new file mode 100644 index 0000000000000000000000000000000000000000..2a57d5139b62cd9a9ec374638fee5cd596d00b4e --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_compare.py @@ -0,0 +1,140 @@ +import numpy as np +import pytest + +import pandas as pd +import pandas._testing as tm + + +def test_compare_axis(axis): + # GH#30429 + s1 = pd.Series(["a", "b", "c"]) + s2 = pd.Series(["x", "b", "z"]) + + result = s1.compare(s2, align_axis=axis) + + if axis in (1, "columns"): + indices = pd.Index([0, 2]) + columns = pd.Index(["self", "other"]) + expected = pd.DataFrame( + [["a", "x"], ["c", "z"]], index=indices, columns=columns + ) + tm.assert_frame_equal(result, expected) + else: + indices = pd.MultiIndex.from_product([[0, 2], ["self", "other"]]) + expected = pd.Series(["a", "x", "c", "z"], index=indices) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "keep_shape, keep_equal", + [ + (True, False), + (False, True), + (True, True), + # False, False case is already covered in test_compare_axis + ], +) +def test_compare_various_formats(keep_shape, keep_equal): + s1 = pd.Series(["a", "b", "c"]) + s2 = pd.Series(["x", "b", "z"]) + + result = s1.compare(s2, keep_shape=keep_shape, keep_equal=keep_equal) + + if keep_shape: + indices = pd.Index([0, 1, 2]) + columns = pd.Index(["self", "other"]) + if keep_equal: + expected = pd.DataFrame( + [["a", "x"], ["b", "b"], ["c", "z"]], index=indices, columns=columns + ) + else: + expected = pd.DataFrame( + [["a", "x"], [np.nan, np.nan], ["c", "z"]], + index=indices, + columns=columns, + ) + else: + indices = pd.Index([0, 2]) + columns = pd.Index(["self", "other"]) + expected = pd.DataFrame( + [["a", "x"], ["c", "z"]], index=indices, columns=columns + ) + tm.assert_frame_equal(result, expected) + + +def test_compare_with_equal_nulls(): + # We want to make sure two NaNs are considered the same + # and dropped where applicable + s1 = pd.Series(["a", "b", np.nan]) + s2 = pd.Series(["x", "b", np.nan]) + + result = s1.compare(s2) + expected = pd.DataFrame([["a", "x"]], columns=["self", "other"]) + tm.assert_frame_equal(result, expected) + + +def test_compare_with_non_equal_nulls(): + # We want to make sure the relevant NaNs do not get dropped + s1 = pd.Series(["a", "b", "c"]) + s2 = pd.Series(["x", "b", np.nan]) + + result = s1.compare(s2, align_axis=0) + + indices = pd.MultiIndex.from_product([[0, 2], ["self", "other"]]) + expected = pd.Series(["a", "x", "c", np.nan], index=indices) + tm.assert_series_equal(result, expected) + + +def test_compare_multi_index(): + index = pd.MultiIndex.from_arrays([[0, 0, 1], [0, 1, 2]]) + s1 = pd.Series(["a", "b", "c"], index=index) + s2 = pd.Series(["x", "b", "z"], index=index) + + result = s1.compare(s2, align_axis=0) + + indices = pd.MultiIndex.from_arrays( + [[0, 0, 1, 1], [0, 0, 2, 2], ["self", "other", "self", "other"]] + ) + expected = pd.Series(["a", "x", "c", "z"], index=indices) + tm.assert_series_equal(result, expected) + + +def test_compare_different_indices(): + msg = "Can only compare identically-labeled Series objects" + ser1 = pd.Series([1, 2, 3], index=["a", "b", "c"]) + ser2 = pd.Series([1, 2, 3], index=["a", "b", "d"]) + with pytest.raises(ValueError, match=msg): + ser1.compare(ser2) + + +def test_compare_different_lengths(): + msg = "Can only compare identically-labeled Series objects" + ser1 = pd.Series([1, 2, 3]) + ser2 = pd.Series([1, 2, 3, 4]) + with pytest.raises(ValueError, match=msg): + ser1.compare(ser2) + + +def test_compare_datetime64_and_string(): + # Issue https://github.com/pandas-dev/pandas/issues/45506 + # Catch OverflowError when comparing datetime64 and string + data = [ + {"a": "2015-07-01", "b": "08335394550"}, + {"a": "2015-07-02", "b": "+49 (0) 0345 300033"}, + {"a": "2015-07-03", "b": "+49(0)2598 04457"}, + {"a": "2015-07-04", "b": "0741470003"}, + {"a": "2015-07-05", "b": "04181 83668"}, + ] + dtypes = {"a": "datetime64[ns]", "b": "string"} + df = pd.DataFrame(data=data).astype(dtypes) + + result_eq1 = df["a"].eq(df["b"]) + result_eq2 = df["a"] == df["b"] + result_neq = df["a"] != df["b"] + + expected_eq = pd.Series([False] * 5) # For .eq and == + expected_neq = pd.Series([True] * 5) # For != + + tm.assert_series_equal(result_eq1, expected_eq) + tm.assert_series_equal(result_eq2, expected_eq) + tm.assert_series_equal(result_neq, expected_neq) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_convert_dtypes.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_convert_dtypes.py new file mode 100644 index 0000000000000000000000000000000000000000..57757fcf9ac29fc92186418d53fec372e321ebfa --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_convert_dtypes.py @@ -0,0 +1,340 @@ +from itertools import product + +import numpy as np +import pytest + +from pandas._libs import lib +import pandas.util._test_decorators as td + +import pandas as pd +import pandas._testing as tm + + +class TestSeriesConvertDtypes: + @pytest.mark.parametrize( + "data, maindtype, expected_default, expected_other", + [ + ( + # data + [1, 2, 3], + # original dtype + np.dtype("int32"), + # default expected dtype + "Int32", + # exceptions on expected dtype + {("convert_integer", False): np.dtype("int32")}, + ), + ( + [1, 2, 3], + np.dtype("int64"), + "Int64", + {("convert_integer", False): np.dtype("int64")}, + ), + ( + ["x", "y", "z"], + np.dtype("O"), + pd.StringDtype(), + {("convert_string", False): np.dtype("O")}, + ), + ( + [True, False, np.nan], + np.dtype("O"), + pd.BooleanDtype(), + {("convert_boolean", False): np.dtype("O")}, + ), + ( + ["h", "i", np.nan], + np.dtype("O"), + pd.StringDtype(), + {("convert_string", False): np.dtype("O")}, + ), + ( # GH32117 + ["h", "i", 1], + np.dtype("O"), + np.dtype("O"), + {}, + ), + ( + [10, np.nan, 20], + np.dtype("float"), + "Int64", + { + ("convert_integer", False, "convert_floating", True): "Float64", + ("convert_integer", False, "convert_floating", False): np.dtype( + "float" + ), + }, + ), + ( + [np.nan, 100.5, 200], + np.dtype("float"), + "Float64", + {("convert_floating", False): np.dtype("float")}, + ), + ( + [3, 4, 5], + "Int8", + "Int8", + {}, + ), + ( + [[1, 2], [3, 4], [5]], + None, + np.dtype("O"), + {}, + ), + ( + [4, 5, 6], + np.dtype("uint32"), + "UInt32", + {("convert_integer", False): np.dtype("uint32")}, + ), + ( + [-10, 12, 13], + np.dtype("i1"), + "Int8", + {("convert_integer", False): np.dtype("i1")}, + ), + ( + [1.2, 1.3], + np.dtype("float32"), + "Float32", + {("convert_floating", False): np.dtype("float32")}, + ), + ( + [1, 2.0], + object, + "Int64", + { + ("convert_integer", False): "Float64", + ("convert_integer", False, "convert_floating", False): np.dtype( + "float" + ), + ("infer_objects", False): np.dtype("object"), + }, + ), + ( + [1, 2.5], + object, + "Float64", + { + ("convert_floating", False): np.dtype("float"), + ("infer_objects", False): np.dtype("object"), + }, + ), + (["a", "b"], pd.CategoricalDtype(), pd.CategoricalDtype(), {}), + ( + pd.to_datetime(["2020-01-14 10:00", "2020-01-15 11:11"]).as_unit("s"), + pd.DatetimeTZDtype(tz="UTC"), + pd.DatetimeTZDtype(tz="UTC"), + {}, + ), + ( + pd.to_datetime(["2020-01-14 10:00", "2020-01-15 11:11"]).as_unit("ms"), + pd.DatetimeTZDtype(tz="UTC"), + pd.DatetimeTZDtype(tz="UTC"), + {}, + ), + ( + pd.to_datetime(["2020-01-14 10:00", "2020-01-15 11:11"]).as_unit("us"), + pd.DatetimeTZDtype(tz="UTC"), + pd.DatetimeTZDtype(tz="UTC"), + {}, + ), + ( + pd.to_datetime(["2020-01-14 10:00", "2020-01-15 11:11"]).as_unit("ns"), + pd.DatetimeTZDtype(tz="UTC"), + pd.DatetimeTZDtype(tz="UTC"), + {}, + ), + ( + pd.to_datetime(["2020-01-14 10:00", "2020-01-15 11:11"]).as_unit("ns"), + "datetime64[ns]", + np.dtype("datetime64[ns]"), + {}, + ), + ( + pd.to_datetime(["2020-01-14 10:00", "2020-01-15 11:11"]).as_unit("ns"), + object, + np.dtype("datetime64[ns]"), + {("infer_objects", False): np.dtype("object")}, + ), + ( + pd.period_range("1/1/2011", freq="M", periods=3), + None, + pd.PeriodDtype("M"), + {}, + ), + ( + pd.arrays.IntervalArray([pd.Interval(0, 1), pd.Interval(1, 5)]), + None, + pd.IntervalDtype("int64", "right"), + {}, + ), + ], + ) + @pytest.mark.parametrize("params", product(*[(True, False)] * 5)) + def test_convert_dtypes( + self, + data, + maindtype, + expected_default, + expected_other, + params, + using_infer_string, + using_nan_is_na, + ): + if ( + hasattr(data, "dtype") + and lib.is_np_dtype(data.dtype, "M") + and isinstance(maindtype, pd.DatetimeTZDtype) + ): + # this astype is deprecated in favor of tz_localize + msg = "Cannot use .astype to convert from timezone-naive dtype" + with pytest.raises(TypeError, match=msg): + pd.Series(data, dtype=maindtype) + return + + if maindtype is not None: + series = pd.Series(data, dtype=maindtype) + else: + series = pd.Series(data) + + result = series.convert_dtypes(*params) + + param_names = [ + "infer_objects", + "convert_string", + "convert_integer", + "convert_boolean", + "convert_floating", + ] + params_dict = dict(zip(param_names, params, strict=True)) + + expected_dtype = expected_default + for spec, dtype in expected_other.items(): + if all( + params_dict[key] is val + for key, val in zip(spec[::2], spec[1::2], strict=False) + ): + expected_dtype = dtype + if ( + using_infer_string + and expected_default == "string" + and expected_dtype == object + and params[0] + and not params[1] + ): + # If convert_string=False and infer_objects=True, we end up with the + # default string dtype instead of preserving object for string data + expected_dtype = pd.StringDtype(na_value=np.nan) + if ( + not using_nan_is_na + and expected_dtype == "Int64" + and isinstance(data[1], float) + and np.isnan(data[1]) + ): + if params_dict["convert_floating"]: + expected_dtype = "Float64" + else: + expected_dtype = "float64" + + expected = pd.Series(data, dtype=expected_dtype) + tm.assert_series_equal(result, expected) + + # Test that it is a copy + copy = series.copy(deep=True) + + if result.notna().sum() > 0 and result.dtype in ["interval[int64, right]"]: + with pytest.raises(TypeError, match="Invalid value"): + result[result.notna()] = np.nan + else: + result[result.notna()] = pd.NA + + # Make sure original not changed + tm.assert_series_equal(series, copy) + + def test_convert_string_dtype(self, nullable_string_dtype): + # https://github.com/pandas-dev/pandas/issues/31731 -> converting columns + # that are already string dtype + df = pd.DataFrame( + {"A": ["a", "b", pd.NA], "B": ["ä", "ö", "ü"]}, dtype=nullable_string_dtype + ) + result = df.convert_dtypes() + tm.assert_frame_equal(df, result) + + def test_convert_bool_dtype(self): + # GH32287 + df = pd.DataFrame({"A": pd.array([True])}) + tm.assert_frame_equal(df, df.convert_dtypes()) + + def test_convert_byte_string_dtype(self): + # GH-43183 + byte_str = b"binary-string" + + df = pd.DataFrame(data={"A": byte_str}, index=[0]) + result = df.convert_dtypes() + expected = df + tm.assert_frame_equal(result, expected) + + @pytest.mark.parametrize( + "infer_objects, dtype", [(True, "Int64"), (False, "object")] + ) + def test_convert_dtype_object_with_na(self, infer_objects, dtype): + # GH#48791 + ser = pd.Series([1, pd.NA]) + result = ser.convert_dtypes(infer_objects=infer_objects) + expected = pd.Series([1, pd.NA], dtype=dtype) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "infer_objects, dtype", [(True, "Float64"), (False, "object")] + ) + def test_convert_dtype_object_with_na_float(self, infer_objects, dtype): + # GH#48791 + ser = pd.Series([1.5, pd.NA]) + result = ser.convert_dtypes(infer_objects=infer_objects) + expected = pd.Series([1.5, pd.NA], dtype=dtype) + tm.assert_series_equal(result, expected) + + def test_convert_dtypes_pyarrow_to_np_nullable(self): + # GH 53648 + pytest.importorskip("pyarrow") + ser = pd.Series(range(2), dtype="int32[pyarrow]") + result = ser.convert_dtypes(dtype_backend="numpy_nullable") + expected = pd.Series(range(2), dtype="Int32") + tm.assert_series_equal(result, expected) + + def test_convert_dtypes_pyarrow_null(self): + # GH#55346 + pa = pytest.importorskip("pyarrow") + ser = pd.Series([None, None]) + result = ser.convert_dtypes(dtype_backend="pyarrow") + expected = pd.Series([None, None], dtype=pd.ArrowDtype(pa.null())) + tm.assert_series_equal(result, expected) + + @td.skip_if_no("pyarrow") + @pytest.mark.parametrize("categories", [None, ["S1", "S2"]]) + def test_convert_empty_categorical_to_pyarrow(self, categories): + # GH#59934 + ser = pd.Series(pd.Categorical([None] * 5, categories=categories)) + converted = ser.convert_dtypes(dtype_backend="pyarrow") + expected = ser + tm.assert_series_equal(converted, expected) + + def test_convert_dtype_pyarrow_timezone_preserve(self): + # GH 60237 + pytest.importorskip("pyarrow") + ser = pd.Series( + pd.to_datetime(range(5), utc=True, unit="h"), + dtype="timestamp[ns, tz=UTC][pyarrow]", + ) + result = ser.convert_dtypes(dtype_backend="pyarrow") + expected = ser.copy() + tm.assert_series_equal(result, expected) + + def test_convert_dtypes_complex(self): + # GH 60129 + ser = pd.Series([1.5 + 3.0j, 1.5 - 3.0j]) + result = ser.convert_dtypes() + tm.assert_series_equal(result, ser) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_copy.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_copy.py new file mode 100644 index 0000000000000000000000000000000000000000..ad5417d330d510fcfba81a8bfaf26e138e462074 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_copy.py @@ -0,0 +1,77 @@ +import numpy as np +import pytest + +from pandas import ( + Series, + Timestamp, +) +import pandas._testing as tm + + +class TestCopy: + @pytest.mark.parametrize("deep", ["default", None, False, True]) + def test_copy(self, deep): + ser = Series(np.arange(10), dtype="float64") + + # default deep is True + if deep == "default": + ser2 = ser.copy() + else: + ser2 = ser.copy(deep=deep) + + # INFO(CoW) a shallow copy doesn't yet copy the data + # but parent will not be modified (CoW) + if deep is None or deep is False: + assert np.may_share_memory(ser.values, ser2.values) + else: + assert not np.may_share_memory(ser.values, ser2.values) + + ser2[::2] = np.nan + + # Did not modify original Series + assert np.isnan(ser2[0]) + assert not np.isnan(ser[0]) + + @pytest.mark.filterwarnings("ignore:Setting a value on a view:FutureWarning") + @pytest.mark.parametrize("deep", ["default", None, False, True]) + def test_copy_tzaware(self, deep): + # GH#11794 + # copy of tz-aware + expected = Series([Timestamp("2012/01/01", tz="UTC")]) + expected2 = Series([Timestamp("1999/01/01", tz="UTC")]) + + ser = Series([Timestamp("2012/01/01", tz="UTC")]) + + if deep == "default": + ser2 = ser.copy() + else: + ser2 = ser.copy(deep=deep) + + # INFO(CoW) a shallow copy doesn't yet copy the data + # but parent will not be modified (CoW) + if deep is None or deep is False: + assert np.may_share_memory(ser.values, ser2.values) + else: + assert not np.may_share_memory(ser.values, ser2.values) + + ser2[0] = Timestamp("1999/01/01", tz="UTC") + + # Did not modify original Series + tm.assert_series_equal(ser2, expected2) + tm.assert_series_equal(ser, expected) + + def test_copy_name(self, datetime_series): + result = datetime_series.copy() + assert result.name == datetime_series.name + + def test_copy_index_name_checking(self, datetime_series): + # don't want to be able to modify the index stored elsewhere after + # making a copy + + datetime_series.index.name = None + assert datetime_series.index.name is None + assert datetime_series is datetime_series + + cp = datetime_series.copy() + cp.index.name = "foo" + assert datetime_series.index.name is None diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_count.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_count.py new file mode 100644 index 0000000000000000000000000000000000000000..2172a3205ef55487dfe20ab95848330f49717412 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_count.py @@ -0,0 +1,24 @@ +import numpy as np + +from pandas import ( + Categorical, + Series, +) + + +class TestSeriesCount: + def test_count(self, datetime_series): + assert datetime_series.count() == len(datetime_series) + + datetime_series[::2] = np.nan + + assert datetime_series.count() == np.isfinite(datetime_series).sum() + + def test_count_categorical(self): + ser = Series( + Categorical( + [np.nan, 1, 2, np.nan], categories=[5, 4, 3, 2, 1], ordered=True + ) + ) + result = ser.count() + assert result == 2 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_cov_corr.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_cov_corr.py new file mode 100644 index 0000000000000000000000000000000000000000..7a4d48fb76940605b8ce80be6cdca21d9fb5419e --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_cov_corr.py @@ -0,0 +1,186 @@ +import math + +import numpy as np +import pytest + +import pandas as pd +from pandas import ( + Series, + date_range, + isna, +) +import pandas._testing as tm + + +class TestSeriesCov: + def test_cov(self, datetime_series): + # full overlap + tm.assert_almost_equal( + datetime_series.cov(datetime_series), datetime_series.std() ** 2 + ) + + # partial overlap + tm.assert_almost_equal( + datetime_series[:15].cov(datetime_series[5:]), + datetime_series[5:15].std() ** 2, + ) + + # No overlap + assert np.isnan(datetime_series[::2].cov(datetime_series[1::2])) + + # all NA + cp = datetime_series[:10].copy() + cp[:] = np.nan + assert isna(cp.cov(cp)) + + # min_periods + assert isna(datetime_series[:15].cov(datetime_series[5:], min_periods=12)) + + ts1 = datetime_series[:15].reindex(datetime_series.index) + ts2 = datetime_series[5:].reindex(datetime_series.index) + assert isna(ts1.cov(ts2, min_periods=12)) + + @pytest.mark.parametrize("test_ddof", [None, 0, 1, 2, 3]) + @pytest.mark.parametrize("dtype", ["float64", "Float64"]) + def test_cov_ddof(self, test_ddof, dtype): + # GH#34611 + np_array1 = np.random.default_rng(2).random(10) + np_array2 = np.random.default_rng(2).random(10) + + s1 = Series(np_array1, dtype=dtype) + s2 = Series(np_array2, dtype=dtype) + + result = s1.cov(s2, ddof=test_ddof) + expected = np.cov(np_array1, np_array2, ddof=test_ddof)[0][1] + assert math.isclose(expected, result) + + +class TestSeriesCorr: + def test_corr(self, datetime_series, any_float_dtype): + stats = pytest.importorskip("scipy.stats") + + datetime_series = datetime_series.astype(any_float_dtype) + + # full overlap + tm.assert_almost_equal(datetime_series.corr(datetime_series), 1) + + # partial overlap + tm.assert_almost_equal(datetime_series[:15].corr(datetime_series[5:]), 1) + + assert isna(datetime_series[:15].corr(datetime_series[5:], min_periods=12)) + + ts1 = datetime_series[:15].reindex(datetime_series.index) + ts2 = datetime_series[5:].reindex(datetime_series.index) + assert isna(ts1.corr(ts2, min_periods=12)) + + # No overlap + assert np.isnan(datetime_series[::2].corr(datetime_series[1::2])) + + # all NA + cp = datetime_series[:10].copy() + cp[:] = np.nan + assert isna(cp.corr(cp)) + + A = Series( + np.arange(10, dtype=np.float64), + index=date_range("2020-01-01", periods=10), + name="ts", + ) + result = A.corr(A) + expected, _ = stats.pearsonr(A, A) + tm.assert_almost_equal(result, expected) + + def test_corr_rank(self): + stats = pytest.importorskip("scipy.stats") + + # kendall and spearman + B = Series( + np.arange(10, dtype=np.float64), + index=date_range("2020-01-01", periods=10), + name="ts", + ) + A = Series( + np.concatenate([np.arange(5, dtype=np.float64)] * 2), + index=date_range("2020-01-01", periods=10), + name="ts", + ) + result = A.corr(B, method="kendall") + expected = stats.kendalltau(A, B)[0] + tm.assert_almost_equal(result, expected) + + result = A.corr(B, method="spearman") + expected = stats.spearmanr(A, B)[0] + tm.assert_almost_equal(result, expected) + + # results from R + A = Series( + [ + -0.89926396, + 0.94209606, + -1.03289164, + -0.95445587, + 0.76910310, + -0.06430576, + -2.09704447, + 0.40660407, + -0.89926396, + 0.94209606, + ] + ) + B = Series( + [ + -1.01270225, + -0.62210117, + -1.56895827, + 0.59592943, + -0.01680292, + 1.17258718, + -1.06009347, + -0.10222060, + -0.89076239, + 0.89372375, + ] + ) + kexp = 0.4319297 + sexp = 0.5853767 + tm.assert_almost_equal(A.corr(B, method="kendall"), kexp) + tm.assert_almost_equal(A.corr(B, method="spearman"), sexp) + + def test_corr_invalid_method(self): + # GH PR #22298 + s1 = Series(np.random.default_rng(2).standard_normal(10)) + s2 = Series(np.random.default_rng(2).standard_normal(10)) + msg = "method must be either 'pearson', 'spearman', 'kendall', or a callable, " + with pytest.raises(ValueError, match=msg): + s1.corr(s2, method="____") + + def test_corr_callable_method(self, datetime_series): + # simple correlation example + # returns 1 if exact equality, 0 otherwise + my_corr = lambda a, b: 1.0 if (a == b).all() else 0.0 + + # simple example + s1 = Series([1, 2, 3, 4, 5]) + s2 = Series([5, 4, 3, 2, 1]) + expected = 0 + tm.assert_almost_equal(s1.corr(s2, method=my_corr), expected) + + # full overlap + tm.assert_almost_equal( + datetime_series.corr(datetime_series, method=my_corr), 1.0 + ) + + # partial overlap + tm.assert_almost_equal( + datetime_series[:15].corr(datetime_series[5:], method=my_corr), 1.0 + ) + + # No overlap + assert np.isnan( + datetime_series[::2].corr(datetime_series[1::2], method=my_corr) + ) + + # dataframe example + df = pd.DataFrame([s1, s2]) + expected = pd.DataFrame([{0: 1.0, 1: 0}, {0: 0, 1: 1.0}]) + tm.assert_almost_equal(df.transpose().corr(method=my_corr), expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_describe.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_describe.py new file mode 100644 index 0000000000000000000000000000000000000000..c3246fd35227c98e1a476483351bb62878a61dfe --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_describe.py @@ -0,0 +1,201 @@ +import numpy as np +import pytest + +from pandas.core.dtypes.common import ( + is_complex_dtype, + is_extension_array_dtype, +) + +from pandas import ( + NA, + Period, + Series, + Timedelta, + Timestamp, + date_range, +) +import pandas._testing as tm + + +class TestSeriesDescribe: + def test_describe_ints(self): + ser = Series([0, 1, 2, 3, 4], name="int_data") + result = ser.describe() + expected = Series( + [5, 2, ser.std(), 0, 1, 2, 3, 4], + name="int_data", + index=["count", "mean", "std", "min", "25%", "50%", "75%", "max"], + ) + tm.assert_series_equal(result, expected) + + def test_describe_bools(self): + ser = Series([True, True, False, False, False], name="bool_data") + result = ser.describe() + expected = Series( + [5, 2, False, 3], name="bool_data", index=["count", "unique", "top", "freq"] + ) + tm.assert_series_equal(result, expected) + + def test_describe_strs(self): + ser = Series(["a", "a", "b", "c", "d"], name="str_data") + result = ser.describe() + expected = Series( + [5, 4, "a", 2], name="str_data", index=["count", "unique", "top", "freq"] + ) + tm.assert_series_equal(result, expected) + + def test_describe_timedelta64(self): + ser = Series( + [ + Timedelta("1 days"), + Timedelta("2 days"), + Timedelta("3 days"), + Timedelta("4 days"), + Timedelta("5 days"), + ], + name="timedelta_data", + ) + result = ser.describe() + expected = Series( + [5, ser[2], ser.std(), ser[0], ser[1], ser[2], ser[3], ser[4]], + name="timedelta_data", + index=["count", "mean", "std", "min", "25%", "50%", "75%", "max"], + ) + tm.assert_series_equal(result, expected) + + def test_describe_period(self): + ser = Series( + [Period("2020-01", "M"), Period("2020-01", "M"), Period("2019-12", "M")], + name="period_data", + ) + result = ser.describe() + expected = Series( + [3, 2, ser[0], 2], + name="period_data", + index=["count", "unique", "top", "freq"], + ) + tm.assert_series_equal(result, expected) + + def test_describe_empty_object(self): + # https://github.com/pandas-dev/pandas/issues/27183 + s = Series([None, None], dtype=object) + result = s.describe() + expected = Series( + [0, 0, np.nan, np.nan], + dtype=object, + index=["count", "unique", "top", "freq"], + ) + tm.assert_series_equal(result, expected) + + result = s[:0].describe() + tm.assert_series_equal(result, expected) + # ensure NaN, not None + assert np.isnan(result.iloc[2]) + assert np.isnan(result.iloc[3]) + + def test_describe_with_tz(self, tz_naive_fixture): + # GH 21332 + tz = tz_naive_fixture + name = str(tz_naive_fixture) + start = Timestamp(2018, 1, 1) + end = Timestamp(2018, 1, 5) + s = Series(date_range(start, end, tz=tz), name=name) + result = s.describe() + expected = Series( + [ + 5, + Timestamp(2018, 1, 3).tz_localize(tz), + start.tz_localize(tz), + s[1], + s[2], + s[3], + end.tz_localize(tz), + ], + name=name, + index=["count", "mean", "min", "25%", "50%", "75%", "max"], + ) + tm.assert_series_equal(result, expected) + + def test_describe_with_tz_numeric(self): + name = tz = "CET" + start = Timestamp(2018, 1, 1) + end = Timestamp(2018, 1, 5) + s = Series(date_range(start, end, tz=tz), name=name) + + result = s.describe() + + expected = Series( + [ + 5, + Timestamp("2018-01-03 00:00:00", tz=tz), + Timestamp("2018-01-01 00:00:00", tz=tz), + Timestamp("2018-01-02 00:00:00", tz=tz), + Timestamp("2018-01-03 00:00:00", tz=tz), + Timestamp("2018-01-04 00:00:00", tz=tz), + Timestamp("2018-01-05 00:00:00", tz=tz), + ], + name=name, + index=["count", "mean", "min", "25%", "50%", "75%", "max"], + ) + tm.assert_series_equal(result, expected) + + def test_datetime_is_numeric_includes_datetime(self): + s = Series(date_range("2012", periods=3)) + result = s.describe() + expected = Series( + [ + 3, + Timestamp("2012-01-02"), + Timestamp("2012-01-01"), + Timestamp("2012-01-01T12:00:00"), + Timestamp("2012-01-02"), + Timestamp("2012-01-02T12:00:00"), + Timestamp("2012-01-03"), + ], + index=["count", "mean", "min", "25%", "50%", "75%", "max"], + ) + tm.assert_series_equal(result, expected) + + @pytest.mark.filterwarnings("ignore:Casting complex values to real discards") + def test_numeric_result_dtype(self, any_numeric_dtype): + # GH#48340 - describe should always return float on non-complex numeric input + if is_extension_array_dtype(any_numeric_dtype): + dtype = "Float64" + else: + dtype = "complex128" if is_complex_dtype(any_numeric_dtype) else None + + ser = Series([0, 1], dtype=any_numeric_dtype) + if dtype == "complex128": + with pytest.raises( + TypeError, match=r"^a must be an array of real numbers$" + ): + ser.describe() + return + result = ser.describe() + expected = Series( + [ + 2.0, + 0.5, + ser.std(), + 0, + 0.25, + 0.5, + 0.75, + 1.0, + ], + index=["count", "mean", "std", "min", "25%", "50%", "75%", "max"], + dtype=dtype, + ) + tm.assert_series_equal(result, expected) + + def test_describe_one_element_ea(self): + # GH#52515 + ser = Series([0.0], dtype="Float64") + with tm.assert_produces_warning(None): + result = ser.describe() + expected = Series( + [1, 0, NA, 0, 0, 0, 0, 0], + dtype="Float64", + index=["count", "mean", "std", "min", "25%", "50%", "75%", "max"], + ) + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_diff.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_diff.py new file mode 100644 index 0000000000000000000000000000000000000000..dde51e5ac56ded5d785cc8e910e040f61dff534b --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_diff.py @@ -0,0 +1,92 @@ +import numpy as np +import pytest + +from pandas import ( + Series, + TimedeltaIndex, + date_range, +) +import pandas._testing as tm + + +class TestSeriesDiff: + def test_diff_series_requires_integer(self): + series = Series(np.random.default_rng(2).standard_normal(2)) + with pytest.raises(ValueError, match="periods must be an integer"): + series.diff(1.5) + + def test_diff_np(self): + # TODO(__array_function__): could make np.diff return a Series + # matching ser.diff() + + ser = Series(np.arange(5)) + + res = np.diff(ser) + expected = np.array([1, 1, 1, 1]) + tm.assert_numpy_array_equal(res, expected) + + def test_diff_int(self): + # int dtype + a = 10000000000000000 + b = a + 1 + ser = Series([a, b]) + + result = ser.diff() + assert result[1] == 1 + + def test_diff_tz(self): + # Combined datetime diff, normal diff and boolean diff test + ts = Series( + np.arange(10, dtype=np.float64), + index=date_range("2020-01-01", periods=10), + name="ts", + ) + ts.diff() + + # neg n + result = ts.diff(-1) + expected = ts - ts.shift(-1) + tm.assert_series_equal(result, expected) + + # 0 + result = ts.diff(0) + expected = ts - ts + tm.assert_series_equal(result, expected) + + def test_diff_dt64(self): + # datetime diff (GH#3100) + ser = Series(date_range("20130102", periods=5)) + result = ser.diff() + expected = ser - ser.shift(1) + tm.assert_series_equal(result, expected) + + # timedelta diff + result = result - result.shift(1) # previous result + expected = expected.diff() # previously expected + tm.assert_series_equal(result, expected) + + def test_diff_dt64tz(self): + # with tz + ser = Series( + date_range("2000-01-01 09:00:00", periods=5, tz="US/Eastern"), + name="foo", + ) + result = ser.diff() + expected = Series(TimedeltaIndex(["NaT"] + ["1 days"] * 4), name="foo") + tm.assert_series_equal(result, expected) + + def test_diff_bool(self): + # boolean series (test for fixing #17294) + data = [False, True, True, False, False] + output = [np.nan, True, False, True, False] + ser = Series(data) + result = ser.diff() + expected = Series(output) + tm.assert_series_equal(result, expected) + + def test_diff_object_dtype(self): + # object series + ser = Series([False, True, 5.0, np.nan, True, False]) + result = ser.diff() + expected = ser - ser.shift(1) + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_drop.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_drop.py new file mode 100644 index 0000000000000000000000000000000000000000..d2a5a3324e886f894ae70dc9c77ac2bdcfd2c3db --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_drop.py @@ -0,0 +1,99 @@ +import pytest + +from pandas import ( + Index, + Series, +) +import pandas._testing as tm +from pandas.api.types import is_bool_dtype + + +@pytest.mark.parametrize( + "data, index, drop_labels, axis, expected_data, expected_index", + [ + # Unique Index + ([1, 2], ["one", "two"], ["two"], 0, [1], ["one"]), + ([1, 2], ["one", "two"], ["two"], "rows", [1], ["one"]), + ([1, 1, 2], ["one", "two", "one"], ["two"], 0, [1, 2], ["one", "one"]), + # GH 5248 Non-Unique Index + ([1, 1, 2], ["one", "two", "one"], "two", 0, [1, 2], ["one", "one"]), + ([1, 1, 2], ["one", "two", "one"], ["one"], 0, [1], ["two"]), + ([1, 1, 2], ["one", "two", "one"], "one", 0, [1], ["two"]), + ], +) +def test_drop_unique_and_non_unique_index( + data, index, axis, drop_labels, expected_data, expected_index +): + ser = Series(data=data, index=index) + result = ser.drop(drop_labels, axis=axis) + expected = Series(data=expected_data, index=expected_index) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "drop_labels, axis, error_type, error_desc", + [ + # single string/tuple-like + ("bc", 0, KeyError, "not found in axis"), + # bad axis + (("a",), 0, KeyError, "not found in axis"), + ("one", "columns", ValueError, "No axis named columns"), + ], +) +def test_drop_exception_raised(drop_labels, axis, error_type, error_desc): + ser = Series(range(3), index=list("abc")) + with pytest.raises(error_type, match=error_desc): + ser.drop(drop_labels, axis=axis) + + +def test_drop_with_ignore_errors(): + # errors='ignore' + ser = Series(range(3), index=list("abc")) + result = ser.drop("bc", errors="ignore") + tm.assert_series_equal(result, ser) + result = ser.drop(["a", "d"], errors="ignore") + expected = ser.iloc[1:] + tm.assert_series_equal(result, expected) + + # GH 8522 + ser = Series([2, 3], index=[True, False]) + assert is_bool_dtype(ser.index) + assert ser.index.dtype == bool + result = ser.drop(True) + expected = Series([3], index=[False]) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize("index", [[1, 2, 3], [1, 1, 3]]) +@pytest.mark.parametrize("drop_labels", [[], [1], [3]]) +def test_drop_empty_list(index, drop_labels): + # GH 21494 + expected_index = [i for i in index if i not in drop_labels] + series = Series(index=index, dtype=object).drop(drop_labels) + expected = Series(index=expected_index, dtype=object) + tm.assert_series_equal(series, expected) + + +@pytest.mark.parametrize( + "data, index, drop_labels", + [ + (None, [1, 2, 3], [1, 4]), + (None, [1, 2, 2], [1, 4]), + ([2, 3], [0, 1], [False, True]), + ], +) +def test_drop_non_empty_list(data, index, drop_labels): + # GH 21494 and GH 16877 + dtype = object if data is None else None + ser = Series(data=data, index=index, dtype=dtype) + with pytest.raises(KeyError, match="not found in axis"): + ser.drop(drop_labels) + + +def test_drop_index_ea_dtype(any_numeric_ea_dtype): + # GH#45860 + df = Series(100, index=Index([1, 2, 2], dtype=any_numeric_ea_dtype)) + idx = Index([df.index[1]]) + result = df.drop(idx) + expected = Series(100, index=Index([1], dtype=any_numeric_ea_dtype)) + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_drop_duplicates.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_drop_duplicates.py new file mode 100644 index 0000000000000000000000000000000000000000..2dbd61530dc41575446c4ffb009449445db24c33 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_drop_duplicates.py @@ -0,0 +1,275 @@ +import numpy as np +import pytest + +import pandas as pd +from pandas import ( + Categorical, + Series, +) +import pandas._testing as tm + + +@pytest.mark.parametrize( + "keep, expected", + [ + ("first", Series([False, False, False, False, True, True, False])), + ("last", Series([False, True, True, False, False, False, False])), + (False, Series([False, True, True, False, True, True, False])), + ], +) +def test_drop_duplicates(any_numpy_dtype, keep, expected): + tc = Series([1, 0, 3, 5, 3, 0, 4], dtype=np.dtype(any_numpy_dtype)) + + if tc.dtype == "bool": + pytest.skip("tested separately in test_drop_duplicates_bool") + + tm.assert_series_equal(tc.duplicated(keep=keep), expected) + tm.assert_series_equal(tc.drop_duplicates(keep=keep), tc[~expected]) + sc = tc.copy() + return_value = sc.drop_duplicates(keep=keep, inplace=True) + assert return_value is None + tm.assert_series_equal(sc, tc[~expected]) + + +@pytest.mark.parametrize( + "keep, expected", + [ + ("first", [False, False, True, True]), + ("last", [True, True, False, False]), + (False, [True, True, True, True]), + ], +) +def test_drop_duplicates_bool(keep, expected): + tc = Series([True, False, True, False]) + expected = Series(expected) + tm.assert_series_equal(tc.duplicated(keep=keep), expected) + tm.assert_series_equal(tc.drop_duplicates(keep=keep), tc[~expected]) + sc = tc.copy() + return_value = sc.drop_duplicates(keep=keep, inplace=True) + tm.assert_series_equal(sc, tc[~expected]) + assert return_value is None + + +@pytest.mark.parametrize("values", [[], list(range(5))]) +def test_drop_duplicates_no_duplicates(any_numpy_dtype, keep, values): + tc = Series(values, dtype=np.dtype(any_numpy_dtype)) + expected = Series([False] * len(tc), dtype="bool") + + if tc.dtype == "bool": + # 0 -> False and 1-> True + # any other value would be duplicated + tc = tc[:2] + expected = expected[:2] + + tm.assert_series_equal(tc.duplicated(keep=keep), expected) + + result_dropped = tc.drop_duplicates(keep=keep) + tm.assert_series_equal(result_dropped, tc) + + # validate shallow copy + assert result_dropped is not tc + + +class TestSeriesDropDuplicates: + @pytest.fixture( + params=["int_", "uint", "float64", "str_", "timedelta64[h]", "datetime64[D]"] + ) + def dtype(self, request): + """ + Fixture that provides different data types for testing. + """ + return request.param + + @pytest.fixture + def cat_series_unused_category(self, dtype, ordered): + """ + Fixture that creates a Categorical Series with some unused categories. + """ + # Test case 1 + cat_array = np.array([1, 2, 3, 4, 5], dtype=np.dtype(dtype)) + + input1 = np.array([1, 2, 3, 3], dtype=np.dtype(dtype)) + cat = Categorical(input1, categories=cat_array, ordered=ordered) + tc1 = Series(cat) + return tc1 + + def test_drop_duplicates_categorical_non_bool(self, cat_series_unused_category): + tc1 = cat_series_unused_category + + expected = Series([False, False, False, True]) + + result = tc1.duplicated() + tm.assert_series_equal(result, expected) + + result = tc1.drop_duplicates() + tm.assert_series_equal(result, tc1[~expected]) + + sc = tc1.copy() + return_value = sc.drop_duplicates(inplace=True) + assert return_value is None + tm.assert_series_equal(sc, tc1[~expected]) + + def test_drop_duplicates_categorical_non_bool_keeplast( + self, cat_series_unused_category + ): + tc1 = cat_series_unused_category + + expected = Series([False, False, True, False]) + + result = tc1.duplicated(keep="last") + tm.assert_series_equal(result, expected) + + result = tc1.drop_duplicates(keep="last") + tm.assert_series_equal(result, tc1[~expected]) + + sc = tc1.copy() + return_value = sc.drop_duplicates(keep="last", inplace=True) + assert return_value is None + tm.assert_series_equal(sc, tc1[~expected]) + + def test_drop_duplicates_categorical_non_bool_keepfalse( + self, cat_series_unused_category + ): + tc1 = cat_series_unused_category + + expected = Series([False, False, True, True]) + + result = tc1.duplicated(keep=False) + tm.assert_series_equal(result, expected) + + result = tc1.drop_duplicates(keep=False) + tm.assert_series_equal(result, tc1[~expected]) + + sc = tc1.copy() + return_value = sc.drop_duplicates(keep=False, inplace=True) + assert return_value is None + tm.assert_series_equal(sc, tc1[~expected]) + + @pytest.fixture + def cat_series(self, dtype, ordered): + """ + Fixture that creates a Categorical Series with no unused categories. + """ + cat_array = np.array([1, 2, 3, 4, 5], dtype=np.dtype(dtype)) + + input2 = np.array([1, 2, 3, 5, 3, 2, 4], dtype=np.dtype(dtype)) + cat = Categorical(input2, categories=cat_array, ordered=ordered) + tc2 = Series(cat) + return tc2 + + def test_drop_duplicates_categorical_non_bool2(self, cat_series): + tc2 = cat_series + + expected = Series([False, False, False, False, True, True, False]) + + result = tc2.duplicated() + tm.assert_series_equal(result, expected) + + result = tc2.drop_duplicates() + tm.assert_series_equal(result, tc2[~expected]) + + sc = tc2.copy() + return_value = sc.drop_duplicates(inplace=True) + assert return_value is None + tm.assert_series_equal(sc, tc2[~expected]) + + def test_drop_duplicates_categorical_non_bool2_keeplast(self, cat_series): + tc2 = cat_series + + expected = Series([False, True, True, False, False, False, False]) + + result = tc2.duplicated(keep="last") + tm.assert_series_equal(result, expected) + + result = tc2.drop_duplicates(keep="last") + tm.assert_series_equal(result, tc2[~expected]) + + sc = tc2.copy() + return_value = sc.drop_duplicates(keep="last", inplace=True) + assert return_value is None + tm.assert_series_equal(sc, tc2[~expected]) + + def test_drop_duplicates_categorical_non_bool2_keepfalse(self, cat_series): + tc2 = cat_series + + expected = Series([False, True, True, False, True, True, False]) + + result = tc2.duplicated(keep=False) + tm.assert_series_equal(result, expected) + + result = tc2.drop_duplicates(keep=False) + tm.assert_series_equal(result, tc2[~expected]) + + sc = tc2.copy() + return_value = sc.drop_duplicates(keep=False, inplace=True) + assert return_value is None + tm.assert_series_equal(sc, tc2[~expected]) + + def test_drop_duplicates_categorical_bool(self, ordered): + tc = Series( + Categorical( + [True, False, True, False], categories=[True, False], ordered=ordered + ) + ) + + expected = Series([False, False, True, True]) + tm.assert_series_equal(tc.duplicated(), expected) + tm.assert_series_equal(tc.drop_duplicates(), tc[~expected]) + sc = tc.copy() + return_value = sc.drop_duplicates(inplace=True) + assert return_value is None + tm.assert_series_equal(sc, tc[~expected]) + + expected = Series([True, True, False, False]) + tm.assert_series_equal(tc.duplicated(keep="last"), expected) + tm.assert_series_equal(tc.drop_duplicates(keep="last"), tc[~expected]) + sc = tc.copy() + return_value = sc.drop_duplicates(keep="last", inplace=True) + assert return_value is None + tm.assert_series_equal(sc, tc[~expected]) + + expected = Series([True, True, True, True]) + tm.assert_series_equal(tc.duplicated(keep=False), expected) + tm.assert_series_equal(tc.drop_duplicates(keep=False), tc[~expected]) + sc = tc.copy() + return_value = sc.drop_duplicates(keep=False, inplace=True) + assert return_value is None + tm.assert_series_equal(sc, tc[~expected]) + + def test_drop_duplicates_categorical_bool_na(self, nulls_fixture): + # GH#44351 + ser = Series( + Categorical( + [True, False, True, False, nulls_fixture], + categories=[True, False], + ordered=True, + ) + ) + result = ser.drop_duplicates() + expected = Series( + Categorical([True, False, np.nan], categories=[True, False], ordered=True), + index=[0, 1, 4], + ) + tm.assert_series_equal(result, expected) + + def test_drop_duplicates_ignore_index(self): + # GH#48304 + ser = Series([1, 2, 2, 3]) + result = ser.drop_duplicates(ignore_index=True) + expected = Series([1, 2, 3]) + tm.assert_series_equal(result, expected) + + def test_duplicated_arrow_dtype(self): + pytest.importorskip("pyarrow") + ser = Series([True, False, None, False], dtype="bool[pyarrow]") + result = ser.drop_duplicates() + expected = Series([True, False, None], dtype="bool[pyarrow]") + tm.assert_series_equal(result, expected) + + def test_drop_duplicates_arrow_strings(self): + # GH#54904 + pa = pytest.importorskip("pyarrow") + ser = Series(["a", "a"], dtype=pd.ArrowDtype(pa.string())) + result = ser.drop_duplicates() + expecetd = Series(["a"], dtype=pd.ArrowDtype(pa.string())) + tm.assert_series_equal(result, expecetd) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_dropna.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_dropna.py new file mode 100644 index 0000000000000000000000000000000000000000..d03fcac24003e99b724a2e7ac43b66d3d9b51bcf --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_dropna.py @@ -0,0 +1,117 @@ +import numpy as np +import pytest + +from pandas import ( + DatetimeIndex, + IntervalIndex, + NaT, + Period, + Series, + Timestamp, +) +import pandas._testing as tm + + +class TestDropna: + def test_dropna_empty(self): + ser = Series([], dtype=object) + + assert len(ser.dropna()) == 0 + return_value = ser.dropna(inplace=True) + assert return_value is None + assert len(ser) == 0 + + # invalid axis + msg = "No axis named 1 for object type Series" + with pytest.raises(ValueError, match=msg): + ser.dropna(axis=1) + + def test_dropna_preserve_name(self, datetime_series): + datetime_series[:5] = np.nan + result = datetime_series.dropna() + assert result.name == datetime_series.name + name = datetime_series.name + ts = datetime_series.copy() + return_value = ts.dropna(inplace=True) + assert return_value is None + assert ts.name == name + + def test_dropna_no_nan(self): + for ser in [ + Series([1, 2, 3], name="x"), + Series([False, True, False], name="x"), + ]: + result = ser.dropna() + tm.assert_series_equal(result, ser) + assert result is not ser + + s2 = ser.copy() + return_value = s2.dropna(inplace=True) + assert return_value is None + tm.assert_series_equal(s2, ser) + + def test_dropna_intervals(self): + ser = Series( + [np.nan, 1, 2, 3], + IntervalIndex.from_arrays([np.nan, 0, 1, 2], [np.nan, 1, 2, 3]), + ) + + result = ser.dropna() + expected = ser.iloc[1:] + tm.assert_series_equal(result, expected) + + def test_dropna_period_dtype(self): + # GH#13737 + ser = Series([Period("2011-01", freq="M"), Period("NaT", freq="M")]) + result = ser.dropna() + expected = Series([Period("2011-01", freq="M")]) + + tm.assert_series_equal(result, expected) + + def test_datetime64_tz_dropna(self, unit): + # DatetimeLikeBlock + ser = Series( + [ + Timestamp("2011-01-01 10:00"), + NaT, + Timestamp("2011-01-03 10:00"), + NaT, + ], + dtype=f"M8[{unit}]", + ) + result = ser.dropna() + expected = Series( + [Timestamp("2011-01-01 10:00"), Timestamp("2011-01-03 10:00")], + index=[0, 2], + dtype=f"M8[{unit}]", + ) + tm.assert_series_equal(result, expected) + + # DatetimeTZBlock + idx = DatetimeIndex( + ["2011-01-01 10:00", NaT, "2011-01-03 10:00", NaT], tz="Asia/Tokyo" + ).as_unit(unit) + ser = Series(idx) + assert ser.dtype == f"datetime64[{unit}, Asia/Tokyo]" + result = ser.dropna() + expected = Series( + [ + Timestamp("2011-01-01 10:00", tz="Asia/Tokyo"), + Timestamp("2011-01-03 10:00", tz="Asia/Tokyo"), + ], + index=[0, 2], + dtype=f"datetime64[{unit}, Asia/Tokyo]", + ) + assert result.dtype == f"datetime64[{unit}, Asia/Tokyo]" + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("val", [1, 1.5]) + def test_dropna_ignore_index(self, val): + # GH#31725 + ser = Series([1, 2, val], index=[3, 2, 1]) + result = ser.dropna(ignore_index=True) + expected = Series([1, 2, val]) + tm.assert_series_equal(result, expected) + + ser.dropna(ignore_index=True, inplace=True) + tm.assert_series_equal(ser, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_dtypes.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_dtypes.py new file mode 100644 index 0000000000000000000000000000000000000000..82260bc2a65b9f7e387532b380b9ce013d29cbb7 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_dtypes.py @@ -0,0 +1,7 @@ +import numpy as np + + +class TestSeriesDtypes: + def test_dtype(self, datetime_series): + assert datetime_series.dtype == np.dtype("float64") + assert datetime_series.dtypes == np.dtype("float64") diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_duplicated.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_duplicated.py new file mode 100644 index 0000000000000000000000000000000000000000..45aa9b1c3f63e93f02a07fcad1fcc7f31b6bb8ee --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_duplicated.py @@ -0,0 +1,79 @@ +import numpy as np +import pytest + +from pandas import ( + NA, + Categorical, + Series, +) +import pandas._testing as tm + + +@pytest.mark.parametrize( + "keep, expected", + [ + ("first", [False, False, True, False, True]), + ("last", [True, True, False, False, False]), + (False, [True, True, True, False, True]), + ], +) +def test_duplicated_keep(keep, expected): + ser = Series(["a", "b", "b", "c", "a"], name="name") + + result = ser.duplicated(keep=keep) + expected = Series(expected, name="name") + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "keep, expected", + [ + ("first", [False, False, True, False, True]), + ("last", [True, True, False, False, False]), + (False, [True, True, True, False, True]), + ], +) +def test_duplicated_nan_none(keep, expected): + ser = Series([np.nan, 3, 3, None, np.nan], dtype=object) + + result = ser.duplicated(keep=keep) + expected = Series(expected) + tm.assert_series_equal(result, expected) + + +def test_duplicated_categorical_bool_na(nulls_fixture): + # GH#44351 + ser = Series( + Categorical( + [True, False, True, False, nulls_fixture], + categories=[True, False], + ordered=True, + ) + ) + result = ser.duplicated() + expected = Series([False, False, True, True, False]) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "keep, vals", + [ + ("last", [True, True, False]), + ("first", [False, True, True]), + (False, [True, True, True]), + ], +) +def test_duplicated_mask(keep, vals): + # GH#48150 + ser = Series([1, 2, NA, NA, NA], dtype="Int64") + result = ser.duplicated(keep=keep) + expected = Series([False, False, *vals]) + tm.assert_series_equal(result, expected) + + +def test_duplicated_mask_no_duplicated_na(keep): + # GH#48150 + ser = Series([1, 2, NA], dtype="Int64") + result = ser.duplicated(keep=keep) + expected = Series([False, False, False]) + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_equals.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_equals.py new file mode 100644 index 0000000000000000000000000000000000000000..bc6ed35680ea6fcde925f2668397fb81cfadf52c --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_equals.py @@ -0,0 +1,133 @@ +import copy + +import numpy as np +import pytest + +from pandas._libs.missing import is_matching_na + +from pandas.core.dtypes.common import is_float + +from pandas import ( + Index, + MultiIndex, + Series, +) + + +@pytest.mark.parametrize( + "arr, idx", + [ + ([1, 2, 3, 4], [0, 2, 1, 3]), + ([1, np.nan, 3, np.nan], [0, 2, 1, 3]), + ( + [1, np.nan, 3, np.nan], + MultiIndex.from_tuples([(0, "a"), (1, "b"), (2, "c"), (3, "c")]), + ), + ], +) +def test_equals(arr, idx): + s1 = Series(arr, index=idx) + s2 = s1.copy() + assert s1.equals(s2) + + s1[1] = 9 + assert not s1.equals(s2) + + +@pytest.mark.parametrize( + "val", [1, 1.1, 1 + 1j, True, "abc", [1, 2], (1, 2), {1, 2}, {"a": 1}, None] +) +def test_equals_list_array(val): + # GH20676 Verify equals operator for list of Numpy arrays + arr = np.array([1, 2]) + s1 = Series([arr, arr]) + s2 = s1.copy() + assert s1.equals(s2) + + s1[1] = val + assert not s1.equals(s2) + + +def test_equals_false_negative(): + # GH8437 Verify false negative behavior of equals function for dtype object + arr = [False, np.nan] + s1 = Series(arr) + s2 = s1.copy() + s3 = Series(index=range(2), dtype=object) + s4 = s3.copy() + s5 = s3.copy() + s6 = s3.copy() + + s3[:-1] = s4[:-1] = s5[0] = s6[0] = False + assert s1.equals(s1) + assert s1.equals(s2) + assert s1.equals(s3) + assert s1.equals(s4) + assert s1.equals(s5) + assert s5.equals(s6) + + +def test_equals_matching_nas(): + # matching but not identical NAs + left = Series([np.datetime64("NaT", "ns")], dtype=object) + right = Series([np.datetime64("NaT", "ns")], dtype=object) + assert left.equals(right) + assert Index(left).equals(Index(right)) + assert left.array.equals(right.array) + + left = Series([np.timedelta64("NaT", "ns")], dtype=object) + right = Series([np.timedelta64("NaT", "ns")], dtype=object) + assert left.equals(right) + assert Index(left).equals(Index(right)) + assert left.array.equals(right.array) + + left = Series([np.float64("NaN")], dtype=object) + right = Series([np.float64("NaN")], dtype=object) + assert left.equals(right) + assert Index(left, dtype=left.dtype).equals(Index(right, dtype=right.dtype)) + assert left.array.equals(right.array) + + +def test_equals_mismatched_nas(nulls_fixture, nulls_fixture2): + # GH#39650 + left = nulls_fixture + right = nulls_fixture2 + if hasattr(right, "copy"): + right = right.copy() + else: + right = copy.copy(right) + + ser = Series([left], dtype=object) + ser2 = Series([right], dtype=object) + + if is_matching_na(left, right): + assert ser.equals(ser2) + elif (left is None and is_float(right)) or (right is None and is_float(left)): + assert ser.equals(ser2) + else: + assert not ser.equals(ser2) + + +def test_equals_none_vs_nan(): + # GH#39650 + ser = Series([1, None], dtype=object) + ser2 = Series([1, np.nan], dtype=object) + + assert ser.equals(ser2) + assert Index(ser, dtype=ser.dtype).equals(Index(ser2, dtype=ser2.dtype)) + assert ser.array.equals(ser2.array) + + +def test_equals_None_vs_float(): + # GH#44190 + left = Series([-np.inf, np.nan, -1.0, 0.0, 1.0, 10 / 3, np.inf], dtype=object) + right = Series([None] * len(left)) + + # these series were found to be equal due to a bug, check that they are correctly + # found to not equal + assert not left.equals(right) + assert not right.equals(left) + assert not left.to_frame().equals(right.to_frame()) + assert not right.to_frame().equals(left.to_frame()) + assert not Index(left, dtype="object").equals(Index(right, dtype="object")) + assert not Index(right, dtype="object").equals(Index(left, dtype="object")) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_explode.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_explode.py new file mode 100644 index 0000000000000000000000000000000000000000..9c08f47c0d6783a9e606335f3289f43f96976844 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_explode.py @@ -0,0 +1,185 @@ +import numpy as np +import pytest + +import pandas as pd +import pandas._testing as tm + + +def test_basic(): + s = pd.Series([[0, 1, 2], np.nan, [], (3, 4)], index=list("abcd"), name="foo") + result = s.explode() + expected = pd.Series( + [0, 1, 2, np.nan, np.nan, 3, 4], index=list("aaabcdd"), dtype=object, name="foo" + ) + tm.assert_series_equal(result, expected) + + +def test_mixed_type(): + s = pd.Series( + [[0, 1, 2], np.nan, None, np.array([]), pd.Series(["a", "b"])], name="foo" + ) + result = s.explode() + expected = pd.Series( + [0, 1, 2, np.nan, None, np.nan, "a", "b"], + index=[0, 0, 0, 1, 2, 3, 4, 4], + dtype=object, + name="foo", + ) + tm.assert_series_equal(result, expected) + + +def test_empty(): + s = pd.Series(dtype=object) + result = s.explode() + expected = s.copy() + tm.assert_series_equal(result, expected) + + +def test_nested_lists(): + s = pd.Series([[[1, 2, 3]], [1, 2], 1]) + result = s.explode() + expected = pd.Series([[1, 2, 3], 1, 2, 1], index=[0, 1, 1, 2]) + tm.assert_series_equal(result, expected) + + +def test_multi_index(): + s = pd.Series( + [[0, 1, 2], np.nan, [], (3, 4)], + name="foo", + index=pd.MultiIndex.from_product([list("ab"), range(2)], names=["foo", "bar"]), + ) + result = s.explode() + index = pd.MultiIndex.from_tuples( + [("a", 0), ("a", 0), ("a", 0), ("a", 1), ("b", 0), ("b", 1), ("b", 1)], + names=["foo", "bar"], + ) + expected = pd.Series( + [0, 1, 2, np.nan, np.nan, 3, 4], index=index, dtype=object, name="foo" + ) + tm.assert_series_equal(result, expected) + + +def test_large(): + s = pd.Series([range(256)]).explode() + result = s.explode() + tm.assert_series_equal(result, s) + + +def test_invert_array(): + df = pd.DataFrame({"a": pd.date_range("20190101", periods=3, tz="UTC")}) + + listify = df.apply(lambda x: x.array, axis=1) + result = listify.explode() + tm.assert_series_equal(result, df["a"].rename()) + + +@pytest.mark.parametrize( + "data", [[1, 2, 3], pd.date_range("2019", periods=3, tz="UTC")] +) +def test_non_object_dtype(data): + ser = pd.Series(data) + result = ser.explode() + tm.assert_series_equal(result, ser) + + +def test_typical_usecase(): + df = pd.DataFrame( + [{"var1": "a,b,c", "var2": 1}, {"var1": "d,e,f", "var2": 2}], + columns=["var1", "var2"], + ) + exploded = df.var1.str.split(",").explode() + result = df[["var2"]].join(exploded) + expected = pd.DataFrame( + {"var2": [1, 1, 1, 2, 2, 2], "var1": list("abcdef")}, + columns=["var2", "var1"], + index=[0, 0, 0, 1, 1, 1], + ) + tm.assert_frame_equal(result, expected) + + +def test_nested_EA(): + # a nested EA array + s = pd.Series( + [ + pd.date_range("20170101", periods=3, tz="UTC"), + pd.date_range("20170104", periods=3, tz="UTC"), + ] + ) + result = s.explode() + expected = pd.Series( + pd.date_range("20170101", periods=6, tz="UTC"), index=[0, 0, 0, 1, 1, 1] + ) + tm.assert_series_equal(result, expected) + + +def test_duplicate_index(): + # GH 28005 + s = pd.Series([[1, 2], [3, 4]], index=[0, 0]) + result = s.explode() + expected = pd.Series([1, 2, 3, 4], index=[0, 0, 0, 0], dtype=object) + tm.assert_series_equal(result, expected) + + +def test_ignore_index(): + # GH 34932 + s = pd.Series([[1, 2], [3, 4]]) + result = s.explode(ignore_index=True) + expected = pd.Series([1, 2, 3, 4], index=[0, 1, 2, 3], dtype=object) + tm.assert_series_equal(result, expected) + + +def test_explode_sets(): + # https://github.com/pandas-dev/pandas/issues/35614 + s = pd.Series([{"a", "b", "c"}], index=[1]) + result = s.explode().sort_values() + expected = pd.Series(["a", "b", "c"], index=[1, 1, 1]) + tm.assert_series_equal(result, expected) + + +def test_explode_scalars_can_ignore_index(): + # https://github.com/pandas-dev/pandas/issues/40487 + s = pd.Series([1, 2, 3], index=["a", "b", "c"]) + result = s.explode(ignore_index=True) + expected = pd.Series([1, 2, 3]) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize("ignore_index", [True, False]) +@pytest.mark.parametrize("list_type", ["list_", "large_list"]) +def test_explode_pyarrow_list_type(ignore_index, list_type): + # GH 53602, 61091 + pa = pytest.importorskip("pyarrow") + + data = [ + [None, None], + [1], + [], + [2, 3], + None, + ] + ser = pd.Series(data, dtype=pd.ArrowDtype(getattr(pa, list_type)(pa.int64()))) + result = ser.explode(ignore_index=ignore_index) + expected = pd.Series( + data=[None, None, 1, None, 2, 3, None], + index=None if ignore_index else [0, 0, 1, 2, 3, 3, 4], + dtype=pd.ArrowDtype(pa.int64()), + ) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize("ignore_index", [True, False]) +def test_explode_pyarrow_non_list_type(ignore_index): + pa = pytest.importorskip("pyarrow") + data = [1, 2, 3] + ser = pd.Series(data, dtype=pd.ArrowDtype(pa.int64())) + result = ser.explode(ignore_index=ignore_index) + expected = pd.Series([1, 2, 3], dtype="int64[pyarrow]", index=[0, 1, 2]) + tm.assert_series_equal(result, expected) + + +def test_str_dtype(): + # https://github.com/pandas-dev/pandas/pull/61623 + ser = pd.Series(["x", "y"], dtype="str") + result = ser.explode() + assert result is not ser + tm.assert_series_equal(result, ser) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_fillna.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_fillna.py new file mode 100644 index 0000000000000000000000000000000000000000..557fe280476f5974bc2d74787530eab2571c7b09 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_fillna.py @@ -0,0 +1,1037 @@ +from datetime import ( + datetime, + timedelta, + timezone, +) + +import numpy as np +import pytest + +from pandas import ( + Categorical, + DataFrame, + DatetimeIndex, + NaT, + Period, + Series, + Timedelta, + Timestamp, + date_range, + isna, + timedelta_range, +) +import pandas._testing as tm +from pandas.core.arrays import period_array + + +class TestSeriesFillNA: + def test_fillna_nat(self): + series = Series([0, 1, 2, NaT._value], dtype="M8[ns]") + + filled = series.ffill() + filled2 = series.fillna(value=series.values[2]) + + expected = series.copy() + expected.iloc[3] = expected.iloc[2] + + tm.assert_series_equal(filled, expected) + tm.assert_series_equal(filled2, expected) + + df = DataFrame({"A": series}) + filled = df.ffill() + filled2 = df.fillna(value=series.values[2]) + expected = DataFrame({"A": expected}) + tm.assert_frame_equal(filled, expected) + tm.assert_frame_equal(filled2, expected) + + series = Series([NaT._value, 0, 1, 2], dtype="M8[ns]") + + filled = series.bfill() + filled2 = series.fillna(value=series[1]) + + expected = series.copy() + expected[0] = expected[1] + + tm.assert_series_equal(filled, expected) + tm.assert_series_equal(filled2, expected) + + df = DataFrame({"A": series}) + filled = df.bfill() + filled2 = df.fillna(value=series[1]) + expected = DataFrame({"A": expected}) + tm.assert_frame_equal(filled, expected) + tm.assert_frame_equal(filled2, expected) + + def test_fillna(self): + ts = Series( + [0.0, 1.0, 2.0, 3.0, 4.0], index=date_range("2020-01-01", periods=5) + ) + + tm.assert_series_equal(ts, ts.ffill()) + + ts.iloc[2] = np.nan + + exp = Series([0.0, 1.0, 1.0, 3.0, 4.0], index=ts.index) + tm.assert_series_equal(ts.ffill(), exp) + + exp = Series([0.0, 1.0, 3.0, 3.0, 4.0], index=ts.index) + tm.assert_series_equal(ts.bfill(), exp) + + exp = Series([0.0, 1.0, 5.0, 3.0, 4.0], index=ts.index) + tm.assert_series_equal(ts.fillna(value=5), exp) + + def test_fillna_nonscalar(self): + # GH#5703 + s1 = Series([np.nan]) + s2 = Series([1]) + result = s1.fillna(s2) + expected = Series([1.0]) + tm.assert_series_equal(result, expected) + result = s1.fillna({}) + tm.assert_series_equal(result, s1) + result = s1.fillna(Series((), dtype=object)) + tm.assert_series_equal(result, s1) + result = s2.fillna(s1) + tm.assert_series_equal(result, s2) + result = s1.fillna({0: 1}) + tm.assert_series_equal(result, expected) + result = s1.fillna({1: 1}) + tm.assert_series_equal(result, Series([np.nan])) + result = s1.fillna({0: 1, 1: 1}) + tm.assert_series_equal(result, expected) + result = s1.fillna(Series({0: 1, 1: 1})) + tm.assert_series_equal(result, expected) + result = s1.fillna(Series({0: 1, 1: 1}, index=[4, 5])) + tm.assert_series_equal(result, s1) + + def test_fillna_aligns(self): + s1 = Series([0, 1, 2], list("abc")) + s2 = Series([0, np.nan, 2], list("bac")) + result = s2.fillna(s1) + expected = Series([0, 0, 2.0], list("bac")) + tm.assert_series_equal(result, expected) + + def test_fillna_limit(self): + ser = Series(np.nan, index=[0, 1, 2]) + result = ser.fillna(999, limit=1) + expected = Series([999, np.nan, np.nan], index=[0, 1, 2]) + tm.assert_series_equal(result, expected) + + result = ser.fillna(999, limit=2) + expected = Series([999, 999, np.nan], index=[0, 1, 2]) + tm.assert_series_equal(result, expected) + + def test_fillna_dont_cast_strings(self): + # GH#9043 + # make sure a string representation of int/float values can be filled + # correctly without raising errors or being converted + vals = ["0", "1.5", "-0.3"] + for val in vals: + ser = Series([0, 1, np.nan, np.nan, 4], dtype="float64") + result = ser.fillna(val) + expected = Series([0, 1, val, val, 4], dtype="object") + tm.assert_series_equal(result, expected) + + def test_fillna_consistency(self): + # GH#16402 + # fillna with a tz aware to a tz-naive, should result in object + + ser = Series([Timestamp("20130101"), NaT]) + + result = ser.fillna(Timestamp("20130101", tz="US/Eastern")) + expected = Series( + [Timestamp("20130101"), Timestamp("2013-01-01", tz="US/Eastern")], + dtype="object", + ) + tm.assert_series_equal(result, expected) + + result = ser.where([True, False], Timestamp("20130101", tz="US/Eastern")) + tm.assert_series_equal(result, expected) + + result = ser.where([True, False], Timestamp("20130101", tz="US/Eastern")) + tm.assert_series_equal(result, expected) + + # with a non-datetime + result = ser.fillna("foo") + expected = Series([Timestamp("20130101"), "foo"]) + tm.assert_series_equal(result, expected) + + # assignment + ser2 = ser.copy() + with pytest.raises(TypeError, match="Invalid value"): + ser2[1] = "foo" + + def test_timedelta_fillna(self, frame_or_series, unit): + # GH#3371 + ser = Series( + [ + Timestamp("20130101"), + Timestamp("20130101"), + Timestamp("20130102"), + Timestamp("20130103 9:01:01"), + ], + dtype=f"M8[{unit}]", + ) + td = ser.diff() + obj = frame_or_series(td).copy() + + # reg fillna + result = obj.fillna(Timedelta(seconds=0)) + expected = Series( + [ + timedelta(0), + timedelta(0), + timedelta(1), + timedelta(days=1, seconds=9 * 3600 + 60 + 1), + ], + dtype=f"m8[{unit}]", + ) + expected = frame_or_series(expected) + tm.assert_equal(result, expected) + + # GH#45746 pre-1.? ints were interpreted as seconds. then that was + # deprecated and changed to raise. In 2.0 it casts to common dtype, + # consistent with every other dtype's behavior + res = obj.fillna(1) + expected = obj.astype(object).fillna(1) + tm.assert_equal(res, expected) + + result = obj.fillna(Timedelta(seconds=1)) + expected = Series( + [ + timedelta(seconds=1), + timedelta(0), + timedelta(1), + timedelta(days=1, seconds=9 * 3600 + 60 + 1), + ], + dtype=f"m8[{unit}]", + ) + expected = frame_or_series(expected) + tm.assert_equal(result, expected) + + result = obj.fillna(timedelta(days=1, seconds=1)) + expected = Series( + [ + timedelta(days=1, seconds=1), + timedelta(0), + timedelta(1), + timedelta(days=1, seconds=9 * 3600 + 60 + 1), + ], + dtype=f"m8[{unit}]", + ) + expected = frame_or_series(expected) + tm.assert_equal(result, expected) + + result = obj.fillna(np.timedelta64(10**9, "ns")) + expected = Series( + [ + timedelta(seconds=1), + timedelta(0), + timedelta(1), + timedelta(days=1, seconds=9 * 3600 + 60 + 1), + ], + dtype=f"m8[{unit}]", + ) + expected = frame_or_series(expected) + tm.assert_equal(result, expected) + + result = obj.fillna(NaT) + expected = Series( + [ + NaT, + timedelta(0), + timedelta(1), + timedelta(days=1, seconds=9 * 3600 + 60 + 1), + ], + dtype=f"m8[{unit}]", + ) + expected = frame_or_series(expected) + tm.assert_equal(result, expected) + + # ffill + td[2] = np.nan + obj = frame_or_series(td).copy() + result = obj.ffill() + expected = td.fillna(Timedelta(seconds=0)) + expected[0] = np.nan + expected = frame_or_series(expected) + + tm.assert_equal(result, expected) + + # bfill + td[2] = np.nan + obj = frame_or_series(td) + result = obj.bfill() + expected = td.fillna(Timedelta(seconds=0)) + expected[2] = timedelta(days=1, seconds=9 * 3600 + 60 + 1) + expected = frame_or_series(expected) + tm.assert_equal(result, expected) + + def test_datetime64_fillna(self): + ser = Series( + [ + Timestamp("20130101"), + Timestamp("20130101"), + Timestamp("20130102"), + Timestamp("20130103 9:01:01"), + ] + ) + ser[2] = np.nan + + # ffill + result = ser.ffill() + expected = Series( + [ + Timestamp("20130101"), + Timestamp("20130101"), + Timestamp("20130101"), + Timestamp("20130103 9:01:01"), + ] + ) + tm.assert_series_equal(result, expected) + + # bfill + result = ser.bfill() + expected = Series( + [ + Timestamp("20130101"), + Timestamp("20130101"), + Timestamp("20130103 9:01:01"), + Timestamp("20130103 9:01:01"), + ] + ) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "scalar", + [ + False, + True, + ], + ) + @pytest.mark.parametrize("tz", [None, "UTC"]) + def test_datetime64_fillna_mismatched_reso_no_rounding(self, tz, scalar): + # GH#56410 + dti = date_range("2016-01-01", periods=3, unit="s", tz=tz) + item = Timestamp("2016-02-03 04:05:06.789", tz=tz).as_unit("ms") + vec = date_range(item, periods=3, unit="ms") + + exp_dtype = "M8[ms]" if tz is None else "M8[ms, UTC]" + expected = Series([item, dti[1], dti[2]], dtype=exp_dtype) + + ser = Series(dti) + ser[0] = NaT + ser2 = ser.copy() + + res = ser.fillna(item) + res2 = ser2.fillna(Series(vec)) + + if scalar: + tm.assert_series_equal(res, expected) + else: + tm.assert_series_equal(res2, expected) + + @pytest.mark.parametrize( + "scalar", + [ + False, + True, + ], + ) + def test_timedelta64_fillna_mismatched_reso_no_rounding(self, scalar): + # GH#56410 + ts = Timestamp("1970-01-01").as_unit("s") + tdi = date_range("2016-01-01", periods=3, unit="s") - ts + item = Timestamp("2016-02-03 04:05:06.789").as_unit("ms") - ts + vec = timedelta_range(item, periods=3, unit="ms") + + expected = Series([item, tdi[1], tdi[2]], dtype="m8[ms]") + + ser = Series(tdi) + ser[0] = NaT + ser2 = ser.copy() + + res = ser.fillna(item) + res2 = ser2.fillna(Series(vec)) + + if scalar: + tm.assert_series_equal(res, expected) + else: + tm.assert_series_equal(res2, expected) + + def test_datetime64_fillna_backfill(self): + # GH#6587 + # make sure that we are treating as integer when filling + ser = Series([NaT, NaT, "2013-08-05 15:30:00.000001"], dtype="M8[ns]") + + expected = Series( + [ + "2013-08-05 15:30:00.000001", + "2013-08-05 15:30:00.000001", + "2013-08-05 15:30:00.000001", + ], + dtype="M8[ns]", + ) + result = ser.bfill() + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("tz", ["US/Eastern", "Asia/Tokyo"]) + def test_datetime64_tz_fillna(self, tz, unit): + # DatetimeLikeBlock + ser = Series( + [ + Timestamp("2011-01-01 10:00"), + NaT, + Timestamp("2011-01-03 10:00"), + NaT, + ], + dtype=f"M8[{unit}]", + ) + null_loc = Series([False, True, False, True]) + + result = ser.fillna(Timestamp("2011-01-02 10:00").as_unit("s")) + expected = Series( + [ + Timestamp("2011-01-01 10:00"), + Timestamp("2011-01-02 10:00"), + Timestamp("2011-01-03 10:00"), + Timestamp("2011-01-02 10:00"), + ], + dtype=f"M8[{unit}]", + ) + tm.assert_series_equal(expected, result) + # check s is not changed + tm.assert_series_equal(isna(ser), null_loc) + + result = ser.fillna(Timestamp("2011-01-02 10:00", tz=tz).as_unit("s")) + expected = Series( + [ + Timestamp("2011-01-01 10:00").as_unit("s"), + Timestamp("2011-01-02 10:00", tz=tz).as_unit("s"), + Timestamp("2011-01-03 10:00").as_unit("s"), + Timestamp("2011-01-02 10:00", tz=tz).as_unit("s"), + ], + ) + tm.assert_series_equal(expected, result) + tm.assert_series_equal(isna(ser), null_loc) + + result = ser.fillna("AAA") + expected = Series( + [ + Timestamp("2011-01-01 10:00").as_unit("s"), + "AAA", + Timestamp("2011-01-03 10:00").as_unit("s"), + "AAA", + ], + dtype=object, + ) + tm.assert_series_equal(expected, result) + tm.assert_series_equal(isna(ser), null_loc) + + result = ser.fillna( + { + 1: Timestamp("2011-01-02 10:00", tz=tz).as_unit("s"), + 3: Timestamp("2011-01-04 10:00").as_unit("s"), + } + ) + expected = Series( + [ + Timestamp("2011-01-01 10:00").as_unit("s"), + Timestamp("2011-01-02 10:00", tz=tz).as_unit("s"), + Timestamp("2011-01-03 10:00").as_unit("s"), + Timestamp("2011-01-04 10:00").as_unit("s"), + ] + ) + tm.assert_series_equal(expected, result) + tm.assert_series_equal(isna(ser), null_loc) + + result = ser.fillna( + { + 1: Timestamp("2011-01-02 10:00").as_unit("s"), + 3: Timestamp("2011-01-04 10:00").as_unit("s"), + } + ) + expected = Series( + [ + Timestamp("2011-01-01 10:00"), + Timestamp("2011-01-02 10:00"), + Timestamp("2011-01-03 10:00"), + Timestamp("2011-01-04 10:00"), + ], + dtype=f"M8[{unit}]", + ) + tm.assert_series_equal(expected, result) + tm.assert_series_equal(isna(ser), null_loc) + + # DatetimeTZBlock + idx = DatetimeIndex( + ["2011-01-01 10:00", NaT, "2011-01-03 10:00", NaT], tz=tz + ).as_unit(unit) + ser = Series(idx) + assert ser.dtype == f"datetime64[{unit}, {tz}]" + tm.assert_series_equal(isna(ser), null_loc) + + result = ser.fillna(Timestamp("2011-01-02 10:00").as_unit("s")) + expected = Series( + [ + Timestamp("2011-01-01 10:00", tz=tz).as_unit("s"), + Timestamp("2011-01-02 10:00").as_unit("s"), + Timestamp("2011-01-03 10:00", tz=tz).as_unit("s"), + Timestamp("2011-01-02 10:00").as_unit("s"), + ] + ) + tm.assert_series_equal(expected, result) + tm.assert_series_equal(isna(ser), null_loc) + + result = ser.fillna(Timestamp("2011-01-02 10:00", tz=tz).as_unit("s")) + idx = DatetimeIndex( + [ + "2011-01-01 10:00", + "2011-01-02 10:00", + "2011-01-03 10:00", + "2011-01-02 10:00", + ], + tz=tz, + ).as_unit(unit) + expected = Series(idx) + tm.assert_series_equal(expected, result) + tm.assert_series_equal(isna(ser), null_loc) + + result = ser.fillna(Timestamp("2011-01-02 10:00", tz=tz).to_pydatetime()) + idx = DatetimeIndex( + [ + "2011-01-01 10:00", + "2011-01-02 10:00", + "2011-01-03 10:00", + "2011-01-02 10:00", + ], + tz=tz, + ).as_unit(unit) + expected = Series(idx) + tm.assert_series_equal(expected, result) + tm.assert_series_equal(isna(ser), null_loc) + + result = ser.fillna("AAA") + expected = Series( + [ + Timestamp("2011-01-01 10:00", tz=tz).as_unit("s"), + "AAA", + Timestamp("2011-01-03 10:00", tz=tz).as_unit("s"), + "AAA", + ], + dtype=object, + ) + tm.assert_series_equal(expected, result) + tm.assert_series_equal(isna(ser), null_loc) + + result = ser.fillna( + { + 1: Timestamp("2011-01-02 10:00", tz=tz).as_unit("s"), + 3: Timestamp("2011-01-04 10:00").as_unit("s"), + } + ) + expected = Series( + [ + Timestamp("2011-01-01 10:00", tz=tz).as_unit("s"), + Timestamp("2011-01-02 10:00", tz=tz).as_unit("s"), + Timestamp("2011-01-03 10:00", tz=tz).as_unit("s"), + Timestamp("2011-01-04 10:00").as_unit("s"), + ] + ) + tm.assert_series_equal(expected, result) + tm.assert_series_equal(isna(ser), null_loc) + + result = ser.fillna( + { + 1: Timestamp("2011-01-02 10:00", tz=tz).as_unit("s"), + 3: Timestamp("2011-01-04 10:00", tz=tz).as_unit("s"), + } + ) + expected = Series( + [ + Timestamp("2011-01-01 10:00", tz=tz), + Timestamp("2011-01-02 10:00", tz=tz), + Timestamp("2011-01-03 10:00", tz=tz), + Timestamp("2011-01-04 10:00", tz=tz), + ] + ).dt.as_unit(unit) + tm.assert_series_equal(expected, result) + tm.assert_series_equal(isna(ser), null_loc) + + # filling with a naive/other zone, coerce to object + result = ser.fillna(Timestamp("20130101").as_unit("s")) + expected = Series( + [ + Timestamp("2011-01-01 10:00", tz=tz).as_unit("s"), + Timestamp("2013-01-01").as_unit("s"), + Timestamp("2011-01-03 10:00", tz=tz).as_unit("s"), + Timestamp("2013-01-01").as_unit("s"), + ] + ) + tm.assert_series_equal(expected, result) + tm.assert_series_equal(isna(ser), null_loc) + + # pre-2.0 fillna with mixed tzs would cast to object, in 2.0 + # it retains dtype. + result = ser.fillna(Timestamp("20130101", tz="US/Pacific").as_unit("s")) + expected = Series( + [ + Timestamp("2011-01-01 10:00", tz=tz).as_unit("s"), + Timestamp("2013-01-01", tz="US/Pacific").as_unit("s").tz_convert(tz), + Timestamp("2011-01-03 10:00", tz=tz).as_unit("s"), + Timestamp("2013-01-01", tz="US/Pacific").as_unit("s").tz_convert(tz), + ] + ).dt.as_unit(unit) + tm.assert_series_equal(expected, result) + tm.assert_series_equal(isna(ser), null_loc) + + def test_fillna_dt64tz_with_method(self): + # with timezone + # GH#15855 + ser = Series([Timestamp("2012-11-11 00:00:00+01:00").as_unit("s"), NaT]) + exp = Series( + [ + Timestamp("2012-11-11 00:00:00+01:00").as_unit("s"), + Timestamp("2012-11-11 00:00:00+01:00").as_unit("s"), + ] + ) + tm.assert_series_equal(ser.ffill(), exp) + + ser = Series([NaT, Timestamp("2012-11-11 00:00:00+01:00").as_unit("s")]) + exp = Series( + [ + Timestamp("2012-11-11 00:00:00+01:00").as_unit("s"), + Timestamp("2012-11-11 00:00:00+01:00").as_unit("s"), + ] + ) + tm.assert_series_equal(ser.bfill(), exp) + + def test_fillna_pytimedelta(self): + # GH#8209 + ser = Series([np.nan, Timedelta("1 days")], index=["A", "B"]) + + result = ser.fillna(timedelta(1)) + expected = Series(Timedelta("1 days"), index=["A", "B"], dtype="m8[us]") + tm.assert_series_equal(result, expected) + + def test_fillna_period(self): + # GH#13737 + ser = Series([Period("2011-01", freq="M"), Period("NaT", freq="M")]) + + res = ser.fillna(Period("2012-01", freq="M")) + exp = Series([Period("2011-01", freq="M"), Period("2012-01", freq="M")]) + tm.assert_series_equal(res, exp) + assert res.dtype == "Period[M]" + + def test_fillna_dt64_timestamp(self, frame_or_series): + ser = Series( + [ + Timestamp("20130101"), + Timestamp("20130101"), + Timestamp("20130102"), + Timestamp("20130103 9:01:01"), + ] + ) + ser[2] = np.nan + obj = frame_or_series(ser) + + # reg fillna + result = obj.fillna(Timestamp("20130104")) + expected = Series( + [ + Timestamp("20130101"), + Timestamp("20130101"), + Timestamp("20130104"), + Timestamp("20130103 9:01:01"), + ] + ) + expected = frame_or_series(expected) + tm.assert_equal(result, expected) + + result = obj.fillna(NaT) + expected = obj + tm.assert_equal(result, expected) + + def test_fillna_dt64_non_nao(self): + # GH#27419 + ser = Series([Timestamp("2010-01-01"), NaT, Timestamp("2000-01-01")]) + val = np.datetime64("1975-04-05", "ms") + + result = ser.fillna(val) + expected = Series( + [Timestamp("2010-01-01"), Timestamp("1975-04-05"), Timestamp("2000-01-01")] + ) + tm.assert_series_equal(result, expected) + + def test_fillna_numeric_inplace(self): + x = Series([np.nan, 1.0, np.nan, 3.0, np.nan], ["z", "a", "b", "c", "d"]) + y = x.copy() + + result = y.fillna(value=0, inplace=True) + assert result is y + + expected = x.fillna(value=0) + tm.assert_series_equal(y, expected) + + # --------------------------------------------------------------- + # CategoricalDtype + + @pytest.mark.parametrize( + "fill_value, expected_output", + [ + ("a", ["a", "a", "b", "a", "a"]), + ({1: "a", 3: "b", 4: "b"}, ["a", "a", "b", "b", "b"]), + ({1: "a"}, ["a", "a", "b", np.nan, np.nan]), + ({1: "a", 3: "b"}, ["a", "a", "b", "b", np.nan]), + (Series("a"), ["a", np.nan, "b", np.nan, np.nan]), + (Series("a", index=[1]), ["a", "a", "b", np.nan, np.nan]), + (Series({1: "a", 3: "b"}), ["a", "a", "b", "b", np.nan]), + (Series(["a", "b"], index=[3, 4]), ["a", np.nan, "b", "a", "b"]), + ], + ) + def test_fillna_categorical(self, fill_value, expected_output): + # GH#17033 + # Test fillna for a Categorical series + data = ["a", np.nan, "b", np.nan, np.nan] + ser = Series(Categorical(data, categories=["a", "b"])) + exp = Series(Categorical(expected_output, categories=["a", "b"])) + result = ser.fillna(fill_value) + tm.assert_series_equal(result, exp) + + @pytest.mark.parametrize( + "fill_value, expected_output", + [ + (["a", "b", "c", "d", "e"], ["a", "b", "b", "d", "e"]), + (["b", "d", "a", "d", "a"], ["a", "d", "b", "d", "a"]), + ( + Categorical( + ["b", "d", "a", "d", "a"], categories=["b", "c", "d", "e", "a"] + ), + ["a", "d", "b", "d", "a"], + ), + ], + ) + def test_fillna_categorical_with_new_categories(self, fill_value, expected_output): + # GH#26215 + data = ["a", np.nan, "b", np.nan, np.nan] + ser = Series(Categorical(data, categories=["a", "b", "c", "d", "e"])) + exp = Series(Categorical(expected_output, categories=["a", "b", "c", "d", "e"])) + fill_value = Series(fill_value) + result = ser.fillna(fill_value) + tm.assert_series_equal(result, exp) + + def test_fillna_categorical_raises(self): + data = ["a", np.nan, "b", np.nan, np.nan] + ser = Series(Categorical(data, categories=["a", "b"])) + cat = ser._values + + msg = "Cannot setitem on a Categorical with a new category" + with pytest.raises(TypeError, match=msg): + ser.fillna("d") + + msg2 = "Length of 'value' does not match." + with pytest.raises(ValueError, match=msg2): + cat.fillna(Series("d")) + + with pytest.raises(TypeError, match=msg): + ser.fillna({1: "d", 3: "a"}) + + msg = '"value" parameter must be a scalar or dict, but you passed a "list"' + with pytest.raises(TypeError, match=msg): + ser.fillna(["a", "b"]) + + msg = '"value" parameter must be a scalar or dict, but you passed a "tuple"' + with pytest.raises(TypeError, match=msg): + ser.fillna(("a", "b")) + + msg = ( + '"value" parameter must be a scalar, dict ' + 'or Series, but you passed a "DataFrame"' + ) + with pytest.raises(TypeError, match=msg): + ser.fillna(DataFrame({1: ["a"], 3: ["b"]})) + + @pytest.mark.parametrize("dtype", [float, "float32", "float64"]) + @pytest.mark.parametrize("scalar", [True, False]) + def test_fillna_float_casting(self, dtype, any_real_numpy_dtype, scalar): + # GH-43424 + ser = Series([np.nan, 1.2], dtype=dtype) + fill_values = Series([2, 2], dtype=any_real_numpy_dtype) + if scalar: + fill_values = fill_values.dtype.type(2) + + result = ser.fillna(fill_values) + expected = Series([2.0, 1.2], dtype=dtype) + tm.assert_series_equal(result, expected) + + ser = Series([np.nan, 1.2], dtype=dtype) + mask = ser.isna().to_numpy() + ser[mask] = fill_values + tm.assert_series_equal(ser, expected) + + ser = Series([np.nan, 1.2], dtype=dtype) + ser.mask(mask, fill_values, inplace=True) + tm.assert_series_equal(ser, expected) + + ser = Series([np.nan, 1.2], dtype=dtype) + res = ser.where(~mask, fill_values) + tm.assert_series_equal(res, expected) + + def test_fillna_f32_upcast_with_dict(self): + # GH-43424 + ser = Series([np.nan, 1.2], dtype=np.float32) + result = ser.fillna({0: 1}) + expected = Series([1.0, 1.2], dtype=np.float32) + tm.assert_series_equal(result, expected) + + # --------------------------------------------------------------- + # Invalid Usages + + def test_fillna_listlike_invalid(self): + ser = Series(np.random.default_rng(2).integers(-100, 100, 50)) + msg = '"value" parameter must be a scalar or dict, but you passed a "list"' + with pytest.raises(TypeError, match=msg): + ser.fillna([1, 2]) + + msg = '"value" parameter must be a scalar or dict, but you passed a "tuple"' + with pytest.raises(TypeError, match=msg): + ser.fillna((1, 2)) + + def test_fillna_method_and_limit_invalid(self): + # related GH#9217, make sure limit is an int and greater than 0 + ser = Series([1, 2, 3, None]) + msg = "|".join( + [ + r"Cannot specify both 'value' and 'method'\.", + "Limit must be greater than 0", + "Limit must be an integer", + ] + ) + for limit in [-1, 0, 1.0, 2.0]: + with pytest.raises(ValueError, match=msg): + ser.fillna(1, limit=limit) + + def test_fillna_datetime64_with_timezone_tzinfo(self): + # https://github.com/pandas-dev/pandas/issues/38851 + # different tzinfos representing UTC treated as equal + ser = Series(date_range("2020", periods=3, tz="UTC")) + expected = ser.copy() + ser[1] = NaT + result = ser.fillna(datetime(2020, 1, 2, tzinfo=timezone.utc)) + tm.assert_series_equal(result, expected) + + # pre-2.0 we cast to object with mixed tzs, in 2.0 we retain dtype + ts = Timestamp("2000-01-01", tz="US/Pacific") + ser2 = Series(ser._values.tz_convert("dateutil/US/Pacific")) + assert ser2.dtype.kind == "M" + result = ser2.fillna(ts) + expected = Series( + [ser2[0], ts.tz_convert(ser2.dtype.tz), ser2[2]], + dtype=ser2.dtype, + ) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "input, input_fillna, expected_data, expected_categories", + [ + (["A", "B", None, "A"], "B", ["A", "B", "B", "A"], ["A", "B"]), + (["A", "B", np.nan, "A"], "B", ["A", "B", "B", "A"], ["A", "B"]), + ], + ) + def test_fillna_categorical_accept_same_type( + self, input, input_fillna, expected_data, expected_categories + ): + # GH32414 + cat = Categorical(input) + ser = Series(cat).fillna(input_fillna) + filled = cat.fillna(ser) + result = cat.fillna(filled) + expected = Categorical(expected_data, categories=expected_categories) + tm.assert_categorical_equal(result, expected) + + +class TestFillnaPad: + def test_fillna_bug(self): + ser = Series([np.nan, 1.0, np.nan, 3.0, np.nan], ["z", "a", "b", "c", "d"]) + filled = ser.ffill() + expected = Series([np.nan, 1.0, 1.0, 3.0, 3.0], ser.index) + tm.assert_series_equal(filled, expected) + + filled = ser.bfill() + expected = Series([1.0, 1.0, 3.0, 3.0, np.nan], ser.index) + tm.assert_series_equal(filled, expected) + + def test_ffill_mixed_dtypes_without_missing_data(self): + # GH#14956 + series = Series([datetime(2015, 1, 1, tzinfo=timezone.utc), 1]) + result = series.ffill() + tm.assert_series_equal(series, result) + + def test_pad_nan(self): + x = Series( + [np.nan, 1.0, np.nan, 3.0, np.nan], ["z", "a", "b", "c", "d"], dtype=float + ) + + result = x.ffill(inplace=True) + assert result is x + + expected = Series( + [np.nan, 1.0, 1.0, 3.0, 3.0], ["z", "a", "b", "c", "d"], dtype=float + ) + tm.assert_series_equal(x[1:], expected[1:]) + assert np.isnan(x.iloc[0]), np.isnan(expected.iloc[0]) + + def test_series_fillna_limit(self): + index = np.arange(10) + s = Series(np.random.default_rng(2).standard_normal(10), index=index) + + result = s[:2].reindex(index) + result = result.ffill(limit=5) + + expected = s[:2].reindex(index).ffill() + expected[-3:] = np.nan + tm.assert_series_equal(result, expected) + + result = s[-2:].reindex(index) + result = result.bfill(limit=5) + + expected = s[-2:].reindex(index).bfill() + expected[:3] = np.nan + tm.assert_series_equal(result, expected) + + def test_series_pad_backfill_limit(self): + index = np.arange(10) + s = Series(np.random.default_rng(2).standard_normal(10), index=index) + + result = s[:2].reindex(index, method="pad", limit=5) + + expected = s[:2].reindex(index).ffill() + expected[-3:] = np.nan + tm.assert_series_equal(result, expected) + + result = s[-2:].reindex(index, method="backfill", limit=5) + + expected = s[-2:].reindex(index).bfill() + expected[:3] = np.nan + tm.assert_series_equal(result, expected) + + def test_fillna_int(self): + ser = Series(np.random.default_rng(2).integers(-100, 100, 50)) + result = ser.ffill(inplace=True) + assert result is ser + tm.assert_series_equal(ser.ffill(inplace=False), ser) + + def test_datetime64tz_fillna_round_issue(self): + # GH#14872 + + data = Series( + [NaT, NaT, datetime(2016, 12, 12, 22, 24, 6, 100001, tzinfo=timezone.utc)] + ) + + filled = data.bfill() + + expected = Series( + [ + datetime(2016, 12, 12, 22, 24, 6, 100001, tzinfo=timezone.utc), + datetime(2016, 12, 12, 22, 24, 6, 100001, tzinfo=timezone.utc), + datetime(2016, 12, 12, 22, 24, 6, 100001, tzinfo=timezone.utc), + ] + ) + + tm.assert_series_equal(filled, expected) + + def test_fillna_parr(self): + # GH-24537 + dti = date_range( + Timestamp.max - Timedelta(nanoseconds=10), periods=5, freq="ns" + ) + ser = Series(dti.to_period("ns")) + ser[2] = NaT + arr = period_array( + [ + Timestamp("2262-04-11 23:47:16.854775797"), + Timestamp("2262-04-11 23:47:16.854775798"), + Timestamp("2262-04-11 23:47:16.854775798"), + Timestamp("2262-04-11 23:47:16.854775800"), + Timestamp("2262-04-11 23:47:16.854775801"), + ], + freq="ns", + ) + expected = Series(arr) + + filled = ser.ffill() + + tm.assert_series_equal(filled, expected) + + +@pytest.mark.parametrize( + "data, expected_data, method, kwargs", + ( + ( + [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan], + [np.nan, np.nan, 3.0, 3.0, 3.0, 3.0, 7.0, np.nan, np.nan], + "ffill", + {"limit_area": "inside"}, + ), + ( + [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan], + [np.nan, np.nan, 3.0, 3.0, np.nan, np.nan, 7.0, np.nan, np.nan], + "ffill", + {"limit_area": "inside", "limit": 1}, + ), + ( + [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan], + [np.nan, np.nan, 3.0, np.nan, np.nan, np.nan, 7.0, 7.0, 7.0], + "ffill", + {"limit_area": "outside"}, + ), + ( + [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan], + [np.nan, np.nan, 3.0, np.nan, np.nan, np.nan, 7.0, 7.0, np.nan], + "ffill", + {"limit_area": "outside", "limit": 1}, + ), + ( + [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan], + [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan], + "ffill", + {"limit_area": "outside", "limit": 1}, + ), + ( + range(5), + range(5), + "ffill", + {"limit_area": "outside", "limit": 1}, + ), + ( + [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan], + [np.nan, np.nan, 3.0, 7.0, 7.0, 7.0, 7.0, np.nan, np.nan], + "bfill", + {"limit_area": "inside"}, + ), + ( + [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan], + [np.nan, np.nan, 3.0, np.nan, np.nan, 7.0, 7.0, np.nan, np.nan], + "bfill", + {"limit_area": "inside", "limit": 1}, + ), + ( + [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan], + [3.0, 3.0, 3.0, np.nan, np.nan, np.nan, 7.0, np.nan, np.nan], + "bfill", + {"limit_area": "outside"}, + ), + ( + [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan], + [np.nan, 3.0, 3.0, np.nan, np.nan, np.nan, 7.0, np.nan, np.nan], + "bfill", + {"limit_area": "outside", "limit": 1}, + ), + ), +) +def test_ffill_bfill_limit_area(data, expected_data, method, kwargs): + # GH#56492 + s = Series(data) + expected = Series(expected_data) + result = getattr(s, method)(**kwargs) + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_get_numeric_data.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_get_numeric_data.py new file mode 100644 index 0000000000000000000000000000000000000000..31225d595343dfe744824e79e7494b9054a2fd27 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_get_numeric_data.py @@ -0,0 +1,31 @@ +from pandas import ( + Series, + date_range, +) +import pandas._testing as tm + + +class TestGetNumericData: + def test_get_numeric_data_preserve_dtype(self): + # get the numeric data + obj = Series([1, 2, 3]) + result = obj._get_numeric_data() + tm.assert_series_equal(result, obj) + + # returned object is a shallow copy + result.iloc[0] = 0 + assert obj.iloc[0] == 1 + + obj = Series([1, "2", 3.0]) + result = obj._get_numeric_data() + expected = Series([], dtype=object) + tm.assert_series_equal(result, expected) + + obj = Series([True, False, True]) + result = obj._get_numeric_data() + tm.assert_series_equal(result, obj) + + obj = Series(date_range("20130101", periods=3, unit="ns")) + result = obj._get_numeric_data() + expected = Series([], dtype="M8[ns]") + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_head_tail.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_head_tail.py new file mode 100644 index 0000000000000000000000000000000000000000..d9f8d85eda350e0627790626d52b7703d23ead02 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_head_tail.py @@ -0,0 +1,8 @@ +import pandas._testing as tm + + +def test_head_tail(string_series): + tm.assert_series_equal(string_series.head(), string_series[:5]) + tm.assert_series_equal(string_series.head(0), string_series[0:0]) + tm.assert_series_equal(string_series.tail(), string_series[-5:]) + tm.assert_series_equal(string_series.tail(0), string_series[0:0]) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_infer_objects.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_infer_objects.py new file mode 100644 index 0000000000000000000000000000000000000000..0d411e08a010706b59799ffcb14d66a664ced31b --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_infer_objects.py @@ -0,0 +1,56 @@ +import numpy as np + +from pandas import ( + Series, + interval_range, +) +import pandas._testing as tm + + +class TestInferObjects: + def test_copy(self, index_or_series): + # GH#50096 + # case where we don't need to do inference because it is already non-object + obj = index_or_series(np.array([1, 2, 3], dtype="int64")) + + result = obj.infer_objects() + assert tm.shares_memory(result, obj) + + # case where we try to do inference but can't do better than object + obj2 = index_or_series(np.array(["foo", 2], dtype=object)) + result2 = obj2.infer_objects() + assert tm.shares_memory(result2, obj2) + + def test_infer_objects_series(self, index_or_series): + # GH#11221 + actual = index_or_series(np.array([1, 2, 3], dtype="O")).infer_objects() + expected = index_or_series([1, 2, 3]) + tm.assert_equal(actual, expected) + + actual = index_or_series(np.array([1, 2, 3, None], dtype="O")).infer_objects() + expected = index_or_series([1.0, 2.0, 3.0, np.nan]) + tm.assert_equal(actual, expected) + + # only soft conversions, unconvertible pass thru unchanged + + obj = index_or_series(np.array([1, 2, 3, None, "a"], dtype="O")) + actual = obj.infer_objects() + expected = index_or_series([1, 2, 3, None, "a"], dtype=object) + + assert actual.dtype == "object" + tm.assert_equal(actual, expected) + + def test_infer_objects_interval(self, index_or_series): + # GH#50090 + ii = interval_range(1, 10) + obj = index_or_series(ii) + + result = obj.astype(object).infer_objects() + tm.assert_equal(result, obj) + + def test_infer_objects_bytes(self): + # GH#49650 + ser = Series([b"a"], dtype="bytes") + expected = ser.copy() + result = ser.infer_objects() + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_info.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_info.py new file mode 100644 index 0000000000000000000000000000000000000000..c1fee2b2ee648026ae1050097e06c17471c3bc5b --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_info.py @@ -0,0 +1,212 @@ +from io import StringIO +from string import ascii_uppercase +import textwrap + +import numpy as np +import pytest + +from pandas._config import using_string_dtype + +from pandas.compat import ( + HAS_PYARROW, + PYPY, +) + +from pandas import ( + CategoricalIndex, + Index, + MultiIndex, + Series, + date_range, +) + + +def test_info_categorical_column_just_works(): + n = 2500 + data = np.array(list("abcdefghij")).take( + np.random.default_rng(2).integers(0, 10, size=n, dtype=int) + ) + s = Series(data).astype("category") + s.isna() + buf = StringIO() + s.info(buf=buf) + + s2 = s[s == "d"] + buf = StringIO() + s2.info(buf=buf) + + +def test_info_categorical(): + # GH14298 + idx = CategoricalIndex(["a", "b"]) + s = Series(np.zeros(2), index=idx) + buf = StringIO() + s.info(buf=buf) + + +@pytest.mark.parametrize("verbose", [True, False]) +def test_info_series( + lexsorted_two_level_string_multiindex, verbose, using_infer_string +): + index = lexsorted_two_level_string_multiindex + ser = Series(range(len(index)), index=index, name="sth") + buf = StringIO() + ser.info(verbose=verbose, buf=buf) + result = buf.getvalue() + + expected = textwrap.dedent( + """\ + + MultiIndex: 10 entries, ('foo', 'one') to ('qux', 'three') + """ + ) + if verbose: + expected += textwrap.dedent( + """\ + Series name: sth + Non-Null Count Dtype + -------------- ----- + 10 non-null int64 + """ + ) + qualifier = "" if using_infer_string and HAS_PYARROW else "+" + expected += textwrap.dedent( + f"""\ + dtypes: int64(1) + memory usage: {ser.memory_usage()}.0{qualifier} bytes + """ + ) + assert result == expected + + +def test_info_memory(): + s = Series([1, 2], dtype="i8") + buf = StringIO() + s.info(buf=buf) + result = buf.getvalue() + memory_bytes = float(s.memory_usage()) + expected = textwrap.dedent( + f"""\ + + RangeIndex: 2 entries, 0 to 1 + Series name: None + Non-Null Count Dtype + -------------- ----- + 2 non-null int64 + dtypes: int64(1) + memory usage: {memory_bytes} bytes + """ + ) + assert result == expected + + +def test_info_wide(): + s = Series(np.random.default_rng(2).standard_normal(101)) + msg = "Argument `max_cols` can only be passed in DataFrame.info, not Series.info" + with pytest.raises(ValueError, match=msg): + s.info(max_cols=1) + + +def test_info_shows_dtypes(): + dtypes = [ + "int64", + "float64", + "datetime64[ns]", + "timedelta64[ns]", + "complex128", + "object", + "bool", + ] + n = 10 + for dtype in dtypes: + s = Series(np.random.default_rng(2).integers(2, size=n).astype(dtype)) + buf = StringIO() + s.info(buf=buf) + res = buf.getvalue() + name = f"{n:d} non-null {dtype}" + assert name in res + + +@pytest.mark.xfail(PYPY, reason="on PyPy deep=True doesn't change result") +def test_info_memory_usage_deep_not_pypy(): + s_with_object_index = Series({"a": [1]}, index=["foo"]) + assert s_with_object_index.memory_usage( + index=True, deep=True + ) > s_with_object_index.memory_usage(index=True) + + s_object = Series({"a": ["a"]}) + assert s_object.memory_usage(deep=True) > s_object.memory_usage() + + +@pytest.mark.xfail(not PYPY, reason="on PyPy deep=True does not change result") +def test_info_memory_usage_deep_pypy(): + s_with_object_index = Series({"a": [1]}, index=["foo"]) + assert s_with_object_index.memory_usage( + index=True, deep=True + ) == s_with_object_index.memory_usage(index=True) + + s_object = Series({"a": ["a"]}) + assert s_object.memory_usage(deep=True) == s_object.memory_usage() + + +@pytest.mark.parametrize( + "index, plus", + [ + ([1, 2, 3], False), + (Index(list("ABC"), dtype="str"), not (using_string_dtype() and HAS_PYARROW)), + (Index(list("ABC"), dtype=object), True), + (MultiIndex.from_product([range(3), range(3)]), False), + ( + MultiIndex.from_product([range(3), ["foo", "bar"]]), + not (using_string_dtype() and HAS_PYARROW), + ), + ], +) +def test_info_memory_usage_qualified(index, plus): + buf = StringIO() + series = Series(1, index=index) + series.info(buf=buf) + if plus: + assert "+" in buf.getvalue() + else: + assert "+" not in buf.getvalue() + + +def test_info_memory_usage_bug_on_multiindex(): + # GH 14308 + # memory usage introspection should not materialize .values + N = 100 + M = len(ascii_uppercase) + index = MultiIndex.from_product( + [list(ascii_uppercase), date_range("20160101", periods=N)], + names=["id", "date"], + ) + s = Series(np.random.default_rng(2).standard_normal(N * M), index=index) + + unstacked = s.unstack("id") + assert s.values.nbytes == unstacked.values.nbytes + assert s.memory_usage(deep=True) > unstacked.memory_usage(deep=True).sum() + + # high upper bound + diff = unstacked.memory_usage(deep=True).sum() - s.memory_usage(deep=True) + assert diff < 2000 + + +def test_info_show_counts_false(): + s = Series([1]) + buf = StringIO() + s.info(buf=buf, show_counts=False) + result = buf.getvalue() + memory_bytes = float(s.memory_usage()) + expected = textwrap.dedent( + f"""\ + + RangeIndex: 1 entries, 0 to 0 + Series name: None + Dtype + ----- + int64 + dtypes: int64(1) + memory usage: {memory_bytes} bytes""" + ) + assert result.strip() == expected.strip() diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_interpolate.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_interpolate.py new file mode 100644 index 0000000000000000000000000000000000000000..341a5405420e7c6b97b8853da64bfefb50d55064 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_interpolate.py @@ -0,0 +1,824 @@ +import numpy as np +import pytest + +import pandas.util._test_decorators as td + +import pandas as pd +from pandas import ( + Index, + MultiIndex, + Series, + date_range, + isna, +) +import pandas._testing as tm + + +@pytest.fixture( + params=[ + "linear", + "index", + "values", + "nearest", + "slinear", + "zero", + "quadratic", + "cubic", + "barycentric", + "krogh", + "polynomial", + "spline", + "piecewise_polynomial", + "from_derivatives", + "pchip", + "akima", + "cubicspline", + ] +) +def nontemporal_method(request): + """Fixture that returns an (method name, required kwargs) pair. + + This fixture does not include method 'time' as a parameterization; that + method requires a Series with a DatetimeIndex, and is generally tested + separately from these non-temporal methods. + """ + method = request.param + kwargs = {"order": 1} if method in ("spline", "polynomial") else {} + return method, kwargs + + +@pytest.fixture( + params=[ + "linear", + "slinear", + "zero", + "quadratic", + "cubic", + "barycentric", + "krogh", + "polynomial", + "spline", + "piecewise_polynomial", + "from_derivatives", + "pchip", + "akima", + "cubicspline", + ] +) +def interp_methods_ind(request): + """Fixture that returns a (method name, required kwargs) pair to + be tested for various Index types. + + This fixture does not include methods - 'time', 'index', 'nearest', + 'values' as a parameterization + """ + method = request.param + kwargs = {"order": 1} if method in ("spline", "polynomial") else {} + return method, kwargs + + +class TestSeriesInterpolateData: + @pytest.mark.xfail(reason="EA.fillna does not handle 'linear' method") + def test_interpolate_period_values(self): + orig = Series(date_range("2012-01-01", periods=5)) + ser = orig.copy() + ser[2] = pd.NaT + + # period cast + ser_per = ser.dt.to_period("D") + res_per = ser_per.interpolate() + expected_per = orig.dt.to_period("D") + tm.assert_series_equal(res_per, expected_per) + + def test_interpolate(self, datetime_series): + ts = Series(np.arange(len(datetime_series), dtype=float), datetime_series.index) + + ts_copy = ts.copy() + + # Set data between Tuesday and Thursday to NaN for 2 consecutive weeks. + # Linear interpolation should fill in the missing values correctly, + # as the index is equally-spaced within each week. + ts_copy[1:4] = np.nan + ts_copy[6:9] = np.nan + + linear_interp = ts_copy.interpolate(method="linear") + tm.assert_series_equal(linear_interp, ts) + + ord_ts = Series( + [d.toordinal() for d in datetime_series.index], index=datetime_series.index + ).astype(float) + + ord_ts_copy = ord_ts.copy() + ord_ts_copy[5:10] = np.nan + + time_interp = ord_ts_copy.interpolate(method="time") + tm.assert_series_equal(time_interp, ord_ts) + + def test_interpolate_time_raises_for_non_timeseries(self): + # When method='time' is used on a non-TimeSeries that contains a null + # value, a ValueError should be raised. + non_ts = Series([0, 1, 2, np.nan]) + msg = "time-weighted interpolation only works on Series.* with a DatetimeIndex" + with pytest.raises(ValueError, match=msg): + non_ts.interpolate(method="time") + + def test_interpolate_cubicspline(self): + pytest.importorskip("scipy") + ser = Series([10, 11, 12, 13]) + + expected = Series( + [11.00, 11.25, 11.50, 11.75, 12.00, 12.25, 12.50, 12.75, 13.00], + index=Index([1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0]), + ) + # interpolate at new_index + new_index = ser.index.union(Index([1.25, 1.5, 1.75, 2.25, 2.5, 2.75])).astype( + float + ) + result = ser.reindex(new_index).interpolate(method="cubicspline").loc[1:3] + tm.assert_series_equal(result, expected) + + def test_interpolate_pchip(self): + pytest.importorskip("scipy") + ser = Series(np.sort(np.random.default_rng(2).uniform(size=100))) + + # interpolate at new_index + new_index = ser.index.union( + Index([49.25, 49.5, 49.75, 50.25, 50.5, 50.75]) + ).astype(float) + interp_s = ser.reindex(new_index).interpolate(method="pchip") + # does not blow up, GH5977 + interp_s.loc[49:51] + + def test_interpolate_akima(self): + pytest.importorskip("scipy") + ser = Series([10, 11, 12, 13]) + + # interpolate at new_index where `der` is zero + expected = Series( + [11.00, 11.25, 11.50, 11.75, 12.00, 12.25, 12.50, 12.75, 13.00], + index=Index([1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0]), + ) + new_index = ser.index.union(Index([1.25, 1.5, 1.75, 2.25, 2.5, 2.75])).astype( + float + ) + interp_s = ser.reindex(new_index).interpolate(method="akima") + tm.assert_series_equal(interp_s.loc[1:3], expected) + + # interpolate at new_index where `der` is a non-zero int + expected = Series( + [11.0, 1.0, 1.0, 1.0, 12.0, 1.0, 1.0, 1.0, 13.0], + index=Index([1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0]), + ) + new_index = ser.index.union(Index([1.25, 1.5, 1.75, 2.25, 2.5, 2.75])).astype( + float + ) + interp_s = ser.reindex(new_index).interpolate(method="akima", der=1) + tm.assert_series_equal(interp_s.loc[1:3], expected) + + def test_interpolate_piecewise_polynomial(self): + pytest.importorskip("scipy") + ser = Series([10, 11, 12, 13]) + + expected = Series( + [11.00, 11.25, 11.50, 11.75, 12.00, 12.25, 12.50, 12.75, 13.00], + index=Index([1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0]), + ) + # interpolate at new_index + new_index = ser.index.union(Index([1.25, 1.5, 1.75, 2.25, 2.5, 2.75])).astype( + float + ) + interp_s = ser.reindex(new_index).interpolate(method="piecewise_polynomial") + tm.assert_series_equal(interp_s.loc[1:3], expected) + + def test_interpolate_from_derivatives(self): + pytest.importorskip("scipy") + ser = Series([10, 11, 12, 13]) + + expected = Series( + [11.00, 11.25, 11.50, 11.75, 12.00, 12.25, 12.50, 12.75, 13.00], + index=Index([1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0]), + ) + # interpolate at new_index + new_index = ser.index.union(Index([1.25, 1.5, 1.75, 2.25, 2.5, 2.75])).astype( + float + ) + interp_s = ser.reindex(new_index).interpolate(method="from_derivatives") + tm.assert_series_equal(interp_s.loc[1:3], expected) + + @pytest.mark.parametrize( + "kwargs", + [ + {}, + pytest.param( + {"method": "polynomial", "order": 1}, marks=td.skip_if_no("scipy") + ), + ], + ) + def test_interpolate_corners(self, kwargs): + s = Series([np.nan, np.nan]) + tm.assert_series_equal(s.interpolate(**kwargs), s) + + s = Series([], dtype=object).interpolate() + tm.assert_series_equal(s.interpolate(**kwargs), s) + + def test_interpolate_index_values(self): + s = Series(np.nan, index=np.sort(np.random.default_rng(2).random(30))) + s.loc[::3] = np.random.default_rng(2).standard_normal(10) + + vals = s.index.values.astype(float) + + result = s.interpolate(method="index") + + bad = isna(s) + good = ~bad + expected = Series( + np.interp(vals[bad], vals[good], s.values[good]), index=s.index[bad] + ) + + tm.assert_series_equal(result[bad], expected) + + # 'values' is synonymous with 'index' for the method kwarg + other_result = s.interpolate(method="values") + + tm.assert_series_equal(other_result, result) + tm.assert_series_equal(other_result[bad], expected) + + def test_interpolate_non_ts(self): + s = Series([1, 3, np.nan, np.nan, np.nan, 11]) + msg = ( + "time-weighted interpolation only works on Series or DataFrames " + "with a DatetimeIndex" + ) + with pytest.raises(ValueError, match=msg): + s.interpolate(method="time") + + @pytest.mark.parametrize( + "kwargs", + [ + {}, + pytest.param( + {"method": "polynomial", "order": 1}, marks=td.skip_if_no("scipy") + ), + ], + ) + def test_nan_interpolate(self, kwargs): + s = Series([0, 1, np.nan, 3]) + result = s.interpolate(**kwargs) + expected = Series([0.0, 1.0, 2.0, 3.0]) + tm.assert_series_equal(result, expected) + + def test_nan_irregular_index(self): + s = Series([1, 2, np.nan, 4], index=[1, 3, 5, 9]) + result = s.interpolate() + expected = Series([1.0, 2.0, 3.0, 4.0], index=[1, 3, 5, 9]) + tm.assert_series_equal(result, expected) + + def test_nan_str_index(self): + s = Series([0, 1, 2, np.nan], index=list("abcd")) + result = s.interpolate() + expected = Series([0.0, 1.0, 2.0, 2.0], index=list("abcd")) + tm.assert_series_equal(result, expected) + + def test_interp_quad(self): + pytest.importorskip("scipy") + sq = Series([1, 4, np.nan, 16], index=[1, 2, 3, 4]) + result = sq.interpolate(method="quadratic") + expected = Series([1.0, 4.0, 9.0, 16.0], index=[1, 2, 3, 4]) + tm.assert_series_equal(result, expected) + + def test_interp_scipy_basic(self): + pytest.importorskip("scipy") + s = Series([1, 3, np.nan, 12, np.nan, 25]) + # slinear + expected = Series([1.0, 3.0, 7.5, 12.0, 18.5, 25.0]) + result = s.interpolate(method="slinear") + tm.assert_series_equal(result, expected) + result = s.interpolate(method="slinear") + tm.assert_series_equal(result, expected) + # nearest + expected = Series([1, 3, 3, 12, 12, 25.0]) + result = s.interpolate(method="nearest") + tm.assert_series_equal(result, expected.astype("float")) + + result = s.interpolate(method="nearest") + tm.assert_series_equal(result, expected) + # zero + expected = Series([1, 3, 3, 12, 12, 25.0]) + result = s.interpolate(method="zero") + tm.assert_series_equal(result, expected.astype("float")) + + result = s.interpolate(method="zero") + tm.assert_series_equal(result, expected) + # quadratic + # GH #15662. + expected = Series([1, 3.0, 6.823529, 12.0, 18.058824, 25.0]) + result = s.interpolate(method="quadratic") + tm.assert_series_equal(result, expected) + + result = s.interpolate(method="quadratic") + tm.assert_series_equal(result, expected) + # cubic + expected = Series([1.0, 3.0, 6.8, 12.0, 18.2, 25.0]) + result = s.interpolate(method="cubic") + tm.assert_series_equal(result, expected) + + def test_interp_limit(self): + s = Series([1, 3, np.nan, np.nan, np.nan, 11]) + + expected = Series([1.0, 3.0, 5.0, 7.0, np.nan, 11.0]) + result = s.interpolate(method="linear", limit=2) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("limit", [-1, 0]) + def test_interpolate_invalid_nonpositive_limit(self, nontemporal_method, limit): + # GH 9217: make sure limit is greater than zero. + s = Series([1, 2, np.nan, 4]) + method, kwargs = nontemporal_method + with pytest.raises(ValueError, match="Limit must be greater than 0"): + s.interpolate(limit=limit, method=method, **kwargs) + + def test_interpolate_invalid_float_limit(self, nontemporal_method): + # GH 9217: make sure limit is an integer. + s = Series([1, 2, np.nan, 4]) + method, kwargs = nontemporal_method + limit = 2.0 + with pytest.raises(ValueError, match="Limit must be an integer"): + s.interpolate(limit=limit, method=method, **kwargs) + + @pytest.mark.parametrize("invalid_method", [None, "nonexistent_method"]) + def test_interp_invalid_method(self, invalid_method): + s = Series([1, 3, np.nan, 12, np.nan, 25]) + + msg = "Can not interpolate with method=nonexistent_method" + if invalid_method is None: + msg = "'method' should be a string, not None" + with pytest.raises(ValueError, match=msg): + s.interpolate(method=invalid_method) + + # When an invalid method and invalid limit (such as -1) are + # provided, the error message reflects the invalid method. + with pytest.raises(ValueError, match=msg): + s.interpolate(method=invalid_method, limit=-1) + + def test_interp_limit_forward(self): + s = Series([1, 3, np.nan, np.nan, np.nan, 11]) + + # Provide 'forward' (the default) explicitly here. + expected = Series([1.0, 3.0, 5.0, 7.0, np.nan, 11.0]) + + result = s.interpolate(method="linear", limit=2, limit_direction="forward") + tm.assert_series_equal(result, expected) + + result = s.interpolate(method="linear", limit=2, limit_direction="FORWARD") + tm.assert_series_equal(result, expected) + + def test_interp_unlimited(self): + # these test are for issue #16282 default Limit=None is unlimited + s = Series([np.nan, 1.0, 3.0, np.nan, np.nan, np.nan, 11.0, np.nan]) + expected = Series([1.0, 1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 11.0]) + result = s.interpolate(method="linear", limit_direction="both") + tm.assert_series_equal(result, expected) + + expected = Series([np.nan, 1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 11.0]) + result = s.interpolate(method="linear", limit_direction="forward") + tm.assert_series_equal(result, expected) + + expected = Series([1.0, 1.0, 3.0, 5.0, 7.0, 9.0, 11.0, np.nan]) + result = s.interpolate(method="linear", limit_direction="backward") + tm.assert_series_equal(result, expected) + + def test_interp_limit_bad_direction(self): + s = Series([1, 3, np.nan, np.nan, np.nan, 11]) + + msg = ( + r"Invalid limit_direction: expecting one of \['forward', " + r"'backward', 'both'\], got 'abc'" + ) + with pytest.raises(ValueError, match=msg): + s.interpolate(method="linear", limit=2, limit_direction="abc") + + # raises an error even if no limit is specified. + with pytest.raises(ValueError, match=msg): + s.interpolate(method="linear", limit_direction="abc") + + # limit_area introduced GH #16284 + def test_interp_limit_area(self): + # These tests are for issue #9218 -- fill NaNs in both directions. + s = Series([np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan]) + + expected = Series([np.nan, np.nan, 3.0, 4.0, 5.0, 6.0, 7.0, np.nan, np.nan]) + result = s.interpolate(method="linear", limit_area="inside") + tm.assert_series_equal(result, expected) + + expected = Series( + [np.nan, np.nan, 3.0, 4.0, np.nan, np.nan, 7.0, np.nan, np.nan] + ) + result = s.interpolate(method="linear", limit_area="inside", limit=1) + tm.assert_series_equal(result, expected) + + expected = Series([np.nan, np.nan, 3.0, 4.0, np.nan, 6.0, 7.0, np.nan, np.nan]) + result = s.interpolate( + method="linear", limit_area="inside", limit_direction="both", limit=1 + ) + tm.assert_series_equal(result, expected) + + expected = Series([np.nan, np.nan, 3.0, np.nan, np.nan, np.nan, 7.0, 7.0, 7.0]) + result = s.interpolate(method="linear", limit_area="outside") + tm.assert_series_equal(result, expected) + + expected = Series( + [np.nan, np.nan, 3.0, np.nan, np.nan, np.nan, 7.0, 7.0, np.nan] + ) + result = s.interpolate(method="linear", limit_area="outside", limit=1) + tm.assert_series_equal(result, expected) + + expected = Series([np.nan, 3.0, 3.0, np.nan, np.nan, np.nan, 7.0, 7.0, np.nan]) + result = s.interpolate( + method="linear", limit_area="outside", limit_direction="both", limit=1 + ) + tm.assert_series_equal(result, expected) + + expected = Series([3.0, 3.0, 3.0, np.nan, np.nan, np.nan, 7.0, np.nan, np.nan]) + result = s.interpolate( + method="linear", limit_area="outside", limit_direction="backward" + ) + tm.assert_series_equal(result, expected) + + # raises an error even if limit type is wrong. + msg = r"Invalid limit_area: expecting one of \['inside', 'outside'\], got abc" + with pytest.raises(ValueError, match=msg): + s.interpolate(method="linear", limit_area="abc") + + @pytest.mark.parametrize( + "data, kwargs", + ( + ( + [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan], + {"method": "pad", "limit_area": "inside"}, + ), + ( + [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan], + {"method": "pad", "limit_area": "inside", "limit": 1}, + ), + ( + [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan], + {"method": "pad", "limit_area": "outside"}, + ), + ( + [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan], + {"method": "pad", "limit_area": "outside", "limit": 1}, + ), + ( + [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan], + {"method": "pad", "limit_area": "outside", "limit": 1}, + ), + ( + range(5), + {"method": "pad", "limit_area": "outside", "limit": 1}, + ), + ), + ) + def test_interp_limit_area_with_pad(self, data, kwargs): + # GH26796 + + s = Series(data) + msg = "Can not interpolate with method=pad" + with pytest.raises(ValueError, match=msg): + s.interpolate(**kwargs) + + @pytest.mark.parametrize( + "data, kwargs", + ( + ( + [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan], + {"method": "bfill", "limit_area": "inside"}, + ), + ( + [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan], + {"method": "bfill", "limit_area": "inside", "limit": 1}, + ), + ( + [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan], + {"method": "bfill", "limit_area": "outside"}, + ), + ( + [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan], + {"method": "bfill", "limit_area": "outside", "limit": 1}, + ), + ), + ) + def test_interp_limit_area_with_backfill(self, data, kwargs): + # GH26796 + s = Series(data) + + msg = "Can not interpolate with method=bfill" + with pytest.raises(ValueError, match=msg): + s.interpolate(**kwargs) + + def test_interp_limit_direction(self): + # These tests are for issue #9218 -- fill NaNs in both directions. + s = Series([1, 3, np.nan, np.nan, np.nan, 11]) + + expected = Series([1.0, 3.0, np.nan, 7.0, 9.0, 11.0]) + result = s.interpolate(method="linear", limit=2, limit_direction="backward") + tm.assert_series_equal(result, expected) + + expected = Series([1.0, 3.0, 5.0, np.nan, 9.0, 11.0]) + result = s.interpolate(method="linear", limit=1, limit_direction="both") + tm.assert_series_equal(result, expected) + + # Check that this works on a longer series of nans. + s = Series([1, 3, np.nan, np.nan, np.nan, 7, 9, np.nan, np.nan, 12, np.nan]) + + expected = Series([1.0, 3.0, 4.0, 5.0, 6.0, 7.0, 9.0, 10.0, 11.0, 12.0, 12.0]) + result = s.interpolate(method="linear", limit=2, limit_direction="both") + tm.assert_series_equal(result, expected) + + expected = Series( + [1.0, 3.0, 4.0, np.nan, 6.0, 7.0, 9.0, 10.0, 11.0, 12.0, 12.0] + ) + result = s.interpolate(method="linear", limit=1, limit_direction="both") + tm.assert_series_equal(result, expected) + + def test_interp_limit_direction_limit_exceeds_length(self): + # GH#64322 + s = Series([np.nan, 1, 1, 1, 1, np.nan]) + result = s.interpolate(limit_direction="both", limit=10) + expected = Series([1.0, 1.0, 1.0, 1.0, 1.0, 1.0]) + tm.assert_series_equal(result, expected) + + s2 = Series([1, np.nan, np.nan, 4]) + result_fwd = s2.interpolate(limit_direction="forward", limit=100) + expected_fwd = Series([1.0, 2.0, 3.0, 4.0]) + tm.assert_series_equal(result_fwd, expected_fwd) + + result_bwd = s2.interpolate(limit_direction="backward", limit=100) + expected_bwd = Series([1.0, 2.0, 3.0, 4.0]) + tm.assert_series_equal(result_bwd, expected_bwd) + + def test_interp_limit_to_ends(self): + # These test are for issue #10420 -- flow back to beginning. + s = Series([np.nan, np.nan, 5, 7, 9, np.nan]) + + expected = Series([5.0, 5.0, 5.0, 7.0, 9.0, np.nan]) + result = s.interpolate(method="linear", limit=2, limit_direction="backward") + tm.assert_series_equal(result, expected) + + expected = Series([5.0, 5.0, 5.0, 7.0, 9.0, 9.0]) + result = s.interpolate(method="linear", limit=2, limit_direction="both") + tm.assert_series_equal(result, expected) + + def test_interp_limit_before_ends(self): + # These test are for issue #11115 -- limit ends properly. + s = Series([np.nan, np.nan, 5, 7, np.nan, np.nan]) + + expected = Series([np.nan, np.nan, 5.0, 7.0, 7.0, np.nan]) + result = s.interpolate(method="linear", limit=1, limit_direction="forward") + tm.assert_series_equal(result, expected) + + expected = Series([np.nan, 5.0, 5.0, 7.0, np.nan, np.nan]) + result = s.interpolate(method="linear", limit=1, limit_direction="backward") + tm.assert_series_equal(result, expected) + + expected = Series([np.nan, 5.0, 5.0, 7.0, 7.0, np.nan]) + result = s.interpolate(method="linear", limit=1, limit_direction="both") + tm.assert_series_equal(result, expected) + + def test_interp_all_good(self): + pytest.importorskip("scipy") + s = Series([1, 2, 3]) + result = s.interpolate(method="polynomial", order=1) + tm.assert_series_equal(result, s) + + # non-scipy + result = s.interpolate() + tm.assert_series_equal(result, s) + + @pytest.mark.parametrize( + "check_scipy", [False, pytest.param(True, marks=td.skip_if_no("scipy"))] + ) + def test_interp_multiIndex(self, check_scipy): + idx = MultiIndex.from_tuples([(0, "a"), (1, "b"), (2, "c")]) + s = Series([1, 2, np.nan], index=idx) + + expected = s.copy() + expected.loc[2] = 2 + result = s.interpolate() + tm.assert_series_equal(result, expected) + + msg = "Only `method=linear` interpolation is supported on MultiIndexes" + if check_scipy: + with pytest.raises(ValueError, match=msg): + s.interpolate(method="polynomial", order=1) + + def test_interp_nonmono_raise(self): + pytest.importorskip("scipy") + s = Series([1, np.nan, 3], index=[0, 2, 1]) + msg = "krogh interpolation requires that the index be monotonic" + with pytest.raises(ValueError, match=msg): + s.interpolate(method="krogh") + + @pytest.mark.parametrize("method", ["nearest", "pad"]) + def test_interp_datetime64(self, method, tz_naive_fixture): + pytest.importorskip("scipy") + df = Series( + [1, np.nan, 3], index=date_range("1/1/2000", periods=3, tz=tz_naive_fixture) + ) + + if method == "nearest": + result = df.interpolate(method=method) + expected = Series( + [1.0, 1.0, 3.0], + index=date_range("1/1/2000", periods=3, tz=tz_naive_fixture), + ) + tm.assert_series_equal(result, expected) + else: + msg = "Can not interpolate with method=pad" + with pytest.raises(ValueError, match=msg): + df.interpolate(method=method) + + def test_interp_pad_datetime64tz_values(self): + # GH#27628 missing.interpolate_2d should handle datetimetz values + dti = date_range("2015-04-05", periods=3, tz="US/Central") + ser = Series(dti) + ser[1] = pd.NaT + + msg = "Can not interpolate with method=pad" + with pytest.raises(ValueError, match=msg): + ser.interpolate(method="pad") + + def test_interp_limit_no_nans(self): + # GH 7173 + s = Series([1.0, 2.0, 3.0]) + result = s.interpolate(limit=1) + expected = s + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("method", ["polynomial", "spline"]) + def test_no_order(self, method): + # see GH-10633, GH-24014 + pytest.importorskip("scipy") + s = Series([0, 1, np.nan, 3]) + msg = "You must specify the order of the spline or polynomial" + with pytest.raises(ValueError, match=msg): + s.interpolate(method=method) + + @pytest.mark.parametrize("order", [-1, -1.0, 0, 0.0, np.nan]) + def test_interpolate_spline_invalid_order(self, order): + pytest.importorskip("scipy") + s = Series([0, 1, np.nan, 3]) + msg = "order needs to be specified and greater than 0" + with pytest.raises(ValueError, match=msg): + s.interpolate(method="spline", order=order) + + def test_spline(self): + pytest.importorskip("scipy") + s = Series([1, 2, np.nan, 4, 5, np.nan, 7]) + result = s.interpolate(method="spline", order=1) + expected = Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]) + tm.assert_series_equal(result, expected) + + def test_spline_extrapolate(self): + pytest.importorskip("scipy") + s = Series([1, 2, 3, 4, np.nan, 6, np.nan]) + result3 = s.interpolate(method="spline", order=1, ext=3) + expected3 = Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 6.0]) + tm.assert_series_equal(result3, expected3) + + result1 = s.interpolate(method="spline", order=1, ext=0) + expected1 = Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]) + tm.assert_series_equal(result1, expected1) + + def test_spline_smooth(self): + pytest.importorskip("scipy") + s = Series([1, 2, np.nan, 4, 5.1, np.nan, 7]) + assert ( + s.interpolate(method="spline", order=3, s=0)[5] + != s.interpolate(method="spline", order=3)[5] + ) + + def test_spline_interpolation(self): + # Explicit cast to float to avoid implicit cast when setting np.nan + pytest.importorskip("scipy") + s = Series(np.arange(10) ** 2, dtype="float") + s[np.random.default_rng(2).integers(0, 9, 3)] = np.nan + result1 = s.interpolate(method="spline", order=1) + expected1 = s.interpolate(method="spline", order=1) + tm.assert_series_equal(result1, expected1) + + def test_interp_timedelta64(self): + # GH 6424 + df = Series([1, np.nan, 3], index=pd.to_timedelta([1, 2, 3])) + result = df.interpolate(method="time") + expected = Series([1.0, 2.0, 3.0], index=pd.to_timedelta([1, 2, 3])) + tm.assert_series_equal(result, expected) + + # test for non uniform spacing + df = Series([1, np.nan, 3], index=pd.to_timedelta([1, 2, 4])) + result = df.interpolate(method="time") + expected = Series([1.0, 1.666667, 3.0], index=pd.to_timedelta([1, 2, 4])) + tm.assert_series_equal(result, expected) + + def test_series_interpolate_method_values(self): + # GH#1646 + rng = date_range("1/1/2000", "1/20/2000", freq="D") + ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng) + + ts[::2] = np.nan + + result = ts.interpolate(method="values") + exp = ts.interpolate() + tm.assert_series_equal(result, exp) + + def test_series_interpolate_intraday(self): + # #1698 + index = date_range("1/1/2012", periods=4, freq="12D") + ts = Series([0, 12, 24, 36], index) + new_index = index.append(index + pd.DateOffset(days=1)).sort_values() + + exp = ts.reindex(new_index).interpolate(method="time") + + index = date_range("1/1/2012", periods=4, freq="12h") + ts = Series([0, 12, 24, 36], index) + new_index = index.append(index + pd.DateOffset(hours=1)).sort_values() + result = ts.reindex(new_index).interpolate(method="time") + + tm.assert_numpy_array_equal(result.values, exp.values) + + @pytest.mark.parametrize( + "ind", + [ + ["a", "b", "c", "d"], + pd.period_range(start="2019-01-01", periods=4), + pd.interval_range(start=0, end=4), + ], + ) + def test_interp_non_timedelta_index(self, interp_methods_ind, ind): + # gh 21662 + df = pd.DataFrame([0, 1, np.nan, 3], index=ind) + + method, kwargs = interp_methods_ind + if method == "pchip": + pytest.importorskip("scipy") + + if method == "linear": + result = df[0].interpolate(**kwargs) + expected = Series([0.0, 1.0, 2.0, 3.0], name=0, index=ind) + tm.assert_series_equal(result, expected) + else: + expected_error = ( + "Index column must be numeric or datetime type when " + f"using {method} method other than linear. " + "Try setting a numeric or datetime index column before " + "interpolating." + ) + with pytest.raises(ValueError, match=expected_error): + df[0].interpolate(method=method, **kwargs) + + def test_interpolate_timedelta_index(self, request, interp_methods_ind): + """ + Tests for non numerical index types - object, period, timedelta + Note that all methods except time, index, nearest and values + are tested here. + """ + # gh 21662 + pytest.importorskip("scipy") + ind = pd.timedelta_range(start=1, periods=4) + df = pd.DataFrame([0, 1, np.nan, 3], index=ind) + + method, kwargs = interp_methods_ind + + if method in {"cubic", "zero"}: + request.applymarker( + pytest.mark.xfail( + reason=f"{method} interpolation is not supported for TimedeltaIndex" + ) + ) + result = df[0].interpolate(method=method, **kwargs) + expected = Series([0.0, 1.0, 2.0, 3.0], name=0, index=ind) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "ascending, expected_values", + [(True, [1, 2, 3, 9, 10]), (False, [10, 9, 3, 2, 1])], + ) + def test_interpolate_unsorted_index(self, ascending, expected_values): + # GH 21037 + ts = Series(data=[10, 9, np.nan, 2, 1], index=[10, 9, 3, 2, 1]) + result = ts.sort_index(ascending=ascending).interpolate(method="index") + expected = Series(data=expected_values, index=expected_values, dtype=float) + tm.assert_series_equal(result, expected) + + def test_interpolate_asfreq_raises(self): + ser = Series(["a", None, "b"], dtype=object) + msg = "Can not interpolate with method=asfreq" + with pytest.raises(ValueError, match=msg): + ser.interpolate(method="asfreq") + + def test_interpolate_fill_value(self): + # GH#54920 + pytest.importorskip("scipy") + ser = Series([np.nan, 0, 1, np.nan, 3, np.nan]) + result = ser.interpolate(method="nearest", fill_value=0) + expected = Series([np.nan, 0, 1, 1, 3, 0]) + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_is_monotonic.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_is_monotonic.py new file mode 100644 index 0000000000000000000000000000000000000000..073ec4172aff6b041d29011bc3151f84f3cbeb19 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_is_monotonic.py @@ -0,0 +1,26 @@ +import numpy as np + +from pandas import ( + Series, + date_range, +) + + +class TestIsMonotonic: + def test_is_monotonic_numeric(self): + ser = Series(np.random.default_rng(2).integers(0, 10, size=1000)) + assert not ser.is_monotonic_increasing + ser = Series(np.arange(1000)) + assert ser.is_monotonic_increasing is True + assert ser.is_monotonic_increasing is True + ser = Series(np.arange(1000, 0, -1)) + assert ser.is_monotonic_decreasing is True + + def test_is_monotonic_dt64(self): + ser = Series(date_range("20130101", periods=10)) + assert ser.is_monotonic_increasing is True + assert ser.is_monotonic_increasing is True + + ser = Series(list(reversed(ser))) + assert ser.is_monotonic_increasing is False + assert ser.is_monotonic_decreasing is True diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_is_unique.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_is_unique.py new file mode 100644 index 0000000000000000000000000000000000000000..edf3839c2cebb6f51d0bb21b06ea8a1c47dec0fe --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_is_unique.py @@ -0,0 +1,40 @@ +import numpy as np +import pytest + +from pandas import Series + + +@pytest.mark.parametrize( + "data, expected", + [ + (np.random.default_rng(2).integers(0, 10, size=1000), False), + (np.arange(1000), True), + ([], True), + ([np.nan], True), + (["foo", "bar", np.nan], True), + (["foo", "foo", np.nan], False), + (["foo", "bar", np.nan, np.nan], False), + ], +) +def test_is_unique(data, expected): + # GH#11946 / GH#25180 + ser = Series(data) + assert ser.is_unique is expected + + +def test_is_unique_class_ne(capsys): + # GH#20661 + class Foo: + def __init__(self, val) -> None: + self._value = val + + def __ne__(self, other): + raise Exception("NEQ not supported") + + with capsys.disabled(): + li = [Foo(i) for i in range(5)] + ser = Series(li, index=list(range(5))) + + ser.is_unique + captured = capsys.readouterr() + assert len(captured.err) == 0 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_isin.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_isin.py new file mode 100644 index 0000000000000000000000000000000000000000..d7786a71c8c7dccfda586ef174e79b09b67ec998 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_isin.py @@ -0,0 +1,286 @@ +import numpy as np +import pytest + +import pandas as pd +from pandas import ( + Series, + date_range, +) +import pandas._testing as tm +from pandas.core import algorithms +from pandas.core.arrays import PeriodArray + + +class TestSeriesIsIn: + def test_isin(self): + s = Series(["A", "B", "C", "a", "B", "B", "A", "C"]) + + result = s.isin(["A", "C"]) + expected = Series([True, False, True, False, False, False, True, True]) + tm.assert_series_equal(result, expected) + + # GH#16012 + # This specific issue has to have a series over 1e6 in len, but the + # comparison array (in_list) must be large enough so that numpy doesn't + # do a manual masking trick that will avoid this issue altogether + s = Series(list("abcdefghijk" * 10**5)) + # If numpy doesn't do the manual comparison/mask, these + # unorderable mixed types are what cause the exception in numpy + in_list = [-1, "a", "b", "G", "Y", "Z", "E", "K", "E", "S", "I", "R", "R"] * 6 + + assert s.isin(in_list).sum() == 200000 + + def test_isin_with_string_scalar(self): + # GH#4763 + s = Series(["A", "B", "C", "a", "B", "B", "A", "C"]) + msg = ( + r"only list-like objects are allowed to be passed to isin\(\), " + r"you passed a `str`" + ) + with pytest.raises(TypeError, match=msg): + s.isin("a") + + s = Series(["aaa", "b", "c"]) + with pytest.raises(TypeError, match=msg): + s.isin("aaa") + + def test_isin_datetimelike_mismatched_reso(self): + expected = Series([True, True, False, False, False]) + + ser = Series(date_range("jan-01-2013", "jan-05-2013")) + + # fails on dtype conversion in the first place + day_values = np.asarray(ser[0:2].values).astype("datetime64[D]") + result = ser.isin(day_values) + tm.assert_series_equal(result, expected) + + dta = ser[:2]._values.astype("M8[s]") + result = ser.isin(dta) + tm.assert_series_equal(result, expected) + + def test_isin_datetimelike_mismatched_reso_list(self): + expected = Series([True, True, False, False, False]) + + ser = Series(date_range("jan-01-2013", "jan-05-2013")) + + dta = ser[:2]._values.astype("M8[s]") + result = ser.isin(list(dta)) + tm.assert_series_equal(result, expected) + + def test_isin_with_i8(self): + # GH#5021 + + expected = Series([True, True, False, False, False]) + expected2 = Series([False, True, False, False, False]) + + # datetime64[ns] + s = Series(date_range("jan-01-2013", "jan-05-2013")) + + result = s.isin(s[0:2]) + tm.assert_series_equal(result, expected) + + result = s.isin(s[0:2].values) + tm.assert_series_equal(result, expected) + + result = s.isin([s[1]]) + tm.assert_series_equal(result, expected2) + + result = s.isin([np.datetime64(s[1])]) + tm.assert_series_equal(result, expected2) + + result = s.isin(set(s[0:2])) + tm.assert_series_equal(result, expected) + + # timedelta64[ns] + s = Series(pd.to_timedelta(range(5), unit="D")) + result = s.isin(s[0:2]) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("empty", [[], Series(dtype=object), np.array([])]) + def test_isin_empty(self, empty): + # see GH#16991 + s = Series(["a", "b"]) + expected = Series([False, False]) + + result = s.isin(empty) + tm.assert_series_equal(expected, result) + + def test_isin_read_only(self): + # https://github.com/pandas-dev/pandas/issues/37174 + arr = np.array([1, 2, 3]) + arr.setflags(write=False) + s = Series([1, 2, 3]) + result = s.isin(arr) + expected = Series([True, True, True]) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("dtype", [object, None]) + def test_isin_dt64_values_vs_ints(self, dtype): + # GH#36621 dont cast integers to datetimes for isin + dti = date_range("2013-01-01", "2013-01-05") + ser = Series(dti) + + comps = np.asarray([1356998400000000000], dtype=dtype) + + res = dti.isin(comps) + expected = np.array([False] * len(dti), dtype=bool) + tm.assert_numpy_array_equal(res, expected) + + res = ser.isin(comps) + tm.assert_series_equal(res, Series(expected)) + + res = pd.core.algorithms.isin(ser, comps) + tm.assert_numpy_array_equal(res, expected) + + def test_isin_tzawareness_mismatch(self): + dti = date_range("2013-01-01", "2013-01-05") + ser = Series(dti) + + other = dti.tz_localize("UTC") + + res = dti.isin(other) + expected = np.array([False] * len(dti), dtype=bool) + tm.assert_numpy_array_equal(res, expected) + + res = ser.isin(other) + tm.assert_series_equal(res, Series(expected)) + + res = pd.core.algorithms.isin(ser, other) + tm.assert_numpy_array_equal(res, expected) + + def test_isin_period_freq_mismatch(self): + dti = date_range("2013-01-01", "2013-01-05") + pi = dti.to_period("M") + ser = Series(pi) + + # We construct another PeriodIndex with the same i8 values + # but different dtype + dtype = dti.to_period("Y").dtype + other = PeriodArray._simple_new(pi.asi8, dtype=dtype) + + res = pi.isin(other) + expected = np.array([False] * len(pi), dtype=bool) + tm.assert_numpy_array_equal(res, expected) + + res = ser.isin(other) + tm.assert_series_equal(res, Series(expected)) + + res = pd.core.algorithms.isin(ser, other) + tm.assert_numpy_array_equal(res, expected) + + @pytest.mark.parametrize("values", [[-9.0, 0.0], [-9, 0]]) + def test_isin_float_in_int_series(self, values): + # GH#19356 GH#21804 + ser = Series(values) + result = ser.isin([-9, -0.5]) + expected = Series([True, False]) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("dtype", ["boolean", "Int64", "Float64"]) + @pytest.mark.parametrize( + "data,values,expected", + [ + ([0, 1, 0], [1], [False, True, False]), + ([0, 1, 0], [1, pd.NA], [False, True, False]), + ([0, pd.NA, 0], [1, 0], [True, False, True]), + ([0, 1, pd.NA], [1, pd.NA], [False, True, True]), + ([0, 1, pd.NA], [1, np.nan], [False, True, False]), + ([0, pd.NA, pd.NA], [np.nan, pd.NaT, None], [False, False, False]), + ], + ) + def test_isin_masked_types(self, dtype, data, values, expected): + # GH#42405 + ser = Series(data, dtype=dtype) + + result = ser.isin(values) + expected = Series(expected, dtype="boolean") + + tm.assert_series_equal(result, expected) + + +def test_isin_large_series_mixed_dtypes_and_nan(monkeypatch): + # https://github.com/pandas-dev/pandas/issues/37094 + # combination of object dtype for the values + # and > _MINIMUM_COMP_ARR_LEN elements + min_isin_comp = 5 + ser = Series([1, 2, np.nan] * min_isin_comp) + with monkeypatch.context() as m: + m.setattr(algorithms, "_MINIMUM_COMP_ARR_LEN", min_isin_comp) + result = ser.isin({"foo", "bar"}) + expected = Series([False] * 3 * min_isin_comp) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "dtype, data, values, expected", + [ + ("boolean", [pd.NA, False, True], [False, pd.NA], [True, True, False]), + ("Int64", [pd.NA, 2, 1], [1, pd.NA], [True, False, True]), + ("boolean", [pd.NA, False, True], [pd.NA, True, "a", 20], [True, False, True]), + ("boolean", [pd.NA, False, True], [], [False, False, False]), + ("Float64", [20.0, 30.0, pd.NA], [pd.NA], [False, False, True]), + ], +) +def test_isin_large_series_and_pdNA(dtype, data, values, expected, monkeypatch): + # https://github.com/pandas-dev/pandas/issues/60678 + # combination of large series (> _MINIMUM_COMP_ARR_LEN elements) and + # values contains pdNA + min_isin_comp = 2 + ser = Series(data, dtype=dtype) + expected = Series(expected, dtype="boolean") + + with monkeypatch.context() as m: + m.setattr(algorithms, "_MINIMUM_COMP_ARR_LEN", min_isin_comp) + result = ser.isin(values) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "dtype, data, values, expected", + [ + ("int64[pyarrow]", [1, None], [1, pd.NA], [True, True]), + ("binary[pyarrow]", [None, b"\xe3"], [pd.NA, b"\xe3"], [True, True]), + ], +) +def test_isin_arrow_dtype_with_na_value(dtype, data, values, expected): + # GH#63304 + pytest.importorskip("pyarrow") + + result = Series(data, dtype=dtype).isin(values) + expected = Series(expected) + + tm.assert_series_equal(result, expected) + + +def test_isin_complex_numbers(): + # GH 17927 + array = [0, 1j, 1j, 1, 1 + 1j, 1 + 2j, 1 + 1j] + result = Series(array).isin([1j, 1 + 1j, 1 + 2j]) + expected = Series([False, True, True, False, True, True, True], dtype=bool) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "data,is_in", + [([1, [2]], [1]), (["simple str", [{"values": 3}]], ["simple str"])], +) +def test_isin_filtering_with_mixed_object_types(data, is_in): + # GH 20883 + + ser = Series(data) + result = ser.isin(is_in) + expected = Series([True, False]) + + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize("data", [[1, 2, 3], [1.0, 2.0, 3.0]]) +@pytest.mark.parametrize("isin", [[1, 2], [1.0, 2.0]]) +def test_isin_filtering_on_iterable(data, isin): + # GH 50234 + + ser = Series(data) + result = ser.isin(i for i in isin) + expected_result = Series([True, True, False]) + + tm.assert_series_equal(result, expected_result) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_isna.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_isna.py new file mode 100644 index 0000000000000000000000000000000000000000..92bf2945cc0d1f5967683a8b7edb35a896388e60 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_isna.py @@ -0,0 +1,36 @@ +""" +We also test Series.notna in this file. +""" + +import numpy as np + +from pandas import ( + Period, + Series, +) +import pandas._testing as tm + + +class TestIsna: + def test_isna_period_dtype(self): + # GH#13737 + ser = Series([Period("2011-01", freq="M"), Period("NaT", freq="M")]) + + expected = Series([False, True]) + + result = ser.isna() + tm.assert_series_equal(result, expected) + + result = ser.notna() + tm.assert_series_equal(result, ~expected) + + def test_isna(self): + ser = Series([0, 5.4, 3, np.nan, -0.001]) + expected = Series([False, False, False, True, False]) + tm.assert_series_equal(ser.isna(), expected) + tm.assert_series_equal(ser.notna(), ~expected) + + ser = Series(["hi", "", np.nan]) + expected = Series([False, False, True]) + tm.assert_series_equal(ser.isna(), expected) + tm.assert_series_equal(ser.notna(), ~expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_item.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_item.py new file mode 100644 index 0000000000000000000000000000000000000000..761ab84485b7987fd2f39ccaa25898b84b6ba96b --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_item.py @@ -0,0 +1,60 @@ +""" +Series.item method, mainly testing that we get python scalars as opposed to +numpy scalars. +""" + +import pytest + +from pandas import ( + Series, + Timedelta, + Timestamp, + date_range, +) + + +class TestItem: + def test_item(self): + # We are testing that we get python scalars as opposed to numpy scalars + ser = Series([1]) + result = ser.item() + assert result == 1 + assert result == ser.iloc[0] + assert isinstance(result, int) # i.e. not np.int64 + + ser = Series([0.5], index=[3]) + result = ser.item() + assert type(result) == float + assert result == 0.5 + + ser = Series([1, 2]) + msg = "can only convert an array of size 1" + with pytest.raises(ValueError, match=msg): + ser.item() + + dti = date_range("2016-01-01", periods=2) + with pytest.raises(ValueError, match=msg): + dti.item() + with pytest.raises(ValueError, match=msg): + Series(dti).item() + + val = dti[:1].item() + assert isinstance(val, Timestamp) + val = Series(dti)[:1].item() + assert isinstance(val, Timestamp) + + tdi = dti - dti + with pytest.raises(ValueError, match=msg): + tdi.item() + with pytest.raises(ValueError, match=msg): + Series(tdi).item() + + val = tdi[:1].item() + assert isinstance(val, Timedelta) + val = Series(tdi)[:1].item() + assert isinstance(val, Timedelta) + + # Case where ser[0] would not work + ser = Series(dti, index=[5, 6]) + val = ser.iloc[:1].item() + assert val == dti[0] diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_map.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_map.py new file mode 100644 index 0000000000000000000000000000000000000000..bfda028b58011c922f52f904f6d98d05e955b48f --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_map.py @@ -0,0 +1,679 @@ +from collections import ( + Counter, + defaultdict, +) +from decimal import Decimal +import math + +import numpy as np +import pytest + +from pandas.errors import Pandas4Warning +import pandas.util._test_decorators as td + +import pandas as pd +from pandas import ( + DataFrame, + Index, + MultiIndex, + Series, + bdate_range, + date_range, + isna, + timedelta_range, +) +import pandas._testing as tm + +# The fixture it's mostly used in pandas/tests/apply, so it's defined in that +# conftest, which is out of scope here. So we need to manually import +from pandas.tests.apply.conftest import engine # noqa: F401 + + +def test_series_map_box_timedelta(): + # GH#11349 + ser = Series(timedelta_range("1 day 1 s", periods=5, freq="h")) + + def f(x): + return x.total_seconds() + + ser.map(f) + + +def test_map_callable(datetime_series, engine): # noqa: F811 + with np.errstate(all="ignore"): + tm.assert_series_equal( + datetime_series.map(np.sqrt, engine=engine), np.sqrt(datetime_series) + ) + + # map function element-wise + tm.assert_series_equal( + datetime_series.map(math.exp, engine=engine), np.exp(datetime_series) + ) + + # empty series + s = Series(dtype=object, name="foo", index=Index([], name="bar")) + rs = s.map(lambda x: x, engine=engine) + tm.assert_series_equal(s, rs) + + # check all metadata (GH 9322) + assert s is not rs + assert s.index is rs.index + assert s.dtype == rs.dtype + assert s.name == rs.name + + # index but no data + s = Series(index=[1, 2, 3], dtype=np.float64) + rs = s.map(lambda x: x, engine=engine) + tm.assert_series_equal(s, rs) + + +def test_map_same_length_inference_bug(): + s = Series([1, 2]) + + def f(x): + return (x, x + 1) + + s = Series([1, 2, 3]) + result = s.map(f) + expected = Series([(1, 2), (2, 3), (3, 4)]) + tm.assert_series_equal(result, expected) + + s = Series(["foo,bar"]) + result = s.map(lambda x: x.split(",")) + expected = Series([("foo", "bar")]) + tm.assert_series_equal(result, expected) + + +def test_series_map_box_timestamps(): + # GH#2689, GH#2627 + ser = Series(date_range("1/1/2000", periods=3)) + + def func(x): + return (x.hour, x.day, x.month) + + result = ser.map(func) + expected = Series([(0, 1, 1), (0, 2, 1), (0, 3, 1)]) + tm.assert_series_equal(result, expected) + + +def test_map_series_stringdtype(any_string_dtype, using_infer_string): + # map test on StringDType, GH#40823 + ser1 = Series( + data=["cat", "dog", "rabbit"], + index=["id1", "id2", "id3"], + dtype=any_string_dtype, + ) + ser2 = Series(["id3", "id2", "id1", "id7000"], dtype=any_string_dtype) + result = ser2.map(ser1) + + item = pd.NA + if ser2.dtype == object: + item = np.nan + + expected = Series(data=["rabbit", "dog", "cat", item], dtype=any_string_dtype) + if using_infer_string and any_string_dtype == "object": + expected = expected.astype("str") + + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "data, expected_dtype", + [(["1-1", "1-1", np.nan], "category"), (["1-1", "1-2", np.nan], "str")], +) +def test_map_categorical_with_nan_values(data, expected_dtype): + # GH 20714 bug fixed in: GH 24275 + def func(val): + return val.split("-")[0] + + s = Series(data, dtype="category") + + result = s.map(func, na_action="ignore") + expected = Series(["1", "1", np.nan], dtype=expected_dtype) + tm.assert_series_equal(result, expected) + + +def test_map_empty_integer_series(): + # GH52384 + s = Series([], dtype=int) + result = s.map(lambda x: x) + tm.assert_series_equal(result, s) + + +def test_map_empty_integer_series_with_datetime_index(): + # GH 21245 + s = Series([], index=date_range(start="2018-01-01", periods=0), dtype=int) + result = s.map(lambda x: x) + tm.assert_series_equal(result, s) + + +@pytest.mark.parametrize("func", [str, lambda x: str(x)]) +def test_map_simple_str_callables_same_as_astype( + string_series, func, using_infer_string +): + # test that we are evaluating row-by-row first + # before vectorized evaluation + result = string_series.map(func) + expected = string_series.astype(str if not using_infer_string else "str") + tm.assert_series_equal(result, expected) + + +def test_list_raises(string_series): + with pytest.raises(TypeError, match="'list' object is not callable"): + string_series.map([lambda x: x]) + + +def test_map(): + data = { + "A": [0.0, 1.0, 2.0, 3.0, 4.0], + "B": [0.0, 1.0, 0.0, 1.0, 0.0], + "C": ["foo1", "foo2", "foo3", "foo4", "foo5"], + "D": bdate_range("1/1/2009", periods=5), + } + + source = Series(data["B"], index=data["C"]) + target = Series(data["C"][:4], index=data["D"][:4]) + + merged = target.map(source) + + for k, v in merged.items(): + assert v == source[target[k]] + + # input could be a dict + merged = target.map(source.to_dict()) + + for k, v in merged.items(): + assert v == source[target[k]] + + +def test_map_datetime(datetime_series): + # function + result = datetime_series.map(lambda x: x * 2) + tm.assert_series_equal(result, datetime_series * 2) + + +def test_map_category(): + # GH 10324 + a = Series([1, 2, 3, 4]) + b = Series(["even", "odd", "even", "odd"], dtype="category") + c = Series(["even", "odd", "even", "odd"]) + + exp = Series(["odd", "even", "odd", np.nan], dtype="category") + tm.assert_series_equal(a.map(b), exp) + exp = Series(["odd", "even", "odd", np.nan]) + tm.assert_series_equal(a.map(c), exp) + + +def test_map_category_numeric(): + a = Series(["a", "b", "c", "d"]) + b = Series([1, 2, 3, 4], index=pd.CategoricalIndex(["b", "c", "d", "e"])) + c = Series([1, 2, 3, 4], index=Index(["b", "c", "d", "e"])) + + exp = Series([np.nan, 1, 2, 3]) + tm.assert_series_equal(a.map(b), exp) + exp = Series([np.nan, 1, 2, 3]) + tm.assert_series_equal(a.map(c), exp) + + +def test_map_category_string(): + a = Series(["a", "b", "c", "d"]) + b = Series( + ["B", "C", "D", "E"], + dtype="category", + index=pd.CategoricalIndex(["b", "c", "d", "e"]), + ) + c = Series(["B", "C", "D", "E"], index=Index(["b", "c", "d", "e"])) + + exp = Series( + pd.Categorical([np.nan, "B", "C", "D"], categories=["B", "C", "D", "E"]) + ) + tm.assert_series_equal(a.map(b), exp) + exp = Series([np.nan, "B", "C", "D"]) + tm.assert_series_equal(a.map(c), exp) + + +def test_map_empty(request, index): + if isinstance(index, MultiIndex): + request.applymarker( + pytest.mark.xfail( + reason="Initializing a Series from a MultiIndex is not supported" + ) + ) + + s = Series(index) + result = s.map({}) + + expected = Series(np.nan, index=s.index) + tm.assert_series_equal(result, expected) + + +def test_map_compat(): + # related GH 8024 + s = Series([True, True, False], index=[1, 2, 3]) + result = s.map({True: "foo", False: "bar"}) + expected = Series(["foo", "foo", "bar"], index=[1, 2, 3]) + tm.assert_series_equal(result, expected) + + +def test_map_int(): + left = Series({"a": 1.0, "b": 2.0, "c": 3.0, "d": 4}) + right = Series({1: 11, 2: 22, 3: 33}) + + assert left.dtype == np.float64 + assert issubclass(right.dtype.type, np.integer) + + merged = left.map(right) + assert merged.dtype == np.float64 + assert isna(merged["d"]) + assert not isna(merged["c"]) + + +def test_map_type_inference(): + s = Series(range(3)) + s2 = s.map(lambda x: np.where(x == 0, 0, 1)) + assert issubclass(s2.dtype.type, np.integer) + + +def test_map_decimal(string_series): + result = string_series.map(lambda x: Decimal(str(x))) + assert result.dtype == np.object_ + assert isinstance(result.iloc[0], Decimal) + + +def test_map_na_exclusion(engine): # noqa: F811 + s = Series([1.5, np.nan, 3, np.nan, 5]) + + result = s.map(lambda x: x * 2, na_action="ignore", engine=engine) + exp = s * 2 + tm.assert_series_equal(result, exp) + + +def test_map_dict_with_tuple_keys(): + """ + Due to new MultiIndex-ing behaviour in v0.14.0, + dicts with tuple keys passed to map were being + converted to a multi-index, preventing tuple values + from being mapped properly. + """ + # GH 18496 + df = DataFrame({"a": [(1,), (2,), (3, 4), (5, 6)]}) + label_mappings = {(1,): "A", (2,): "B", (3, 4): "A", (5, 6): "B"} + + df["labels"] = df["a"].map(label_mappings) + df["expected_labels"] = Series(["A", "B", "A", "B"], index=df.index) + # All labels should be filled now + tm.assert_series_equal(df["labels"], df["expected_labels"], check_names=False) + + +def test_map_counter(): + s = Series(["a", "b", "c"], index=[1, 2, 3]) + counter = Counter() + counter["b"] = 5 + counter["c"] += 1 + result = s.map(counter) + expected = Series([0, 5, 1], index=[1, 2, 3]) + tm.assert_series_equal(result, expected) + + +def test_map_defaultdict(): + s = Series([1, 2, 3], index=["a", "b", "c"]) + default_dict = defaultdict(lambda: "blank") + default_dict[1] = "stuff" + result = s.map(default_dict) + expected = Series(["stuff", "blank", "blank"], index=["a", "b", "c"]) + tm.assert_series_equal(result, expected) + + +def test_map_dict_na_key(): + # https://github.com/pandas-dev/pandas/issues/17648 + # Checks that np.nan key is appropriately mapped + s = Series([1, 2, np.nan]) + expected = Series(["a", "b", "c"]) + result = s.map({1: "a", 2: "b", np.nan: "c"}) + tm.assert_series_equal(result, expected) + + +def test_map_defaultdict_na_key(na_action): + # GH 48813 + s = Series([1, 2, np.nan]) + default_map = defaultdict(lambda: "missing", {1: "a", 2: "b", np.nan: "c"}) + result = s.map(default_map, na_action=na_action) + expected = Series({0: "a", 1: "b", 2: "c" if na_action is None else np.nan}) + tm.assert_series_equal(result, expected) + + +def test_map_defaultdict_missing_key(na_action): + # GH 48813 + s = Series([1, 2, np.nan]) + default_map = defaultdict(lambda: "missing", {1: "a", 2: "b", 3: "c"}) + result = s.map(default_map, na_action=na_action) + expected = Series({0: "a", 1: "b", 2: "missing" if na_action is None else np.nan}) + tm.assert_series_equal(result, expected) + + +def test_map_defaultdict_unmutated(na_action): + # GH 48813 + s = Series([1, 2, np.nan]) + default_map = defaultdict(lambda: "missing", {1: "a", 2: "b", np.nan: "c"}) + expected_default_map = default_map.copy() + s.map(default_map, na_action=na_action) + assert default_map == expected_default_map + + +@pytest.mark.parametrize("arg_func", [dict, Series]) +def test_map_dict_ignore_na(arg_func): + # GH#47527 + mapping = arg_func({1: 10, np.nan: 42}) + ser = Series([1, np.nan, 2]) + result = ser.map(mapping, na_action="ignore") + expected = Series([10, np.nan, np.nan]) + tm.assert_series_equal(result, expected) + + +def test_map_defaultdict_ignore_na(): + # GH#47527 + mapping = defaultdict(int, {1: 10, np.nan: 42}) + ser = Series([1, np.nan, 2]) + result = ser.map(mapping) + expected = Series([10, 42, 0]) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "na_action, expected", + [(None, Series([10.0, 42.0, np.nan])), ("ignore", Series([10, np.nan, np.nan]))], +) +def test_map_categorical_na_ignore(na_action, expected): + # GH#47527 + values = pd.Categorical([1, np.nan, 2], categories=[10, 1, 2]) + ser = Series(values) + result = ser.map({1: 10, np.nan: 42}, na_action=na_action) + tm.assert_series_equal(result, expected) + + +def test_map_dict_subclass_with_missing(): + """ + Test Series.map with a dictionary subclass that defines __missing__, + i.e. sets a default value (GH #15999). + """ + + class DictWithMissing(dict): + def __missing__(self, key): + return "missing" + + s = Series([1, 2, 3]) + dictionary = DictWithMissing({3: "three"}) + result = s.map(dictionary) + expected = Series(["missing", "missing", "three"]) + tm.assert_series_equal(result, expected) + + +def test_map_dict_subclass_without_missing(): + class DictWithoutMissing(dict): + pass + + s = Series([1, 2, 3]) + dictionary = DictWithoutMissing({3: "three"}) + result = s.map(dictionary) + expected = Series([np.nan, np.nan, "three"]) + tm.assert_series_equal(result, expected) + + +def test_map_abc_mapping(non_dict_mapping_subclass): + # https://github.com/pandas-dev/pandas/issues/29733 + # Check collections.abc.Mapping support as mapper for Series.map + s = Series([1, 2, 3]) + not_a_dictionary = non_dict_mapping_subclass({3: "three"}) + result = s.map(not_a_dictionary) + expected = Series([np.nan, np.nan, "three"]) + tm.assert_series_equal(result, expected) + + +def test_map_abc_mapping_with_missing(non_dict_mapping_subclass): + # https://github.com/pandas-dev/pandas/issues/29733 + # Check collections.abc.Mapping support as mapper for Series.map + class NonDictMappingWithMissing(non_dict_mapping_subclass): + def __missing__(self, key): + return "missing" + + s = Series([1, 2, 3]) + not_a_dictionary = NonDictMappingWithMissing({3: "three"}) + result = s.map(not_a_dictionary) + # __missing__ is a dict concept, not a Mapping concept, + # so it should not change the result! + expected = Series([np.nan, np.nan, "three"]) + tm.assert_series_equal(result, expected) + + +def test_map_box_dt64(unit): + vals = [pd.Timestamp("2011-01-01"), pd.Timestamp("2011-01-02")] + ser = Series(vals).dt.as_unit(unit) + assert ser.dtype == f"datetime64[{unit}]" + # boxed value must be Timestamp instance + res = ser.map(lambda x: f"{type(x).__name__}_{x.day}_{x.tz}") + exp = Series(["Timestamp_1_None", "Timestamp_2_None"]) + tm.assert_series_equal(res, exp) + + +def test_map_box_dt64tz(unit): + vals = [ + pd.Timestamp("2011-01-01", tz="US/Eastern"), + pd.Timestamp("2011-01-02", tz="US/Eastern"), + ] + ser = Series(vals).dt.as_unit(unit) + assert ser.dtype == f"datetime64[{unit}, US/Eastern]" + res = ser.map(lambda x: f"{type(x).__name__}_{x.day}_{x.tz}") + exp = Series(["Timestamp_1_US/Eastern", "Timestamp_2_US/Eastern"]) + tm.assert_series_equal(res, exp) + + +def test_map_box_td64(unit): + # timedelta + vals = [pd.Timedelta("1 days"), pd.Timedelta("2 days")] + ser = Series(vals).dt.as_unit(unit) + assert ser.dtype == f"timedelta64[{unit}]" + res = ser.map(lambda x: f"{type(x).__name__}_{x.days}") + exp = Series(["Timedelta_1", "Timedelta_2"]) + tm.assert_series_equal(res, exp) + + +def test_map_box_period(): + # period + vals = [pd.Period("2011-01-01", freq="M"), pd.Period("2011-01-02", freq="M")] + ser = Series(vals) + assert ser.dtype == "Period[M]" + res = ser.map(lambda x: f"{type(x).__name__}_{x.freqstr}") + exp = Series(["Period_M", "Period_M"]) + tm.assert_series_equal(res, exp) + + +def test_map_categorical(na_action, using_infer_string): + values = pd.Categorical(list("ABBABCD"), categories=list("DCBA"), ordered=True) + s = Series(values, name="XX", index=list("abcdefg")) + + result = s.map(lambda x: x.lower(), na_action=na_action) + exp_values = pd.Categorical(list("abbabcd"), categories=list("dcba"), ordered=True) + exp = Series(exp_values, name="XX", index=list("abcdefg")) + tm.assert_series_equal(result, exp) + tm.assert_categorical_equal(result.values, exp_values) + + result = s.map(lambda x: "A", na_action=na_action) + exp = Series(["A"] * 7, name="XX", index=list("abcdefg")) + tm.assert_series_equal(result, exp) + assert result.dtype == object if not using_infer_string else "str" + + +@pytest.mark.parametrize( + "na_action, expected", + ( + [None, Series(["A", "B", "nan"], name="XX")], + [ + "ignore", + Series( + ["A", "B", np.nan], + name="XX", + dtype=pd.CategoricalDtype(list("DCBA"), True), + ), + ], + ), +) +def test_map_categorical_na_action(na_action, expected): + dtype = pd.CategoricalDtype(list("DCBA"), ordered=True) + values = pd.Categorical([*list("AB"), np.nan], dtype=dtype) + s = Series(values, name="XX") + result = s.map(str, na_action=na_action) + tm.assert_series_equal(result, expected) + + +def test_map_datetimetz(): + values = date_range("2011-01-01", "2011-01-02", freq="h").tz_localize("Asia/Tokyo") + s = Series(values, name="XX") + + # keep tz + result = s.map(lambda x: x + pd.offsets.Day()) + exp_values = date_range("2011-01-02", "2011-01-03", freq="h").tz_localize( + "Asia/Tokyo" + ) + exp = Series(exp_values, name="XX") + tm.assert_series_equal(result, exp) + + result = s.map(lambda x: x.hour) + exp = Series([*list(range(24)), 0], name="XX", dtype=np.int64) + tm.assert_series_equal(result, exp) + + # not vectorized + def f(x): + if not isinstance(x, pd.Timestamp): + raise ValueError + return str(x.tz) + + result = s.map(f) + exp = Series(["Asia/Tokyo"] * 25, name="XX") + tm.assert_series_equal(result, exp) + + +@pytest.mark.parametrize( + "vals,mapping,exp", + [ + (list("abc"), {np.nan: "not NaN"}, [np.nan] * 3 + ["not NaN"]), + (list("abc"), {"a": "a letter"}, ["a letter"] + [np.nan] * 3), + (list(range(3)), {0: 42}, [42] + [np.nan] * 3), + ], +) +def test_map_missing_mixed(vals, mapping, exp): + # GH20495 + s = Series([*vals, np.nan]) + result = s.map(mapping) + exp = Series(exp) + tm.assert_series_equal(result, exp) + + +def test_map_scalar_on_date_time_index_aware_series(): + # GH 25959 + # Calling map on a localized time series should not cause an error + series = Series( + np.arange(10, dtype=np.float64), + index=date_range("2020-01-01", periods=10, tz="UTC"), + name="ts", + ) + result = Series(series.index).map(lambda x: 1) + tm.assert_series_equal(result, Series(np.ones(len(series)), dtype="int64")) + + +def test_map_float_to_string_precision(): + # GH 13228 + ser = Series(1 / 3) + result = ser.map(lambda val: str(val)).to_dict() + expected = {0: "0.3333333333333333"} + assert result == expected + + +def test_map_to_timedelta(): + list_of_valid_strings = ["00:00:01", "00:00:02"] + a = pd.to_timedelta(list_of_valid_strings) + b = Series(list_of_valid_strings).map(pd.to_timedelta) + tm.assert_series_equal(Series(a), b) + + list_of_strings = ["00:00:01", np.nan, pd.NaT, pd.NaT] + + a = pd.to_timedelta(list_of_strings) + ser = Series(list_of_strings) + b = ser.map(pd.to_timedelta) + tm.assert_series_equal(Series(a), b) + + +def test_map_type(): + # GH 46719 + s = Series([3, "string", float], index=["a", "b", "c"]) + result = s.map(type) + expected = Series([int, str, type], index=["a", "b", "c"]) + tm.assert_series_equal(result, expected) + + +def test_map_kwargs(): + # GH 59814 + result = Series([2, 4, 5]).map(lambda x, y: x + y, y=2) + expected = Series([4, 6, 7]) + tm.assert_series_equal(result, expected) + + +def test_map_arg_as_kwarg(): + with tm.assert_produces_warning( + Pandas4Warning, match="`arg` has been renamed to `func`" + ): + Series([1, 2]).map(arg={}) + + +def test_map_func_and_arg(): + # `arg`is considered a normal kwarg that should be passed to the function + result = Series([1, 2]).map(lambda _, arg: arg, arg=3) + expected = Series([3, 3]) + tm.assert_series_equal(result, expected) + + +def test_map_no_func_or_arg(): + with pytest.raises(ValueError, match="The `func` parameter is required"): + Series([1, 2]).map() + + +def test_map_func_is_none(): + with pytest.raises(ValueError, match="The `func` parameter is required"): + Series([1, 2]).map(func=None) + + +@pytest.mark.parametrize("func", [{}, {1: 2}, Series([3, 4])]) +def test_map_engine_no_function(func): + s = Series([1, 2]) + + with pytest.raises(ValueError, match="engine argument can only be specified"): + s.map(func, engine="something") + + +def test_map_engine_not_executor(): + s = Series([1, 2]) + + with pytest.raises(ValueError, match="Not a valid engine: 'something'"): + s.map(lambda x: x, engine="something") + + +@td.skip_if_no("pyarrow") +@pytest.mark.parametrize("as_td", [True, False]) +def test_map_pyarrow_timestamp(as_td): + # GH#61231 + dti = date_range("2018-01-01 00:00:00", "2018-01-07 00:00:00") + ser = Series(dti, dtype="timestamp[ns][pyarrow]", name="a") + if as_td: + # duration dtype + ser = ser - ser[0] + + mapper = {date: i for i, date in enumerate(ser)} + + res_series = ser.map(mapper) + expected = Series(range(len(ser)), name="a", dtype="int64") + tm.assert_series_equal(res_series, expected) + + res_index = Index(ser).map(mapper) + # For now (as of 2025-09-06) at least, we do inference on Index.map that + # we don't for Series.map + expected_index = Index(expected).astype("int64[pyarrow]") + tm.assert_index_equal(res_index, expected_index) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_matmul.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_matmul.py new file mode 100644 index 0000000000000000000000000000000000000000..4ca3ad3f7031e20b8d6cec36caadbcefef8c4196 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_matmul.py @@ -0,0 +1,82 @@ +import operator + +import numpy as np +import pytest + +from pandas import ( + DataFrame, + Series, +) +import pandas._testing as tm + + +class TestMatmul: + def test_matmul(self): + # matmul test is for GH#10259 + a = Series( + np.random.default_rng(2).standard_normal(4), index=["p", "q", "r", "s"] + ) + b = DataFrame( + np.random.default_rng(2).standard_normal((3, 4)), + index=["1", "2", "3"], + columns=["p", "q", "r", "s"], + ).T + + # Series @ DataFrame -> Series + result = operator.matmul(a, b) + expected = Series(np.dot(a.values, b.values), index=["1", "2", "3"]) + tm.assert_series_equal(result, expected) + + # DataFrame @ Series -> Series + result = operator.matmul(b.T, a) + expected = Series(np.dot(b.T.values, a.T.values), index=["1", "2", "3"]) + tm.assert_series_equal(result, expected) + + # Series @ Series -> scalar + result = operator.matmul(a, a) + expected = np.dot(a.values, a.values) + tm.assert_almost_equal(result, expected) + + # GH#21530 + # vector (1D np.array) @ Series (__rmatmul__) + result = operator.matmul(a.values, a) + expected = np.dot(a.values, a.values) + tm.assert_almost_equal(result, expected) + + # GH#21530 + # vector (1D list) @ Series (__rmatmul__) + result = operator.matmul(a.values.tolist(), a) + expected = np.dot(a.values, a.values) + tm.assert_almost_equal(result, expected) + + # GH#21530 + # matrix (2D np.array) @ Series (__rmatmul__) + result = operator.matmul(b.T.values, a) + expected = np.dot(b.T.values, a.values) + tm.assert_almost_equal(result, expected) + + # GH#21530 + # matrix (2D nested lists) @ Series (__rmatmul__) + result = operator.matmul(b.T.values.tolist(), a) + expected = np.dot(b.T.values, a.values) + tm.assert_almost_equal(result, expected) + + # mixed dtype DataFrame @ Series + a["p"] = int(a.p) + result = operator.matmul(b.T, a) + expected = Series(np.dot(b.T.values, a.T.values), index=["1", "2", "3"]) + tm.assert_series_equal(result, expected) + + # different dtypes DataFrame @ Series + a = a.astype(int) + result = operator.matmul(b.T, a) + expected = Series(np.dot(b.T.values, a.T.values), index=["1", "2", "3"]) + tm.assert_series_equal(result, expected) + + msg = r"Dot product shape mismatch, \(4,\) vs \(3,\)" + # exception raised is of type Exception + with pytest.raises(Exception, match=msg): + a.dot(a.values[:3]) + msg = "matrices are not aligned" + with pytest.raises(ValueError, match=msg): + a.dot(b.T) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_nlargest.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_nlargest.py new file mode 100644 index 0000000000000000000000000000000000000000..67ba1d7ca51b70325610aadfb48f60c6a3d68741 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_nlargest.py @@ -0,0 +1,202 @@ +""" +Note: for naming purposes, most tests are title with as e.g. "test_nlargest_foo" +but are implicitly also testing nsmallest_foo. +""" + +import numpy as np +import pytest + +import pandas as pd +from pandas import Series +import pandas._testing as tm + + +def assert_check_nselect_boundary(vals, dtype, method): + # helper function for 'test_boundary_{dtype}' tests + ser = Series(vals, dtype=dtype) + result = getattr(ser, method)(3) + expected_idxr = range(3) if method == "nsmallest" else range(3, 0, -1) + expected = ser.loc[expected_idxr] + tm.assert_series_equal(result, expected) + + +class TestSeriesNLargestNSmallest: + @pytest.mark.parametrize( + "r", + [ + Series([3.0, 2, 1, 2, "5"], dtype="object"), + Series([3.0, 2, 1, 2, 5], dtype="object"), + # not supported on some archs + # Series([3., 2, 1, 2, 5], dtype='complex256'), + Series([3.0, 2, 1, 2, 5], dtype="complex128"), + Series(list("abcde")), + Series(list("abcde"), dtype="category"), + ], + ) + @pytest.mark.parametrize("method", ["nlargest", "nsmallest"]) + @pytest.mark.parametrize("arg", [2, 5, 0, -1]) + def test_nlargest_error(self, r, method, arg): + dt = r.dtype + msg = f"Cannot use method 'n(largest|smallest)' with dtype {dt}" + with pytest.raises(TypeError, match=msg): + getattr(r, method)(arg) + + @pytest.mark.parametrize( + "data", + [ + pd.to_datetime(["2003", "2002", "2001", "2002", "2005"]), + pd.to_datetime(["2003", "2002", "2001", "2002", "2005"], utc=True), + pd.to_timedelta(["3D", "2D", "1D", "2D", "5D"]), + np.array([3, 2, 1, 2, 5], dtype="int8"), + np.array([3, 2, 1, 2, 5], dtype="int16"), + np.array([3, 2, 1, 2, 5], dtype="int32"), + np.array([3, 2, 1, 2, 5], dtype="int64"), + np.array([3, 2, 1, 2, 5], dtype="uint8"), + np.array([3, 2, 1, 2, 5], dtype="uint16"), + np.array([3, 2, 1, 2, 5], dtype="uint32"), + np.array([3, 2, 1, 2, 5], dtype="uint64"), + np.array([3, 2, 1, 2, 5], dtype="float32"), + np.array([3, 2, 1, 2, 5], dtype="float64"), + ], + ) + def test_nsmallest_nlargest(self, data): + # float, int, datetime64 (use i8), timedelts64 (same), + # object that are numbers, object that are strings + ser = Series(data) + + tm.assert_series_equal(ser.nsmallest(2), ser.iloc[[2, 1]]) + tm.assert_series_equal(ser.nsmallest(2, keep="last"), ser.iloc[[2, 3]]) + + empty = ser.iloc[0:0] + tm.assert_series_equal(ser.nsmallest(0), empty) + tm.assert_series_equal(ser.nsmallest(-1), empty) + tm.assert_series_equal(ser.nlargest(0), empty) + tm.assert_series_equal(ser.nlargest(-1), empty) + + tm.assert_series_equal(ser.nsmallest(len(ser)), ser.sort_values()) + tm.assert_series_equal(ser.nsmallest(len(ser) + 1), ser.sort_values()) + tm.assert_series_equal(ser.nlargest(len(ser)), ser.iloc[[4, 0, 1, 3, 2]]) + tm.assert_series_equal(ser.nlargest(len(ser) + 1), ser.iloc[[4, 0, 1, 3, 2]]) + + def test_nlargest_misc(self): + ser = Series([3.0, np.nan, 1, 2, 5]) + result = ser.nlargest() + expected = ser.iloc[[4, 0, 3, 2, 1]] + tm.assert_series_equal(result, expected) + result = ser.nsmallest() + expected = ser.iloc[[2, 3, 0, 4, 1]] + tm.assert_series_equal(result, expected) + + msg = 'keep must be either "first", "last"' + with pytest.raises(ValueError, match=msg): + ser.nsmallest(keep="invalid") + with pytest.raises(ValueError, match=msg): + ser.nlargest(keep="invalid") + + # GH#15297 + ser = Series([1] * 5, index=[1, 2, 3, 4, 5]) + expected_first = Series([1] * 3, index=[1, 2, 3]) + expected_last = Series([1] * 3, index=[5, 4, 3]) + + result = ser.nsmallest(3) + tm.assert_series_equal(result, expected_first) + + result = ser.nsmallest(3, keep="last") + tm.assert_series_equal(result, expected_last) + + result = ser.nlargest(3) + tm.assert_series_equal(result, expected_first) + + result = ser.nlargest(3, keep="last") + tm.assert_series_equal(result, expected_last) + + @pytest.mark.parametrize("n", range(1, 5)) + def test_nlargest_n(self, n): + # GH 13412 + ser = Series([1, 4, 3, 2], index=[0, 0, 1, 1]) + result = ser.nlargest(n) + expected = ser.sort_values(ascending=False).head(n) + tm.assert_series_equal(result, expected) + + result = ser.nsmallest(n) + expected = ser.sort_values().head(n) + tm.assert_series_equal(result, expected) + + def test_nlargest_boundary_integer(self, nselect_method, any_int_numpy_dtype): + # GH#21426 + dtype_info = np.iinfo(any_int_numpy_dtype) + min_val, max_val = dtype_info.min, dtype_info.max + vals = [min_val, min_val + 1, max_val - 1, max_val] + assert_check_nselect_boundary(vals, any_int_numpy_dtype, nselect_method) + + def test_nlargest_boundary_float(self, nselect_method, float_numpy_dtype): + # GH#21426 + dtype_info = np.finfo(float_numpy_dtype) + min_val, max_val = dtype_info.min, dtype_info.max + min_2nd, max_2nd = np.nextafter([min_val, max_val], 0, dtype=float_numpy_dtype) + vals = [min_val, min_2nd, max_2nd, max_val] + assert_check_nselect_boundary(vals, float_numpy_dtype, nselect_method) + + @pytest.mark.parametrize("dtype", ["datetime64[ns]", "timedelta64[ns]"]) + def test_nlargest_boundary_datetimelike(self, nselect_method, dtype): + # GH#21426 + # use int64 bounds and +1 to min_val since true minimum is NaT + # (include min_val/NaT at end to maintain same expected_idxr) + dtype_info = np.iinfo("int64") + min_val, max_val = dtype_info.min, dtype_info.max + vals = [min_val + 1, min_val + 2, max_val - 1, max_val, min_val] + assert_check_nselect_boundary(vals, dtype, nselect_method) + + def test_nlargest_duplicate_keep_all_ties(self): + # see GH#16818 + ser = Series([10, 9, 8, 7, 7, 7, 7, 6]) + result = ser.nlargest(4, keep="all") + expected = Series([10, 9, 8, 7, 7, 7, 7]) + tm.assert_series_equal(result, expected) + + result = ser.nsmallest(2, keep="all") + expected = Series([6, 7, 7, 7, 7], index=[7, 3, 4, 5, 6]) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "data,expected", [([True, False], [True]), ([True, False, True, True], [True])] + ) + def test_nlargest_boolean(self, data, expected): + # GH#26154 : ensure True > False + ser = Series(data) + result = ser.nlargest(1) + expected = Series(expected) + tm.assert_series_equal(result, expected) + + def test_nlargest_nullable(self, any_numeric_ea_dtype): + # GH#42816 + dtype = any_numeric_ea_dtype + if dtype.startswith("UInt"): + # Can't cast from negative float to uint on some platforms + arr = np.random.default_rng(2).integers(1, 10, 10) + else: + arr = np.random.default_rng(2).standard_normal(10) + arr = arr.astype(dtype.lower(), copy=False) + + ser = Series(arr, dtype=dtype, copy=True) + ser[1] = pd.NA + result = ser.nlargest(5) + + expected = ( + Series(np.delete(arr, 1), index=ser.index.delete(1)) + .nlargest(5) + .astype(dtype) + ) + tm.assert_series_equal(result, expected) + + def test_nsmallest_nan_when_keep_is_all(self): + # GH#46589 + s = Series([1, 2, 3, 3, 3, None]) + result = s.nsmallest(3, keep="all") + expected = Series([1.0, 2.0, 3.0, 3.0, 3.0]) + tm.assert_series_equal(result, expected) + + s = Series([1, 2, None, None, None]) + result = s.nsmallest(3, keep="all") + expected = Series([1, 2, None, None, None]) + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_nunique.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_nunique.py new file mode 100644 index 0000000000000000000000000000000000000000..826132eb28162603d03635add59c3ea3da569256 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_nunique.py @@ -0,0 +1,24 @@ +import numpy as np + +from pandas import ( + Categorical, + Series, +) + + +def test_nunique(): + # basics.rst doc example + series = Series(np.random.default_rng(2).standard_normal(500)) + series[20:500] = np.nan + series[10:20] = 5000 + result = series.nunique() + assert result == 11 + + +def test_nunique_categorical(): + # GH#18051 + ser = Series(Categorical([])) + assert ser.nunique() == 0 + + ser = Series(Categorical([np.nan])) + assert ser.nunique() == 0 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_pct_change.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_pct_change.py new file mode 100644 index 0000000000000000000000000000000000000000..6279cf64818b865e7d5a640354497ad45e0b8f1b --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_pct_change.py @@ -0,0 +1,76 @@ +import numpy as np +import pytest + +from pandas import ( + Series, + date_range, +) +import pandas._testing as tm + + +class TestSeriesPctChange: + def test_pct_change(self, datetime_series): + rs = datetime_series.pct_change() + tm.assert_series_equal(rs, datetime_series / datetime_series.shift(1) - 1) + + rs = datetime_series.pct_change(2) + filled = datetime_series.ffill() + tm.assert_series_equal(rs, filled / filled.shift(2) - 1) + + rs = datetime_series.pct_change(freq="5D") + filled = datetime_series.ffill() + tm.assert_series_equal( + rs, (filled / filled.shift(freq="5D") - 1).reindex_like(filled) + ) + + def test_pct_change_with_duplicate_axis(self): + # GH#28664 + common_idx = date_range("2019-11-14", periods=5, freq="D") + result = Series(range(5), common_idx).pct_change(freq="B") + + # the reason that the expected should be like this is documented at PR 28681 + expected = Series([np.nan, np.inf, np.nan, np.nan, 3.0], common_idx) + + tm.assert_series_equal(result, expected) + + def test_pct_change_shift_over_nas(self): + s = Series([1.0, 1.5, np.nan, 2.5, 3.0]) + chg = s.pct_change() + expected = Series([np.nan, 0.5, np.nan, np.nan, 0.2]) + tm.assert_series_equal(chg, expected) + + @pytest.mark.parametrize("freq, periods", [("5B", 5), ("3B", 3), ("14B", 14)]) + def test_pct_change_periods_freq(self, freq, periods, datetime_series): + # GH#7292 + rs_freq = datetime_series.pct_change(freq=freq) + rs_periods = datetime_series.pct_change(periods) + tm.assert_series_equal(rs_freq, rs_periods) + + empty_ts = Series(index=datetime_series.index, dtype=object) + rs_freq = empty_ts.pct_change(freq=freq) + rs_periods = empty_ts.pct_change(periods) + tm.assert_series_equal(rs_freq, rs_periods) + + +def test_pct_change_with_duplicated_indices(): + # GH30463 + s = Series([np.nan, 1, 2, 3, 9, 18], index=["a", "b"] * 3) + result = s.pct_change() + expected = Series([np.nan, np.nan, 1.0, 0.5, 2.0, 1.0], index=["a", "b"] * 3) + tm.assert_series_equal(result, expected) + + +def test_pct_change_no_warning_na_beginning(): + # GH#54981 + ser = Series([None, None, 1, 2, 3]) + result = ser.pct_change() + expected = Series([np.nan, np.nan, np.nan, 1, 0.5]) + tm.assert_series_equal(result, expected) + + +def test_pct_change_empty(): + # GH 57056 + ser = Series([], dtype="float64") + expected = ser.copy() + result = ser.pct_change(periods=0) + tm.assert_series_equal(expected, result) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_pop.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_pop.py new file mode 100644 index 0000000000000000000000000000000000000000..7453f98ab3735e924dd7601622d23b4bafdd2176 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_pop.py @@ -0,0 +1,13 @@ +from pandas import Series +import pandas._testing as tm + + +def test_pop(): + # GH#6600 + ser = Series([0, 4, 0], index=["A", "B", "C"], name=4) + + result = ser.pop("B") + assert result == 4 + + expected = Series([0, 0], index=["A", "C"], name=4) + tm.assert_series_equal(ser, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_quantile.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_quantile.py new file mode 100644 index 0000000000000000000000000000000000000000..4369a0dbe504fc7a056b26640f8a6ddfb5ad275e --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_quantile.py @@ -0,0 +1,254 @@ +import numpy as np +import pytest + +from pandas.core.dtypes.common import is_integer + +import pandas as pd +from pandas import ( + Index, + Series, +) +import pandas._testing as tm +from pandas.core.indexes.datetimes import Timestamp + + +class TestSeriesQuantile: + def test_quantile(self, datetime_series): + q = datetime_series.quantile(0.1) + assert q == np.percentile(datetime_series.dropna(), 10) + + q = datetime_series.quantile(0.9) + assert q == np.percentile(datetime_series.dropna(), 90) + + # object dtype + q = Series(datetime_series, dtype=object).quantile(0.9) + assert q == np.percentile(datetime_series.dropna(), 90) + + # datetime64[ns] dtype + dts = datetime_series.index.to_series() + q = dts.quantile(0.2) + assert q == Timestamp("2000-01-10 19:12:00") + + # timedelta64[ns] dtype + tds = dts.diff() + q = tds.quantile(0.25) + assert q == pd.to_timedelta("24:00:00") + + # GH7661 + result = Series([np.timedelta64("NaT", "ns")]).sum() + assert result == pd.Timedelta(0) + + msg = "percentiles should all be in the interval \\[0, 1\\]" + for invalid in [-1, 2, [0.5, -1], [0.5, 2]]: + with pytest.raises(ValueError, match=msg): + datetime_series.quantile(invalid) + + s = Series(np.random.default_rng(2).standard_normal(100)) + percentile_array = [-0.5, 0.25, 1.5] + with pytest.raises(ValueError, match=msg): + s.quantile(percentile_array) + + def test_quantile_multi(self, datetime_series, unit): + datetime_series.index = datetime_series.index.as_unit(unit) + qs = [0.1, 0.9] + result = datetime_series.quantile(qs) + expected = Series( + [ + np.percentile(datetime_series.dropna(), 10), + np.percentile(datetime_series.dropna(), 90), + ], + index=qs, + name=datetime_series.name, + ) + tm.assert_series_equal(result, expected) + + dts = datetime_series.index.to_series() + dts.name = "xxx" + result = dts.quantile((0.2, 0.2)) + expected = Series( + [Timestamp("2000-01-10 19:12:00"), Timestamp("2000-01-10 19:12:00")], + index=[0.2, 0.2], + name="xxx", + dtype=f"M8[{unit}]", + ) + tm.assert_series_equal(result, expected) + + result = datetime_series.quantile([]) + expected = Series( + [], name=datetime_series.name, index=Index([], dtype=float), dtype="float64" + ) + tm.assert_series_equal(result, expected) + + def test_quantile_interpolation(self, datetime_series): + # see gh-10174 + + # interpolation = linear (default case) + q = datetime_series.quantile(0.1, interpolation="linear") + assert q == np.percentile(datetime_series.dropna(), 10) + q1 = datetime_series.quantile(0.1) + assert q1 == np.percentile(datetime_series.dropna(), 10) + + # test with and without interpolation keyword + assert q == q1 + + def test_quantile_interpolation_dtype(self): + # GH #10174 + + # interpolation = linear (default case) + q = Series([1, 3, 4]).quantile(0.5, interpolation="lower") + assert q == np.percentile(np.array([1, 3, 4]), 50) + assert is_integer(q) + + q = Series([1, 3, 4]).quantile(0.5, interpolation="higher") + assert q == np.percentile(np.array([1, 3, 4]), 50) + assert is_integer(q) + + def test_quantile_nan(self): + # GH 13098 + ser = Series([1, 2, 3, 4, np.nan]) + result = ser.quantile(0.5) + expected = 2.5 + assert result == expected + + # all nan/empty + s1 = Series([], dtype=object) + cases = [s1, Series([np.nan, np.nan])] + + for ser in cases: + res = ser.quantile(0.5) + assert np.isnan(res) + + res = ser.quantile([0.5]) + tm.assert_series_equal(res, Series([np.nan], index=[0.5])) + + res = ser.quantile([0.2, 0.3]) + tm.assert_series_equal(res, Series([np.nan, np.nan], index=[0.2, 0.3])) + + @pytest.mark.parametrize( + "case", + [ + [ + Timestamp("2011-01-01"), + Timestamp("2011-01-02"), + Timestamp("2011-01-03"), + ], + [ + Timestamp("2011-01-01", tz="US/Eastern"), + Timestamp("2011-01-02", tz="US/Eastern"), + Timestamp("2011-01-03", tz="US/Eastern"), + ], + [pd.Timedelta("1 days"), pd.Timedelta("2 days"), pd.Timedelta("3 days")], + # NaT + [ + Timestamp("2011-01-01"), + Timestamp("2011-01-02"), + Timestamp("2011-01-03"), + pd.NaT, + ], + [ + Timestamp("2011-01-01", tz="US/Eastern"), + Timestamp("2011-01-02", tz="US/Eastern"), + Timestamp("2011-01-03", tz="US/Eastern"), + pd.NaT, + ], + [ + pd.Timedelta("1 days"), + pd.Timedelta("2 days"), + pd.Timedelta("3 days"), + pd.NaT, + ], + ], + ) + def test_quantile_box(self, case): + ser = Series(case, name="XXX") + res = ser.quantile(0.5) + assert res == case[1] + + res = ser.quantile([0.5]) + exp = Series([case[1]], index=[0.5], name="XXX") + tm.assert_series_equal(res, exp) + + def test_datetime_timedelta_quantiles(self): + # covers #9694 + assert pd.isna(Series([], dtype="M8[ns]").quantile(0.5)) + assert pd.isna(Series([], dtype="m8[ns]").quantile(0.5)) + + def test_quantile_nat(self): + res = Series([pd.NaT, pd.NaT]).quantile(0.5) + assert res is pd.NaT + + res = Series([pd.NaT, pd.NaT]).quantile([0.5]) + tm.assert_series_equal(res, Series([pd.NaT], index=[0.5])) + + @pytest.mark.parametrize( + "values, dtype", + [([0, 0, 0, 1, 2, 3], "Sparse[int]"), ([0.0, None, 1.0, 2.0], "Sparse[float]")], + ) + def test_quantile_sparse(self, values, dtype): + ser = Series(values, dtype=dtype) + result = ser.quantile([0.5]) + expected = Series(np.asarray(ser)).quantile([0.5]).astype("Sparse[float]") + tm.assert_series_equal(result, expected) + + def test_quantile_empty_float64(self): + # floats + ser = Series([], dtype="float64") + + res = ser.quantile(0.5) + assert np.isnan(res) + + res = ser.quantile([0.5]) + exp = Series([np.nan], index=[0.5]) + tm.assert_series_equal(res, exp) + + def test_quantile_empty_int64(self): + # int + ser = Series([], dtype="int64") + + res = ser.quantile(0.5) + assert np.isnan(res) + + res = ser.quantile([0.5]) + exp = Series([np.nan], index=[0.5]) + tm.assert_series_equal(res, exp) + + def test_quantile_empty_dt64(self): + # datetime + ser = Series([], dtype="datetime64[ns]") + + res = ser.quantile(0.5) + assert res is pd.NaT + + res = ser.quantile([0.5]) + exp = Series([pd.NaT], index=[0.5], dtype=ser.dtype) + tm.assert_series_equal(res, exp) + + @pytest.mark.parametrize("dtype", [int, float, "Int64"]) + def test_quantile_dtypes(self, dtype): + result = Series([1, 2, 3], dtype=dtype).quantile(np.arange(0, 1, 0.25)) + expected = Series(np.arange(1, 3, 0.5), index=np.arange(0, 1, 0.25)) + if dtype == "Int64": + expected = expected.astype("Float64") + tm.assert_series_equal(result, expected) + + def test_quantile_all_na(self, any_int_ea_dtype): + # GH#50681 + ser = Series([pd.NA, pd.NA], dtype=any_int_ea_dtype) + with tm.assert_produces_warning(None): + result = ser.quantile([0.1, 0.5]) + expected = Series([pd.NA, pd.NA], dtype=any_int_ea_dtype, index=[0.1, 0.5]) + tm.assert_series_equal(result, expected) + + def test_quantile_dtype_size(self, any_int_ea_dtype): + # GH#50681 + ser = Series([pd.NA, pd.NA, 1], dtype=any_int_ea_dtype) + result = ser.quantile([0.1, 0.5]) + expected = Series([1, 1], dtype=any_int_ea_dtype, index=[0.1, 0.5]) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize("typ", ["datetime64", "timedelta64"]) +def test_quantile_empty_datetimelike(typ, unit): + ser = Series([], dtype=f"{typ}[{unit}]") + result = ser.quantile() + assert result is pd.NaT diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_rank.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_rank.py new file mode 100644 index 0000000000000000000000000000000000000000..0102bc811de9b53e637bb40cd1589244e3fac9ca --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_rank.py @@ -0,0 +1,625 @@ +from itertools import chain +import operator + +import numpy as np +import pytest + +from pandas._libs.algos import ( + Infinity, + NegInfinity, +) +import pandas.util._test_decorators as td + +from pandas import ( + NA, + NaT, + Series, + Timestamp, + date_range, +) +import pandas._testing as tm +from pandas.api.types import CategoricalDtype + + +@pytest.fixture +def ser(): + return Series([1, 3, 4, 2, np.nan, 2, 1, 5, np.nan, 3]) + + +@pytest.fixture( + params=[ + ["average", np.array([1.5, 5.5, 7.0, 3.5, np.nan, 3.5, 1.5, 8.0, np.nan, 5.5])], + ["min", np.array([1, 5, 7, 3, np.nan, 3, 1, 8, np.nan, 5])], + ["max", np.array([2, 6, 7, 4, np.nan, 4, 2, 8, np.nan, 6])], + ["first", np.array([1, 5, 7, 3, np.nan, 4, 2, 8, np.nan, 6])], + ["dense", np.array([1, 3, 4, 2, np.nan, 2, 1, 5, np.nan, 3])], + ], + ids=lambda x: x[0], +) +def results(request): + return request.param + + +@pytest.fixture( + params=[ + "object", + "float64", + "int64", + "Float64", + "Int64", + pytest.param("float64[pyarrow]", marks=td.skip_if_no("pyarrow")), + pytest.param("int64[pyarrow]", marks=td.skip_if_no("pyarrow")), + pytest.param("string[pyarrow]", marks=td.skip_if_no("pyarrow")), + "string[python]", + "str", + ] +) +def dtype(request): + return request.param + + +def expected_dtype(dtype, method, pct=False): + exp_dtype = "float64" + # elif dtype in ["Int64", "Float64", "string[pyarrow]", "string[python]"]: + if dtype in ["string[pyarrow]"]: + exp_dtype = "Float64" + elif dtype in ["float64[pyarrow]", "int64[pyarrow]"]: + if method == "average" or pct: + exp_dtype = "double[pyarrow]" + else: + exp_dtype = "uint64[pyarrow]" + elif dtype in ["Float64", "Int64"]: + if method == "average" or pct: + exp_dtype = "Float64" + else: + exp_dtype = "UInt64" + + return exp_dtype + + +class TestSeriesRank: + def test_rank(self, datetime_series): + sp_stats = pytest.importorskip("scipy.stats") + + datetime_series[::2] = np.nan + datetime_series[:10:3] = 4.0 + + ranks = datetime_series.rank() + oranks = datetime_series.astype("O").rank() + + tm.assert_series_equal(ranks, oranks) + + mask = np.isnan(datetime_series) + filled = datetime_series.fillna(np.inf) + + # rankdata returns an ndarray + exp = Series(sp_stats.rankdata(filled), index=filled.index, name="ts") + exp[mask] = np.nan + + tm.assert_series_equal(ranks, exp) + + iseries = Series(np.arange(5).repeat(2)) + + iranks = iseries.rank() + exp = iseries.astype(float).rank() + tm.assert_series_equal(iranks, exp) + iseries = Series(np.arange(5)) + 1.0 + exp = iseries / 5.0 + iranks = iseries.rank(pct=True) + + tm.assert_series_equal(iranks, exp) + + iseries = Series(np.repeat(1, 100)) + exp = Series(np.repeat(0.505, 100)) + iranks = iseries.rank(pct=True) + tm.assert_series_equal(iranks, exp) + + # Explicit cast to float to avoid implicit cast when setting nan + iseries = iseries.astype("float") + iseries[1] = np.nan + exp = Series(np.repeat(50.0 / 99.0, 100)) + exp[1] = np.nan + iranks = iseries.rank(pct=True) + tm.assert_series_equal(iranks, exp) + + iseries = Series(np.arange(5)) + 1.0 + iseries[4] = np.nan + exp = iseries / 4.0 + iranks = iseries.rank(pct=True) + tm.assert_series_equal(iranks, exp) + + iseries = Series(np.repeat(np.nan, 100)) + exp = iseries + iranks = iseries.rank(pct=True) + tm.assert_series_equal(iranks, exp) + + # Explicit cast to float to avoid implicit cast when setting nan + iseries = Series(np.arange(5), dtype="float") + 1 + iseries[4] = np.nan + exp = iseries / 4.0 + iranks = iseries.rank(pct=True) + tm.assert_series_equal(iranks, exp) + + rng = date_range("1/1/1990", periods=5) + # Explicit cast to float to avoid implicit cast when setting nan + iseries = Series(np.arange(5), rng, dtype="float") + 1 + iseries.iloc[4] = np.nan + exp = iseries / 4.0 + iranks = iseries.rank(pct=True) + tm.assert_series_equal(iranks, exp) + + iseries = Series([1e-50, 1e-100, 1e-20, 1e-2, 1e-20 + 1e-30, 1e-1]) + exp = Series([2, 1, 3, 5, 4, 6.0]) + iranks = iseries.rank() + tm.assert_series_equal(iranks, exp) + + # GH 5968 + iseries = Series(["3 day", "1 day 10m", "-2 day", NaT], dtype="m8[ns]") + exp = Series([3, 2, 1, np.nan]) + iranks = iseries.rank() + tm.assert_series_equal(iranks, exp) + + values = np.array( + [-50, -1, -1e-20, -1e-25, -1e-50, 0, 1e-40, 1e-20, 1e-10, 2, 40], + dtype="float64", + ) + random_order = np.random.default_rng(2).permutation(len(values)) + iseries = Series(values[random_order]) + exp = Series(random_order + 1.0, dtype="float64") + iranks = iseries.rank() + tm.assert_series_equal(iranks, exp) + + def test_rank_categorical(self): + # GH issue #15420 rank incorrectly orders ordered categories + + # Test ascending/descending ranking for ordered categoricals + exp = Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) + exp_desc = Series([6.0, 5.0, 4.0, 3.0, 2.0, 1.0]) + ordered = Series( + ["first", "second", "third", "fourth", "fifth", "sixth"] + ).astype( + CategoricalDtype( + categories=["first", "second", "third", "fourth", "fifth", "sixth"], + ordered=True, + ) + ) + tm.assert_series_equal(ordered.rank(), exp) + tm.assert_series_equal(ordered.rank(ascending=False), exp_desc) + + # Unordered categoricals should be ranked as objects + unordered = Series( + ["first", "second", "third", "fourth", "fifth", "sixth"] + ).astype( + CategoricalDtype( + categories=["first", "second", "third", "fourth", "fifth", "sixth"], + ordered=False, + ) + ) + exp_unordered = Series([2.0, 4.0, 6.0, 3.0, 1.0, 5.0]) + res = unordered.rank() + tm.assert_series_equal(res, exp_unordered) + + unordered1 = Series([1, 2, 3, 4, 5, 6]).astype( + CategoricalDtype([1, 2, 3, 4, 5, 6], False) + ) + exp_unordered1 = Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) + res1 = unordered1.rank() + tm.assert_series_equal(res1, exp_unordered1) + + # Test na_option for rank data + na_ser = Series( + ["first", "second", "third", "fourth", "fifth", "sixth", np.nan] + ).astype( + CategoricalDtype( + ["first", "second", "third", "fourth", "fifth", "sixth", "seventh"], + True, + ) + ) + + exp_top = Series([2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 1.0]) + exp_bot = Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]) + exp_keep = Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, np.nan]) + + tm.assert_series_equal(na_ser.rank(na_option="top"), exp_top) + tm.assert_series_equal(na_ser.rank(na_option="bottom"), exp_bot) + tm.assert_series_equal(na_ser.rank(na_option="keep"), exp_keep) + + # Test na_option for rank data with ascending False + exp_top = Series([7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0]) + exp_bot = Series([6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 7.0]) + exp_keep = Series([6.0, 5.0, 4.0, 3.0, 2.0, 1.0, np.nan]) + + tm.assert_series_equal(na_ser.rank(na_option="top", ascending=False), exp_top) + tm.assert_series_equal( + na_ser.rank(na_option="bottom", ascending=False), exp_bot + ) + tm.assert_series_equal(na_ser.rank(na_option="keep", ascending=False), exp_keep) + + # Test invalid values for na_option + msg = "na_option must be one of 'keep', 'top', or 'bottom'" + + with pytest.raises(ValueError, match=msg): + na_ser.rank(na_option="bad", ascending=False) + + # invalid type + with pytest.raises(ValueError, match=msg): + na_ser.rank(na_option=True, ascending=False) + + # Test with pct=True + na_ser = Series(["first", "second", "third", "fourth", np.nan]).astype( + CategoricalDtype(["first", "second", "third", "fourth"], True) + ) + exp_top = Series([0.4, 0.6, 0.8, 1.0, 0.2]) + exp_bot = Series([0.2, 0.4, 0.6, 0.8, 1.0]) + exp_keep = Series([0.25, 0.5, 0.75, 1.0, np.nan]) + + tm.assert_series_equal(na_ser.rank(na_option="top", pct=True), exp_top) + tm.assert_series_equal(na_ser.rank(na_option="bottom", pct=True), exp_bot) + tm.assert_series_equal(na_ser.rank(na_option="keep", pct=True), exp_keep) + + def test_rank_nullable_integer(self): + # GH 56976 + exp = Series([None, 2, None, 3, 3, 2, 3, 1], dtype="Int64") + result = exp.rank(na_option="keep") + + expected = Series([None, 2.5, None, 5.0, 5.0, 2.5, 5.0, 1.0], dtype="Float64") + + tm.assert_series_equal(result, expected) + + def test_rank_signature(self): + s = Series([0, 1]) + s.rank(method="average") + msg = "No axis named average for object type Series" + with pytest.raises(ValueError, match=msg): + s.rank("average") + + def test_rank_tie_methods( + self, ser, results, dtype, using_infer_string, using_nan_is_na + ): + method, exp = results + if ( + dtype == "int64" + or ( + # TODO: these can work but need to update ser construction. + dtype in ["int64[pyarrow]", "uint64[pyarrow]", "Int64"] + and not using_nan_is_na + ) + or (not using_infer_string and dtype == "str") + ): + pytest.skip("int64/str does not support NaN") + + ser = ser if dtype is None else ser.astype(dtype) + if dtype in ["float64[pyarrow]", "Float64"] and not using_nan_is_na: + # TODO: use ser.replace(np.nan, NA) once that works + ser[np.isnan(ser.to_numpy(dtype=np.float64, na_value=np.nan))] = NA + mask = np.isnan(exp) + exp = exp.astype(object) + exp[mask] = NA + + result = ser.rank(method=method) + + if dtype == "string[pyarrow]" and not using_nan_is_na: + mask = np.isnan(exp) + exp = exp.astype(object) + exp[mask] = NA + + expected = Series(exp, dtype=expected_dtype(dtype, method)) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("na_option", ["top", "bottom", "keep"]) + @pytest.mark.parametrize( + "dtype, na_value, pos_inf, neg_inf", + [ + ("object", None, Infinity(), NegInfinity()), + ("float64", np.nan, np.inf, -np.inf), + ("Float64", NA, np.inf, -np.inf), + pytest.param( + "float64[pyarrow]", + NA, + np.inf, + -np.inf, + marks=td.skip_if_no("pyarrow"), + ), + ], + ) + def test_rank_tie_methods_on_infs_nans( + self, + rank_method, + na_option, + ascending, + dtype, + na_value, + pos_inf, + neg_inf, + using_nan_is_na, + ): + pytest.importorskip("scipy") + if dtype == "float64[pyarrow]": + if rank_method == "average": + exp_dtype = "float64[pyarrow]" + else: + exp_dtype = "uint64[pyarrow]" + elif dtype == "Float64": + # GH#62043 + if rank_method == "average": + exp_dtype = "Float64" + else: + exp_dtype = "UInt64" + else: + exp_dtype = "float64" + + chunk = 3 + in_arr = [neg_inf] * chunk + [na_value] * chunk + [pos_inf] * chunk + iseries = Series(in_arr, dtype=dtype) + exp_ranks = { + "average": ([2, 2, 2], [5, 5, 5], [8, 8, 8]), + "min": ([1, 1, 1], [4, 4, 4], [7, 7, 7]), + "max": ([3, 3, 3], [6, 6, 6], [9, 9, 9]), + "first": ([1, 2, 3], [4, 5, 6], [7, 8, 9]), + "dense": ([1, 1, 1], [2, 2, 2], [3, 3, 3]), + } + ranks = exp_ranks[rank_method] + if na_option == "top": + order = [ranks[1], ranks[0], ranks[2]] + elif na_option == "bottom": + order = [ranks[0], ranks[2], ranks[1]] + elif dtype in ("float64[pyarrow]", "Float64") and not using_nan_is_na: + order = [ranks[0], [NA] * chunk, ranks[1]] + else: + order = [ranks[0], [np.nan] * chunk, ranks[1]] + expected = order if ascending else order[::-1] + expected = list(chain.from_iterable(expected)) + result = iseries.rank( + method=rank_method, na_option=na_option, ascending=ascending + ) + exp_ser = Series(expected, dtype=exp_dtype) + tm.assert_series_equal(result, exp_ser) + + def test_rank_desc_mix_nans_infs(self): + # GH 19538 + # check descending ranking when mix nans and infs + iseries = Series([1, np.nan, np.inf, -np.inf, 25]) + result = iseries.rank(ascending=False) + exp = Series([3, np.nan, 1, 4, 2], dtype="float64") + tm.assert_series_equal(result, exp) + + @pytest.mark.parametrize( + "op, value", + [ + [operator.add, 0], + [operator.add, 1e6], + [operator.mul, 1e-6], + ], + ) + def test_rank_methods_series(self, rank_method, op, value): + sp_stats = pytest.importorskip("scipy.stats") + + xs = np.random.default_rng(2).standard_normal(9) + xs = np.concatenate([xs[i:] for i in range(0, 9, 2)]) # add duplicates + np.random.default_rng(2).shuffle(xs) + + index = [chr(ord("a") + i) for i in range(len(xs))] + vals = op(xs, value) + ts = Series(vals, index=index) + result = ts.rank(method=rank_method) + sprank = sp_stats.rankdata( + vals, rank_method if rank_method != "first" else "ordinal" + ) + expected = Series(sprank, index=index).astype("float64") + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "ser, exp", + [ + ([1], [1]), + ([2], [1]), + ([0], [1]), + ([2, 2], [1, 1]), + ([1, 2, 3], [1, 2, 3]), + ([4, 2, 1], [3, 2, 1]), + ([1, 1, 5, 5, 3], [1, 1, 3, 3, 2]), + ([-5, -4, -3, -2, -1], [1, 2, 3, 4, 5]), + ], + ) + def test_rank_dense_method(self, dtype, ser, exp): + if ser[0] < 0 and dtype.startswith("str"): + exp = exp[::-1] + s = Series(ser).astype(dtype) + result = s.rank(method="dense") + expected = Series(exp).astype(expected_dtype(dtype, "dense")) + tm.assert_series_equal(result, expected) + + def test_rank_descending( + self, ser, results, dtype, using_infer_string, using_nan_is_na + ): + method, _ = results + if ( + dtype == "int64" + or (dtype in ["int64[pyarrow]", "Int64", "Float64"] and not using_nan_is_na) + or (not using_infer_string and dtype == "str") + ): + s = ser.dropna().astype(dtype) + else: + s = ser.astype(dtype) + + res = s.rank(ascending=False) + if dtype.startswith("str"): + expected = (s.astype("float64").max() - s.astype("float64")).rank() + else: + expected = (s.max() - s).rank() + if dtype == "string[pyarrow]" and not using_nan_is_na: + expected = expected.replace(np.nan, NA) + tm.assert_series_equal(res, expected.astype(expected_dtype(dtype, "average"))) + + if dtype.startswith("str"): + expected = (s.astype("float64").max() - s.astype("float64")).rank( + method=method + ) + else: + expected = (s.max() - s).rank(method=method) + res2 = s.rank(method=method, ascending=False) + if dtype == "string[pyarrow]" and not using_nan_is_na: + expected = expected.replace(np.nan, NA) + tm.assert_series_equal(res2, expected.astype(expected_dtype(dtype, method))) + + def test_rank_int(self, ser, results): + method, exp = results + s = ser.dropna().astype("i8") + + result = s.rank(method=method) + expected = Series(exp).dropna() + expected.index = result.index + tm.assert_series_equal(result, expected) + + def test_rank_object_bug(self): + # GH 13445 + + # smoke tests + Series([np.nan] * 32).astype(object).rank(ascending=True) + Series([np.nan] * 32).astype(object).rank(ascending=False) + + def test_rank_modify_inplace(self): + # GH 18521 + # Check rank does not mutate series + s = Series([Timestamp("2017-01-05 10:20:27.569000"), NaT]) + expected = s.copy() + + s.rank() + result = s + tm.assert_series_equal(result, expected) + + def test_rank_ea_small_values(self): + # GH#52471 + ser = Series( + [5.4954145e29, -9.791984e-21, 9.3715776e-26, NA, 1.8790257e-28], + dtype="Float64", + ) + result = ser.rank(method="min") + expected = Series([4, 1, 3, NA, 2], dtype="UInt64") + tm.assert_series_equal(result, expected) + + +# GH15630, pct should be on 100% basis when method='dense' + + +@pytest.mark.parametrize( + "ser, exp", + [ + ([1], [1.0]), + ([1, 2], [1.0 / 2, 2.0 / 2]), + ([2, 2], [1.0, 1.0]), + ([1, 2, 3], [1.0 / 3, 2.0 / 3, 3.0 / 3]), + ([1, 2, 2], [1.0 / 2, 2.0 / 2, 2.0 / 2]), + ([4, 2, 1], [3.0 / 3, 2.0 / 3, 1.0 / 3]), + ([1, 1, 5, 5, 3], [1.0 / 3, 1.0 / 3, 3.0 / 3, 3.0 / 3, 2.0 / 3]), + ([1, 1, 3, 3, 5, 5], [1.0 / 3, 1.0 / 3, 2.0 / 3, 2.0 / 3, 3.0 / 3, 3.0 / 3]), + ([-5, -4, -3, -2, -1], [1.0 / 5, 2.0 / 5, 3.0 / 5, 4.0 / 5, 5.0 / 5]), + ], +) +def test_rank_dense_pct(dtype, ser, exp): + if ser[0] < 0 and dtype.startswith("str"): + exp = exp[::-1] + s = Series(ser).astype(dtype) + result = s.rank(method="dense", pct=True) + expected = Series(exp).astype(expected_dtype(dtype, "dense", pct=True)) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "ser, exp", + [ + ([1], [1.0]), + ([1, 2], [1.0 / 2, 2.0 / 2]), + ([2, 2], [1.0 / 2, 1.0 / 2]), + ([1, 2, 3], [1.0 / 3, 2.0 / 3, 3.0 / 3]), + ([1, 2, 2], [1.0 / 3, 2.0 / 3, 2.0 / 3]), + ([4, 2, 1], [3.0 / 3, 2.0 / 3, 1.0 / 3]), + ([1, 1, 5, 5, 3], [1.0 / 5, 1.0 / 5, 4.0 / 5, 4.0 / 5, 3.0 / 5]), + ([1, 1, 3, 3, 5, 5], [1.0 / 6, 1.0 / 6, 3.0 / 6, 3.0 / 6, 5.0 / 6, 5.0 / 6]), + ([-5, -4, -3, -2, -1], [1.0 / 5, 2.0 / 5, 3.0 / 5, 4.0 / 5, 5.0 / 5]), + ], +) +def test_rank_min_pct(dtype, ser, exp): + if ser[0] < 0 and dtype.startswith("str"): + exp = exp[::-1] + s = Series(ser).astype(dtype) + result = s.rank(method="min", pct=True) + expected = Series(exp).astype(expected_dtype(dtype, "min", pct=True)) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "ser, exp", + [ + ([1], [1.0]), + ([1, 2], [1.0 / 2, 2.0 / 2]), + ([2, 2], [1.0, 1.0]), + ([1, 2, 3], [1.0 / 3, 2.0 / 3, 3.0 / 3]), + ([1, 2, 2], [1.0 / 3, 3.0 / 3, 3.0 / 3]), + ([4, 2, 1], [3.0 / 3, 2.0 / 3, 1.0 / 3]), + ([1, 1, 5, 5, 3], [2.0 / 5, 2.0 / 5, 5.0 / 5, 5.0 / 5, 3.0 / 5]), + ([1, 1, 3, 3, 5, 5], [2.0 / 6, 2.0 / 6, 4.0 / 6, 4.0 / 6, 6.0 / 6, 6.0 / 6]), + ([-5, -4, -3, -2, -1], [1.0 / 5, 2.0 / 5, 3.0 / 5, 4.0 / 5, 5.0 / 5]), + ], +) +def test_rank_max_pct(dtype, ser, exp): + if ser[0] < 0 and dtype.startswith("str"): + exp = exp[::-1] + s = Series(ser).astype(dtype) + result = s.rank(method="max", pct=True) + expected = Series(exp).astype(expected_dtype(dtype, "max", pct=True)) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "ser, exp", + [ + ([1], [1.0]), + ([1, 2], [1.0 / 2, 2.0 / 2]), + ([2, 2], [1.5 / 2, 1.5 / 2]), + ([1, 2, 3], [1.0 / 3, 2.0 / 3, 3.0 / 3]), + ([1, 2, 2], [1.0 / 3, 2.5 / 3, 2.5 / 3]), + ([4, 2, 1], [3.0 / 3, 2.0 / 3, 1.0 / 3]), + ([1, 1, 5, 5, 3], [1.5 / 5, 1.5 / 5, 4.5 / 5, 4.5 / 5, 3.0 / 5]), + ([1, 1, 3, 3, 5, 5], [1.5 / 6, 1.5 / 6, 3.5 / 6, 3.5 / 6, 5.5 / 6, 5.5 / 6]), + ([-5, -4, -3, -2, -1], [1.0 / 5, 2.0 / 5, 3.0 / 5, 4.0 / 5, 5.0 / 5]), + ], +) +def test_rank_average_pct(dtype, ser, exp): + if ser[0] < 0 and dtype.startswith("str"): + exp = exp[::-1] + s = Series(ser).astype(dtype) + result = s.rank(method="average", pct=True) + expected = Series(exp).astype(expected_dtype(dtype, "average", pct=True)) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "ser, exp", + [ + ([1], [1.0]), + ([1, 2], [1.0 / 2, 2.0 / 2]), + ([2, 2], [1.0 / 2, 2.0 / 2.0]), + ([1, 2, 3], [1.0 / 3, 2.0 / 3, 3.0 / 3]), + ([1, 2, 2], [1.0 / 3, 2.0 / 3, 3.0 / 3]), + ([4, 2, 1], [3.0 / 3, 2.0 / 3, 1.0 / 3]), + ([1, 1, 5, 5, 3], [1.0 / 5, 2.0 / 5, 4.0 / 5, 5.0 / 5, 3.0 / 5]), + ([1, 1, 3, 3, 5, 5], [1.0 / 6, 2.0 / 6, 3.0 / 6, 4.0 / 6, 5.0 / 6, 6.0 / 6]), + ([-5, -4, -3, -2, -1], [1.0 / 5, 2.0 / 5, 3.0 / 5, 4.0 / 5, 5.0 / 5]), + ], +) +def test_rank_first_pct(dtype, ser, exp): + if ser[0] < 0 and dtype.startswith("str"): + exp = exp[::-1] + s = Series(ser).astype(dtype) + result = s.rank(method="first", pct=True) + expected = Series(exp).astype(expected_dtype(dtype, "first", pct=True)) + tm.assert_series_equal(result, expected) + + +@pytest.mark.single_cpu +def test_pct_max_many_rows(): + # GH 18271 + s = Series(np.arange(2**24 + 1)) + result = s.rank(pct=True).max() + assert result == 1 diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_reindex.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_reindex.py new file mode 100644 index 0000000000000000000000000000000000000000..a99ed68ec9edb2151e6d0eb6df690f87873202a2 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_reindex.py @@ -0,0 +1,474 @@ +import numpy as np +import pytest + +from pandas import ( + NA, + Categorical, + Float64Dtype, + Index, + MultiIndex, + NaT, + Period, + PeriodIndex, + RangeIndex, + Series, + Timedelta, + Timestamp, + date_range, + isna, +) +import pandas._testing as tm + + +def test_reindex(datetime_series, string_series): + identity = string_series.reindex(string_series.index) + + assert tm.shares_memory(string_series.index, identity.index) + + assert identity.index.is_(string_series.index) + assert identity.index.identical(string_series.index) + + subIndex = string_series.index[10:20] + subSeries = string_series.reindex(subIndex) + + for idx, val in subSeries.items(): + assert val == string_series[idx] + + subIndex2 = datetime_series.index[10:20] + subTS = datetime_series.reindex(subIndex2) + + for idx, val in subTS.items(): + assert val == datetime_series[idx] + stuffSeries = datetime_series.reindex(subIndex) + + assert np.isnan(stuffSeries).all() + + # This is extremely important for the Cython code to not screw up + nonContigIndex = datetime_series.index[::2] + subNonContig = datetime_series.reindex(nonContigIndex) + for idx, val in subNonContig.items(): + assert val == datetime_series[idx] + + # return a copy the same index here + result = datetime_series.reindex() + assert result is not datetime_series + + +def test_reindex_nan(): + ts = Series([2, 3, 5, 7], index=[1, 4, np.nan, 8]) + + i, j = [np.nan, 1, np.nan, 8, 4, np.nan], [2, 0, 2, 3, 1, 2] + tm.assert_series_equal(ts.reindex(i), ts.iloc[j]) + + ts.index = ts.index.astype("object") + + # reindex coerces index.dtype to float, loc/iloc doesn't + tm.assert_series_equal(ts.reindex(i), ts.iloc[j], check_index_type=False) + + +def test_reindex_series_add_nat(): + rng = date_range("1/1/2000 00:00:00", periods=10, freq="10s") + series = Series(rng) + + result = series.reindex(range(15)) + assert np.issubdtype(result.dtype, np.dtype("M8[ns]")) + + mask = result.isna() + assert mask[-5:].all() + assert not mask[:-5].any() + + +def test_reindex_with_datetimes(): + rng = date_range("1/1/2000", periods=20) + ts = Series(np.random.default_rng(2).standard_normal(20), index=rng) + + result = ts.reindex(list(ts.index[5:10])) + expected = ts[5:10] + expected.index = expected.index._with_freq(None) + tm.assert_series_equal(result, expected) + + result = ts[list(ts.index[5:10])] + tm.assert_series_equal(result, expected) + + +def test_reindex_corner(datetime_series): + # (don't forget to fix this) I think it's fixed + empty = Series(index=[]) + empty.reindex(datetime_series.index, method="pad") # it works + + # corner case: pad empty series + reindexed = empty.reindex(datetime_series.index, method="pad") + + # pass non-Index + reindexed = datetime_series.reindex(list(datetime_series.index)) + datetime_series.index = datetime_series.index._with_freq(None) + tm.assert_series_equal(datetime_series, reindexed) + + # bad fill method + ts = datetime_series[::2] + msg = ( + r"Invalid fill method\. Expecting pad \(ffill\), backfill " + r"\(bfill\) or nearest\. Got foo" + ) + with pytest.raises(ValueError, match=msg): + ts.reindex(datetime_series.index, method="foo") + + +def test_reindex_pad(): + s = Series(np.arange(10), dtype="int64") + s2 = s[::2] + + reindexed = s2.reindex(s.index, method="pad") + reindexed2 = s2.reindex(s.index, method="ffill") + tm.assert_series_equal(reindexed, reindexed2) + + expected = Series([0, 0, 2, 2, 4, 4, 6, 6, 8, 8]) + tm.assert_series_equal(reindexed, expected) + + +def test_reindex_pad2(): + # GH4604 + s = Series([1, 2, 3, 4, 5], index=["a", "b", "c", "d", "e"]) + new_index = ["a", "g", "c", "f"] + expected = Series([1, 1, 3, 3.0], index=new_index) + + # this changes dtype because the ffill happens after + result = s.reindex(new_index).ffill() + tm.assert_series_equal(result, expected) + + result = s.reindex(new_index).ffill() + tm.assert_series_equal(result, expected) + + expected = Series([1, 5, 3, 5], index=new_index) + result = s.reindex(new_index, method="ffill") + tm.assert_series_equal(result, expected) + + +def test_reindex_inference(): + # inference of new dtype + s = Series([True, False, False, True], index=list("abcd")) + new_index = "agc" + result = s.reindex(list(new_index)).ffill() + expected = Series([True, True, False], index=list(new_index), dtype=object) + tm.assert_series_equal(result, expected) + + +def test_reindex_downcasting(): + # GH4618 shifted series downcasting + s = Series(False, index=range(5)) + result = s.shift(1).bfill() + expected = Series(False, index=range(5), dtype=object) + tm.assert_series_equal(result, expected) + + +def test_reindex_nearest(): + s = Series(np.arange(10, dtype="int64")) + target = [0.1, 0.9, 1.5, 2.0] + result = s.reindex(target, method="nearest") + expected = Series(np.around(target).astype("int64"), target) + tm.assert_series_equal(expected, result) + + result = s.reindex(target, method="nearest", tolerance=0.2) + expected = Series([0, 1, np.nan, 2], target) + tm.assert_series_equal(expected, result) + + result = s.reindex(target, method="nearest", tolerance=[0.3, 0.01, 0.4, 3]) + expected = Series([0, np.nan, np.nan, 2], target) + tm.assert_series_equal(expected, result) + + +def test_reindex_int(datetime_series): + ts = datetime_series[::2] + int_ts = Series(np.zeros(len(ts), dtype=int), index=ts.index) + + # this should work fine + reindexed_int = int_ts.reindex(datetime_series.index) + + # if NaNs introduced + assert reindexed_int.dtype == np.float64 + + # NO NaNs introduced + reindexed_int = int_ts.reindex(int_ts.index[::2]) + assert reindexed_int.dtype == np.dtype(int) + + +def test_reindex_bool(datetime_series): + # A series other than float, int, string, or object + ts = datetime_series[::2] + bool_ts = Series(np.zeros(len(ts), dtype=bool), index=ts.index) + + # this should work fine + reindexed_bool = bool_ts.reindex(datetime_series.index) + + # if NaNs introduced + assert reindexed_bool.dtype == np.object_ + + # NO NaNs introduced + reindexed_bool = bool_ts.reindex(bool_ts.index[::2]) + assert reindexed_bool.dtype == np.bool_ + + +def test_reindex_bool_pad(datetime_series): + # fail + ts = datetime_series[5:] + bool_ts = Series(np.zeros(len(ts), dtype=bool), index=ts.index) + filled_bool = bool_ts.reindex(datetime_series.index, method="pad") + assert isna(filled_bool[:5]).all() + + +def test_reindex_categorical(): + index = date_range("20000101", periods=3) + + # reindexing to an invalid Categorical + s = Series(["a", "b", "c"], dtype="category") + result = s.reindex(index) + expected = Series( + Categorical(values=[np.nan, np.nan, np.nan], categories=["a", "b", "c"]) + ) + expected.index = index + tm.assert_series_equal(result, expected) + + # partial reindexing + expected = Series( + Categorical(values=["b", "c"], categories=["a", "b", "c"]), index=range(1, 3) + ) + result = s.reindex([1, 2]) + tm.assert_series_equal(result, expected) + + expected = Series( + Categorical(values=["c", np.nan], categories=["a", "b", "c"]), index=range(2, 4) + ) + result = s.reindex([2, 3]) + tm.assert_series_equal(result, expected) + + +def test_reindex_astype_order_consistency(): + # GH#17444 + ser = Series([1, 2, 3], index=[2, 0, 1]) + new_index = [0, 1, 2] + temp_dtype = "category" + new_dtype = str + result = ser.reindex(new_index).astype(temp_dtype).astype(new_dtype) + expected = ser.astype(temp_dtype).reindex(new_index).astype(new_dtype) + tm.assert_series_equal(result, expected) + + +def test_reindex_fill_value(): + # ----------------------------------------------------------- + # floats + floats = Series([1.0, 2.0, 3.0]) + result = floats.reindex([1, 2, 3]) + expected = Series([2.0, 3.0, np.nan], index=range(1, 4)) + tm.assert_series_equal(result, expected) + + result = floats.reindex([1, 2, 3], fill_value=0) + expected = Series([2.0, 3.0, 0], index=range(1, 4)) + tm.assert_series_equal(result, expected) + + # ----------------------------------------------------------- + # ints + ints = Series([1, 2, 3]) + + result = ints.reindex([1, 2, 3]) + expected = Series([2.0, 3.0, np.nan], index=range(1, 4)) + tm.assert_series_equal(result, expected) + + # don't upcast + result = ints.reindex([1, 2, 3], fill_value=0) + expected = Series([2, 3, 0], index=range(1, 4)) + assert issubclass(result.dtype.type, np.integer) + tm.assert_series_equal(result, expected) + + # ----------------------------------------------------------- + # objects + objects = Series([1, 2, 3], dtype=object) + + result = objects.reindex([1, 2, 3]) + expected = Series([2, 3, np.nan], index=range(1, 4), dtype=object) + tm.assert_series_equal(result, expected) + + result = objects.reindex([1, 2, 3], fill_value="foo") + expected = Series([2, 3, "foo"], index=range(1, 4), dtype=object) + tm.assert_series_equal(result, expected) + + # ------------------------------------------------------------ + # bools + bools = Series([True, False, True]) + + result = bools.reindex([1, 2, 3]) + expected = Series([False, True, np.nan], index=range(1, 4), dtype=object) + tm.assert_series_equal(result, expected) + + result = bools.reindex([1, 2, 3], fill_value=False) + expected = Series([False, True, False], index=range(1, 4)) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize("dtype", ["datetime64[ns]", "timedelta64[ns]"]) +@pytest.mark.parametrize("fill_value", ["string", 0, Timedelta(0)]) +def test_reindex_fill_value_datetimelike_upcast(dtype, fill_value): + # https://github.com/pandas-dev/pandas/issues/42921 + if dtype == "timedelta64[ns]" and fill_value == Timedelta(0): + # use the scalar that is not compatible with the dtype for this test + fill_value = Timestamp(0) + + ser = Series([NaT], dtype=dtype) + + result = ser.reindex([0, 1], fill_value=fill_value) + expected = Series([NaT, fill_value], index=range(2), dtype=object) + tm.assert_series_equal(result, expected) + + +def test_reindex_datetimeindexes_tz_naive_and_aware(): + # GH 8306 + idx = date_range("20131101", tz="America/Chicago", periods=7, unit="ns") + newidx = date_range("20131103", periods=10, freq="h", unit="ns") + s = Series(range(7), index=idx) + msg = ( + r"Cannot compare dtypes datetime64\[ns, America/Chicago\] " + r"and datetime64\[ns\]" + ) + with pytest.raises(TypeError, match=msg): + s.reindex(newidx, method="ffill") + + +def test_reindex_empty_series_tz_dtype(): + # GH 20869 + result = Series(dtype="datetime64[ns, UTC]").reindex([0, 1]) + expected = Series([NaT] * 2, dtype="datetime64[ns, UTC]") + tm.assert_equal(result, expected) + + +@pytest.mark.parametrize( + "p_values, o_values, values, expected_values", + [ + ( + [Period("2019Q1", "Q-DEC"), Period("2019Q2", "Q-DEC")], + [Period("2019Q1", "Q-DEC"), Period("2019Q2", "Q-DEC"), "All"], + [1.0, 1.0], + [1.0, 1.0, np.nan], + ), + ( + [Period("2019Q1", "Q-DEC"), Period("2019Q2", "Q-DEC")], + [Period("2019Q1", "Q-DEC"), Period("2019Q2", "Q-DEC")], + [1.0, 1.0], + [1.0, 1.0], + ), + ], +) +def test_reindex_periodindex_with_object(p_values, o_values, values, expected_values): + # GH#28337 + period_index = PeriodIndex(p_values) + object_index = Index(o_values) + + ser = Series(values, index=period_index) + result = ser.reindex(object_index) + expected = Series(expected_values, index=object_index) + tm.assert_series_equal(result, expected) + + +def test_reindex_too_many_args(): + # GH 40980 + ser = Series([1, 2]) + msg = r"reindex\(\) takes from 1 to 2 positional arguments but 3 were given" + with pytest.raises(TypeError, match=msg): + ser.reindex([2, 3], False) + + +def test_reindex_double_index(): + # GH 40980 + ser = Series([1, 2]) + msg = r"reindex\(\) got multiple values for argument 'index'" + with pytest.raises(TypeError, match=msg): + ser.reindex([2, 3], index=[3, 4]) + + +def test_reindex_no_posargs(): + # GH 40980 + ser = Series([1, 2]) + result = ser.reindex(index=[1, 0]) + expected = Series([2, 1], index=[1, 0]) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize("values", [[["a"], ["x"]], [[], []]]) +def test_reindex_empty_with_level(values): + # GH41170 + ser = Series( + range(len(values[0])), index=MultiIndex.from_arrays(values), dtype="object" + ) + result = ser.reindex(np.array(["b"]), level=0) + expected = Series( + index=MultiIndex(levels=[["b"], values[1]], codes=[[], []]), dtype="object" + ) + tm.assert_series_equal(result, expected) + + +def test_reindex_missing_category(): + # GH#18185 + ser = Series([1, 2, 3, 1], dtype="category") + msg = r"Cannot setitem on a Categorical with a new category \(-1\)" + with pytest.raises(TypeError, match=msg): + ser.reindex([1, 2, 3, 4, 5], fill_value=-1) + + +def test_reindexing_with_float64_NA_log(): + # GH 47055 + s = Series([1.0, NA], dtype=Float64Dtype()) + s_reindex = s.reindex(range(3)) + result = s_reindex.values._data + expected = np.array([1, np.nan, np.nan]) + tm.assert_numpy_array_equal(result, expected) + with tm.assert_produces_warning(None): + result_log = np.log(s_reindex) + expected_log = Series([0, NA, NA], dtype=Float64Dtype()) + tm.assert_series_equal(result_log, expected_log) + + +@pytest.mark.parametrize("dtype", ["timedelta64", "datetime64"]) +def test_reindex_expand_nonnano_nat(dtype): + # GH 53497 + ser = Series(np.array([1], dtype=f"{dtype}[s]")) + result = ser.reindex(RangeIndex(2)) + expected = Series( + np.array([1, getattr(np, dtype)("nat", "s")], dtype=f"{dtype}[s]") + ) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "name, expected_match_level_a", + [ + ("a", True), + (None, False), + ("x", False), + ], +) +def test_reindex_multiindex_automatic_level(name, expected_match_level_a): + series = Series([26.73, 24.255], index=Index([81, 82], name=name)) + target = MultiIndex.from_product( + [[81, 82], [np.nan], ["2018-06-01", "2018-07-01"]], names=["a", "b", "c"] + ) + + result = series.reindex(target) + + if expected_match_level_a: + expected = Series( + data=[26.73, 26.73, 24.255, 24.255], + index=MultiIndex.from_product( + [[81, 82], [np.nan], ["2018-06-01", "2018-07-01"]], + names=["a", "b", "c"], + ), + dtype=series.dtype, + ) + else: + expected = Series( + data=[np.nan] * 4, + index=MultiIndex.from_product( + [[81, 82], [np.nan], ["2018-06-01", "2018-07-01"]], + names=["a", "b", "c"], + ), + dtype=series.dtype, + ) + + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_reindex_like.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_reindex_like.py new file mode 100644 index 0000000000000000000000000000000000000000..15cd1502dadfb935da8c0d3415af1c9669025d9b --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_reindex_like.py @@ -0,0 +1,47 @@ +from datetime import datetime + +import numpy as np + +from pandas.errors import Pandas4Warning + +from pandas import Series +import pandas._testing as tm + + +def test_reindex_like(datetime_series): + other = datetime_series[::2] + tm.assert_series_equal( + datetime_series.reindex(other.index), datetime_series.reindex_like(other) + ) + + # GH#7179 + day1 = datetime(2013, 3, 5) + day2 = datetime(2013, 5, 5) + day3 = datetime(2014, 3, 5) + + series1 = Series([5, None, None], [day1, day2, day3]) + series2 = Series([None, None], [day1, day3]) + + with tm.assert_produces_warning(Pandas4Warning): + result = series1.reindex_like(series2, method="pad") + expected = Series([5, np.nan], index=[day1, day3]) + tm.assert_series_equal(result, expected) + + +def test_reindex_like_nearest(): + ser = Series(np.arange(10, dtype="int64")) + + target = [0.1, 0.9, 1.5, 2.0] + other = ser.reindex(target, method="nearest") + expected = Series(np.around(target).astype("int64"), target) + + with tm.assert_produces_warning(Pandas4Warning): + result = ser.reindex_like(other, method="nearest") + tm.assert_series_equal(expected, result) + + with tm.assert_produces_warning(Pandas4Warning): + result = ser.reindex_like(other, method="nearest", tolerance=1) + tm.assert_series_equal(expected, result) + with tm.assert_produces_warning(Pandas4Warning): + result = ser.reindex_like(other, method="nearest", tolerance=[1, 2, 3, 4]) + tm.assert_series_equal(expected, result) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_rename.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_rename.py new file mode 100644 index 0000000000000000000000000000000000000000..c912b9abd22ca86682de287060042b58719b1fb1 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_rename.py @@ -0,0 +1,179 @@ +from datetime import datetime +import re + +import numpy as np +import pytest + +from pandas import ( + Index, + MultiIndex, + Series, + array, +) +import pandas._testing as tm + + +class TestRename: + def test_rename(self, datetime_series): + ts = datetime_series + renamer = lambda x: x.strftime("%Y%m%d") + renamed = ts.rename(renamer) + assert renamed.index[0] == renamer(ts.index[0]) + + # dict + rename_dict = dict(zip(ts.index, renamed.index, strict=True)) + renamed2 = ts.rename(rename_dict) + tm.assert_series_equal(renamed, renamed2) + + def test_rename_partial_dict(self): + # partial dict + ser = Series(np.arange(4), index=["a", "b", "c", "d"], dtype="int64") + renamed = ser.rename({"b": "foo", "d": "bar"}) + tm.assert_index_equal(renamed.index, Index(["a", "foo", "c", "bar"])) + + def test_rename_retain_index_name(self): + # index with name + renamer = Series( + np.arange(4), index=Index(["a", "b", "c", "d"], name="name"), dtype="int64" + ) + renamed = renamer.rename({}) + assert renamed.index.name == renamer.index.name + + def test_rename_by_series(self): + ser = Series(range(5), name="foo") + renamer = Series({1: 10, 2: 20}) + result = ser.rename(renamer) + expected = Series(range(5), index=[0, 10, 20, 3, 4], name="foo") + tm.assert_series_equal(result, expected) + + def test_rename_set_name(self, using_infer_string): + ser = Series(range(4), index=list("abcd")) + for name in ["foo", 123, 123.0, datetime(2001, 11, 11), ("foo",)]: + result = ser.rename(name) + assert result.name == name + if using_infer_string: + tm.assert_extension_array_equal(result.index.values, ser.index.values) + else: + tm.assert_numpy_array_equal(result.index.values, ser.index.values) + assert ser.name is None + + def test_rename_set_name_inplace(self, using_infer_string): + ser = Series(range(3), index=list("abc")) + for name in ["foo", 123, 123.0, datetime(2001, 11, 11), ("foo",)]: + ser.rename(name, inplace=True) + assert ser.name == name + exp = np.array(["a", "b", "c"], dtype=np.object_) + if using_infer_string: + exp = array(exp, dtype="str") + tm.assert_extension_array_equal(ser.index.values, exp) + else: + tm.assert_numpy_array_equal(ser.index.values, exp) + + def test_rename_axis_supported(self): + # Supporting axis for compatibility, detailed in GH-18589 + ser = Series(range(5)) + ser.rename({}, axis=0) + ser.rename({}, axis="index") + + with pytest.raises(ValueError, match="No axis named 5"): + ser.rename({}, axis=5) + + def test_rename_inplace(self, datetime_series): + renamer = lambda x: x.strftime("%Y%m%d") + expected = renamer(datetime_series.index[0]) + + datetime_series.rename(renamer, inplace=True) + assert datetime_series.index[0] == expected + + def test_rename_with_custom_indexer(self): + # GH 27814 + class MyIndexer: + pass + + ix = MyIndexer() + ser = Series([1, 2, 3]).rename(ix) + assert ser.name is ix + + def test_rename_with_custom_indexer_inplace(self): + # GH 27814 + class MyIndexer: + pass + + ix = MyIndexer() + ser = Series([1, 2, 3]) + ser.rename(ix, inplace=True) + assert ser.name is ix + + def test_rename_callable(self): + # GH 17407 + ser = Series(range(1, 6), index=Index(range(2, 7), name="IntIndex")) + result = ser.rename(str) + expected = ser.rename(lambda i: str(i)) + tm.assert_series_equal(result, expected) + + assert result.name == expected.name + + def test_rename_none(self): + # GH 40977 + ser = Series([1, 2], name="foo") + result = ser.rename(None) + expected = Series([1, 2]) + tm.assert_series_equal(result, expected) + + def test_rename_series_with_multiindex(self): + # issue #43659 + arrays = [ + ["bar", "baz", "baz", "foo", "qux"], + ["one", "one", "two", "two", "one"], + ] + + index = MultiIndex.from_arrays(arrays, names=["first", "second"]) + ser = Series(np.ones(5), index=index) + result = ser.rename(index={"one": "yes"}, level="second", errors="raise") + + arrays_expected = [ + ["bar", "baz", "baz", "foo", "qux"], + ["yes", "yes", "two", "two", "yes"], + ] + + index_expected = MultiIndex.from_arrays( + arrays_expected, names=["first", "second"] + ) + series_expected = Series(np.ones(5), index=index_expected) + + tm.assert_series_equal(result, series_expected) + + def test_rename_series_with_multiindex_keeps_ea_dtypes(self): + # GH21055 + arrays = [ + Index([1, 2, 3], dtype="Int64").astype("category"), + Index([1, 2, 3], dtype="Int64"), + ] + mi = MultiIndex.from_arrays(arrays, names=["A", "B"]) + ser = Series(1, index=mi) + result = ser.rename({1: 4}, level=1) + + arrays_expected = [ + Index([1, 2, 3], dtype="Int64").astype("category"), + Index([4, 2, 3], dtype="Int64"), + ] + mi_expected = MultiIndex.from_arrays(arrays_expected, names=["A", "B"]) + expected = Series(1, index=mi_expected) + + tm.assert_series_equal(result, expected) + + def test_rename_error_arg(self): + # GH 46889 + ser = Series(["foo", "bar"]) + match = re.escape("[2] not found in axis") + with pytest.raises(KeyError, match=match): + ser.rename({2: 9}, errors="raise") + + def test_rename_copy_false(self): + # GH 46889 + ser = Series(["foo", "bar"]) + ser_orig = ser.copy() + shallow_copy = ser.rename({1: 9}) + ser[0] = "foobar" + assert ser_orig[0] == shallow_copy[0] + assert ser_orig[1] == shallow_copy[9] diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_rename_axis.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_rename_axis.py new file mode 100644 index 0000000000000000000000000000000000000000..58c095d697ede213be5c730e305bf1789810ac4a --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_rename_axis.py @@ -0,0 +1,47 @@ +import pytest + +from pandas import ( + Index, + MultiIndex, + Series, +) +import pandas._testing as tm + + +class TestSeriesRenameAxis: + def test_rename_axis_mapper(self): + # GH 19978 + mi = MultiIndex.from_product([["a", "b", "c"], [1, 2]], names=["ll", "nn"]) + ser = Series(list(range(len(mi))), index=mi) + + result = ser.rename_axis(index={"ll": "foo"}) + assert result.index.names == ["foo", "nn"] + + result = ser.rename_axis(index=str.upper, axis=0) + assert result.index.names == ["LL", "NN"] + + result = ser.rename_axis(index=["foo", "goo"]) + assert result.index.names == ["foo", "goo"] + + with pytest.raises(TypeError, match="unexpected"): + ser.rename_axis(columns="wrong") + + def test_rename_axis_inplace(self, datetime_series): + # GH 15704 + expected = datetime_series.rename_axis("foo") + result = datetime_series + no_return = result.rename_axis("foo", inplace=True) + + assert no_return is None + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("kwargs", [{"mapper": None}, {"index": None}, {}]) + def test_rename_axis_none(self, kwargs): + # GH 25034 + index = Index(list("abc"), name="foo") + ser = Series([1, 2, 3], index=index) + + result = ser.rename_axis(**kwargs) + expected_index = index.rename(None) if kwargs else index + expected = Series([1, 2, 3], index=expected_index) + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_repeat.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_repeat.py new file mode 100644 index 0000000000000000000000000000000000000000..8ecc8052ff49c150444cf395b68e6163fb761775 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_repeat.py @@ -0,0 +1,40 @@ +import numpy as np +import pytest + +from pandas import ( + MultiIndex, + Series, +) +import pandas._testing as tm + + +class TestRepeat: + def test_repeat(self): + ser = Series(np.random.default_rng(2).standard_normal(3), index=["a", "b", "c"]) + + reps = ser.repeat(5) + exp = Series(ser.values.repeat(5), index=ser.index.values.repeat(5)) + tm.assert_series_equal(reps, exp) + + to_rep = [2, 3, 4] + reps = ser.repeat(to_rep) + exp = Series(ser.values.repeat(to_rep), index=ser.index.values.repeat(to_rep)) + tm.assert_series_equal(reps, exp) + + def test_numpy_repeat(self): + ser = Series(np.arange(3), name="x") + expected = Series( + ser.values.repeat(2), name="x", index=ser.index.values.repeat(2) + ) + tm.assert_series_equal(np.repeat(ser, 2), expected) + + msg = "the 'axis' parameter is not supported" + with pytest.raises(ValueError, match=msg): + np.repeat(ser, 2, axis=0) + + def test_repeat_with_multiindex(self): + # GH#9361, fixed by GH#7891 + m_idx = MultiIndex.from_tuples([(1, 2), (3, 4), (5, 6), (7, 8)]) + data = ["a", "b", "c", "d"] + m_df = Series(data, index=m_idx) + assert m_df.repeat(3).shape == (3 * len(data),) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_replace.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_replace.py new file mode 100644 index 0000000000000000000000000000000000000000..57cab07ada9a4e84ce3cc037e151d233efd5831b --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_replace.py @@ -0,0 +1,741 @@ +import re + +import numpy as np +import pytest + +import pandas.util._test_decorators as td + +import pandas as pd +import pandas._testing as tm +from pandas.core.arrays import IntervalArray + + +class TestSeriesReplace: + def test_replace_explicit_none(self): + # GH#36984 if the user explicitly passes value=None, give it to them + ser = pd.Series([0, 0, ""], dtype=object) + result = ser.replace("", None) + expected = pd.Series([0, 0, None], dtype=object) + tm.assert_series_equal(result, expected) + + # Cast column 2 to object to avoid implicit cast when setting entry to "" + df = pd.DataFrame(np.zeros((3, 3))).astype({2: object}) + df.iloc[2, 2] = "" + result = df.replace("", None) + expected = pd.DataFrame( + { + 0: np.zeros(3), + 1: np.zeros(3), + 2: np.array([0.0, 0.0, None], dtype=object), + } + ) + assert expected.iloc[2, 2] is None + tm.assert_frame_equal(result, expected) + + # GH#19998 same thing with object dtype + ser = pd.Series([10, 20, 30, "a", "a", "b", "a"]) + result = ser.replace("a", None) + expected = pd.Series([10, 20, 30, None, None, "b", None]) + assert expected.iloc[-1] is None + tm.assert_series_equal(result, expected) + + def test_replace_noop_doesnt_downcast(self): + # GH#44498 + ser = pd.Series([None, None, pd.Timestamp("2021-12-16 17:31")], dtype=object) + res = ser.replace({np.nan: None}) # should be a no-op + tm.assert_series_equal(res, ser) + assert res.dtype == object + + # same thing but different calling convention + res = ser.replace(np.nan, None) + tm.assert_series_equal(res, ser) + assert res.dtype == object + + def test_replace(self): + N = 50 + ser = pd.Series(np.random.default_rng(2).standard_normal(N)) + ser[0:4] = np.nan + ser[6:10] = 0 + + # replace list with a single value + result = ser.replace([np.nan], -1, inplace=True) + assert result is ser + + exp = ser.fillna(-1) + tm.assert_series_equal(ser, exp) + + rs = ser.replace(0.0, np.nan) + ser[ser == 0.0] = np.nan + tm.assert_series_equal(rs, ser) + + ser = pd.Series( + np.fabs(np.random.default_rng(2).standard_normal(N)), + pd.date_range("2020-01-01", periods=N), + dtype=object, + ) + ser[:5] = np.nan + ser[6:10] = "foo" + ser[20:30] = "bar" + + # replace list with a single value + + rs = ser.replace([np.nan, "foo", "bar"], -1) + + assert (rs[:5] == -1).all() + assert (rs[6:10] == -1).all() + assert (rs[20:30] == -1).all() + assert (pd.isna(ser[:5])).all() + + # replace with different values + rs = ser.replace({np.nan: -1, "foo": -2, "bar": -3}) + + assert (rs[:5] == -1).all() + assert (rs[6:10] == -2).all() + assert (rs[20:30] == -3).all() + assert (pd.isna(ser[:5])).all() + + # replace with different values with 2 lists + rs2 = ser.replace([np.nan, "foo", "bar"], [-1, -2, -3]) + tm.assert_series_equal(rs, rs2) + + # replace inplace + result = ser.replace([np.nan, "foo", "bar"], -1, inplace=True) + assert result is ser + + assert (ser[:5] == -1).all() + assert (ser[6:10] == -1).all() + assert (ser[20:30] == -1).all() + + def test_replace_nan_with_inf(self): + ser = pd.Series([np.nan, 0, np.inf]) + tm.assert_series_equal(ser.replace(np.nan, 0), ser.fillna(0)) + + ser = pd.Series([np.nan, 0, "foo", "bar", np.inf, None, pd.NaT]) + tm.assert_series_equal(ser.replace(np.nan, 0), ser.fillna(0)) + filled = ser.copy() + filled[4] = 0 + tm.assert_series_equal(ser.replace(np.inf, 0), filled) + + def test_replace_listlike_value_listlike_target(self, datetime_series): + ser = pd.Series(datetime_series.index) + tm.assert_series_equal(ser.replace(np.nan, 0), ser.fillna(0)) + + # malformed + msg = r"Replacement lists must match in length\. Expecting 3 got 2" + with pytest.raises(ValueError, match=msg): + ser.replace([1, 2, 3], [np.nan, 0]) + + # ser is dt64 so can't hold 1 or 2, so this replace is a no-op + result = ser.replace([1, 2], [np.nan, 0]) + tm.assert_series_equal(result, ser) + + ser = pd.Series([0, 1, 2, 3, 4]) + result = ser.replace([0, 1, 2, 3, 4], [4, 3, 2, 1, 0]) + tm.assert_series_equal(result, pd.Series([4, 3, 2, 1, 0])) + + def test_replace_gh5319(self): + # API change from 0.12? + # GH 5319 + ser = pd.Series([0, np.nan, 2, 3, 4]) + msg = ( + "Series.replace must specify either 'value', " + "a dict-like 'to_replace', or dict-like 'regex'" + ) + with pytest.raises(ValueError, match=msg): + ser.replace([np.nan]) + + with pytest.raises(ValueError, match=msg): + ser.replace(np.nan) + + def test_replace_datetime64(self): + # GH 5797 + ser = pd.Series(pd.date_range("20130101", periods=5)) + expected = ser.copy() + expected.loc[2] = pd.Timestamp("20120101") + result = ser.replace({pd.Timestamp("20130103"): pd.Timestamp("20120101")}) + tm.assert_series_equal(result, expected) + result = ser.replace(pd.Timestamp("20130103"), pd.Timestamp("20120101")) + tm.assert_series_equal(result, expected) + + def test_replace_nat_with_tz(self): + # GH 11792: Test with replacing NaT in a list with tz data + ts = pd.Timestamp("2015/01/01", tz="UTC") + s = pd.Series([pd.NaT, pd.Timestamp("2015/01/01", tz="UTC")]) + result = s.replace([np.nan, pd.NaT], pd.Timestamp.min) + expected = pd.Series([pd.Timestamp.min, ts], dtype=object) + tm.assert_series_equal(expected, result) + + def test_replace_timedelta_td64(self): + tdi = pd.timedelta_range(0, periods=5) + ser = pd.Series(tdi) + + # Using a single dict argument means we go through replace_list + result = ser.replace({ser[1]: ser[3]}) + + expected = pd.Series([ser[0], ser[3], ser[2], ser[3], ser[4]]) + tm.assert_series_equal(result, expected) + + def test_replace_with_single_list(self): + ser = pd.Series([0, 1, 2, 3, 4]) + msg = ( + "Series.replace must specify either 'value', " + "a dict-like 'to_replace', or dict-like 'regex'" + ) + with pytest.raises(ValueError, match=msg): + ser.replace([1, 2, 3]) + + s = ser.copy() + with pytest.raises(ValueError, match=msg): + s.replace([1, 2, 3], inplace=True) + + def test_replace_mixed_types(self): + ser = pd.Series(np.arange(5), dtype="int64") + + def check_replace(to_rep, val, expected): + sc = ser.copy() + result = ser.replace(to_rep, val) + result = sc.replace(to_rep, val, inplace=True) + assert result is sc + tm.assert_series_equal(expected, result) + tm.assert_series_equal(expected, sc) + + # 3.0 can still be held in our int64 series, so we do not upcast GH#44940 + tr, v = [3], [3.0] + check_replace(tr, v, ser) + # Note this matches what we get with the scalars 3 and 3.0 + check_replace(tr[0], v[0], ser) + + # MUST upcast to float + e = pd.Series([0, 1, 2, 3.5, 4]) + tr, v = [3], [3.5] + check_replace(tr, v, e) + + # casts to object + e = pd.Series([0, 1, 2, 3.5, "a"]) + tr, v = [3, 4], [3.5, "a"] + check_replace(tr, v, e) + + # again casts to object + e = pd.Series([0, 1, 2, 3.5, pd.Timestamp("20130101")]) + tr, v = [3, 4], [3.5, pd.Timestamp("20130101")] + check_replace(tr, v, e) + + # casts to object + e = pd.Series([0, 1, 2, 3.5, True], dtype="object") + tr, v = [3, 4], [3.5, True] + check_replace(tr, v, e) + + # test an object with dates + floats + integers + strings + dr = pd.Series(pd.date_range("1/1/2001", "1/10/2001", freq="D")) + result = dr.astype(object).replace([dr[0], dr[1], dr[2]], [1.0, 2, "a"]) + expected = pd.Series([1.0, 2, "a", *dr[3:].tolist()], dtype=object) + tm.assert_series_equal(result, expected) + + def test_replace_bool_with_string_no_op(self): + s = pd.Series([True, False, True]) + result = s.replace("fun", "in-the-sun") + tm.assert_series_equal(s, result) + + def test_replace_bool_with_string(self): + # nonexistent elements + s = pd.Series([True, False, True]) + result = s.replace(True, "2u") + expected = pd.Series(["2u", False, "2u"]) + tm.assert_series_equal(expected, result) + + def test_replace_bool_with_bool(self): + s = pd.Series([True, False, True]) + result = s.replace(True, False) + expected = pd.Series([False] * len(s)) + tm.assert_series_equal(expected, result) + + def test_replace_with_dict_with_bool_keys(self): + s = pd.Series([True, False, True]) + result = s.replace({"asdf": "asdb", True: "yes"}) + expected = pd.Series(["yes", False, "yes"]) + tm.assert_series_equal(result, expected) + + def test_replace_Int_with_na(self, any_int_ea_dtype): + # GH 38267 + result = pd.Series([0, None], dtype=any_int_ea_dtype).replace(0, pd.NA) + expected = pd.Series([pd.NA, pd.NA], dtype=any_int_ea_dtype) + tm.assert_series_equal(result, expected) + result = pd.Series([0, 1], dtype=any_int_ea_dtype).replace(0, pd.NA) + result2 = result.replace(1, pd.NA, inplace=True) + assert result2 is result + tm.assert_series_equal(result, expected) + + def test_replace2(self): + N = 50 + ser = pd.Series( + np.fabs(np.random.default_rng(2).standard_normal(N)), + pd.date_range("2020-01-01", periods=N), + dtype=object, + ) + ser[:5] = np.nan + ser[6:10] = "foo" + ser[20:30] = "bar" + + # replace list with a single value + rs = ser.replace([np.nan, "foo", "bar"], -1) + + assert (rs[:5] == -1).all() + assert (rs[6:10] == -1).all() + assert (rs[20:30] == -1).all() + assert (pd.isna(ser[:5])).all() + + # replace with different values + rs = ser.replace({np.nan: -1, "foo": -2, "bar": -3}) + + assert (rs[:5] == -1).all() + assert (rs[6:10] == -2).all() + assert (rs[20:30] == -3).all() + assert (pd.isna(ser[:5])).all() + + # replace with different values with 2 lists + rs2 = ser.replace([np.nan, "foo", "bar"], [-1, -2, -3]) + tm.assert_series_equal(rs, rs2) + + # replace inplace + result = ser.replace([np.nan, "foo", "bar"], -1, inplace=True) + assert result is ser + assert (ser[:5] == -1).all() + assert (ser[6:10] == -1).all() + assert (ser[20:30] == -1).all() + + @pytest.mark.parametrize("inplace", [True, False]) + def test_replace_cascade(self, inplace): + # Test that replaced values are not replaced again + # GH #50778 + ser = pd.Series([1, 2, 3]) + expected = pd.Series([2, 3, 4]) + + res = ser.replace([1, 2, 3], [2, 3, 4], inplace=inplace) + if inplace: + tm.assert_series_equal(ser, expected) + else: + tm.assert_series_equal(res, expected) + + def test_replace_with_dictlike_and_string_dtype(self, nullable_string_dtype): + # GH 32621, GH#44940 + ser = pd.Series(["one", "two", np.nan], dtype=nullable_string_dtype) + expected = pd.Series(["1", "2", np.nan], dtype=nullable_string_dtype) + result = ser.replace({"one": "1", "two": "2"}) + tm.assert_series_equal(expected, result) + + def test_replace_with_empty_dictlike(self): + # GH 15289 + s = pd.Series(list("abcd")) + tm.assert_series_equal(s, s.replace({})) + + empty_series = pd.Series([]) + tm.assert_series_equal(s, s.replace(empty_series)) + + def test_replace_string_with_number(self): + # GH 15743 + s = pd.Series([1, 2, 3]) + result = s.replace("2", np.nan) + expected = pd.Series([1, 2, 3]) + tm.assert_series_equal(expected, result) + + def test_replace_replacer_equals_replacement(self): + # GH 20656 + # make sure all replacers are matching against original values + s = pd.Series(["a", "b"]) + expected = pd.Series(["b", "a"]) + result = s.replace({"a": "b", "b": "a"}) + tm.assert_series_equal(expected, result) + + def test_replace_unicode_with_number(self): + # GH 15743 + s = pd.Series([1, 2, 3]) + result = s.replace("2", np.nan) + expected = pd.Series([1, 2, 3]) + tm.assert_series_equal(expected, result) + + def test_replace_mixed_types_with_string(self): + # Testing mixed + s = pd.Series([1, 2, 3, "4", 4, 5]) + result = s.replace([2, "4"], np.nan) + expected = pd.Series([1, np.nan, 3, np.nan, 4, 5], dtype=object) + tm.assert_series_equal(expected, result) + + @pytest.mark.parametrize( + "categorical, numeric", + [ + (["A"], [1]), + (["A", "B"], [1, 2]), + ], + ) + def test_replace_categorical(self, categorical, numeric): + # GH 24971, GH#23305 + ser = pd.Series(pd.Categorical(categorical, categories=["A", "B"])) + result = ser.cat.rename_categories({"A": 1, "B": 2}) + expected = pd.Series(numeric).astype("category") + if 2 not in expected.cat.categories: + # i.e. categories should be [1, 2] even if there are no "B"s present + # GH#44940 + expected = expected.cat.add_categories(2) + tm.assert_series_equal(expected, result, check_categorical=False) + + def test_replace_categorical_inplace(self): + # GH 53358 + data = ["a", "b", "c"] + data_exp = ["b", "b", "c"] + ser = pd.Series(data, dtype="category") + result = ser.replace(to_replace="a", value="b", inplace=True) + assert result is ser + expected = pd.Series(pd.Categorical(data_exp, categories=data)) + tm.assert_series_equal(ser, expected) + + def test_replace_categorical_single(self): + # GH 26988 + dti = pd.date_range("2016-01-01", periods=3, tz="US/Pacific") + s = pd.Series(dti) + c = s.astype("category") + + expected = c.copy() + expected = expected.cat.add_categories("foo") + expected[2] = "foo" + expected = expected.cat.remove_unused_categories() + assert c[2] != "foo" + + result = c.cat.rename_categories({c.values[2]: "foo"}) + tm.assert_series_equal(expected, result) + assert c[2] != "foo" # ensure non-inplace call does not alter original + + def test_replace_with_no_overflowerror(self): + # GH 25616 + # casts to object without Exception from OverflowError + s = pd.Series([0, 1, 2, 3, 4]) + result = s.replace([3], ["100000000000000000000"]) + expected = pd.Series([0, 1, 2, "100000000000000000000", 4]) + tm.assert_series_equal(result, expected) + + s = pd.Series([0, "100000000000000000000", "100000000000000000001"]) + result = s.replace(["100000000000000000000"], [1]) + expected = pd.Series([0, 1, "100000000000000000001"]) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "ser, to_replace, exp", + [ + ([1, 2, 3], {1: 2, 2: 3, 3: 4}, [2, 3, 4]), + (["1", "2", "3"], {"1": "2", "2": "3", "3": "4"}, ["2", "3", "4"]), + ], + ) + def test_replace_commutative(self, ser, to_replace, exp): + # GH 16051 + # DataFrame.replace() overwrites when values are non-numeric + + series = pd.Series(ser) + + expected = pd.Series(exp) + result = series.replace(to_replace) + + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "ser, exp", [([1, 2, 3], [1, True, 3]), (["x", 2, 3], ["x", True, 3])] + ) + def test_replace_no_cast(self, ser, exp): + # GH 9113 + # BUG: replace int64 dtype with bool coerces to int64 + + series = pd.Series(ser) + result = series.replace(2, True) + expected = pd.Series(exp) + + tm.assert_series_equal(result, expected) + + def test_replace_invalid_to_replace(self): + # GH 18634 + # API: replace() should raise an exception if invalid argument is given + series = pd.Series(["a", "b", "c "]) + msg = ( + r"Expecting 'to_replace' to be either a scalar, array-like, " + r"dict or None, got invalid type.*" + ) + with pytest.raises(TypeError, match=msg): + series.replace(lambda x: x.strip()) + + @pytest.mark.parametrize("frame", [False, True]) + def test_replace_nonbool_regex(self, frame): + obj = pd.Series(["a", "b", "c "]) + if frame: + obj = obj.to_frame() + + msg = "'to_replace' must be 'None' if 'regex' is not a bool" + with pytest.raises(ValueError, match=msg): + obj.replace(to_replace=["a"], regex="foo") + + @pytest.mark.parametrize("frame", [False, True]) + def test_replace_empty_copy(self, frame): + obj = pd.Series([], dtype=np.float64) + if frame: + obj = obj.to_frame() + + res = obj.replace(4, 5, inplace=True) + assert res is obj + + res = obj.replace(4, 5, inplace=False) + tm.assert_equal(res, obj) + assert res is not obj + + def test_replace_only_one_dictlike_arg(self, fixed_now_ts): + # GH#33340 + + ser = pd.Series([1, 2, "A", fixed_now_ts, True]) + to_replace = {0: 1, 2: "A"} + value = "foo" + msg = ( + "Series.replace cannot specify both a dict-like 'to_replace' and a 'value'" + ) + with pytest.raises(ValueError, match=msg): + ser.replace(to_replace, value) + + to_replace = 1 + value = {0: "foo", 2: "bar"} + msg = "Series.replace cannot use dict-value and non-None to_replace" + with pytest.raises(ValueError, match=msg): + ser.replace(to_replace, value) + + def test_replace_dict_like_with_dict_like(self): + # GH 59452 + s = pd.Series([1, 2, 3, 4, 5]) + to_replace = pd.Series([1]) + value = pd.Series([75]) + msg = "to_replace and value cannot be dict-like for Series.replace" + with pytest.raises(ValueError, match=msg): + s.replace(to_replace, value) + + def test_replace_extension_other(self, frame_or_series): + # https://github.com/pandas-dev/pandas/issues/34530 + obj = frame_or_series(pd.array([1, 2, 3], dtype="Int64")) + result = obj.replace("", "") # no exception + # should not have changed dtype + tm.assert_equal(obj, result) + + def test_replace_with_compiled_regex(self): + # https://github.com/pandas-dev/pandas/issues/35680 + s = pd.Series(["a", "b", "c"]) + regex = re.compile("^a$") + result = s.replace({regex: "z"}, regex=True) + expected = pd.Series(["z", "b", "c"]) + tm.assert_series_equal(result, expected) + + def test_pandas_replace_na(self): + # GH#43344 + # GH#56599 + ser = pd.Series(["AA", "BB", "CC", "DD", "EE", "", pd.NA, "AA"], dtype="string") + regex_mapping = { + "AA": "CC", + "BB": "CC", + "EE": "CC", + "CC": "CC-REPL", + } + result = ser.replace(regex_mapping, regex=True) + exp = pd.Series( + ["CC", "CC", "CC-REPL", "DD", "CC", "", pd.NA, "CC"], dtype="string" + ) + tm.assert_series_equal(result, exp) + + @pytest.mark.parametrize( + "dtype, input_data, to_replace, expected_data", + [ + ("bool", [True, False], {True: False}, [False, False]), + ("int64", [1, 2], {1: 10, 2: 20}, [10, 20]), + ("Int64", [1, 2], {1: 10, 2: 20}, [10, 20]), + ("float64", [1.1, 2.2], {1.1: 10.1, 2.2: 20.5}, [10.1, 20.5]), + ("Float64", [1.1, 2.2], {1.1: 10.1, 2.2: 20.5}, [10.1, 20.5]), + ("string", ["one", "two"], {"one": "1", "two": "2"}, ["1", "2"]), + ( + pd.IntervalDtype("int64"), + IntervalArray([pd.Interval(1, 2), pd.Interval(2, 3)]), + {pd.Interval(1, 2): pd.Interval(10, 20)}, + IntervalArray([pd.Interval(10, 20), pd.Interval(2, 3)]), + ), + ( + pd.IntervalDtype("float64"), + IntervalArray([pd.Interval(1.0, 2.7), pd.Interval(2.8, 3.1)]), + {pd.Interval(1.0, 2.7): pd.Interval(10.6, 20.8)}, + IntervalArray([pd.Interval(10.6, 20.8), pd.Interval(2.8, 3.1)]), + ), + ( + pd.PeriodDtype("M"), + [pd.Period("2020-05", freq="M")], + {pd.Period("2020-05", freq="M"): pd.Period("2020-06", freq="M")}, + [pd.Period("2020-06", freq="M")], + ), + ], + ) + def test_replace_dtype(self, dtype, input_data, to_replace, expected_data): + # GH#33484 + ser = pd.Series(input_data, dtype=dtype) + result = ser.replace(to_replace) + expected = pd.Series(expected_data, dtype=dtype) + tm.assert_series_equal(result, expected) + + def test_replace_string_dtype(self): + # GH#40732, GH#44940 + ser = pd.Series(["one", "two", np.nan], dtype="string") + res = ser.replace({"one": "1", "two": "2"}) + expected = pd.Series(["1", "2", np.nan], dtype="string") + tm.assert_series_equal(res, expected) + + # GH#31644 + ser2 = pd.Series(["A", np.nan], dtype="string") + res2 = ser2.replace("A", "B") + expected2 = pd.Series(["B", np.nan], dtype="string") + tm.assert_series_equal(res2, expected2) + + ser3 = pd.Series(["A", "B"], dtype="string") + res3 = ser3.replace("A", pd.NA) + expected3 = pd.Series([pd.NA, "B"], dtype="string") + tm.assert_series_equal(res3, expected3) + + def test_replace_string_dtype_list_to_replace(self): + # GH#41215, GH#44940 + ser = pd.Series(["abc", "def"], dtype="string") + res = ser.replace(["abc", "any other string"], "xyz") + expected = pd.Series(["xyz", "def"], dtype="string") + tm.assert_series_equal(res, expected) + + def test_replace_string_dtype_regex(self): + # GH#31644 + ser = pd.Series(["A", "B"], dtype="string") + res = ser.replace(r".", "C", regex=True) + expected = pd.Series(["C", "C"], dtype="string") + tm.assert_series_equal(res, expected) + + def test_replace_nullable_numeric(self): + # GH#40732, GH#44940 + + floats = pd.Series([1.0, 2.0, 3.999, 4.4], dtype=pd.Float64Dtype()) + assert floats.replace({1.0: 9}).dtype == floats.dtype + assert floats.replace(1.0, 9).dtype == floats.dtype + assert floats.replace({1.0: 9.0}).dtype == floats.dtype + assert floats.replace(1.0, 9.0).dtype == floats.dtype + + res = floats.replace(to_replace=[1.0, 2.0], value=[9.0, 10.0]) + assert res.dtype == floats.dtype + + ints = pd.Series([1, 2, 3, 4], dtype=pd.Int64Dtype()) + assert ints.replace({1: 9}).dtype == ints.dtype + assert ints.replace(1, 9).dtype == ints.dtype + assert ints.replace({1: 9.0}).dtype == ints.dtype + assert ints.replace(1, 9.0).dtype == ints.dtype + + # nullable (for now) raises instead of casting + with pytest.raises(TypeError, match="Invalid value"): + ints.replace({1: 9.5}) + with pytest.raises(TypeError, match="Invalid value"): + ints.replace(1, 9.5) + + @pytest.mark.parametrize("regex", [False, True]) + def test_replace_regex_dtype_series(self, regex): + # GH-48644 + series = pd.Series(["0"], dtype=object) + expected = pd.Series([1], dtype=object) + result = series.replace(to_replace="0", value=1, regex=regex) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("regex", [False, True]) + def test_replace_regex_dtype_series_string(self, regex): + series = pd.Series(["0"], dtype="str") + expected = pd.Series([1], dtype=object) + result = series.replace(to_replace="0", value=1, regex=regex) + tm.assert_series_equal(result, expected) + + def test_replace_different_int_types(self, any_int_numpy_dtype): + # GH#45311 + labs = pd.Series([1, 1, 1, 0, 0, 2, 2, 2], dtype=any_int_numpy_dtype) + + maps = pd.Series([0, 2, 1], dtype=any_int_numpy_dtype) + map_dict = dict(zip(maps.values, maps.index, strict=True)) + + result = labs.replace(map_dict) + expected = labs.replace({0: 0, 2: 1, 1: 2}) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("val", [2, np.nan, 2.0]) + def test_replace_value_none_dtype_numeric(self, val): + # GH#48231 + ser = pd.Series([1, val]) + result = ser.replace(val, None) + expected = pd.Series([1, None], dtype=object) + tm.assert_series_equal(result, expected) + + def test_replace_change_dtype_series(self): + # GH#25797 + df = pd.DataFrame({"Test": ["0.5", True, "0.6"]}, dtype=object) + df["Test"] = df["Test"].replace([True], [np.nan]) + expected = pd.DataFrame({"Test": ["0.5", np.nan, "0.6"]}, dtype=object) + tm.assert_frame_equal(df, expected) + + df = pd.DataFrame({"Test": ["0.5", None, "0.6"]}, dtype=object) + df["Test"] = df["Test"].replace([None], [np.nan]) + tm.assert_frame_equal(df, expected) + + df = pd.DataFrame({"Test": ["0.5", None, "0.6"]}, dtype=object) + df["Test"] = df["Test"].fillna(np.nan) + tm.assert_frame_equal(df, expected) + + @pytest.mark.parametrize("dtype", ["object", "Int64"]) + def test_replace_na_in_obj_column(self, dtype): + # GH#47480 + ser = pd.Series([0, 1, pd.NA], dtype=dtype) + expected = pd.Series([0, 2, pd.NA], dtype=dtype) + result = ser.replace(to_replace=1, value=2) + tm.assert_series_equal(result, expected) + + result = ser.replace(to_replace=1, value=2, inplace=True) + assert result is ser + tm.assert_series_equal(ser, expected) + + @pytest.mark.parametrize("val", [0, 0.5]) + def test_replace_numeric_column_with_na(self, val): + # GH#50758 + ser = pd.Series([val, 1]) + expected = pd.Series([val, pd.NA]) + result = ser.replace(to_replace=1, value=pd.NA) + tm.assert_series_equal(result, expected) + + result = ser.replace(to_replace=1, value=pd.NA, inplace=True) + assert result is ser + tm.assert_series_equal(ser, expected) + + def test_replace_ea_float_with_bool(self): + # GH#55398 + ser = pd.Series([0.0], dtype="Float64") + expected = ser.copy() + result = ser.replace(False, 1.0) + tm.assert_series_equal(result, expected) + + ser = pd.Series([False], dtype="boolean") + expected = ser.copy() + result = ser.replace(0.0, True) + tm.assert_series_equal(result, expected) + + def test_replace_all_NA(self): + # GH#60688 + df = pd.Series([pd.NA, pd.NA]) + result = df.replace({r"^#": "$"}, regex=True) + expected = pd.Series([pd.NA, pd.NA]) + tm.assert_series_equal(result, expected) + + def test_replace_mixed_types_with_none(self): + # GH#29813 + df = pd.Series([np.nan, 1, "foo"]) + result = df.replace({np.nan: None}) + expected = pd.Series([None, 1, "foo"]) + tm.assert_series_equal(result, expected) + + +@td.skip_if_no("pyarrow") +def test_replace_from_index(): + # https://github.com/pandas-dev/pandas/issues/61622 + idx = pd.Index(["a", "b", "c"], dtype="string[pyarrow]") + expected = pd.Series(["d", "b", "c"], dtype="string[pyarrow]") + result = pd.Series(idx).replace({"z": "b", "a": "d"}) + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_reset_index.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_reset_index.py new file mode 100644 index 0000000000000000000000000000000000000000..82de2349a6e98fd73ffb9571f1c64a54de63a5b1 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_reset_index.py @@ -0,0 +1,227 @@ +from datetime import datetime + +import numpy as np +import pytest + +import pandas as pd +from pandas import ( + DataFrame, + Index, + MultiIndex, + RangeIndex, + Series, + date_range, + option_context, +) +import pandas._testing as tm + + +class TestResetIndex: + def test_reset_index_dti_round_trip(self): + dti = date_range( + start="1/1/2001", end="6/1/2001", freq="D", unit="ns" + )._with_freq(None) + d1 = DataFrame({"v": np.random.default_rng(2).random(len(dti))}, index=dti) + d2 = d1.reset_index() + assert d2.dtypes.iloc[0] == np.dtype("M8[ns]") + d3 = d2.set_index("index") + tm.assert_frame_equal(d1, d3, check_names=False) + + # GH#2329 + stamp = datetime(2012, 11, 22) + df = DataFrame([[stamp, 12.1]], columns=["Date", "Value"]) + df = df.set_index("Date") + + assert df.index[0] == stamp + assert df.reset_index()["Date"].iloc[0] == stamp + + def test_reset_index(self): + df = DataFrame( + 1.1 * np.arange(120).reshape((30, 4)), + columns=Index(list("ABCD"), dtype=object), + index=Index([f"i-{i}" for i in range(30)], dtype=object), + )[:5] + ser = df.stack() + ser.index.names = ["hash", "category"] + + ser.name = "value" + df = ser.reset_index() + assert "value" in df + + df = ser.reset_index(name="value2") + assert "value2" in df + + # check inplace + s = ser.reset_index(drop=True) + s2 = ser + return_value = s2.reset_index(drop=True, inplace=True) + assert return_value is None + tm.assert_series_equal(s, s2) + + # level + index = MultiIndex( + levels=[["bar"], ["one", "two", "three"], [0, 1]], + codes=[[0, 0, 0, 0, 0, 0], [0, 1, 2, 0, 1, 2], [0, 1, 0, 1, 0, 1]], + ) + s = Series(np.random.default_rng(2).standard_normal(6), index=index) + rs = s.reset_index(level=1) + assert len(rs.columns) == 2 + + rs = s.reset_index(level=[0, 2], drop=True) + tm.assert_index_equal(rs.index, Index(index.get_level_values(1))) + assert isinstance(rs, Series) + + def test_reset_index_name(self): + s = Series([1, 2, 3], index=Index(range(3), name="x")) + assert s.reset_index().index.name is None + assert s.reset_index(drop=True).index.name is None + + def test_reset_index_level(self): + df = DataFrame([[1, 2, 3], [4, 5, 6]], columns=["A", "B", "C"]) + + for levels in ["A", "B"], [0, 1]: + # With MultiIndex + s = df.set_index(["A", "B"])["C"] + + result = s.reset_index(level=levels[0]) + tm.assert_frame_equal(result, df.set_index("B")) + + result = s.reset_index(level=levels[:1]) + tm.assert_frame_equal(result, df.set_index("B")) + + result = s.reset_index(level=levels) + tm.assert_frame_equal(result, df) + + result = df.set_index(["A", "B"]).reset_index(level=levels, drop=True) + tm.assert_frame_equal(result, df[["C"]]) + + with pytest.raises(KeyError, match="Level E "): + s.reset_index(level=["A", "E"]) + + # With single-level Index + s = df.set_index("A")["B"] + + result = s.reset_index(level=levels[0]) + tm.assert_frame_equal(result, df[["A", "B"]]) + + result = s.reset_index(level=levels[:1]) + tm.assert_frame_equal(result, df[["A", "B"]]) + + result = s.reset_index(level=levels[0], drop=True) + tm.assert_series_equal(result, df["B"]) + + with pytest.raises(IndexError, match="Too many levels"): + s.reset_index(level=[0, 1, 2]) + + # Check that .reset_index([],drop=True) doesn't fail + result = Series(range(4)).reset_index([], drop=True) + expected = Series(range(4)) + tm.assert_series_equal(result, expected) + + def test_reset_index_range(self): + # GH 12071 + s = Series(range(2), name="A", dtype="int64") + series_result = s.reset_index() + assert isinstance(series_result.index, RangeIndex) + series_expected = DataFrame( + [[0, 0], [1, 1]], columns=["index", "A"], index=RangeIndex(stop=2) + ) + tm.assert_frame_equal(series_result, series_expected) + + def test_reset_index_drop_errors(self): + # GH 20925 + + # KeyError raised for series index when passed level name is missing + s = Series(range(4)) + with pytest.raises(KeyError, match="does not match index name"): + s.reset_index("wrong", drop=True) + with pytest.raises(KeyError, match="does not match index name"): + s.reset_index("wrong") + + # KeyError raised for series when level to be dropped is missing + s = Series(range(4), index=MultiIndex.from_product([[1, 2]] * 2)) + with pytest.raises(KeyError, match="not found"): + s.reset_index("wrong", drop=True) + + def test_reset_index_with_drop(self): + arrays = [ + ["bar", "bar", "baz", "baz", "qux", "qux", "foo", "foo"], + ["one", "two", "one", "two", "one", "two", "one", "two"], + ] + tuples = zip(*arrays, strict=True) + index = MultiIndex.from_tuples(tuples) + data = np.random.default_rng(2).standard_normal(8) + ser = Series(data, index=index) + ser.iloc[3] = np.nan + + deleveled = ser.reset_index() + assert isinstance(deleveled, DataFrame) + assert len(deleveled.columns) == len(ser.index.levels) + 1 + assert deleveled.index.name == ser.index.name + + deleveled = ser.reset_index(drop=True) + assert isinstance(deleveled, Series) + assert deleveled.index.name == ser.index.name + + def test_reset_index_inplace_and_drop_ignore_name(self): + # GH#44575 + ser = Series(range(2), name="old") + ser.reset_index(name="new", drop=True, inplace=True) + expected = Series(range(2), name="old") + tm.assert_series_equal(ser, expected) + + def test_reset_index_drop_infer_string(self): + # GH#56160 + pytest.importorskip("pyarrow") + ser = Series(["a", "b", "c"], dtype=object) + with option_context("future.infer_string", True): + result = ser.reset_index(drop=True) + tm.assert_series_equal(result, ser) + + +@pytest.mark.parametrize( + "array, dtype", + [ + (["a", "b"], object), + ( + pd.period_range("12-1-2000", periods=2, freq="Q-DEC"), + pd.PeriodDtype(freq="Q-DEC"), + ), + ], +) +def test_reset_index_dtypes_on_empty_series_with_multiindex( + array, dtype, using_infer_string +): + # GH 19602 - Preserve dtype on empty Series with MultiIndex + idx = MultiIndex.from_product([[0, 1], [0.5, 1.0], array]) + result = Series(dtype=object, index=idx)[:0].reset_index().dtypes + exp = "str" if using_infer_string else object + expected = Series( + { + "level_0": np.int64, + "level_1": np.float64, + "level_2": exp if dtype == object else dtype, + 0: object, + } + ) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "names, expected_names", + [ + (["A", "A"], ["A", "A"]), + (["level_1", None], ["level_1", "level_1"]), + ], +) +@pytest.mark.parametrize("allow_duplicates", [False, True]) +def test_column_name_duplicates(names, expected_names, allow_duplicates): + # GH#44755 reset_index with duplicate column labels + s = Series([1], index=MultiIndex.from_arrays([[1], [1]], names=names)) + if allow_duplicates: + result = s.reset_index(allow_duplicates=True) + expected = DataFrame([[1, 1, 1]], columns=[*expected_names, 0]) + tm.assert_frame_equal(result, expected) + else: + with pytest.raises(ValueError, match="cannot insert"): + s.reset_index() diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_round.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_round.py new file mode 100644 index 0000000000000000000000000000000000000000..46232863532294091be51a99b9708eff22c3b1ca --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_round.py @@ -0,0 +1,119 @@ +import numpy as np +import pytest + +import pandas as pd +from pandas import Series +import pandas._testing as tm + + +class TestSeriesRound: + def test_round(self, datetime_series): + datetime_series.index.name = "index_name" + result = datetime_series.round(2) + expected = Series( + np.round(datetime_series.values, 2), index=datetime_series.index, name="ts" + ) + tm.assert_series_equal(result, expected) + assert result.name == datetime_series.name + + def test_round_numpy(self, any_float_dtype): + # See GH#12600 + ser = Series([1.53, 1.36, 0.06], dtype=any_float_dtype) + out = np.round(ser, decimals=0) + expected = Series([2.0, 1.0, 0.0], dtype=any_float_dtype) + tm.assert_series_equal(out, expected) + + msg = "the 'out' parameter is not supported" + with pytest.raises(ValueError, match=msg): + np.round(ser, decimals=0, out=ser) + + def test_round_numpy_with_nan(self, any_float_dtype): + # See GH#14197 + ser = Series([1.53, np.nan, 0.06], dtype=any_float_dtype) + with tm.assert_produces_warning(None): + result = ser.round() + expected = Series([2.0, np.nan, 0.0], dtype=any_float_dtype) + tm.assert_series_equal(result, expected) + + def test_round_builtin(self, any_float_dtype): + ser = Series( + [1.123, 2.123, 3.123], + index=range(3), + dtype=any_float_dtype, + ) + result = round(ser) + expected_rounded0 = Series( + [1.0, 2.0, 3.0], index=range(3), dtype=any_float_dtype + ) + tm.assert_series_equal(result, expected_rounded0) + + decimals = 2 + expected_rounded = Series( + [1.12, 2.12, 3.12], index=range(3), dtype=any_float_dtype + ) + result = round(ser, decimals) + tm.assert_series_equal(result, expected_rounded) + + @pytest.mark.parametrize("method", ["round", "floor", "ceil"]) + @pytest.mark.parametrize("freq", ["s", "5s", "min", "5min", "h", "5h"]) + def test_round_nat(self, method, freq, unit): + # GH14940, GH#56158 + ser = Series([pd.NaT], dtype=f"M8[{unit}]") + expected = Series(pd.NaT, dtype=f"M8[{unit}]") + round_method = getattr(ser.dt, method) + result = round_method(freq) + tm.assert_series_equal(result, expected) + + def test_round_ea_boolean(self): + # GH#55936 + ser = Series([True, False], dtype="boolean") + expected = ser.copy() + result = ser.round(2) + tm.assert_series_equal(result, expected) + result.iloc[0] = False + tm.assert_series_equal(ser, expected) + + @pytest.mark.parametrize( + "data,decimals,expected_data", + [ + ([0.2], 0, [0.0]), + ([1.234, 2.567], 1, [1.2, 2.6]), + ([1.234, 2.567], 2, [1.23, 2.57]), + ([1.234, 2.567], 0, [1.0, 3.0]), + ], + ) + def test_round_dtype_object(self, data, decimals, expected_data): + # GH#63444 + ser = Series(data, dtype="object") + result = ser.round(decimals) + expected = Series(expected_data, dtype="object") + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "dtype", + [ + None, + "float64", + "int64", + "object", + ], + ) + def test_round_empty_series(self, dtype): + # GH#63444 + result = Series(dtype=dtype).round(4) + expected = Series(dtype=dtype) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "dtype,data", + [ + ("string", ["a", "b", "c"]), + ("category", ["cat1", "cat2", "cat3"]), + (pd.StringDtype(), ["x", "y", "z"]), + ], + ) + def test_round_non_numeric_dtype_noop(self, dtype, data): + # GH#63444, GH#63559 + ser = Series(data, dtype=dtype) + result = ser.round(2) + tm.assert_series_equal(result, ser) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_searchsorted.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_searchsorted.py new file mode 100644 index 0000000000000000000000000000000000000000..239496052b99b42df262262a9ac89b71c93e0a26 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_searchsorted.py @@ -0,0 +1,77 @@ +import numpy as np +import pytest + +import pandas as pd +from pandas import ( + Series, + Timestamp, + date_range, +) +import pandas._testing as tm +from pandas.api.types import is_scalar + + +class TestSeriesSearchSorted: + def test_searchsorted(self): + ser = Series([1, 2, 3]) + + result = ser.searchsorted(1, side="left") + assert is_scalar(result) + assert result == 0 + + result = ser.searchsorted(1, side="right") + assert is_scalar(result) + assert result == 1 + + def test_searchsorted_numeric_dtypes_scalar(self): + ser = Series([1, 2, 90, 1000, 3e9]) + res = ser.searchsorted(30) + assert is_scalar(res) + assert res == 2 + + res = ser.searchsorted([30]) + exp = np.array([2], dtype=np.intp) + tm.assert_numpy_array_equal(res, exp) + + def test_searchsorted_numeric_dtypes_vector(self): + ser = Series([1, 2, 90, 1000, 3e9]) + res = ser.searchsorted([91, 2e6]) + exp = np.array([3, 4], dtype=np.intp) + tm.assert_numpy_array_equal(res, exp) + + def test_searchsorted_datetime64_scalar(self): + ser = Series(date_range("20120101", periods=10, freq="2D")) + val = Timestamp("20120102") + res = ser.searchsorted(val) + assert is_scalar(res) + assert res == 1 + + def test_searchsorted_datetime64_scalar_mixed_timezones(self): + # GH 30086 + ser = Series(date_range("20120101", periods=10, freq="2D", tz="UTC")) + val = Timestamp("20120102", tz="America/New_York") + res = ser.searchsorted(val) + assert is_scalar(res) + assert res == 1 + + def test_searchsorted_datetime64_list(self): + ser = Series(date_range("20120101", periods=10, freq="2D")) + vals = [Timestamp("20120102"), Timestamp("20120104")] + res = ser.searchsorted(vals) + exp = np.array([1, 2], dtype=np.intp) + tm.assert_numpy_array_equal(res, exp) + + def test_searchsorted_sorter(self): + # GH8490 + ser = Series([3, 1, 2]) + res = ser.searchsorted([0, 3], sorter=np.argsort(ser)) + exp = np.array([0, 2], dtype=np.intp) + tm.assert_numpy_array_equal(res, exp) + + def test_searchsorted_dataframe_fail(self): + # GH#49620 + ser = Series([1, 2, 3, 4, 5]) + vals = pd.DataFrame([[1, 2], [3, 4]]) + msg = "Value must be 1-D array-like or scalar, DataFrame is not supported" + with pytest.raises(ValueError, match=msg): + ser.searchsorted(vals) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_set_name.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_set_name.py new file mode 100644 index 0000000000000000000000000000000000000000..137207053c2258fd2db551d787a1bd70f09bf8ca --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_set_name.py @@ -0,0 +1,21 @@ +from datetime import datetime + +from pandas import Series + + +class TestSetName: + def test_set_name(self): + ser = Series([1, 2, 3]) + ser2 = ser._set_name("foo") + assert ser2.name == "foo" + assert ser.name is None + assert ser is not ser2 + + def test_set_name_attribute(self): + ser = Series([1, 2, 3]) + ser2 = Series([1, 2, 3], name="bar") + for name in [7, 7.0, "name", datetime(2001, 1, 1), (1,), "\u05d0"]: + ser.name = name + assert ser.name == name + ser2.name = name + assert ser2.name == name diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_size.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_size.py new file mode 100644 index 0000000000000000000000000000000000000000..043e4b66dbf16d0163b07b272af23de0ce003e22 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_size.py @@ -0,0 +1,20 @@ +import pytest + +from pandas import Series + + +@pytest.mark.parametrize( + "data, index, expected", + [ + ([1, 2, 3], None, 3), + ({"a": 1, "b": 2, "c": 3}, None, 3), + ([1, 2, 3], ["x", "y", "z"], 3), + ([1, 2, 3, 4, 5], ["x", "y", "z", "w", "n"], 5), + ([1, 2, 3, 4], ["x", "y", "z", "w"], 4), + ], +) +def test_series(data, index, expected): + # GH#52897 + ser = Series(data, index=index) + assert ser.size == expected + assert isinstance(ser.size, int) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_sort_index.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_sort_index.py new file mode 100644 index 0000000000000000000000000000000000000000..861017f448195ef1a1e10f2ebdfac076972921b2 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_sort_index.py @@ -0,0 +1,337 @@ +import numpy as np +import pytest + +from pandas import ( + DatetimeIndex, + IntervalIndex, + MultiIndex, + Series, +) +import pandas._testing as tm + + +@pytest.fixture(params=["quicksort", "mergesort", "heapsort", "stable"]) +def sort_kind(request): + return request.param + + +class TestSeriesSortIndex: + def test_sort_index_name(self, datetime_series): + result = datetime_series.sort_index(ascending=False) + assert result.name == datetime_series.name + + def test_sort_index(self, datetime_series): + datetime_series.index = datetime_series.index._with_freq(None) + + rindex = list(datetime_series.index) + np.random.default_rng(2).shuffle(rindex) + + random_order = datetime_series.reindex(rindex) + sorted_series = random_order.sort_index() + tm.assert_series_equal(sorted_series, datetime_series) + + # descending + sorted_series = random_order.sort_index(ascending=False) + tm.assert_series_equal( + sorted_series, datetime_series.reindex(datetime_series.index[::-1]) + ) + + # compat on level + sorted_series = random_order.sort_index(level=0) + tm.assert_series_equal(sorted_series, datetime_series) + + # compat on axis + sorted_series = random_order.sort_index(axis=0) + tm.assert_series_equal(sorted_series, datetime_series) + + msg = "No axis named 1 for object type Series" + with pytest.raises(ValueError, match=msg): + random_order.sort_values(axis=1) + + sorted_series = random_order.sort_index(level=0, axis=0) + tm.assert_series_equal(sorted_series, datetime_series) + + with pytest.raises(ValueError, match=msg): + random_order.sort_index(level=0, axis=1) + + def test_sort_index_inplace(self, datetime_series): + datetime_series.index = datetime_series.index._with_freq(None) + + # For GH#11402 + rindex = list(datetime_series.index) + np.random.default_rng(2).shuffle(rindex) + + # descending + random_order = datetime_series.reindex(rindex) + result = random_order.sort_index(ascending=False, inplace=True) + + assert result is None + expected = datetime_series.reindex(datetime_series.index[::-1]) + expected.index = expected.index._with_freq(None) + tm.assert_series_equal(random_order, expected) + + # ascending + random_order = datetime_series.reindex(rindex) + result = random_order.sort_index(ascending=True, inplace=True) + + assert result is None + expected = datetime_series.copy() + expected.index = expected.index._with_freq(None) + tm.assert_series_equal(random_order, expected) + + def test_sort_index_level(self): + mi = MultiIndex.from_tuples([[1, 1, 3], [1, 1, 1]], names=list("ABC")) + s = Series([1, 2], mi) + backwards = s.iloc[[1, 0]] + + res = s.sort_index(level="A") + tm.assert_series_equal(backwards, res) + + res = s.sort_index(level=["A", "B"]) + tm.assert_series_equal(backwards, res) + + res = s.sort_index(level="A", sort_remaining=False) + tm.assert_series_equal(s, res) + + res = s.sort_index(level=["A", "B"], sort_remaining=False) + tm.assert_series_equal(s, res) + + @pytest.mark.parametrize("level", ["A", 0]) # GH#21052 + def test_sort_index_multiindex(self, level): + mi = MultiIndex.from_tuples([[1, 1, 3], [1, 1, 1]], names=list("ABC")) + s = Series([1, 2], mi) + backwards = s.iloc[[1, 0]] + + # implicit sort_remaining=True + res = s.sort_index(level=level) + tm.assert_series_equal(backwards, res) + + # GH#13496 + # sort has no effect without remaining lvls + res = s.sort_index(level=level, sort_remaining=False) + tm.assert_series_equal(s, res) + + def test_sort_index_kind(self, sort_kind): + # GH#14444 & GH#13589: Add support for sort algo choosing + series = Series(index=[3, 2, 1, 4, 3], dtype=object) + expected_series = Series(index=[1, 2, 3, 3, 4], dtype=object) + + index_sorted_series = series.sort_index(kind=sort_kind) + tm.assert_series_equal(expected_series, index_sorted_series) + + def test_sort_index_na_position(self): + series = Series(index=[3, 2, 1, 4, 3, np.nan], dtype=object) + expected_series_first = Series(index=[np.nan, 1, 2, 3, 3, 4], dtype=object) + + index_sorted_series = series.sort_index(na_position="first") + tm.assert_series_equal(expected_series_first, index_sorted_series) + + expected_series_last = Series(index=[1, 2, 3, 3, 4, np.nan], dtype=object) + + index_sorted_series = series.sort_index(na_position="last") + tm.assert_series_equal(expected_series_last, index_sorted_series) + + def test_sort_index_intervals(self): + s = Series( + [np.nan, 1, 2, 3], IntervalIndex.from_arrays([0, 1, 2, 3], [1, 2, 3, 4]) + ) + + result = s.sort_index() + expected = s + tm.assert_series_equal(result, expected) + + result = s.sort_index(ascending=False) + expected = Series( + [3, 2, 1, np.nan], IntervalIndex.from_arrays([3, 2, 1, 0], [4, 3, 2, 1]) + ) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("inplace", [True, False]) + @pytest.mark.parametrize( + "original_list, sorted_list, ascending, ignore_index, output_index", + [ + ([2, 3, 6, 1], [2, 3, 6, 1], True, True, [0, 1, 2, 3]), + ([2, 3, 6, 1], [2, 3, 6, 1], True, False, [0, 1, 2, 3]), + ([2, 3, 6, 1], [1, 6, 3, 2], False, True, [0, 1, 2, 3]), + ([2, 3, 6, 1], [1, 6, 3, 2], False, False, [3, 2, 1, 0]), + ], + ) + def test_sort_index_ignore_index( + self, inplace, original_list, sorted_list, ascending, ignore_index, output_index + ): + # GH 30114 + ser = Series(original_list) + expected = Series(sorted_list, index=output_index) + kwargs = { + "ascending": ascending, + "ignore_index": ignore_index, + "inplace": inplace, + } + + if inplace: + result_ser = ser.copy() + result_ser.sort_index(**kwargs) + else: + result_ser = ser.sort_index(**kwargs) + + tm.assert_series_equal(result_ser, expected) + tm.assert_series_equal(ser, Series(original_list)) + + def test_sort_index_ascending_list(self): + # GH#16934 + + # Set up a Series with a three level MultiIndex + arrays = [ + ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"], + ["one", "two", "one", "two", "one", "two", "one", "two"], + [4, 3, 2, 1, 4, 3, 2, 1], + ] + tuples = zip(*arrays, strict=True) + mi = MultiIndex.from_tuples(tuples, names=["first", "second", "third"]) + ser = Series(range(8), index=mi) + + # Sort with boolean ascending + result = ser.sort_index(level=["third", "first"], ascending=False) + expected = ser.iloc[[4, 0, 5, 1, 6, 2, 7, 3]] + tm.assert_series_equal(result, expected) + + # Sort with list of boolean ascending + result = ser.sort_index(level=["third", "first"], ascending=[False, True]) + expected = ser.iloc[[0, 4, 1, 5, 2, 6, 3, 7]] + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "ascending", + [ + None, + (True, None), + (False, "True"), + ], + ) + def test_sort_index_ascending_bad_value_raises(self, ascending): + ser = Series(range(10), index=[0, 3, 2, 1, 4, 5, 7, 6, 8, 9]) + match = 'For argument "ascending" expected type bool' + with pytest.raises(ValueError, match=match): + ser.sort_index(ascending=ascending) + + +class TestSeriesSortIndexKey: + def test_sort_index_multiindex_key(self): + mi = MultiIndex.from_tuples([[1, 1, 3], [1, 1, 1]], names=list("ABC")) + s = Series([1, 2], mi) + backwards = s.iloc[[1, 0]] + + result = s.sort_index(level="C", key=lambda x: -x) + tm.assert_series_equal(s, result) + + result = s.sort_index(level="C", key=lambda x: x) # nothing happens + tm.assert_series_equal(backwards, result) + + def test_sort_index_multiindex_key_multi_level(self): + mi = MultiIndex.from_tuples([[1, 1, 3], [1, 1, 1]], names=list("ABC")) + s = Series([1, 2], mi) + backwards = s.iloc[[1, 0]] + + result = s.sort_index(level=["A", "C"], key=lambda x: -x) + tm.assert_series_equal(s, result) + + result = s.sort_index(level=["A", "C"], key=lambda x: x) # nothing happens + tm.assert_series_equal(backwards, result) + + def test_sort_index_key(self): + series = Series(np.arange(6, dtype="int64"), index=list("aaBBca")) + + result = series.sort_index() + expected = series.iloc[[2, 3, 0, 1, 5, 4]] + tm.assert_series_equal(result, expected) + + result = series.sort_index(key=lambda x: x.str.lower()) + expected = series.iloc[[0, 1, 5, 2, 3, 4]] + tm.assert_series_equal(result, expected) + + result = series.sort_index(key=lambda x: x.str.lower(), ascending=False) + expected = series.iloc[[4, 2, 3, 0, 1, 5]] + tm.assert_series_equal(result, expected) + + def test_sort_index_key_int(self): + series = Series(np.arange(6, dtype="int64"), index=np.arange(6, dtype="int64")) + + result = series.sort_index() + tm.assert_series_equal(result, series) + + result = series.sort_index(key=lambda x: -x) + expected = series.sort_index(ascending=False) + tm.assert_series_equal(result, expected) + + result = series.sort_index(key=lambda x: 2 * x) + tm.assert_series_equal(result, series) + + def test_sort_index_kind_key(self, sort_kind, sort_by_key): + # GH #14444 & #13589: Add support for sort algo choosing + series = Series(index=[3, 2, 1, 4, 3], dtype=object) + expected_series = Series(index=[1, 2, 3, 3, 4], dtype=object) + + index_sorted_series = series.sort_index(kind=sort_kind, key=sort_by_key) + tm.assert_series_equal(expected_series, index_sorted_series) + + def test_sort_index_kind_neg_key(self, sort_kind): + # GH #14444 & #13589: Add support for sort algo choosing + series = Series(index=[3, 2, 1, 4, 3], dtype=object) + expected_series = Series(index=[4, 3, 3, 2, 1], dtype=object) + + index_sorted_series = series.sort_index(kind=sort_kind, key=lambda x: -x) + tm.assert_series_equal(expected_series, index_sorted_series) + + def test_sort_index_na_position_key(self, sort_by_key): + series = Series(index=[3, 2, 1, 4, 3, np.nan], dtype=object) + expected_series_first = Series(index=[np.nan, 1, 2, 3, 3, 4], dtype=object) + + index_sorted_series = series.sort_index(na_position="first", key=sort_by_key) + tm.assert_series_equal(expected_series_first, index_sorted_series) + + expected_series_last = Series(index=[1, 2, 3, 3, 4, np.nan], dtype=object) + + index_sorted_series = series.sort_index(na_position="last", key=sort_by_key) + tm.assert_series_equal(expected_series_last, index_sorted_series) + + def test_changes_length_raises(self): + s = Series([1, 2, 3]) + with pytest.raises(ValueError, match="change the shape"): + s.sort_index(key=lambda x: x[:1]) + + def test_sort_values_key_type(self): + s = Series([1, 2, 3], DatetimeIndex(["2008-10-24", "2008-11-23", "2007-12-22"])) + + result = s.sort_index(key=lambda x: x.month) + expected = s.iloc[[0, 1, 2]] + tm.assert_series_equal(result, expected) + + result = s.sort_index(key=lambda x: x.day) + expected = s.iloc[[2, 1, 0]] + tm.assert_series_equal(result, expected) + + result = s.sort_index(key=lambda x: x.year) + expected = s.iloc[[2, 0, 1]] + tm.assert_series_equal(result, expected) + + result = s.sort_index(key=lambda x: x.month_name()) + expected = s.iloc[[2, 1, 0]] + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "ascending", + [ + [True, False], + [False, True], + ], + ) + def test_sort_index_multi_already_monotonic(self, ascending): + # GH 56049 + mi = MultiIndex.from_product([[1, 2], [3, 4]]) + ser = Series(range(len(mi)), index=mi) + result = ser.sort_index(ascending=ascending) + if ascending == [True, False]: + expected = ser.take([1, 0, 3, 2]) + elif ascending == [False, True]: + expected = ser.take([2, 3, 0, 1]) + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_sort_values.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_sort_values.py new file mode 100644 index 0000000000000000000000000000000000000000..995d9d176fc2b3ff79072f87eefcb656d17bc2b5 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_sort_values.py @@ -0,0 +1,235 @@ +import numpy as np +import pytest + +from pandas import ( + Categorical, + DataFrame, + Series, +) +import pandas._testing as tm + + +class TestSeriesSortValues: + def test_sort_values(self, datetime_series): + # check indexes are reordered corresponding with the values + ser = Series([3, 2, 4, 1], ["A", "B", "C", "D"]) + expected = Series([1, 2, 3, 4], ["D", "B", "A", "C"]) + result = ser.sort_values() + tm.assert_series_equal(expected, result) + + ts = datetime_series.copy() + ts[:5] = np.nan + vals = ts.values + + result = ts.sort_values() + assert np.isnan(result[-5:]).all() + tm.assert_numpy_array_equal(result[:-5].values, np.sort(vals[5:])) + + # na_position + result = ts.sort_values(na_position="first") + assert np.isnan(result[:5]).all() + tm.assert_numpy_array_equal(result[5:].values, np.sort(vals[5:])) + + # something object-type + ser = Series(["A", "B"], [1, 2]) + # no failure + ser.sort_values() + + # ascending=False + ordered = ts.sort_values(ascending=False) + expected = np.sort(ts.dropna().values)[::-1] + tm.assert_almost_equal(expected, ordered.dropna().values) + ordered = ts.sort_values(ascending=False, na_position="first") + tm.assert_almost_equal(expected, ordered.dropna().values) + + # ascending=[False] should behave the same as ascending=False + ordered = ts.sort_values(ascending=[False]) + expected = ts.sort_values(ascending=False) + tm.assert_series_equal(expected, ordered) + ordered = ts.sort_values(ascending=[False], na_position="first") + expected = ts.sort_values(ascending=False, na_position="first") + tm.assert_series_equal(expected, ordered) + + msg = 'For argument "ascending" expected type bool, received type NoneType.' + with pytest.raises(ValueError, match=msg): + ts.sort_values(ascending=None) + msg = r"Length of ascending \(0\) must be 1 for Series" + with pytest.raises(ValueError, match=msg): + ts.sort_values(ascending=[]) + msg = r"Length of ascending \(3\) must be 1 for Series" + with pytest.raises(ValueError, match=msg): + ts.sort_values(ascending=[1, 2, 3]) + msg = r"Length of ascending \(2\) must be 1 for Series" + with pytest.raises(ValueError, match=msg): + ts.sort_values(ascending=[False, False]) + msg = 'For argument "ascending" expected type bool, received type str.' + with pytest.raises(ValueError, match=msg): + ts.sort_values(ascending="foobar") + + # inplace=True + ts = datetime_series.copy() + return_value = ts.sort_values(ascending=False, inplace=True) + assert return_value is None + tm.assert_series_equal(ts, datetime_series.sort_values(ascending=False)) + tm.assert_index_equal( + ts.index, datetime_series.sort_values(ascending=False).index + ) + + # GH#5856/5853 + # Series.sort_values operating on a view + df = DataFrame(np.random.default_rng(2).standard_normal((10, 4))) + s = df.iloc[:, 0] + s.sort_values(inplace=True) + tm.assert_series_equal(s, df.iloc[:, 0].sort_values()) + + def test_sort_values_categorical(self): + cat = Series(Categorical(["a", "b", "b", "a"], ordered=False)) + + # sort in the categories order + expected = Series( + Categorical(["a", "a", "b", "b"], ordered=False), index=[0, 3, 1, 2] + ) + result = cat.sort_values() + tm.assert_series_equal(result, expected) + + cat = Series(Categorical(["a", "c", "b", "d"], ordered=True)) + res = cat.sort_values() + exp = np.array(["a", "b", "c", "d"], dtype=np.object_) + tm.assert_numpy_array_equal(res.__array__(), exp) + + cat = Series( + Categorical( + ["a", "c", "b", "d"], categories=["a", "b", "c", "d"], ordered=True + ) + ) + res = cat.sort_values() + exp = np.array(["a", "b", "c", "d"], dtype=np.object_) + tm.assert_numpy_array_equal(res.__array__(), exp) + + res = cat.sort_values(ascending=False) + exp = np.array(["d", "c", "b", "a"], dtype=np.object_) + tm.assert_numpy_array_equal(res.__array__(), exp) + + raw_cat1 = Categorical( + ["a", "b", "c", "d"], categories=["a", "b", "c", "d"], ordered=False + ) + raw_cat2 = Categorical( + ["a", "b", "c", "d"], categories=["d", "c", "b", "a"], ordered=True + ) + s = ["a", "b", "c", "d"] + df = DataFrame( + {"unsort": raw_cat1, "sort": raw_cat2, "string": s, "values": [1, 2, 3, 4]} + ) + + # Cats must be sorted in a dataframe + res = df.sort_values(by=["string"], ascending=False) + exp = np.array(["d", "c", "b", "a"], dtype=np.object_) + tm.assert_numpy_array_equal(res["sort"].values.__array__(), exp) + assert res["sort"].dtype == "category" + + res = df.sort_values(by=["sort"], ascending=False) + exp = df.sort_values(by=["string"], ascending=True) + tm.assert_series_equal(res["values"], exp["values"]) + assert res["sort"].dtype == "category" + assert res["unsort"].dtype == "category" + + # unordered cat, but we allow this + df.sort_values(by=["unsort"], ascending=False) + + # multi-columns sort + # GH#7848 + df = DataFrame( + {"id": [6, 5, 4, 3, 2, 1], "raw_grade": ["a", "b", "b", "a", "a", "e"]} + ) + df["grade"] = Categorical(df["raw_grade"], ordered=True) + df["grade"] = df["grade"].cat.set_categories(["b", "e", "a"]) + + # sorts 'grade' according to the order of the categories + result = df.sort_values(by=["grade"]) + expected = df.iloc[[1, 2, 5, 0, 3, 4]] + tm.assert_frame_equal(result, expected) + + # multi + result = df.sort_values(by=["grade", "id"]) + expected = df.iloc[[2, 1, 5, 4, 3, 0]] + tm.assert_frame_equal(result, expected) + + @pytest.mark.parametrize("inplace", [True, False]) + @pytest.mark.parametrize( + "original_list, sorted_list, ignore_index, output_index", + [ + ([2, 3, 6, 1], [6, 3, 2, 1], True, [0, 1, 2, 3]), + ([2, 3, 6, 1], [6, 3, 2, 1], False, [2, 1, 0, 3]), + ], + ) + def test_sort_values_ignore_index( + self, inplace, original_list, sorted_list, ignore_index, output_index + ): + # GH 30114 + ser = Series(original_list) + expected = Series(sorted_list, index=output_index) + kwargs = {"ignore_index": ignore_index, "inplace": inplace} + + if inplace: + result_ser = ser.copy() + result_ser.sort_values(ascending=False, **kwargs) + else: + result_ser = ser.sort_values(ascending=False, **kwargs) + + tm.assert_series_equal(result_ser, expected) + tm.assert_series_equal(ser, Series(original_list)) + + def test_mergesort_descending_stability(self): + # GH 28697 + s = Series([1, 2, 1, 3], ["first", "b", "second", "c"]) + result = s.sort_values(ascending=False, kind="mergesort") + expected = Series([3, 2, 1, 1], ["c", "b", "first", "second"]) + tm.assert_series_equal(result, expected) + + def test_sort_values_validate_ascending_for_value_error(self): + # GH41634 + ser = Series([23, 7, 21]) + + msg = 'For argument "ascending" expected type bool, received type str.' + with pytest.raises(ValueError, match=msg): + ser.sort_values(ascending="False") + + def test_sort_values_validate_ascending_functional(self, ascending): + # GH41634 + ser = Series([23, 7, 21]) + expected = np.sort(ser.values) + + sorted_ser = ser.sort_values(ascending=ascending) + if not ascending: + expected = expected[::-1] + + result = sorted_ser.values + tm.assert_numpy_array_equal(result, expected) + + +class TestSeriesSortingKey: + def test_sort_values_key(self): + series = Series(np.array(["Hello", "goodbye"])) + + result = series.sort_values(axis=0) + expected = series + tm.assert_series_equal(result, expected) + + result = series.sort_values(axis=0, key=lambda x: x.str.lower()) + expected = series[::-1] + tm.assert_series_equal(result, expected) + + def test_sort_values_key_nan(self): + series = Series(np.array([0, 5, np.nan, 3, 2, np.nan])) + + result = series.sort_values(axis=0) + expected = series.iloc[[0, 4, 3, 1, 2, 5]] + tm.assert_series_equal(result, expected) + + result = series.sort_values(axis=0, key=lambda x: x + 5) + expected = series.iloc[[0, 4, 3, 1, 2, 5]] + tm.assert_series_equal(result, expected) + + result = series.sort_values(axis=0, key=lambda x: -x, ascending=False) + expected = series.iloc[[0, 4, 3, 1, 2, 5]] + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_to_csv.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_to_csv.py new file mode 100644 index 0000000000000000000000000000000000000000..d67d0b5235aba66e4f52fae11e79fee0be701277 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_to_csv.py @@ -0,0 +1,176 @@ +from datetime import datetime +from io import StringIO + +import numpy as np +import pytest + +import pandas as pd +from pandas import Series +import pandas._testing as tm + +from pandas.io.common import get_handle + + +class TestSeriesToCSV: + def read_csv(self, path, **kwargs): + params = {"index_col": 0, "header": None} + params.update(**kwargs) + + header = params.get("header") + out = pd.read_csv(path, **params).squeeze("columns") + + if header is None: + out.name = out.index.name = None + + return out + + def test_from_csv(self, datetime_series, string_series, temp_file): + # freq doesn't round-trip + datetime_series.index = datetime_series.index._with_freq(None) + + path = temp_file + datetime_series.to_csv(path, header=False) + ts = self.read_csv(path, parse_dates=True) + expected = datetime_series.copy() + expected.index = expected.index.as_unit("us") + tm.assert_series_equal(expected, ts, check_names=False) + + assert ts.name is None + assert ts.index.name is None + + # see gh-10483 + datetime_series.to_csv(path, header=True) + ts_h = self.read_csv(path, header=0) + assert ts_h.name == "ts" + + string_series.to_csv(path, header=False) + series = self.read_csv(path) + tm.assert_series_equal(string_series, series, check_names=False) + + assert series.name is None + assert series.index.name is None + + string_series.to_csv(path, header=True) + series_h = self.read_csv(path, header=0) + assert series_h.name == "series" + + with open(path, "w", encoding="utf-8") as outfile: + outfile.write("1998-01-01|1.0\n1999-01-01|2.0") + + series = self.read_csv(path, sep="|", parse_dates=True) + check_series = Series({datetime(1998, 1, 1): 1.0, datetime(1999, 1, 1): 2.0}) + tm.assert_series_equal(check_series, series) + + series = self.read_csv(path, sep="|", parse_dates=False) + check_series = Series({"1998-01-01": 1.0, "1999-01-01": 2.0}) + tm.assert_series_equal(check_series, series) + + def test_to_csv(self, datetime_series, temp_file): + datetime_series.to_csv(temp_file, header=False) + + with open(temp_file, newline=None, encoding="utf-8") as f: + lines = f.readlines() + assert lines[1] != "\n" + + datetime_series.to_csv(temp_file, index=False, header=False) + arr = np.loadtxt(temp_file) + tm.assert_almost_equal(arr, datetime_series.values) + + def test_to_csv_unicode_index(self): + buf = StringIO() + s = Series(["\u05d0", "d2"], index=["\u05d0", "\u05d1"]) + + s.to_csv(buf, encoding="UTF-8", header=False) + buf.seek(0) + + s2 = self.read_csv(buf, index_col=0, encoding="UTF-8") + tm.assert_series_equal(s, s2) + + def test_to_csv_float_format(self, temp_file): + ser = Series([0.123456, 0.234567, 0.567567]) + ser.to_csv(temp_file, float_format="%.2f", header=False) + + rs = self.read_csv(temp_file) + xp = Series([0.12, 0.23, 0.57]) + tm.assert_series_equal(rs, xp) + + def test_to_csv_list_entries(self): + s = Series(["jack and jill", "jesse and frank"]) + + split = s.str.split(r"\s+and\s+") + + buf = StringIO() + split.to_csv(buf, header=False) + + def test_to_csv_path_is_none(self): + # GH 8215 + # Series.to_csv() was returning None, inconsistent with + # DataFrame.to_csv() which returned string + s = Series([1, 2, 3]) + csv_str = s.to_csv(path_or_buf=None, header=False) + assert isinstance(csv_str, str) + + @pytest.mark.parametrize( + "s,encoding", + [ + ( + Series([0.123456, 0.234567, 0.567567], index=["A", "B", "C"], name="X"), + None, + ), + # GH 21241, 21118 + (Series(["abc", "def", "ghi"], name="X"), "ascii"), + (Series(["123", "你好", "世界"], name="中文"), "gb2312"), + ( + Series(["123", "Γειά σου", "Κόσμε"], name="Ελληνικά"), # noqa: RUF001 + "cp737", + ), + ], + ) + def test_to_csv_compression(self, s, encoding, compression, temp_file): + filename = temp_file + s.to_csv(filename, compression=compression, encoding=encoding, header=True) + # test the round trip - to_csv -> read_csv + result = pd.read_csv( + filename, + compression=compression, + encoding=encoding, + index_col=0, + ).squeeze("columns") + tm.assert_series_equal(s, result) + + # test the round trip using file handle - to_csv -> read_csv + with get_handle( + filename, "w", compression=compression, encoding=encoding + ) as handles: + s.to_csv(handles.handle, encoding=encoding, header=True) + + result = pd.read_csv( + filename, + compression=compression, + encoding=encoding, + index_col=0, + ).squeeze("columns") + tm.assert_series_equal(s, result) + + # explicitly ensure file was compressed + with tm.decompress_file(filename, compression) as fh: + text = fh.read().decode(encoding or "utf8") + assert s.name in text + + with tm.decompress_file(filename, compression) as fh: + tm.assert_series_equal( + s, + pd.read_csv(fh, index_col=0, encoding=encoding).squeeze("columns"), + ) + + def test_to_csv_interval_index(self, using_infer_string, temp_file): + # GH 28210 + s = Series(["foo", "bar", "baz"], index=pd.interval_range(0, 3)) + + s.to_csv(temp_file, header=False) + result = self.read_csv(temp_file, index_col=0) + + # can't roundtrip intervalindex via read_csv so check string repr (GH 23595) + expected = s + expected.index = expected.index.astype("str") + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_to_dict.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_to_dict.py new file mode 100644 index 0000000000000000000000000000000000000000..41c01f4537f23fdb90110685d2e774427916aee1 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_to_dict.py @@ -0,0 +1,38 @@ +import collections + +import numpy as np +import pytest + +from pandas import Series +import pandas._testing as tm + + +class TestSeriesToDict: + @pytest.mark.parametrize( + "mapping", (dict, collections.defaultdict(list), collections.OrderedDict) + ) + def test_to_dict(self, mapping, datetime_series): + # GH#16122 + result = Series(datetime_series.to_dict(into=mapping), name="ts") + expected = datetime_series.copy() + expected.index = expected.index._with_freq(None) + tm.assert_series_equal(result, expected) + + from_method = Series(datetime_series.to_dict(into=collections.Counter)) + from_constructor = Series(collections.Counter(datetime_series.items())) + tm.assert_series_equal(from_method, from_constructor) + + @pytest.mark.parametrize( + "input", + ( + {"a": np.int64(64), "b": 10}, + {"a": np.int64(64), "b": 10, "c": "ABC"}, + {"a": np.uint64(64), "b": 10, "c": "ABC"}, + ), + ) + def test_to_dict_return_types(self, input): + # GH25969 + + d = Series(input).to_dict() + assert isinstance(d["a"], int) + assert isinstance(d["b"], int) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_to_frame.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_to_frame.py new file mode 100644 index 0000000000000000000000000000000000000000..0eadf696b34cc034d2be76ce5daba2cff679da74 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_to_frame.py @@ -0,0 +1,63 @@ +import pytest + +from pandas import ( + DataFrame, + Index, + Series, +) +import pandas._testing as tm + + +class TestToFrame: + def test_to_frame_respects_name_none(self): + # GH#44212 if we explicitly pass name=None, then that should be respected, + # not changed to 0 + # GH-45448 this is first deprecated & enforced in 2.0 + ser = Series(range(3)) + result = ser.to_frame(None) + + exp_index = Index([None], dtype=object) + tm.assert_index_equal(result.columns, exp_index) + + result = ser.rename("foo").to_frame(None) + exp_index = Index([None], dtype=object) + tm.assert_index_equal(result.columns, exp_index) + + def test_to_frame(self, datetime_series): + datetime_series.name = None + rs = datetime_series.to_frame() + xp = DataFrame(datetime_series.values, index=datetime_series.index) + tm.assert_frame_equal(rs, xp) + + datetime_series.name = "testname" + rs = datetime_series.to_frame() + xp = DataFrame( + {"testname": datetime_series.values}, index=datetime_series.index + ) + tm.assert_frame_equal(rs, xp) + + rs = datetime_series.to_frame(name="testdifferent") + xp = DataFrame( + {"testdifferent": datetime_series.values}, index=datetime_series.index + ) + tm.assert_frame_equal(rs, xp) + + @pytest.mark.filterwarnings( + "ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning" + ) + def test_to_frame_expanddim(self): + # GH#9762 + + class SubclassedSeries(Series): + @property + def _constructor_expanddim(self): + return SubclassedFrame + + class SubclassedFrame(DataFrame): + pass + + ser = SubclassedSeries([1, 2, 3], name="X") + result = ser.to_frame() + assert isinstance(result, SubclassedFrame) + expected = SubclassedFrame({"X": [1, 2, 3]}) + tm.assert_frame_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_to_numpy.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_to_numpy.py new file mode 100644 index 0000000000000000000000000000000000000000..4bc7631090761e720c61049f9b8fd2a7fadd89af --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_to_numpy.py @@ -0,0 +1,49 @@ +import numpy as np +import pytest + +import pandas.util._test_decorators as td + +from pandas import ( + NA, + Series, + Timedelta, +) +import pandas._testing as tm + + +@pytest.mark.parametrize("dtype", ["int64", "float64"]) +def test_to_numpy_na_value(dtype): + # GH#48951 + ser = Series([1, 2, NA, 4]) + result = ser.to_numpy(dtype=dtype, na_value=0) + expected = np.array([1, 2, 0, 4], dtype=dtype) + tm.assert_numpy_array_equal(result, expected) + + +def test_to_numpy_cast_before_setting_na(): + # GH#50600 + ser = Series([1]) + result = ser.to_numpy(dtype=np.float64, na_value=np.nan) + expected = np.array([1.0]) + tm.assert_numpy_array_equal(result, expected) + + +@td.skip_if_no("pyarrow") +def test_to_numpy_arrow_dtype_given(): + # GH#57121 + ser = Series([1, NA], dtype="int64[pyarrow]") + result = ser.to_numpy(dtype="float64") + expected = np.array([1.0, np.nan]) + tm.assert_numpy_array_equal(result, expected) + + +def test_astype_ea_int_to_td_ts(): + # GH#57093 + ser = Series([1, None], dtype="Int64") + result = ser.astype("m8[ns]") + expected = Series([1, Timedelta("nat")], dtype="m8[ns]") + tm.assert_series_equal(result, expected) + + result = ser.astype("M8[ns]") + expected = Series([1, Timedelta("nat")], dtype="M8[ns]") + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_tolist.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_tolist.py new file mode 100644 index 0000000000000000000000000000000000000000..4af473528e23850794139ac563cc04c6d3c54617 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_tolist.py @@ -0,0 +1,36 @@ +import pytest + +import pandas.util._test_decorators as td + +from pandas import ( + Interval, + Period, + Series, + Timedelta, + Timestamp, +) + + +@pytest.mark.parametrize( + "values, dtype, expected_dtype", + ( + ([1], "int64", int), + ([1], "Int64", int), + ([1.0], "float64", float), + ([1.0], "Float64", float), + (["abc"], "object", str), + (["abc"], "string", str), + ([Interval(1, 3)], "interval", Interval), + ([Period("2000-01-01", "D")], "period[D]", Period), + ([Timedelta(days=1)], "timedelta64[ns]", Timedelta), + ([Timestamp("2000-01-01")], "datetime64[ns]", Timestamp), + pytest.param([1], "int64[pyarrow]", int, marks=td.skip_if_no("pyarrow")), + pytest.param([1.0], "float64[pyarrow]", float, marks=td.skip_if_no("pyarrow")), + pytest.param(["abc"], "string[pyarrow]", str, marks=td.skip_if_no("pyarrow")), + ), +) +def test_tolist_scalar_dtype(values, dtype, expected_dtype): + # GH49890 + ser = Series(values, dtype=dtype) + result_dtype = type(ser.tolist()[0]) + assert result_dtype == expected_dtype diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_truncate.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_truncate.py new file mode 100644 index 0000000000000000000000000000000000000000..33eb5c10ae163862e342b1871669d64d74602e4e --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_truncate.py @@ -0,0 +1,67 @@ +from datetime import datetime + +import pytest + +import pandas as pd +from pandas import ( + Series, + date_range, +) +import pandas._testing as tm + + +class TestTruncate: + def test_truncate_datetimeindex_tz(self): + # GH 9243 + idx = date_range("4/1/2005", "4/30/2005", freq="D", tz="US/Pacific") + s = Series(range(len(idx)), index=idx) + with pytest.raises(TypeError, match="Cannot compare tz-naive"): + # GH#36148 as of 2.0 we require tzawareness compat + s.truncate(datetime(2005, 4, 2), datetime(2005, 4, 4)) + + lb = idx[1] + ub = idx[3] + result = s.truncate(lb.to_pydatetime(), ub.to_pydatetime()) + expected = Series([1, 2, 3], index=idx[1:4]) + tm.assert_series_equal(result, expected) + + def test_truncate_periodindex(self): + # GH 17717 + idx1 = pd.PeriodIndex( + [pd.Period("2017-09-02"), pd.Period("2017-09-02"), pd.Period("2017-09-03")] + ) + series1 = Series([1, 2, 3], index=idx1) + result1 = series1.truncate(after="2017-09-02") + + expected_idx1 = pd.PeriodIndex( + [pd.Period("2017-09-02"), pd.Period("2017-09-02")] + ) + tm.assert_series_equal(result1, Series([1, 2], index=expected_idx1)) + + idx2 = pd.PeriodIndex( + [pd.Period("2017-09-03"), pd.Period("2017-09-02"), pd.Period("2017-09-03")] + ) + series2 = Series([1, 2, 3], index=idx2) + result2 = series2.sort_index().truncate(after="2017-09-02") + + expected_idx2 = pd.PeriodIndex([pd.Period("2017-09-02")]) + tm.assert_series_equal(result2, Series([2], index=expected_idx2)) + + def test_truncate_one_element_series(self): + # GH 35544 + series = Series([0.1], index=pd.DatetimeIndex(["2020-08-04"])) + before = pd.Timestamp("2020-08-02") + after = pd.Timestamp("2020-08-04") + + result = series.truncate(before=before, after=after) + + # the input Series and the expected Series are the same + tm.assert_series_equal(result, series) + + def test_truncate_index_only_one_unique_value(self): + # GH 42365 + obj = Series(0, index=date_range("2021-06-30", "2021-06-30")).repeat(5) + + truncated = obj.truncate("2021-06-28", "2021-07-01") + + tm.assert_series_equal(truncated, obj) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_tz_localize.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_tz_localize.py new file mode 100644 index 0000000000000000000000000000000000000000..0238246994af6629f1cdcddbc5f0105e6fec22a6 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_tz_localize.py @@ -0,0 +1,130 @@ +from datetime import timezone + +import pytest + +from pandas._libs.tslibs import timezones + +from pandas import ( + DatetimeIndex, + NaT, + Series, + Timestamp, + date_range, +) +import pandas._testing as tm + + +class TestTZLocalize: + def test_series_tz_localize_ambiguous_bool(self): + # make sure that we are correctly accepting bool values as ambiguous + + # GH#14402 + ts = Timestamp("2015-11-01 01:00:03") + expected0 = Timestamp("2015-11-01 01:00:03-0500", tz="US/Central") + expected1 = Timestamp("2015-11-01 01:00:03-0600", tz="US/Central") + + ser = Series([ts]) + expected0 = Series([expected0]) + expected1 = Series([expected1]) + + with tm.external_error_raised(ValueError): + ser.dt.tz_localize("US/Central") + + result = ser.dt.tz_localize("US/Central", ambiguous=True) + tm.assert_series_equal(result, expected0) + + result = ser.dt.tz_localize("US/Central", ambiguous=[True]) + tm.assert_series_equal(result, expected0) + + result = ser.dt.tz_localize("US/Central", ambiguous=False) + tm.assert_series_equal(result, expected1) + + result = ser.dt.tz_localize("US/Central", ambiguous=[False]) + tm.assert_series_equal(result, expected1) + + def test_series_tz_localize_matching_index(self): + # Matching the index of the result with that of the original series + # GH 43080 + dt_series = Series( + date_range(start="2021-01-01T02:00:00", periods=5, freq="1D"), + index=[2, 6, 7, 8, 11], + dtype="category", + ) + result = dt_series.dt.tz_localize("Europe/Berlin") + expected = Series( + date_range( + start="2021-01-01T02:00:00", periods=5, freq="1D", tz="Europe/Berlin" + ), + index=[2, 6, 7, 8, 11], + ) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("year", ["2015", "2201"]) + @pytest.mark.parametrize( + "method, exp", + [ + ["shift_forward", "2015-03-29 03:00:00"], + ["shift_backward", "2015-03-29 01:59:59.999999999"], + ["NaT", NaT], + ["raise", None], + ["foo", "invalid"], + ], + ) + def test_tz_localize_nonexistent(self, warsaw, method, exp, year, unit): + # GH 8917 + tz = warsaw + if year == "2201" and (not isinstance(tz, str) or tz.startswith("dateutil/")): + # zoneinfo tz passed as string + pytest.skip("ZoneInfo required for year 2201") + n = 60 + dti = date_range( + start=f"{year}-03-29 02:00:00", periods=n, freq="min", unit=unit + ) + ser = Series(1, index=dti) + df = ser.to_frame() + + if method == "raise": + with tm.external_error_raised(ValueError): + dti.tz_localize(tz, nonexistent=method) + with tm.external_error_raised(ValueError): + ser.tz_localize(tz, nonexistent=method) + with tm.external_error_raised(ValueError): + df.tz_localize(tz, nonexistent=method) + + elif exp == "invalid": + msg = ( + "The nonexistent argument must be one of " + "'raise', 'NaT', 'shift_forward', 'shift_backward' " + "or a timedelta object" + ) + with pytest.raises(ValueError, match=msg): + dti.tz_localize(tz, nonexistent=method) + with pytest.raises(ValueError, match=msg): + ser.tz_localize(tz, nonexistent=method) + with pytest.raises(ValueError, match=msg): + df.tz_localize(tz, nonexistent=method) + + else: + if isinstance(exp, str): + exp = exp.replace("2015", year) + result = ser.tz_localize(tz, nonexistent=method) + expected = Series(1, index=DatetimeIndex([exp] * n, tz=tz).as_unit(unit)) + tm.assert_series_equal(result, expected) + + result = df.tz_localize(tz, nonexistent=method) + expected = expected.to_frame() + tm.assert_frame_equal(result, expected) + + res_index = dti.tz_localize(tz, nonexistent=method) + tm.assert_index_equal(res_index, expected.index) + + @pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"]) + def test_series_tz_localize_empty(self, tzstr): + # GH#2248 + ser = Series(dtype=object) + + ser2 = ser.tz_localize("utc") + assert ser2.index.tz == timezone.utc + + ser2 = ser.tz_localize(tzstr) + timezones.tz_compare(ser2.index.tz, timezones.maybe_get_tz(tzstr)) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_unique.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_unique.py new file mode 100644 index 0000000000000000000000000000000000000000..14e247332503253ede23e94fe1335e712a2675e7 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_unique.py @@ -0,0 +1,76 @@ +import numpy as np + +from pandas import ( + Categorical, + IntervalIndex, + Series, + date_range, +) +import pandas._testing as tm + + +class TestUnique: + def test_unique_uint64(self): + ser = Series([1, 2, 2**63, 2**63], dtype=np.uint64) + res = ser.unique() + exp = np.array([1, 2, 2**63], dtype=np.uint64) + tm.assert_numpy_array_equal(res, exp) + + def test_unique_data_ownership(self): + # it works! GH#1807 + Series(Series(["a", "c", "b"]).unique()).sort_values() + + def test_unique(self): + # GH#714 also, dtype=float + ser = Series([1.2345] * 100) + ser[::2] = np.nan + result = ser.unique() + assert len(result) == 2 + + # explicit f4 dtype + ser = Series([1.2345] * 100, dtype="f4") + ser[::2] = np.nan + result = ser.unique() + assert len(result) == 2 + + def test_unique_nan_object_dtype(self): + # NAs in object arrays GH#714 + ser = Series(["foo"] * 100, dtype="O") + ser[::2] = np.nan + result = ser.unique() + assert len(result) == 2 + + def test_unique_none(self): + # decision about None + ser = Series([1, 2, 3, None, None, None], dtype=object) + result = ser.unique() + expected = np.array([1, 2, 3, None], dtype=object) + tm.assert_numpy_array_equal(result, expected) + + def test_unique_categorical(self): + # GH#18051 + cat = Categorical([]) + ser = Series(cat) + result = ser.unique() + tm.assert_categorical_equal(result, cat) + + cat = Categorical([np.nan]) + ser = Series(cat) + result = ser.unique() + tm.assert_categorical_equal(result, cat) + + def test_tz_unique(self): + # GH 46128 + dti1 = date_range("2016-01-01", periods=3) + ii1 = IntervalIndex.from_breaks(dti1) + ser1 = Series(ii1) + uni1 = ser1.unique() + tm.assert_interval_array_equal(ser1.array, uni1) + + dti2 = date_range("2016-01-01", periods=3, tz="US/Eastern") + ii2 = IntervalIndex.from_breaks(dti2) + ser2 = Series(ii2) + uni2 = ser2.unique() + tm.assert_interval_array_equal(ser2.array, uni2) + + assert uni1.dtype != uni2.dtype diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_unstack.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_unstack.py new file mode 100644 index 0000000000000000000000000000000000000000..f61e20c43657d0f6b2f30be082b2bbdcb633945a --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_unstack.py @@ -0,0 +1,169 @@ +import numpy as np +import pytest + +import pandas as pd +from pandas import ( + DataFrame, + Index, + MultiIndex, + Series, + date_range, +) +import pandas._testing as tm + + +def test_unstack_preserves_object(): + mi = MultiIndex.from_product([["bar", "foo"], ["one", "two"]]) + + ser = Series(np.arange(4.0), index=mi, dtype=object) + + res1 = ser.unstack() + assert (res1.dtypes == object).all() + + res2 = ser.unstack(level=0) + assert (res2.dtypes == object).all() + + +def test_unstack(): + index = MultiIndex( + levels=[["bar", "foo"], ["one", "three", "two"]], + codes=[[1, 1, 0, 0], [0, 1, 0, 2]], + ) + + s = Series(np.arange(4.0), index=index) + unstacked = s.unstack() + + expected = DataFrame( + [[2.0, np.nan, 3.0], [0.0, 1.0, np.nan]], + index=["bar", "foo"], + columns=["one", "three", "two"], + ) + + tm.assert_frame_equal(unstacked, expected) + + unstacked = s.unstack(level=0) + tm.assert_frame_equal(unstacked, expected.T) + + index = MultiIndex( + levels=[["bar"], ["one", "two", "three"], [0, 1]], + codes=[[0, 0, 0, 0, 0, 0], [0, 1, 2, 0, 1, 2], [0, 1, 0, 1, 0, 1]], + ) + s = Series(np.random.default_rng(2).standard_normal(6), index=index) + exp_index = MultiIndex( + levels=[["one", "two", "three"], [0, 1]], + codes=[[0, 1, 2, 0, 1, 2], [0, 1, 0, 1, 0, 1]], + ) + expected = DataFrame({"bar": s.values}, index=exp_index).sort_index(level=0) + unstacked = s.unstack(0).sort_index() + tm.assert_frame_equal(unstacked, expected) + + # GH5873 + idx = MultiIndex.from_arrays([[101, 102], [3.5, np.nan]]) + ts = Series([1, 2], index=idx) + left = ts.unstack() + right = DataFrame( + [[np.nan, 1], [2, np.nan]], index=[101, 102], columns=[np.nan, 3.5] + ) + tm.assert_frame_equal(left, right) + + idx = MultiIndex.from_arrays( + [ + ["cat", "cat", "cat", "dog", "dog"], + ["a", "a", "b", "a", "b"], + [1, 2, 1, 1, np.nan], + ] + ) + ts = Series([1.0, 1.1, 1.2, 1.3, 1.4], index=idx) + right = DataFrame( + [[1.0, 1.3], [1.1, np.nan], [np.nan, 1.4], [1.2, np.nan]], + columns=["cat", "dog"], + ) + tpls = [("a", 1), ("a", 2), ("b", np.nan), ("b", 1)] + right.index = MultiIndex.from_tuples(tpls) + tm.assert_frame_equal(ts.unstack(level=0), right) + + +def test_unstack_tuplename_in_multiindex(): + # GH 19966 + idx = MultiIndex.from_product( + [["a", "b", "c"], [1, 2, 3]], names=[("A", "a"), ("B", "b")] + ) + ser = Series(1, index=idx) + result = ser.unstack(("A", "a")) + + expected = DataFrame( + [[1, 1, 1], [1, 1, 1], [1, 1, 1]], + columns=MultiIndex.from_tuples([("a",), ("b",), ("c",)], names=[("A", "a")]), + index=Index([1, 2, 3], name=("B", "b")), + ) + tm.assert_frame_equal(result, expected) + + +@pytest.mark.parametrize( + "unstack_idx, expected_values, expected_index, expected_columns", + [ + ( + ("A", "a"), + [[1, 1], [1, 1], [1, 1], [1, 1]], + MultiIndex.from_tuples([(1, 3), (1, 4), (2, 3), (2, 4)], names=["B", "C"]), + MultiIndex.from_tuples([("a",), ("b",)], names=[("A", "a")]), + ), + ( + (("A", "a"), "B"), + [[1, 1, 1, 1], [1, 1, 1, 1]], + Index([3, 4], name="C"), + MultiIndex.from_tuples( + [("a", 1), ("a", 2), ("b", 1), ("b", 2)], names=[("A", "a"), "B"] + ), + ), + ], +) +def test_unstack_mixed_type_name_in_multiindex( + unstack_idx, expected_values, expected_index, expected_columns +): + # GH 19966 + idx = MultiIndex.from_product( + [["a", "b"], [1, 2], [3, 4]], names=[("A", "a"), "B", "C"] + ) + ser = Series(1, index=idx) + result = ser.unstack(unstack_idx) + + expected = DataFrame( + expected_values, columns=expected_columns, index=expected_index + ) + tm.assert_frame_equal(result, expected) + + +def test_unstack_multi_index_categorical_values(): + df = DataFrame( + np.random.default_rng(2).standard_normal((10, 4)), + columns=Index(list("ABCD")), + index=date_range("2000-01-01", periods=10, freq="B"), + ) + mi = df.stack().index.rename(["major", "minor"]) + ser = Series(["foo"] * len(mi), index=mi, name="category", dtype="category") + + result = ser.unstack() + + dti = ser.index.levels[0] + c = pd.Categorical(["foo"] * len(dti)) + expected = DataFrame( + {"A": c, "B": c, "C": c, "D": c}, + columns=Index(list("ABCD"), name="minor"), + index=dti.rename("major"), + ) + tm.assert_frame_equal(result, expected) + + +def test_unstack_mixed_level_names(): + # GH#48763 + arrays = [["a", "a"], [1, 2], ["red", "blue"]] + idx = MultiIndex.from_arrays(arrays, names=("x", 0, "y")) + ser = Series([1, 2], index=idx) + result = ser.unstack("x") + expected = DataFrame( + [[1], [2]], + columns=Index(["a"], name="x"), + index=MultiIndex.from_tuples([(1, "red"), (2, "blue")], names=[0, "y"]), + ) + tm.assert_frame_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_update.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_update.py new file mode 100644 index 0000000000000000000000000000000000000000..9b5fb098bf3eec3146e64e1510a59cfdef526770 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_update.py @@ -0,0 +1,135 @@ +import numpy as np +import pytest + +import pandas.util._test_decorators as td + +from pandas import ( + CategoricalDtype, + DataFrame, + NaT, + Series, + Timestamp, +) +import pandas._testing as tm + + +class TestUpdate: + def test_update(self): + s = Series([1.5, np.nan, 3.0, 4.0, np.nan]) + s2 = Series([np.nan, 3.5, np.nan, 5.0]) + s.update(s2) + + expected = Series([1.5, 3.5, 3.0, 5.0, np.nan]) + tm.assert_series_equal(s, expected) + + # GH 3217 + df = DataFrame([{"a": 1}, {"a": 3, "b": 2}]) + df["c"] = np.nan + # Cast to object to avoid upcast when setting "foo" + df["c"] = df["c"].astype(object) + df_orig = df.copy() + + with tm.raises_chained_assignment_error(): + df["c"].update(Series(["foo"], index=[0])) + expected = df_orig + tm.assert_frame_equal(df, expected) + + @pytest.mark.parametrize( + "other, dtype, expected, raises", + [ + # other is int + ([61, 63], "int32", Series([10, 61, 12], dtype="int32"), False), + ([61, 63], "int64", Series([10, 61, 12]), False), + ([61, 63], float, Series([10.0, 61.0, 12.0]), False), + ([61, 63], object, Series([10, 61, 12], dtype=object), False), + # other is float, but can be cast to int + ([61.0, 63.0], "int32", Series([10, 61, 12], dtype="int32"), False), + ([61.0, 63.0], "int64", Series([10, 61, 12]), False), + ([61.0, 63.0], float, Series([10.0, 61.0, 12.0]), False), + ([61.0, 63.0], object, Series([10, 61.0, 12], dtype=object), False), + # others is float, cannot be cast to int + ([61.1, 63.1], "int32", Series([10.0, 61.1, 12.0]), True), + ([61.1, 63.1], "int64", Series([10.0, 61.1, 12.0]), True), + ([61.1, 63.1], float, Series([10.0, 61.1, 12.0]), False), + ([61.1, 63.1], object, Series([10, 61.1, 12], dtype=object), False), + # other is object, cannot be cast + ([(61,), (63,)], "int32", Series([10, (61,), 12]), True), + ([(61,), (63,)], "int64", Series([10, (61,), 12]), True), + ([(61,), (63,)], float, Series([10.0, (61,), 12.0]), True), + ([(61,), (63,)], object, Series([10, (61,), 12]), False), + ], + ) + def test_update_dtypes(self, other, dtype, expected, raises): + ser = Series([10, 11, 12], dtype=dtype) + other = Series(other, index=[1, 3]) + if raises: + with pytest.raises(TypeError, match="Invalid value"): + ser.update(other) + else: + ser.update(other) + tm.assert_series_equal(ser, expected) + + @pytest.mark.parametrize( + "values, other, expected", + [ + # update by key + ( + {"a": 1, "b": 2, "c": 3, "d": 4}, + {"b": 5, "c": np.nan}, + {"a": 1, "b": 5, "c": 3, "d": 4}, + ), + # update by position + ([1, 2, 3, 4], [np.nan, 5, 1], [1, 5, 1, 4]), + ], + ) + def test_update_from_non_series(self, values, other, expected): + # GH 33215 + series = Series(values) + series.update(other) + expected = Series(expected) + tm.assert_series_equal(series, expected) + + @pytest.mark.parametrize( + "data, other, expected, dtype", + [ + (["a", None], [None, "b"], ["a", "b"], "string[python]"), + pytest.param( + ["a", None], + [None, "b"], + ["a", "b"], + "string[pyarrow]", + marks=td.skip_if_no("pyarrow"), + ), + ([1, None], [None, 2], [1, 2], "Int64"), + ([True, None], [None, False], [True, False], "boolean"), + ( + ["a", None], + [None, "b"], + ["a", "b"], + CategoricalDtype(categories=["a", "b"]), + ), + ( + [Timestamp(year=2020, month=1, day=1, tz="Europe/London"), NaT], + [NaT, Timestamp(year=2020, month=1, day=1, tz="Europe/London")], + [Timestamp(year=2020, month=1, day=1, tz="Europe/London")] * 2, + "datetime64[ns, Europe/London]", + ), + ], + ) + def test_update_extension_array_series(self, data, other, expected, dtype): + result = Series(data, dtype=dtype) + other = Series(other, dtype=dtype) + expected = Series(expected, dtype=dtype) + + result.update(other) + tm.assert_series_equal(result, expected) + + def test_update_with_categorical_type(self): + # GH 25744 + dtype = CategoricalDtype(["a", "b", "c", "d"]) + s1 = Series(["a", "b", "c"], index=[1, 2, 3], dtype=dtype) + s2 = Series(["b", "a"], index=[1, 2], dtype=dtype) + s1.update(s2) + result = s1 + expected = Series(["b", "a", "c"], index=[1, 2, 3], dtype=dtype) + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_value_counts.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_value_counts.py new file mode 100644 index 0000000000000000000000000000000000000000..7f882fa348b7e4f70852d636b8f404092e646dae --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_value_counts.py @@ -0,0 +1,255 @@ +import numpy as np +import pytest + +import pandas as pd +from pandas import ( + Categorical, + CategoricalIndex, + Index, + Series, +) +import pandas._testing as tm + + +class TestSeriesValueCounts: + def test_value_counts_datetime(self, unit): + # most dtypes are tested in tests/base + values = [ + pd.Timestamp("2011-01-01 09:00"), + pd.Timestamp("2011-01-01 10:00"), + pd.Timestamp("2011-01-01 11:00"), + pd.Timestamp("2011-01-01 09:00"), + pd.Timestamp("2011-01-01 09:00"), + pd.Timestamp("2011-01-01 11:00"), + ] + + exp_idx = pd.DatetimeIndex( + ["2011-01-01 09:00", "2011-01-01 11:00", "2011-01-01 10:00"], + name="xxx", + ).as_unit(unit) + exp = Series([3, 2, 1], index=exp_idx, name="count") + + ser = Series(values, name="xxx").dt.as_unit(unit) + tm.assert_series_equal(ser.value_counts(), exp) + # check DatetimeIndex outputs the same result + idx = pd.DatetimeIndex(values, name="xxx").as_unit(unit) + tm.assert_series_equal(idx.value_counts(), exp) + + # normalize + exp = Series(np.array([3.0, 2.0, 1]) / 6.0, index=exp_idx, name="proportion") + tm.assert_series_equal(ser.value_counts(normalize=True), exp) + tm.assert_series_equal(idx.value_counts(normalize=True), exp) + + def test_value_counts_datetime_tz(self, unit): + values = [ + pd.Timestamp("2011-01-01 09:00", tz="US/Eastern"), + pd.Timestamp("2011-01-01 10:00", tz="US/Eastern"), + pd.Timestamp("2011-01-01 11:00", tz="US/Eastern"), + pd.Timestamp("2011-01-01 09:00", tz="US/Eastern"), + pd.Timestamp("2011-01-01 09:00", tz="US/Eastern"), + pd.Timestamp("2011-01-01 11:00", tz="US/Eastern"), + ] + + exp_idx = pd.DatetimeIndex( + ["2011-01-01 09:00", "2011-01-01 11:00", "2011-01-01 10:00"], + tz="US/Eastern", + name="xxx", + ).as_unit(unit) + exp = Series([3, 2, 1], index=exp_idx, name="count") + + ser = Series(values, name="xxx").dt.as_unit(unit) + tm.assert_series_equal(ser.value_counts(), exp) + idx = pd.DatetimeIndex(values, name="xxx").as_unit(unit) + tm.assert_series_equal(idx.value_counts(), exp) + + exp = Series(np.array([3.0, 2.0, 1]) / 6.0, index=exp_idx, name="proportion") + tm.assert_series_equal(ser.value_counts(normalize=True), exp) + tm.assert_series_equal(idx.value_counts(normalize=True), exp) + + def test_value_counts_period(self): + values = [ + pd.Period("2011-01", freq="M"), + pd.Period("2011-02", freq="M"), + pd.Period("2011-03", freq="M"), + pd.Period("2011-01", freq="M"), + pd.Period("2011-01", freq="M"), + pd.Period("2011-03", freq="M"), + ] + + exp_idx = pd.PeriodIndex( + ["2011-01", "2011-03", "2011-02"], freq="M", name="xxx" + ) + exp = Series([3, 2, 1], index=exp_idx, name="count") + + ser = Series(values, name="xxx") + tm.assert_series_equal(ser.value_counts(), exp) + # check DatetimeIndex outputs the same result + idx = pd.PeriodIndex(values, name="xxx") + tm.assert_series_equal(idx.value_counts(), exp) + + # normalize + exp = Series(np.array([3.0, 2.0, 1]) / 6.0, index=exp_idx, name="proportion") + tm.assert_series_equal(ser.value_counts(normalize=True), exp) + tm.assert_series_equal(idx.value_counts(normalize=True), exp) + + def test_value_counts_categorical_ordered(self): + # most dtypes are tested in tests/base + values = Categorical([1, 2, 3, 1, 1, 3], ordered=True) + + exp_idx = CategoricalIndex( + [1, 3, 2], categories=[1, 2, 3], ordered=True, name="xxx" + ) + exp = Series([3, 2, 1], index=exp_idx, name="count") + + ser = Series(values, name="xxx") + tm.assert_series_equal(ser.value_counts(), exp) + # check CategoricalIndex outputs the same result + idx = CategoricalIndex(values, name="xxx") + tm.assert_series_equal(idx.value_counts(), exp) + + # normalize + exp = Series(np.array([3.0, 2.0, 1]) / 6.0, index=exp_idx, name="proportion") + tm.assert_series_equal(ser.value_counts(normalize=True), exp) + tm.assert_series_equal(idx.value_counts(normalize=True), exp) + + def test_value_counts_categorical_not_ordered(self): + values = Categorical([1, 2, 3, 1, 1, 3], ordered=False) + + exp_idx = CategoricalIndex( + [1, 3, 2], categories=[1, 2, 3], ordered=False, name="xxx" + ) + exp = Series([3, 2, 1], index=exp_idx, name="count") + + ser = Series(values, name="xxx") + tm.assert_series_equal(ser.value_counts(), exp) + # check CategoricalIndex outputs the same result + idx = CategoricalIndex(values, name="xxx") + tm.assert_series_equal(idx.value_counts(), exp) + + # normalize + exp = Series(np.array([3.0, 2.0, 1]) / 6.0, index=exp_idx, name="proportion") + tm.assert_series_equal(ser.value_counts(normalize=True), exp) + tm.assert_series_equal(idx.value_counts(normalize=True), exp) + + def test_value_counts_categorical(self): + # GH#12835 + cats = Categorical(list("abcccb"), categories=list("cabd")) + ser = Series(cats, name="xxx") + res = ser.value_counts(sort=False) + + exp_index = CategoricalIndex( + list("cabd"), categories=cats.categories, name="xxx" + ) + exp = Series([3, 1, 2, 0], name="count", index=exp_index) + tm.assert_series_equal(res, exp) + + res = ser.value_counts(sort=True) + + exp_index = CategoricalIndex( + list("cbad"), categories=cats.categories, name="xxx" + ) + exp = Series([3, 2, 1, 0], name="count", index=exp_index) + tm.assert_series_equal(res, exp) + + # check object dtype handles the Series.name as the same + # (tested in tests/base) + ser = Series(["a", "b", "c", "c", "c", "b"], name="xxx") + res = ser.value_counts() + exp = Series([3, 2, 1], name="count", index=Index(["c", "b", "a"], name="xxx")) + tm.assert_series_equal(res, exp) + + def test_value_counts_categorical_with_nan(self): + # see GH#9443 + + # sanity check + ser = Series(["a", "b", "a"], dtype="category") + exp = Series([2, 1], index=CategoricalIndex(["a", "b"]), name="count") + + res = ser.value_counts(dropna=True) + tm.assert_series_equal(res, exp) + + res = ser.value_counts(dropna=True) + tm.assert_series_equal(res, exp) + + # same Series via two different constructions --> same behaviour + series = [ + Series(["a", "b", None, "a", None, None], dtype="category"), + Series( + Categorical(["a", "b", None, "a", None, None], categories=["a", "b"]) + ), + ] + + for ser in series: + # None is a NaN value, so we exclude its count here + exp = Series([2, 1], index=CategoricalIndex(["a", "b"]), name="count") + res = ser.value_counts(dropna=True) + tm.assert_series_equal(res, exp) + + # we don't exclude the count of None and sort by counts + exp = Series( + [3, 2, 1], index=CategoricalIndex([np.nan, "a", "b"]), name="count" + ) + res = ser.value_counts(dropna=False) + tm.assert_series_equal(res, exp) + + # When we aren't sorting by counts, and np.nan isn't a + # category, it should be last. + exp = Series( + [2, 1, 3], index=CategoricalIndex(["a", "b", np.nan]), name="count" + ) + res = ser.value_counts(dropna=False, sort=False) + tm.assert_series_equal(res, exp) + + @pytest.mark.parametrize( + "ser, dropna, exp", + [ + ( + Series([False, True, True, pd.NA]), + False, + Series([2, 1, 1], index=[True, False, pd.NA], name="count"), + ), + ( + Series([False, True, True, pd.NA]), + True, + Series([2, 1], index=Index([True, False], dtype=object), name="count"), + ), + ( + Series(range(3), index=[True, False, np.nan]).index, + False, + Series([1, 1, 1], index=[True, False, np.nan], name="count"), + ), + ], + ) + def test_value_counts_bool_with_nan(self, ser, dropna, exp): + # GH32146 + out = ser.value_counts(dropna=dropna) + tm.assert_series_equal(out, exp) + + @pytest.mark.parametrize("dtype", [np.complex128, np.complex64]) + def test_value_counts_complex_numbers(self, dtype): + # GH 17927 + input_array = np.array([1 + 1j, 1 + 1j, 1, 3j, 3j, 3j], dtype=dtype) + result = Series(input_array).value_counts() + expected = Series( + [3, 2, 1], index=Index([3j, 1 + 1j, 1], dtype=dtype), name="count" + ) + tm.assert_series_equal(result, expected) + + def test_value_counts_masked(self): + # GH#54984 + dtype = "Int64" + ser = Series([1, 2, None, 2, None, 3], dtype=dtype) + result = ser.value_counts(dropna=False) + expected = Series( + [2, 2, 1, 1], + index=Index([2, None, 1, 3], dtype=dtype), + dtype=dtype, + name="count", + ) + tm.assert_series_equal(result, expected) + + result = ser.value_counts(dropna=True) + expected = Series( + [2, 1, 1], index=Index([2, 1, 3], dtype=dtype), dtype=dtype, name="count" + ) + tm.assert_series_equal(result, expected) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_values.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_values.py new file mode 100644 index 0000000000000000000000000000000000000000..cb1595e68264fbe5f07b014be4975657fa2fa8cf --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/methods/test_values.py @@ -0,0 +1,29 @@ +import numpy as np +import pytest + +from pandas import ( + IntervalIndex, + Series, + period_range, +) +import pandas._testing as tm + + +class TestValues: + @pytest.mark.parametrize( + "data", + [ + period_range("2000", periods=4), + IntervalIndex.from_breaks([1, 2, 3, 4]), + ], + ) + def test_values_object_extension_dtypes(self, data): + # https://github.com/pandas-dev/pandas/issues/23995 + result = Series(data).values + expected = np.array(data.astype(object)) + tm.assert_numpy_array_equal(result, expected) + + def test_values(self, datetime_series): + tm.assert_almost_equal( + datetime_series.values, list(datetime_series), check_dtype=False + ) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/test_api.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/test_api.py new file mode 100644 index 0000000000000000000000000000000000000000..4b369bb0bc86935ac94113a858cfb5d99082e34f --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/test_api.py @@ -0,0 +1,278 @@ +import inspect +import pydoc + +import numpy as np +import pytest + +import pandas as pd +from pandas import ( + DataFrame, + Index, + Series, + date_range, + period_range, + timedelta_range, +) +import pandas._testing as tm + + +class TestSeriesMisc: + def test_tab_completion(self): + # GH 9910 + s = Series(list("abcd")) + # Series of str values should have .str but not .dt/.cat in __dir__ + assert "str" in dir(s) + assert "dt" not in dir(s) + assert "cat" not in dir(s) + + def test_tab_completion_dt(self): + # similarly for .dt + s = Series(date_range("1/1/2015", periods=5)) + assert "dt" in dir(s) + assert "str" not in dir(s) + assert "cat" not in dir(s) + + def test_tab_completion_cat(self): + # Similarly for .cat, but with the twist that str and dt should be + # there if the categories are of that type first cat and str. + s = Series(list("abbcd"), dtype="category") + assert "cat" in dir(s) + assert "str" in dir(s) # as it is a string categorical + assert "dt" not in dir(s) + + def test_tab_completion_cat_str(self): + # similar to cat and str + s = Series(date_range("1/1/2015", periods=5)).astype("category") + assert "cat" in dir(s) + assert "str" not in dir(s) + assert "dt" in dir(s) # as it is a datetime categorical + + def test_tab_completion_with_categorical(self): + # test the tab completion display + ok_for_cat = [ + "categories", + "codes", + "ordered", + "set_categories", + "add_categories", + "remove_categories", + "rename_categories", + "reorder_categories", + "remove_unused_categories", + "as_ordered", + "as_unordered", + ] + + s = Series(list("aabbcde")).astype("category") + results = sorted({r for r in s.cat.__dir__() if not r.startswith("_")}) + tm.assert_almost_equal(results, sorted(set(ok_for_cat))) + + @pytest.mark.parametrize( + "index", + [ + Index(list("ab") * 5, dtype="category"), + Index([str(i) for i in range(10)]), + Index(["foo", "bar", "baz"] * 2), + date_range("2020-01-01", periods=10), + period_range("2020-01-01", periods=10, freq="D"), + timedelta_range("1 day", periods=10), + Index(np.arange(10), dtype=np.uint64), + Index(np.arange(10), dtype=np.int64), + Index(np.arange(10), dtype=np.float64), + Index([True, False]), + Index([f"a{i}" for i in range(101)]), + pd.MultiIndex.from_tuples(zip("ABCD", "EFGH")), + pd.MultiIndex.from_tuples(zip([0, 1, 2, 3], "EFGH")), + ], + ) + def test_index_tab_completion(self, index): + # dir contains string-like values of the Index. + s = Series(index=index, dtype=object) + dir_s = dir(s) + for i, x in enumerate(s.index.unique(level=0)): + if i < 100: + assert not isinstance(x, str) or not x.isidentifier() or x in dir_s + else: + assert x not in dir_s + + @pytest.mark.parametrize("ser", [Series(dtype=object), Series([1])]) + def test_not_hashable(self, ser): + msg = "unhashable type: 'Series'" + with pytest.raises(TypeError, match=msg): + hash(ser) + + def test_contains(self, datetime_series): + tm.assert_contains_all(datetime_series.index, datetime_series) + + def test_axis_alias(self): + s = Series([1, 2, np.nan]) + tm.assert_series_equal(s.dropna(axis="rows"), s.dropna(axis="index")) + assert s.dropna().sum(axis="rows") == 3 + assert s._get_axis_number("rows") == 0 + assert s._get_axis_name("rows") == "index" + + def test_class_axis(self): + # https://github.com/pandas-dev/pandas/issues/18147 + # no exception and no empty docstring + assert pydoc.getdoc(Series.index) + + def test_ndarray_compat(self): + # test numpy compat with Series as sub-class of NDFrame + tsdf = DataFrame( + np.random.default_rng(2).standard_normal((1000, 3)), + columns=["A", "B", "C"], + index=date_range("1/1/2000", periods=1000), + ) + + def f(x): + return x[x.idxmax()] + + result = tsdf.apply(f) + expected = tsdf.max() + tm.assert_series_equal(result, expected) + + def test_ndarray_compat_like_func(self): + # using an ndarray like function + s = Series(np.random.default_rng(2).standard_normal(10)) + result = Series(np.ones_like(s)) + expected = Series(1, index=range(10), dtype="float64") + tm.assert_series_equal(result, expected) + + def test_empty_method(self): + s_empty = Series(dtype=object) + assert s_empty.empty + + @pytest.mark.parametrize("dtype", ["int64", object]) + def test_empty_method_full_series(self, dtype): + full_series = Series(index=[1], dtype=dtype) + assert not full_series.empty + + @pytest.mark.parametrize("dtype", [None, "Int64"]) + def test_integer_series_size(self, dtype): + # GH 25580 + s = Series(range(9), dtype=dtype) + assert s.size == 9 + + def test_attrs(self): + s = Series([0, 1], name="abc") + assert s.attrs == {} + s.attrs["version"] = 1 + result = s + 1 + assert result.attrs == {"version": 1} + + def test_inspect_getmembers(self): + # GH38782 + ser = Series(dtype=object) + inspect.getmembers(ser) + + def test_unknown_attribute(self): + # GH#9680 + tdi = timedelta_range(start=0, periods=10, freq="1s") + ser = Series(np.random.default_rng(2).normal(size=10), index=tdi) + assert "foo" not in ser.__dict__ + msg = "'Series' object has no attribute 'foo'" + with pytest.raises(AttributeError, match=msg): + ser.foo + + @pytest.mark.parametrize("op", ["year", "day", "second", "weekday"]) + def test_datetime_series_no_datelike_attrs(self, op, datetime_series): + # GH#7206 + msg = f"'Series' object has no attribute '{op}'" + with pytest.raises(AttributeError, match=msg): + getattr(datetime_series, op) + + def test_series_datetimelike_attribute_access(self): + # attribute access should still work! + ser = Series({"year": 2000, "month": 1, "day": 10}) + assert ser.year == 2000 + assert ser.month == 1 + assert ser.day == 10 + + def test_series_datetimelike_attribute_access_invalid(self): + ser = Series({"year": 2000, "month": 1, "day": 10}) + msg = "'Series' object has no attribute 'weekday'" + with pytest.raises(AttributeError, match=msg): + ser.weekday + + @pytest.mark.parametrize( + "kernel, has_numeric_only", + [ + ("skew", True), + ("var", True), + ("all", False), + ("prod", True), + ("any", False), + ("idxmin", False), + ("quantile", False), + ("idxmax", False), + ("min", True), + ("sem", True), + ("mean", True), + ("nunique", False), + ("max", True), + ("sum", True), + ("count", False), + ("median", True), + ("std", True), + ("rank", True), + ("pct_change", False), + ("cummax", False), + ("shift", False), + ("diff", False), + ("cumsum", False), + ("cummin", False), + ("cumprod", False), + ("fillna", False), + ("ffill", False), + ("bfill", False), + ("sample", False), + ("tail", False), + ("take", False), + ("head", False), + ("cov", False), + ("corr", False), + ], + ) + @pytest.mark.parametrize("dtype", [bool, int, float, object]) + def test_numeric_only(self, kernel, has_numeric_only, dtype): + # GH#47500 + ser = Series([0, 1, 1], dtype=dtype) + if kernel == "corrwith": + args = (ser,) + elif kernel == "corr": + args = (ser,) + elif kernel == "cov": + args = (ser,) + elif kernel == "nth": + args = (0,) + elif kernel == "fillna": + args = (True,) + elif kernel == "fillna": + args = ("ffill",) + elif kernel == "take": + args = ([0],) + elif kernel == "quantile": + args = (0.5,) + else: + args = () + method = getattr(ser, kernel) + if not has_numeric_only: + msg = ( + "(got an unexpected keyword argument 'numeric_only'" + "|too many arguments passed in)" + ) + with pytest.raises(TypeError, match=msg): + method(*args, numeric_only=True) + elif dtype is object: + msg = f"Series.{kernel} does not allow numeric_only=True with non-numeric" + with pytest.raises(TypeError, match=msg): + method(*args, numeric_only=True) + else: + result = method(*args, numeric_only=True) + expected = method(*args, numeric_only=False) + if isinstance(expected, Series): + # transformer + tm.assert_series_equal(result, expected) + else: + # reducer + assert result == expected diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/test_arithmetic.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/test_arithmetic.py new file mode 100644 index 0000000000000000000000000000000000000000..4c2bba16b86cd64353203dcdf473bbe155c13b13 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/test_arithmetic.py @@ -0,0 +1,1085 @@ +from datetime import ( + date, + timedelta, + timezone, +) +from decimal import Decimal +from enum import ( + Enum, + auto, +) +import operator + +import numpy as np +import pytest + +from pandas._libs import lib + +import pandas as pd +from pandas import ( + Categorical, + DatetimeTZDtype, + Index, + Series, + Timedelta, + bdate_range, + date_range, + isna, +) +import pandas._testing as tm +from pandas.core import ops +from pandas.core.computation import expressions as expr + + +@pytest.fixture(autouse=True, params=[0, 1000000], ids=["numexpr", "python"]) +def switch_numexpr_min_elements(request, monkeypatch): + with monkeypatch.context() as m: + m.setattr(expr, "_MIN_ELEMENTS", request.param) + yield + + +def _permute(obj): + return obj.take(np.random.default_rng(2).permutation(len(obj))) + + +class TestSeriesFlexArithmetic: + @pytest.mark.parametrize( + "ts", + [ + (lambda x: x, lambda x: x * 2, False), + (lambda x: x, lambda x: x[::2], False), + (lambda x: x, lambda x: 5, True), + ( + lambda x: Series(range(10), dtype=np.float64), + lambda x: Series(range(10), dtype=np.float64), + True, + ), + ], + ) + @pytest.mark.parametrize( + "opname", ["add", "sub", "mul", "floordiv", "truediv", "pow"] + ) + def test_flex_method_equivalence(self, opname, ts): + # check that Series.{opname} behaves like Series.__{opname}__, + tser = Series( + np.arange(20, dtype=np.float64), + index=date_range("2020-01-01", periods=20), + name="ts", + ) + + series = ts[0](tser) + other = ts[1](tser) + check_reverse = ts[2] + + op = getattr(Series, opname) + alt = getattr(operator, opname) + + result = op(series, other) + expected = alt(series, other) + tm.assert_almost_equal(result, expected) + if check_reverse: + rop = getattr(Series, "r" + opname) + result = rop(series, other) + expected = alt(other, series) + tm.assert_almost_equal(result, expected) + + def test_flex_method_subclass_metadata_preservation(self, all_arithmetic_operators): + # GH 13208 + class MySeries(Series): + _metadata = ["x"] + + @property + def _constructor(self): + return MySeries + + opname = all_arithmetic_operators + op = getattr(Series, opname) + m = MySeries([1, 2, 3], name="test") + m.x = 42 + result = op(m, 1) + assert result.x == 42 + + def test_flex_add_scalar_fill_value(self): + # GH12723 + ser = Series([0, 1, np.nan, 3, 4, 5]) + + exp = ser.fillna(0).add(2) + res = ser.add(2, fill_value=0) + tm.assert_series_equal(res, exp) + + pairings = [(Series.div, operator.truediv, 1), (Series.rdiv, ops.rtruediv, 1)] + for op in ["add", "sub", "mul", "pow", "truediv", "floordiv"]: + fv = 0 + lop = getattr(Series, op) + lequiv = getattr(operator, op) + rop = getattr(Series, "r" + op) + # bind op at definition time... + requiv = lambda x, y, op=op: getattr(operator, op)(y, x) + pairings.append((lop, lequiv, fv)) + pairings.append((rop, requiv, fv)) + + @pytest.mark.parametrize("op, equiv_op, fv", pairings) + def test_operators_combine(self, op, equiv_op, fv): + def _check_fill(meth, op, a, b, fill_value=0): + exp_index = a.index.union(b.index) + a = a.reindex(exp_index) + b = b.reindex(exp_index) + + amask = isna(a) + bmask = isna(b) + + exp_values = [] + for i in range(len(exp_index)): + with np.errstate(all="ignore"): + if amask[i]: + if bmask[i]: + exp_values.append(np.nan) + continue + exp_values.append(op(fill_value, b[i])) + elif bmask[i]: + if amask[i]: + exp_values.append(np.nan) + continue + exp_values.append(op(a[i], fill_value)) + else: + exp_values.append(op(a[i], b[i])) + + result = meth(a, b, fill_value=fill_value) + expected = Series(exp_values, exp_index) + tm.assert_series_equal(result, expected) + + a = Series([np.nan, 1.0, 2.0, 3.0, np.nan], index=np.arange(5)) + b = Series([np.nan, 1, np.nan, 3, np.nan, 4.0], index=np.arange(6)) + + result = op(a, b) + exp = equiv_op(a, b) + tm.assert_series_equal(result, exp) + _check_fill(op, equiv_op, a, b, fill_value=fv) + # should accept axis=0 or axis='rows' + op(a, b, axis=0) + + @pytest.mark.parametrize("kind", ["datetime", "timedelta"]) + def test_rhs_extension_array_sub_with_fill_value(self, kind): + # GH:62467 + if kind == "datetime": + left = Series( + [pd.Timestamp("2025-08-20"), pd.Timestamp("2025-08-21")], + dtype=np.dtype("datetime64[ns]"), + ) + else: + left = Series( + [Timedelta(days=1), Timedelta(days=2)], + dtype=np.dtype("timedelta64[ns]"), + ) + + right = ( + left._values + ) # DatetimeArray or TimedeltaArray which is an ExtensionArray + + result = left.sub(right, fill_value=left.iloc[0]) + expected = Series(np.zeros(len(left), dtype=np.dtype("timedelta64[ns]"))) + tm.assert_series_equal(result, expected) + + def test_flex_disallows_dataframe(self): + # GH#46179 + df = pd.DataFrame( + {2010: [1], 2020: [3]}, + index=pd.MultiIndex.from_product([["a"], ["b"]], names=["scen", "mod"]), + ) + + ser = Series( + [10.0, 20.0, 30.0], + index=pd.MultiIndex.from_product( + [["a"], ["b"], [0, 1, 2]], names=["scen", "mod", "id"] + ), + ) + + msg = "Series.add does not support a DataFrame `other`" + with pytest.raises(TypeError, match=msg): + ser.add(df, axis=0) + + +class TestSeriesArithmetic: + # Some of these may end up in tests/arithmetic, but are not yet sorted + + def test_add_series_with_period_index(self): + rng = pd.period_range("1/1/2000", "1/1/2010", freq="Y") + ts = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng) + + result = ts + ts[::2] + expected = ts + ts + expected.iloc[1::2] = np.nan + tm.assert_series_equal(result, expected) + + result = ts + _permute(ts[::2]) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "target_add,input_value,expected_value", + [ + ("!", ["hello", "world"], ["hello!", "world!"]), + ("m", ["hello", "world"], ["hellom", "worldm"]), + ], + ) + def test_string_addition(self, target_add, input_value, expected_value): + # GH28658 - ensure adding 'm' does not raise an error + a = Series(input_value) + + result = a + target_add + expected = Series(expected_value) + tm.assert_series_equal(result, expected) + + def test_divmod(self): + # GH#25557 + a = Series([1, 1, 1, np.nan], index=["a", "b", "c", "d"]) + b = Series([2, np.nan, 1, np.nan], index=["a", "b", "d", "e"]) + + result = a.divmod(b) + expected = divmod(a, b) + tm.assert_series_equal(result[0], expected[0]) + tm.assert_series_equal(result[1], expected[1]) + + result = a.rdivmod(b) + expected = divmod(b, a) + tm.assert_series_equal(result[0], expected[0]) + tm.assert_series_equal(result[1], expected[1]) + + @pytest.mark.parametrize("index", [None, range(9)]) + def test_series_integer_mod(self, index): + # GH#24396 + s1 = Series(range(1, 10)) + s2 = Series("foo", index=index) + + msg = "not all arguments converted during string formatting|'mod' not supported" + + with pytest.raises(TypeError, match=msg): + s2 % s1 + + def test_add_with_duplicate_index(self): + # GH14227 + s1 = Series([1, 2], index=[1, 1]) + s2 = Series([10, 10], index=[1, 2]) + result = s1 + s2 + expected = Series([11, 12, np.nan], index=[1, 1, 2]) + tm.assert_series_equal(result, expected) + + def test_add_na_handling(self): + ser = Series( + [Decimal("1.3"), Decimal("2.3")], index=[date(2012, 1, 1), date(2012, 1, 2)] + ) + + result = ser + ser.shift(1) + result2 = ser.shift(1) + ser + assert isna(result.iloc[0]) + assert isna(result2.iloc[0]) + + def test_add_corner_cases(self, datetime_series): + empty = Series([], index=Index([]), dtype=np.float64) + + result = datetime_series + empty + assert np.isnan(result).all() + + result = empty + empty + assert len(result) == 0 + + def test_add_float_plus_int(self, datetime_series): + # float + int + int_ts = datetime_series.astype(int)[:-5] + added = datetime_series + int_ts + expected = Series( + datetime_series.values[:-5] + int_ts.values, + index=datetime_series.index[:-5], + name="ts", + ) + tm.assert_series_equal(added[:-5], expected) + + def test_mul_empty_int_corner_case(self): + s1 = Series([], [], dtype=np.int32) + s2 = Series({"x": 0.0}) + tm.assert_series_equal(s1 * s2, Series([np.nan], index=["x"])) + + def test_sub_datetimelike_align(self): + # GH#7500 + # datetimelike ops need to align + dt = Series(date_range("2012-1-1", periods=3, freq="D", unit="ns")) + dt.iloc[2] = np.nan + dt2 = dt[::-1] + + expected = Series([timedelta(0), timedelta(0), pd.NaT], dtype="m8[ns]") + # name is reset + result = dt2 - dt + tm.assert_series_equal(result, expected) + + expected = Series(expected, name=0) + result = (dt2.to_frame() - dt.to_frame())[0] + tm.assert_series_equal(result, expected) + + def test_alignment_doesnt_change_tz(self): + # GH#33671 + dti = date_range("2016-01-01", periods=10, tz="CET") + dti_utc = dti.tz_convert("UTC") + ser = Series(10, index=dti) + ser_utc = Series(10, index=dti_utc) + + # we don't care about the result, just that original indexes are unchanged + ser * ser_utc + + assert ser.index is dti + assert ser_utc.index is dti_utc + + def test_alignment_categorical(self): + # GH13365 + cat = Categorical(["3z53", "3z53", "LoJG", "LoJG", "LoJG", "N503"]) + ser1 = Series(2, index=cat) + ser2 = Series(2, index=cat[:-1]) + result = ser1 * ser2 + + exp_index = ["3z53"] * 4 + ["LoJG"] * 9 + ["N503"] + exp_index = pd.CategoricalIndex(exp_index, categories=cat.categories) + exp_values = [4.0] * 13 + [np.nan] + expected = Series(exp_values, exp_index) + + tm.assert_series_equal(result, expected) + + def test_arithmetic_with_duplicate_index(self): + # GH#8363 + # integer ops with a non-unique index + index = [2, 2, 3, 3, 4] + ser = Series(np.arange(1, 6, dtype="int64"), index=index) + other = Series(np.arange(5, dtype="int64"), index=index) + result = ser - other + expected = Series(1, index=[2, 2, 3, 3, 4]) + tm.assert_series_equal(result, expected) + + # GH#8363 + # datetime ops with a non-unique index + ser = Series(date_range("20130101 09:00:00", periods=5, unit="ns"), index=index) + other = Series(date_range("20130101", periods=5, unit="ns"), index=index) + result = ser - other + expected = Series(Timedelta("9 hours"), index=[2, 2, 3, 3, 4], dtype="m8[ns]") + tm.assert_series_equal(result, expected) + + def test_masked_and_non_masked_propagate_na(self): + # GH#45810 + ser1 = Series([0, np.nan], dtype="float") + ser2 = Series([0, 1], dtype="Int64") + result = ser1 * ser2 + expected = Series([0, pd.NA], dtype="Float64") + tm.assert_series_equal(result, expected) + + def test_mask_div_propagate_na_for_non_na_dtype(self): + # GH#42630 + ser1 = Series([15, pd.NA, 5, 4], dtype="Int64") + ser2 = Series([15, 5, np.nan, 4]) + result = ser1 / ser2 + expected = Series([1.0, pd.NA, pd.NA, 1.0], dtype="Float64") + tm.assert_series_equal(result, expected) + + result = ser2 / ser1 + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("val", [3, 3.5]) + def test_add_list_to_masked_array(self, val): + # GH#22962, behavior changed by GH#62552 + ser = Series([1, None, 3], dtype="Int64") + result = ser + [1, None, val] # noqa: RUF005 + expected = Series([2, pd.NA, 3 + val], dtype="Float64") + tm.assert_series_equal(result, expected) + + result = [1, None, val] + ser # noqa: RUF005 + tm.assert_series_equal(result, expected) + + def test_add_list_to_masked_array_boolean(self): + # GH#22962 + ser = Series([True, None, False], dtype="boolean") + result = ser + [True, None, True] # noqa: RUF005 + expected = Series([2, pd.NA, 1], dtype=object) + tm.assert_series_equal(result, expected) + + result = [True, None, True] + ser # noqa: RUF005 + tm.assert_series_equal(result, expected) + + +# ------------------------------------------------------------------ +# Comparisons + + +class TestSeriesFlexComparison: + @pytest.mark.parametrize("axis", [0, None, "index"]) + def test_comparison_flex_basic(self, axis, comparison_op): + left = Series(np.random.default_rng(2).standard_normal(10)) + right = Series(np.random.default_rng(2).standard_normal(10)) + result = getattr(left, comparison_op.__name__)(right, axis=axis) + expected = comparison_op(left, right) + tm.assert_series_equal(result, expected) + + def test_comparison_bad_axis(self, comparison_op): + left = Series(np.random.default_rng(2).standard_normal(10)) + right = Series(np.random.default_rng(2).standard_normal(10)) + + msg = "No axis named 1 for object type" + with pytest.raises(ValueError, match=msg): + getattr(left, comparison_op.__name__)(right, axis=1) + + @pytest.mark.parametrize( + "values, op", + [ + ([False, False, True, False], "eq"), + ([True, True, False, True], "ne"), + ([False, False, True, False], "le"), + ([False, False, False, False], "lt"), + ([False, True, True, False], "ge"), + ([False, True, False, False], "gt"), + ], + ) + def test_comparison_flex_alignment(self, values, op): + left = Series([1, 3, 2], index=list("abc")) + right = Series([2, 2, 2], index=list("bcd")) + result = getattr(left, op)(right) + expected = Series(values, index=list("abcd")) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "left", + [ + Series(Categorical(["a", "b", "a"])), + Series(pd.period_range("2020Q1", periods=3, freq="Q")), + ], + ids=["categorical", "period"], + ) + def test_rhs_extension_array_eq_with_fill_value(self, left): + # GH:#62467 + right = left._values # this is an ExtensionArray + + result = left.eq(right, fill_value=left.iloc[0]) + expected = Series([True, True, True]) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "values, op, fill_value", + [ + ([False, False, True, True], "eq", 2), + ([True, True, False, False], "ne", 2), + ([False, False, True, True], "le", 0), + ([False, False, False, True], "lt", 0), + ([True, True, True, False], "ge", 0), + ([True, True, False, False], "gt", 0), + ], + ) + def test_comparison_flex_alignment_fill(self, values, op, fill_value): + left = Series([1, 3, 2], index=list("abc")) + right = Series([2, 2, 2], index=list("bcd")) + result = getattr(left, op)(right, fill_value=fill_value) + expected = Series(values, index=list("abcd")) + tm.assert_series_equal(result, expected) + + def test_eq_objects(self) -> None: + # GH#62191 Test eq with Enum and List elements + + class Thing(Enum): + FIRST = auto() + SECOND = auto() + + left = Series([Thing.FIRST, Thing.SECOND]) + py_l = [Thing.FIRST, Thing.SECOND] + + result = left.eq(Thing.FIRST) + expected = Series([True, False]) + tm.assert_series_equal(result, expected) + + result = left.eq(py_l) + expected = Series([True, True]) + tm.assert_series_equal(result, expected) + + result = left.eq(np.asarray(py_l)) + expected = Series([True, True]) + tm.assert_series_equal(result, expected) + + result = left.eq(Series(py_l)) + expected = Series([True, True]) + tm.assert_series_equal(result, expected) + + result = Series([[1, 2], [3, 4]]).eq([1, 2]) + expected = Series([True, False]) + with pytest.raises(AssertionError): + tm.assert_series_equal(result, expected) + expected = Series([False, False]) + tm.assert_series_equal(result, expected) + + def test_eq_with_index(self) -> None: + # GH#62191 Test eq with non-trivial indices + left = Series([1, 2], index=[1, 0]) + py_l = [1, 2] + + # assuming Python list has the same index as the Series + result = left.eq(py_l) + expected = Series([True, True], index=[1, 0]) + tm.assert_series_equal(result, expected) + + # assuming np.ndarray has the same index as the Series + result = left.eq(np.asarray(py_l)) + expected = Series([True, True], index=[1, 0]) + tm.assert_series_equal(result, expected) + + result = left.eq(Series(py_l)) + expected = Series([False, False]) + tm.assert_series_equal(result, expected) + + result = left.eq(Series([2, 1])) + expected = Series([True, True]) + tm.assert_series_equal(result, expected) + + +class TestSeriesComparison: + def test_comparison_different_length(self): + a = Series(["a", "b", "c"]) + b = Series(["b", "a"]) + msg = "only compare identically-labeled Series" + with pytest.raises(ValueError, match=msg): + a < b + + a = Series([1, 2]) + b = Series([2, 3, 4]) + with pytest.raises(ValueError, match=msg): + a == b + + @pytest.mark.parametrize("opname", ["eq", "ne", "gt", "lt", "ge", "le"]) + def test_ser_flex_cmp_return_dtypes(self, opname): + # GH#15115 + ser = Series([1, 3, 2], index=range(3)) + const = 2 + result = getattr(ser, opname)(const).dtypes + expected = np.dtype("bool") + assert result == expected + + @pytest.mark.parametrize("opname", ["eq", "ne", "gt", "lt", "ge", "le"]) + def test_ser_flex_cmp_return_dtypes_empty(self, opname): + # GH#15115 empty Series case + ser = Series([1, 3, 2], index=range(3)) + empty = ser.iloc[:0] + const = 2 + result = getattr(empty, opname)(const).dtypes + expected = np.dtype("bool") + assert result == expected + + @pytest.mark.parametrize( + "names", [(None, None, None), ("foo", "bar", None), ("baz", "baz", "baz")] + ) + def test_ser_cmp_result_names(self, names, comparison_op): + # datetime64 dtype + op = comparison_op + dti = date_range("1949-06-07 03:00:00", freq="h", periods=5, name=names[0]) + ser = Series(dti).rename(names[1]) + result = op(ser, dti) + assert result.name == names[2] + + # datetime64tz dtype + dti = dti.tz_localize("US/Central") + dti = pd.DatetimeIndex(dti, freq="infer") # freq not preserved by tz_localize + ser = Series(dti).rename(names[1]) + result = op(ser, dti) + assert result.name == names[2] + + # timedelta64 dtype + tdi = dti - dti.shift(1) + ser = Series(tdi).rename(names[1]) + result = op(ser, tdi) + assert result.name == names[2] + + # interval dtype + if op in [operator.eq, operator.ne]: + # interval dtype comparisons not yet implemented + ii = pd.interval_range(start=0, periods=5, name=names[0]) + ser = Series(ii).rename(names[1]) + result = op(ser, ii) + assert result.name == names[2] + + # categorical + if op in [operator.eq, operator.ne]: + # categorical dtype comparisons raise for inequalities + cidx = tdi.astype("category") + ser = Series(cidx).rename(names[1]) + result = op(ser, cidx) + assert result.name == names[2] + + def test_comparisons(self): + s = Series(["a", "b", "c"]) + s2 = Series([False, True, False]) + + # it works! + exp = Series([False, False, False]) + tm.assert_series_equal(s == s2, exp) + tm.assert_series_equal(s2 == s, exp) + + # ----------------------------------------------------------------- + # Categorical Dtype Comparisons + + def test_categorical_comparisons(self): + # GH#8938 + # allow equality comparisons + a = Series(list("abc"), dtype="category") + b = Series(list("abc"), dtype="object") + c = Series(["a", "b", "cc"], dtype="object") + d = Series(list("acb"), dtype="object") + e = Categorical(list("abc")) + f = Categorical(list("acb")) + + # vs scalar + assert not (a == "a").all() + assert ((a != "a") == ~(a == "a")).all() + + assert not ("a" == a).all() + assert (a == "a")[0] + assert ("a" == a)[0] + assert not ("a" != a)[0] + + # vs list-like + assert (a == a).all() + assert not (a != a).all() + + assert (a == list(a)).all() + assert (a == b).all() + assert (b == a).all() + assert ((~(a == b)) == (a != b)).all() + assert ((~(b == a)) == (b != a)).all() + + assert not (a == c).all() + assert not (c == a).all() + assert not (a == d).all() + assert not (d == a).all() + + # vs a cat-like + assert (a == e).all() + assert (e == a).all() + assert not (a == f).all() + assert not (f == a).all() + + assert (~(a == e) == (a != e)).all() + assert (~(e == a) == (e != a)).all() + assert (~(a == f) == (a != f)).all() + assert (~(f == a) == (f != a)).all() + + # non-equality is not comparable + msg = "can only compare equality or not" + with pytest.raises(TypeError, match=msg): + a < b + with pytest.raises(TypeError, match=msg): + b < a + with pytest.raises(TypeError, match=msg): + a > b + with pytest.raises(TypeError, match=msg): + b > a + + def test_unequal_categorical_comparison_raises_type_error(self): + # unequal comparison should raise for unordered cats + cat = Series(Categorical(list("abc"))) + msg = "can only compare equality or not" + with pytest.raises(TypeError, match=msg): + cat > "b" + + cat = Series(Categorical(list("abc"), ordered=False)) + with pytest.raises(TypeError, match=msg): + cat > "b" + + # https://github.com/pandas-dev/pandas/issues/9836#issuecomment-92123057 + # and following comparisons with scalars not in categories should raise + # for unequal comps, but not for equal/not equal + cat = Series(Categorical(list("abc"), ordered=True)) + + msg = "Invalid comparison between dtype=category and str" + with pytest.raises(TypeError, match=msg): + cat < "d" + with pytest.raises(TypeError, match=msg): + cat > "d" + with pytest.raises(TypeError, match=msg): + "d" < cat + with pytest.raises(TypeError, match=msg): + "d" > cat + + tm.assert_series_equal(cat == "d", Series([False, False, False])) + tm.assert_series_equal(cat != "d", Series([True, True, True])) + + # ----------------------------------------------------------------- + + def test_comparison_tuples(self): + # GH#11339 + # comparisons vs tuple + s = Series([(1, 1), (1, 2)]) + + result = s == (1, 2) + expected = Series([False, True]) + tm.assert_series_equal(result, expected) + + result = s != (1, 2) + expected = Series([True, False]) + tm.assert_series_equal(result, expected) + + result = s == (0, 0) + expected = Series([False, False]) + tm.assert_series_equal(result, expected) + + result = s != (0, 0) + expected = Series([True, True]) + tm.assert_series_equal(result, expected) + + s = Series([(1, 1), (1, 1)]) + + result = s == (1, 1) + expected = Series([True, True]) + tm.assert_series_equal(result, expected) + + result = s != (1, 1) + expected = Series([False, False]) + tm.assert_series_equal(result, expected) + + def test_comparison_frozenset(self): + ser = Series([frozenset([1]), frozenset([1, 2])]) + + result = ser == frozenset([1]) + expected = Series([True, False]) + tm.assert_series_equal(result, expected) + + def test_comparison_operators_with_nas(self, comparison_op): + ser = Series(bdate_range("1/1/2000", periods=10), dtype=object) + ser[::2] = np.nan + + # test that comparisons work + val = ser[5] + + result = comparison_op(ser, val) + expected = comparison_op(ser.dropna(), val).reindex(ser.index) + + if comparison_op is operator.ne: + expected = expected.fillna(True).astype(bool) + else: + expected = expected.fillna(False).astype(bool) + + tm.assert_series_equal(result, expected) + + def test_ne(self): + ts = Series([3, 4, 5, 6, 7], [3, 4, 5, 6, 7], dtype=float) + expected = np.array([True, True, False, True, True]) + tm.assert_numpy_array_equal(ts.index != 5, expected) + tm.assert_numpy_array_equal(~(ts.index == 5), expected) + + @pytest.mark.parametrize("right_data", [[2, 2, 2], [2, 2, 2, 2]]) + def test_comp_ops_df_compat(self, right_data, frame_or_series): + # GH 1134 + # GH 50083 to clarify that index and columns must be identically labeled + left = Series([1, 2, 3], index=list("ABC"), name="x") + right = Series(right_data, index=list("ABDC")[: len(right_data)], name="x") + if frame_or_series is not Series: + msg = ( + rf"Can only compare identically-labeled \(both index and columns\) " + f"{frame_or_series.__name__} objects" + ) + left = left.to_frame() + right = right.to_frame() + else: + msg = ( + f"Can only compare identically-labeled {frame_or_series.__name__} " + f"objects" + ) + + with pytest.raises(ValueError, match=msg): + left == right + with pytest.raises(ValueError, match=msg): + right == left + + with pytest.raises(ValueError, match=msg): + left != right + with pytest.raises(ValueError, match=msg): + right != left + + with pytest.raises(ValueError, match=msg): + left < right + with pytest.raises(ValueError, match=msg): + right < left + + def test_compare_series_interval_keyword(self): + # GH#25338 + ser = Series(["IntervalA", "IntervalB", "IntervalC"]) + result = ser == "IntervalA" + expected = Series([True, False, False]) + tm.assert_series_equal(result, expected) + + +# ------------------------------------------------------------------ +# Unsorted +# These arithmetic tests were previously in other files, eventually +# should be parametrized and put into tests.arithmetic + + +class TestTimeSeriesArithmetic: + def test_series_add_tz_mismatch_converts_to_utc(self): + rng = date_range("1/1/2011", periods=100, freq="h", tz="utc") + + perm = np.random.default_rng(2).permutation(100)[:90] + ser1 = Series( + np.random.default_rng(2).standard_normal(90), + index=rng.take(perm).tz_convert("US/Eastern"), + ) + + perm = np.random.default_rng(2).permutation(100)[:90] + ser2 = Series( + np.random.default_rng(2).standard_normal(90), + index=rng.take(perm).tz_convert("Europe/Berlin"), + ) + + result = ser1 + ser2 + + uts1 = ser1.tz_convert("utc") + uts2 = ser2.tz_convert("utc") + expected = uts1 + uts2 + + # sort since input indexes are not equal + expected = expected.sort_index() + + assert result.index.tz is timezone.utc + tm.assert_series_equal(result, expected) + + def test_series_add_aware_naive_raises(self): + rng = date_range("1/1/2011", periods=10, freq="h") + ser = Series(np.random.default_rng(2).standard_normal(len(rng)), index=rng) + + ser_utc = ser.tz_localize("utc") + + msg = "Cannot join tz-naive with tz-aware DatetimeIndex" + with pytest.raises(Exception, match=msg): + ser + ser_utc + + with pytest.raises(Exception, match=msg): + ser_utc + ser + + # TODO: belongs in tests/arithmetic? + def test_datetime_understood(self, unit): + # Ensures it doesn't fail to create the right series + # reported in issue#16726 + series = Series(date_range("2012-01-01", periods=3, unit=unit)) + offset = pd.offsets.DateOffset(days=6) + result = series - offset + exp_dti = pd.to_datetime(["2011-12-26", "2011-12-27", "2011-12-28"]).as_unit( + unit + ) + expected = Series(exp_dti) + tm.assert_series_equal(result, expected) + + def test_align_date_objects_with_datetimeindex(self): + rng = date_range("1/1/2000", periods=20) + ts = Series(np.random.default_rng(2).standard_normal(20), index=rng) + + ts_slice = ts[5:] + ts2 = ts_slice.copy() + ts2.index = [x.date() for x in ts2.index] + + result = ts + ts2 + result2 = ts2 + ts + expected = ts + ts[5:] + expected.index = expected.index._with_freq(None) + tm.assert_series_equal(result, expected) + tm.assert_series_equal(result2, expected) + + +class TestNamePreservation: + @pytest.mark.parametrize("box", [list, tuple, np.array, Index, Series, pd.array]) + @pytest.mark.parametrize("flex", [True, False]) + def test_series_ops_name_retention(self, flex, box, names, all_binary_operators): + # GH#33930 consistent name-retention + op = all_binary_operators + + left = Series(range(10), name=names[0]) + right = Series(range(10), name=names[1]) + + name = op.__name__.strip("_") + is_logical = name in ["and", "rand", "xor", "rxor", "or", "ror"] + + msg = ( + r"Logical ops \(and, or, xor\) between Pandas objects and " + "dtype-less sequences" + ) + + right = box(right) + if flex: + if is_logical: + # Series doesn't have these as flex methods + return + result = getattr(left, name)(right) + else: + if is_logical and box in [list, tuple]: + with pytest.raises(TypeError, match=msg): + # GH#52264 logical ops with dtype-less sequences deprecated + op(left, right) + return + result = op(left, right) + + assert isinstance(result, Series) + if box in [Index, Series]: + assert result.name is names[2] or result.name == names[2] + else: + assert result.name is names[0] or result.name == names[0] + + def test_binop_maybe_preserve_name(self, datetime_series): + # names match, preserve + result = datetime_series * datetime_series + assert result.name == datetime_series.name + result = datetime_series.mul(datetime_series) + assert result.name == datetime_series.name + + result = datetime_series * datetime_series[:-2] + assert result.name == datetime_series.name + + # names don't match, don't preserve + cp = datetime_series.copy() + cp.name = "something else" + result = datetime_series + cp + assert result.name is None + result = datetime_series.add(cp) + assert result.name is None + + ops = ["add", "sub", "mul", "div", "truediv", "floordiv", "mod", "pow"] + ops = ops + ["r" + op for op in ops] + for op in ops: + # names match, preserve + ser = datetime_series.copy() + result = getattr(ser, op)(ser) + assert result.name == datetime_series.name + + # names don't match, don't preserve + cp = datetime_series.copy() + cp.name = "changed" + result = getattr(ser, op)(cp) + assert result.name is None + + def test_scalarop_preserve_name(self, datetime_series): + result = datetime_series * 2 + assert result.name == datetime_series.name + + +class TestInplaceOperations: + @pytest.mark.parametrize( + "dtype1, dtype2, dtype_expected, dtype_mul", + ( + ("Int64", "Int64", "Int64", "Int64"), + ("float", "float", "float", "float"), + ("Int64", "float", "Float64", "Float64"), + ("Int64", "Float64", "Float64", "Float64"), + ), + ) + def test_series_inplace_ops(self, dtype1, dtype2, dtype_expected, dtype_mul): + # GH 37910 + + ser1 = Series([1], dtype=dtype1) + ser2 = Series([2], dtype=dtype2) + ser1 += ser2 + expected = Series([3], dtype=dtype_expected) + tm.assert_series_equal(ser1, expected) + + ser1 -= ser2 + expected = Series([1], dtype=dtype_expected) + tm.assert_series_equal(ser1, expected) + + ser1 *= ser2 + expected = Series([2], dtype=dtype_mul) + tm.assert_series_equal(ser1, expected) + + +def test_none_comparison(request, series_with_simple_index): + series = series_with_simple_index + + if len(series) < 1: + request.applymarker( + pytest.mark.xfail(reason="Test doesn't make sense on empty data") + ) + + # bug brought up by #1079 + # changed from TypeError in 0.17.0 + series.iloc[0] = np.nan + + # noinspection PyComparisonWithNone + result = series == None # noqa: E711 + assert not result.iat[0] + assert not result.iat[1] + + # noinspection PyComparisonWithNone + result = series != None # noqa: E711 + assert result.iat[0] + assert result.iat[1] + + result = None == series # noqa: E711 + assert not result.iat[0] + assert not result.iat[1] + + result = None != series # noqa: E711 + assert result.iat[0] + assert result.iat[1] + + if lib.is_np_dtype(series.dtype, "M") or isinstance(series.dtype, DatetimeTZDtype): + # Following DatetimeIndex (and Timestamp) convention, + # inequality comparisons with Series[datetime64] raise + msg = "Invalid comparison" + with pytest.raises(TypeError, match=msg): + None > series + with pytest.raises(TypeError, match=msg): + series > None + else: + result = None > series + assert not result.iat[0] + assert not result.iat[1] + + result = series < None + assert not result.iat[0] + assert not result.iat[1] + + +def test_series_varied_multiindex_alignment(): + # GH 20414 + s1 = Series( + range(8), + index=pd.MultiIndex.from_product( + [list("ab"), list("xy"), [1, 2]], names=["ab", "xy", "num"] + ), + ) + s2 = Series( + [1000 * i for i in range(1, 5)], + index=pd.MultiIndex.from_product([list("xy"), [1, 2]], names=["xy", "num"]), + ) + result = s1.loc[pd.IndexSlice[["a"], :, :]] + s2 + expected = Series( + [1000, 2001, 3002, 4003], + index=pd.MultiIndex.from_tuples( + [("a", "x", 1), ("a", "x", 2), ("a", "y", 1), ("a", "y", 2)], + names=["ab", "xy", "num"], + ), + ) + tm.assert_series_equal(result, expected) + + +def test_rmod_consistent_large_series(): + # GH 29602 + result = Series([2] * 10001).rmod(-1) + expected = Series([1] * 10001) + + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "index", + [ + date_range("2016-01-01", periods=3), + date_range("2016-01-01", tz="US/Pacific", periods=3), + pd.timedelta_range("1 Day", periods=3), + ], +) +def test_comparison_mismatched_datetime_units(index): + # GH#63459 + + ser = Series(1, index=index) + ser2 = Series(1, index=index.as_unit("ns")) + + result = ser == ser2 + expected = Series([True, True, True], index=ser.index) + tm.assert_series_equal(result, expected) + + result2 = ser2 < ser + expected2 = Series([False, False, False], index=ser2.index) + tm.assert_series_equal(result2, expected2) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/test_arrow_interface.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/test_arrow_interface.py new file mode 100644 index 0000000000000000000000000000000000000000..3b4340064ea1bc4827c858d4deff602538240eb2 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/test_arrow_interface.py @@ -0,0 +1,117 @@ +import ctypes + +import pytest + +import pandas.util._test_decorators as td + +import pandas as pd +import pandas._testing as tm + +pa = pytest.importorskip("pyarrow", minversion="16.0") + + +def test_series_arrow_interface(): + s = pd.Series([1, 4, 2]) + + capsule = s.__arrow_c_stream__() + assert ( + ctypes.pythonapi.PyCapsule_IsValid( + ctypes.py_object(capsule), b"arrow_array_stream" + ) + == 1 + ) + + ca = pa.chunked_array(s) + expected = pa.chunked_array([[1, 4, 2]]) + assert ca.equals(expected) + ca = pa.chunked_array(s, type=pa.int32()) + expected = pa.chunked_array([[1, 4, 2]], type=pa.int32()) + assert ca.equals(expected) + + +def test_series_arrow_interface_arrow_dtypes(): + s = pd.Series([1, 4, 2], dtype="Int64[pyarrow]") + + capsule = s.__arrow_c_stream__() + assert ( + ctypes.pythonapi.PyCapsule_IsValid( + ctypes.py_object(capsule), b"arrow_array_stream" + ) + == 1 + ) + + ca = pa.chunked_array(s) + expected = pa.chunked_array([[1, 4, 2]]) + assert ca.equals(expected) + ca = pa.chunked_array(s, type=pa.int32()) + expected = pa.chunked_array([[1, 4, 2]], type=pa.int32()) + assert ca.equals(expected) + + +def test_series_arrow_interface_stringdtype(): + s = pd.Series(["foo", "bar"], dtype="string[pyarrow]") + + capsule = s.__arrow_c_stream__() + assert ( + ctypes.pythonapi.PyCapsule_IsValid( + ctypes.py_object(capsule), b"arrow_array_stream" + ) + == 1 + ) + + ca = pa.chunked_array(s) + expected = pa.chunked_array([["foo", "bar"]], type=pa.large_string()) + assert ca.equals(expected) + + +class ArrowArrayWrapper: + def __init__(self, array): + self.array = array + + def __arrow_c_array__(self, requested_schema=None): + return self.array.__arrow_c_array__(requested_schema) + + +class ArrowStreamWrapper: + def __init__(self, chunked_array): + self.stream = chunked_array + + def __arrow_c_stream__(self, requested_schema=None): + return self.stream.__arrow_c_stream__(requested_schema) + + +@td.skip_if_no("pyarrow", min_version="14.0") +def test_dataframe_from_arrow(): + # objects with __arrow_c_stream__ + arr = pa.chunked_array([[1, 2, 3], [4, 5]]) + + result = pd.Series.from_arrow(arr) + expected = pd.Series([1, 2, 3, 4, 5]) + tm.assert_series_equal(result, expected) + + # not only pyarrow object are supported + result = pd.Series.from_arrow(ArrowStreamWrapper(arr)) + tm.assert_series_equal(result, expected) + + # table works as well, but will be seen as a StructArray + table = pa.table({"a": [1, 2, 3], "b": ["a", "b", "c"]}) + + result = pd.Series.from_arrow(table) + expected = pd.Series([{"a": 1, "b": "a"}, {"a": 2, "b": "b"}, {"a": 3, "b": "c"}]) + tm.assert_series_equal(result, expected) + + # objects with __arrow_c_array__ + arr = pa.array([1, 2, 3]) + + expected = pd.Series([1, 2, 3]) + result = pd.Series.from_arrow(arr) + tm.assert_series_equal(result, expected) + + result = pd.Series.from_arrow(ArrowArrayWrapper(arr)) + tm.assert_series_equal(result, expected) + + # only accept actual Arrow objects + with pytest.raises( + TypeError, match="Expected an Arrow-compatible array-like object" + ): + pd.Series.from_arrow([1, 2, 3]) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/test_constructors.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/test_constructors.py new file mode 100644 index 0000000000000000000000000000000000000000..34409f836c68115e21976c9102d243d06a8a6c43 --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/test_constructors.py @@ -0,0 +1,2294 @@ +from collections import OrderedDict +from collections.abc import Iterator +from datetime import ( + datetime, + timedelta, +) + +from dateutil.tz import tzoffset +import numpy as np +from numpy import ma +import pytest + +from pandas._libs import ( + iNaT, + lib, +) +from pandas.compat import HAS_PYARROW +from pandas.compat.numpy import np_version_gt2 +from pandas.errors import ( + IntCastingNaNError, + Pandas4Warning, +) + +from pandas.core.dtypes.dtypes import CategoricalDtype + +import pandas as pd +from pandas import ( + Categorical, + DataFrame, + DatetimeIndex, + DatetimeTZDtype, + Index, + Interval, + IntervalIndex, + MultiIndex, + NaT, + Period, + RangeIndex, + Series, + Timestamp, + date_range, + isna, + period_range, + timedelta_range, +) +import pandas._testing as tm +from pandas.core.arrays import ( + IntegerArray, + IntervalArray, + period_array, +) +from pandas.core.internals.blocks import NumpyBlock + + +class TestSeriesConstructors: + def test_from_ints_with_non_nano_dt64_dtype(self, index_or_series): + values = np.arange(10) + + res = index_or_series(values, dtype="M8[s]") + expected = index_or_series(values.astype("M8[s]")) + tm.assert_equal(res, expected) + + res = index_or_series(list(values), dtype="M8[s]") + tm.assert_equal(res, expected) + + def test_from_na_value_and_interval_of_datetime_dtype(self): + # GH#41805 + ser = Series([None], dtype="interval[datetime64[ns]]") + assert ser.isna().all() + assert ser.dtype == "interval[datetime64[ns], right]" + + def test_infer_with_date_and_datetime(self): + # GH#49341 pre-2.0 we inferred datetime-and-date to datetime64, which + # was inconsistent with Index behavior + ts = Timestamp(2016, 1, 1) + vals = [ts.to_pydatetime(), ts.date()] + + ser = Series(vals) + expected = Series(vals, dtype=object) + tm.assert_series_equal(ser, expected) + + idx = Index(vals) + expected = Index(vals, dtype=object) + tm.assert_index_equal(idx, expected) + + def test_unparsable_strings_with_dt64_dtype(self): + # pre-2.0 these would be silently ignored and come back with object dtype + vals = ["aa"] + msg = "^Unknown datetime string format, unable to parse: aa$" + with pytest.raises(ValueError, match=msg): + Series(vals, dtype="datetime64[ns]") + + with pytest.raises(ValueError, match=msg): + Series(np.array(vals, dtype=object), dtype="datetime64[ns]") + + def test_invalid_dtype_conversion_datetime_to_timedelta(self): + # GH#60728 + vals = Series([NaT, Timestamp(2025, 1, 1)], dtype="datetime64[ns]") + msg = r"^Cannot cast DatetimeArray to dtype timedelta64\[ns\]$" + with pytest.raises(TypeError, match=msg): + Series(vals, dtype="timedelta64[ns]") + + @pytest.mark.parametrize( + "constructor", + [ + # NOTE: some overlap with test_constructor_empty but that test does not + # test for None or an empty generator. + # test_constructor_pass_none tests None but only with the index also + # passed. + (lambda idx: Series(index=idx)), + (lambda idx: Series(None, index=idx)), + (lambda idx: Series({}, index=idx)), + (lambda idx: Series((), index=idx)), + (lambda idx: Series([], index=idx)), + (lambda idx: Series((_ for _ in []), index=idx)), + (lambda idx: Series(data=None, index=idx)), + (lambda idx: Series(data={}, index=idx)), + (lambda idx: Series(data=(), index=idx)), + (lambda idx: Series(data=[], index=idx)), + (lambda idx: Series(data=(_ for _ in []), index=idx)), + ], + ) + @pytest.mark.parametrize("empty_index", [None, []]) + def test_empty_constructor(self, constructor, empty_index): + # GH 49573 (addition of empty_index parameter) + expected = Series(index=empty_index) + result = constructor(empty_index) + + assert result.dtype == object + assert len(result.index) == 0 + tm.assert_series_equal(result, expected, check_index_type=True) + + def test_invalid_dtype(self): + # GH15520 + msg = "not understood" + invalid_list = [Timestamp, "Timestamp", list] + for dtype in invalid_list: + with pytest.raises(TypeError, match=msg): + Series([], name="time", dtype=dtype) + + def test_invalid_compound_dtype(self): + # GH#13296 + c_dtype = np.dtype([("a", "i8"), ("b", "f4")]) + cdt_arr = np.array([(1, 0.4), (256, -13)], dtype=c_dtype) + + with pytest.raises(ValueError, match="Use DataFrame instead"): + Series(cdt_arr, index=["A", "B"]) + + def test_scalar_conversion(self): + # Pass in scalar is disabled + scalar = Series(0.5) + assert not isinstance(scalar, float) + + def test_scalar_extension_dtype(self, ea_scalar_and_dtype): + # GH 28401 + + ea_scalar, ea_dtype = ea_scalar_and_dtype + + ser = Series(ea_scalar, index=range(3)) + expected = Series([ea_scalar] * 3, dtype=ea_dtype) + + assert ser.dtype == ea_dtype + tm.assert_series_equal(ser, expected) + + def test_constructor(self, datetime_series, using_infer_string): + empty_series = Series() + assert datetime_series.index._is_all_dates + + # Pass in Series + derived = Series(datetime_series) + assert derived.index._is_all_dates + + tm.assert_index_equal(derived.index, datetime_series.index) + # Ensure new index is not created + assert id(datetime_series.index) == id(derived.index) + + # Mixed type Series + mixed = Series(["hello", np.nan], index=[0, 1]) + assert mixed.dtype == np.object_ if not using_infer_string else "str" + assert np.isnan(mixed[1]) + + assert not empty_series.index._is_all_dates + assert not Series().index._is_all_dates + + # exception raised is of type ValueError GH35744 + with pytest.raises( + ValueError, + match=r"Data must be 1-dimensional, got ndarray of shape \(3, 3\) instead", + ): + Series(np.random.default_rng(2).standard_normal((3, 3)), index=np.arange(3)) + + mixed.name = "Series" + rs = Series(mixed).name + xp = "Series" + assert rs == xp + + # raise on MultiIndex GH4187 + m = MultiIndex.from_arrays([[1, 2], [3, 4]]) + msg = "initializing a Series from a MultiIndex is not supported" + with pytest.raises(NotImplementedError, match=msg): + Series(m) + + def test_constructor_index_ndim_gt_1_raises(self): + # GH#18579 + df = DataFrame([[1, 2], [3, 4], [5, 6]], index=[3, 6, 9]) + with pytest.raises(ValueError, match="Index data must be 1-dimensional"): + Series([1, 3, 2], index=df) + + @pytest.mark.parametrize("input_class", [list, dict, OrderedDict]) + def test_constructor_empty(self, input_class, using_infer_string): + empty = Series() + empty2 = Series(input_class()) + + # these are Index() and RangeIndex() which don't compare type equal + # but are just .equals + tm.assert_series_equal(empty, empty2, check_index_type=False) + + # With explicit dtype: + empty = Series(dtype="float64") + empty2 = Series(input_class(), dtype="float64") + tm.assert_series_equal(empty, empty2, check_index_type=False) + + # GH 18515 : with dtype=category: + empty = Series(dtype="category") + empty2 = Series(input_class(), dtype="category") + tm.assert_series_equal(empty, empty2, check_index_type=False) + + if input_class is not list: + # With index: + empty = Series(index=range(10)) + empty2 = Series(input_class(), index=range(10)) + tm.assert_series_equal(empty, empty2) + + # With index and dtype float64: + empty = Series(np.nan, index=range(10)) + empty2 = Series(input_class(), index=range(10), dtype="float64") + tm.assert_series_equal(empty, empty2) + + # GH 19853 : with empty string, index and dtype str + empty = Series("", dtype=str, index=range(3)) + if using_infer_string: + empty2 = Series("", index=range(3), dtype="str") + else: + empty2 = Series("", index=range(3)) + tm.assert_series_equal(empty, empty2) + + @pytest.mark.parametrize("input_arg", [np.nan, float("nan")]) + def test_constructor_nan(self, input_arg): + empty = Series(dtype="float64", index=range(10)) + empty2 = Series(input_arg, index=range(10)) + + tm.assert_series_equal(empty, empty2, check_index_type=False) + + @pytest.mark.parametrize( + "dtype", + ["f8", "i8", "M8[ns]", "m8[ns]", "category", "object", "datetime64[ns, UTC]"], + ) + @pytest.mark.parametrize("index", [None, Index([])]) + def test_constructor_dtype_only(self, dtype, index): + # GH-20865 + result = Series(dtype=dtype, index=index) + assert result.dtype == dtype + assert len(result) == 0 + + def test_constructor_no_data_index_order(self): + result = Series(index=["b", "a", "c"]) + assert result.index.tolist() == ["b", "a", "c"] + + def test_constructor_no_data_string_type(self): + # GH 22477 + result = Series(index=[1], dtype=str) + assert np.isnan(result.iloc[0]) + + @pytest.mark.parametrize("item", ["entry", "ѐ", 13]) + def test_constructor_string_element_string_type(self, item): + # GH 22477 + result = Series(item, index=[1], dtype=str) + assert result.iloc[0] == str(item) + + def test_constructor_dtype_str_na_values(self, string_dtype): + # https://github.com/pandas-dev/pandas/issues/21083 + ser = Series(["x", None], dtype=string_dtype) + result = ser.isna() + expected = Series([False, True]) + tm.assert_series_equal(result, expected) + assert ser.iloc[1] is None + + ser = Series(["x", np.nan], dtype=string_dtype) + assert np.isnan(ser.iloc[1]) + + def test_constructor_series(self): + index1 = ["d", "b", "a", "c"] + index2 = sorted(index1) + s1 = Series([4, 7, -5, 3], index=index1) + s2 = Series(s1, index=index2) + + tm.assert_series_equal(s2, s1.sort_index()) + + def test_constructor_iterable(self): + # GH 21987 + class Iter: + def __iter__(self) -> Iterator: + yield from range(10) + + expected = Series(list(range(10)), dtype="int64") + result = Series(Iter(), dtype="int64") + tm.assert_series_equal(result, expected) + + def test_constructor_sequence(self): + # GH 21987 + expected = Series(list(range(10)), dtype="int64") + result = Series(range(10), dtype="int64") + tm.assert_series_equal(result, expected) + + def test_constructor_single_str(self): + # GH 21987 + expected = Series(["abc"]) + result = Series("abc") + tm.assert_series_equal(result, expected) + + def test_constructor_list_like(self): + # make sure that we are coercing different + # list-likes to standard dtypes and not + # platform specific + expected = Series([1, 2, 3], dtype="int64") + for obj in [[1, 2, 3], (1, 2, 3), np.array([1, 2, 3], dtype="int64")]: + result = Series(obj, index=[0, 1, 2]) + tm.assert_series_equal(result, expected) + + def test_constructor_boolean_index(self): + # GH#18579 + s1 = Series([1, 2, 3], index=[4, 5, 6]) + + index = s1 == 2 + result = Series([1, 3, 2], index=index) + expected = Series([1, 3, 2], index=[False, True, False]) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("dtype", ["bool", "int32", "int64", "float64"]) + def test_constructor_index_dtype(self, dtype): + # GH 17088 + + s = Series(Index([0, 2, 4]), dtype=dtype) + assert s.dtype == dtype + + @pytest.mark.parametrize( + "input_vals", + [ + [1, 2], + ["1", "2"], + list(date_range("1/1/2011", periods=2, freq="h")), + list(date_range("1/1/2011", periods=2, freq="h", tz="US/Eastern")), + [Interval(left=0, right=5)], + ], + ) + def test_constructor_list_str(self, input_vals, string_dtype): + # GH 16605 + # Ensure that data elements from a list are converted to strings + # when dtype is str, 'str', or 'U' + result = Series(input_vals, dtype=string_dtype) + expected = Series(input_vals).astype(string_dtype) + tm.assert_series_equal(result, expected) + + def test_constructor_list_str_na(self, string_dtype): + result = Series([1.0, 2.0, np.nan], dtype=string_dtype) + expected = Series(["1.0", "2.0", np.nan], dtype=object) + tm.assert_series_equal(result, expected) + assert np.isnan(result[2]) + + def test_constructor_generator(self): + gen = (i for i in range(10)) + + result = Series(gen) + exp = Series(range(10)) + tm.assert_series_equal(result, exp) + + # same but with non-default index + gen = (i for i in range(10)) + result = Series(gen, index=range(10, 20)) + exp.index = range(10, 20) + tm.assert_series_equal(result, exp) + + def test_constructor_map(self): + # GH8909 + m = (x for x in range(10)) + + result = Series(m) + exp = Series(range(10)) + tm.assert_series_equal(result, exp) + + # same but with non-default index + m = (x for x in range(10)) + result = Series(m, index=range(10, 20)) + exp.index = range(10, 20) + tm.assert_series_equal(result, exp) + + def test_constructor_categorical(self): + msg = "Constructing a Categorical with a dtype and values containing" + with tm.assert_produces_warning(Pandas4Warning, match=msg): + cat = Categorical([0, 1, 2, 0, 1, 2], ["a", "b", "c"]) + res = Series(cat) + tm.assert_categorical_equal(res.values, cat) + + # can cast to a new dtype + result = Series(Categorical([1, 2, 3]), dtype="int64") + expected = Series([1, 2, 3], dtype="int64") + tm.assert_series_equal(result, expected) + + def test_construct_from_categorical_with_dtype(self): + # GH12574 + ser = Series(Categorical([1, 2, 3]), dtype="category") + assert isinstance(ser.dtype, CategoricalDtype) + + def test_construct_intlist_values_category_dtype(self): + ser = Series([1, 2, 3], dtype="category") + assert isinstance(ser.dtype, CategoricalDtype) + + def test_constructor_categorical_with_coercion(self): + factor = Categorical(["a", "b", "b", "a", "a", "c", "c", "c"]) + # test basic creation / coercion of categoricals + s = Series(factor, name="A") + assert s.dtype == "category" + assert len(s) == len(factor) + + # in a frame + df = DataFrame({"A": factor}) + result = df["A"] + tm.assert_series_equal(result, s) + result = df.iloc[:, 0] + tm.assert_series_equal(result, s) + assert len(df) == len(factor) + + df = DataFrame({"A": s}) + result = df["A"] + tm.assert_series_equal(result, s) + assert len(df) == len(factor) + + # multiples + df = DataFrame({"A": s, "B": s, "C": 1}) + result1 = df["A"] + result2 = df["B"] + tm.assert_series_equal(result1, s) + tm.assert_series_equal(result2, s, check_names=False) + assert result2.name == "B" + assert len(df) == len(factor) + + def test_constructor_categorical_with_coercion2(self): + # GH8623 + x = DataFrame( + [[1, "John P. Doe"], [2, "Jane Dove"], [1, "John P. Doe"]], + columns=["person_id", "person_name"], + ) + x["person_name"] = Categorical(x.person_name) # doing this breaks transform + + expected = x.iloc[0].person_name + result = x.person_name.iloc[0] + assert result == expected + + result = x.person_name[0] + assert result == expected + + result = x.person_name.loc[0] + assert result == expected + + def test_constructor_series_to_categorical(self): + # see GH#16524: test conversion of Series to Categorical + series = Series(["a", "b", "c"]) + + result = Series(series, dtype="category") + expected = Series(["a", "b", "c"], dtype="category") + + tm.assert_series_equal(result, expected) + + def test_constructor_categorical_dtype(self): + result = Series( + ["a", "b"], dtype=CategoricalDtype(["a", "b", "c"], ordered=True) + ) + assert isinstance(result.dtype, CategoricalDtype) + tm.assert_index_equal(result.cat.categories, Index(["a", "b", "c"])) + assert result.cat.ordered + + result = Series(["a", "b"], dtype=CategoricalDtype(["b", "a"])) + assert isinstance(result.dtype, CategoricalDtype) + tm.assert_index_equal(result.cat.categories, Index(["b", "a"])) + assert result.cat.ordered is False + + # GH 19565 - Check broadcasting of scalar with Categorical dtype + result = Series( + "a", index=[0, 1], dtype=CategoricalDtype(["a", "b"], ordered=True) + ) + expected = Series( + ["a", "a"], index=[0, 1], dtype=CategoricalDtype(["a", "b"], ordered=True) + ) + tm.assert_series_equal(result, expected) + + def test_constructor_categorical_string(self): + # GH 26336: the string 'category' maintains existing CategoricalDtype + cdt = CategoricalDtype(categories=list("dabc"), ordered=True) + expected = Series(list("abcabc"), dtype=cdt) + + # Series(Categorical, dtype='category') keeps existing dtype + cat = Categorical(list("abcabc"), dtype=cdt) + result = Series(cat, dtype="category") + tm.assert_series_equal(result, expected) + + # Series(Series[Categorical], dtype='category') keeps existing dtype + result = Series(result, dtype="category") + tm.assert_series_equal(result, expected) + + def test_categorical_sideeffects_free(self): + # Passing a categorical to a Series and then changing values in either + # the series or the categorical should not change the values in the + # other one, IF you specify copy! + cat = Categorical(["a", "b", "c", "a"]) + s = Series(cat, copy=True) + assert s.cat is not cat + s = s.cat.rename_categories([1, 2, 3]) + exp_s = np.array([1, 2, 3, 1], dtype=np.int64) + exp_cat = np.array(["a", "b", "c", "a"], dtype=np.object_) + tm.assert_numpy_array_equal(s.__array__(), exp_s) + tm.assert_numpy_array_equal(cat.__array__(), exp_cat) + + # setting + s[0] = 2 + exp_s2 = np.array([2, 2, 3, 1], dtype=np.int64) + tm.assert_numpy_array_equal(s.__array__(), exp_s2) + tm.assert_numpy_array_equal(cat.__array__(), exp_cat) + + # however, copy is False by default + # so this WILL change values + cat = Categorical(["a", "b", "c", "a"]) + s = Series(cat, copy=False) + assert s._values is cat + s = s.cat.rename_categories([1, 2, 3]) + assert s._values is not cat + exp_s = np.array([1, 2, 3, 1], dtype=np.int64) + tm.assert_numpy_array_equal(s.__array__(), exp_s) + + s[0] = 2 + exp_s2 = np.array([2, 2, 3, 1], dtype=np.int64) + tm.assert_numpy_array_equal(s.__array__(), exp_s2) + + def test_unordered_compare_equal(self): + left = Series(["a", "b", None], dtype=CategoricalDtype(["a", "b"])) + right = Series(Categorical(["a", "b", np.nan], categories=["a", "b"])) + tm.assert_series_equal(left, right) + + def test_constructor_maskedarray(self): + data = ma.masked_all((3,), dtype=float) + result = Series(data) + expected = Series([np.nan, np.nan, np.nan]) + tm.assert_series_equal(result, expected) + + data[0] = 0.0 + data[2] = 2.0 + index = ["a", "b", "c"] + result = Series(data, index=index) + expected = Series([0.0, np.nan, 2.0], index=index) + tm.assert_series_equal(result, expected) + + data[1] = 1.0 + result = Series(data, index=index) + expected = Series([0.0, 1.0, 2.0], index=index) + tm.assert_series_equal(result, expected) + + data = ma.masked_all((3,), dtype=int) + result = Series(data) + expected = Series([np.nan, np.nan, np.nan], dtype=float) + tm.assert_series_equal(result, expected) + + data[0] = 0 + data[2] = 2 + index = ["a", "b", "c"] + result = Series(data, index=index) + expected = Series([0, np.nan, 2], index=index, dtype=float) + tm.assert_series_equal(result, expected) + + data[1] = 1 + result = Series(data, index=index) + expected = Series([0, 1, 2], index=index, dtype=int) + with pytest.raises(AssertionError, match="Series classes are different"): + # TODO should this be raising at all? + # https://github.com/pandas-dev/pandas/issues/56131 + tm.assert_series_equal(result, expected) + + data = ma.masked_all((3,), dtype=bool) + result = Series(data) + expected = Series([np.nan, np.nan, np.nan], dtype=object) + tm.assert_series_equal(result, expected) + + data[0] = True + data[2] = False + index = ["a", "b", "c"] + result = Series(data, index=index) + expected = Series([True, np.nan, False], index=index, dtype=object) + tm.assert_series_equal(result, expected) + + data[1] = True + result = Series(data, index=index) + expected = Series([True, True, False], index=index, dtype=bool) + with pytest.raises(AssertionError, match="Series classes are different"): + # TODO should this be raising at all? + # https://github.com/pandas-dev/pandas/issues/56131 + tm.assert_series_equal(result, expected) + + data = ma.masked_all((3,), dtype="M8[ns]") + result = Series(data) + expected = Series([iNaT, iNaT, iNaT], dtype="M8[ns]") + tm.assert_series_equal(result, expected) + + data[0] = datetime(2001, 1, 1) + data[2] = datetime(2001, 1, 3) + index = ["a", "b", "c"] + result = Series(data, index=index) + expected = Series( + [datetime(2001, 1, 1), iNaT, datetime(2001, 1, 3)], + index=index, + dtype="M8[ns]", + ) + tm.assert_series_equal(result, expected) + + data[1] = datetime(2001, 1, 2) + result = Series(data, index=index) + expected = Series( + [datetime(2001, 1, 1), datetime(2001, 1, 2), datetime(2001, 1, 3)], + index=index, + dtype="M8[ns]", + ) + tm.assert_series_equal(result, expected) + + def test_constructor_maskedarray_hardened(self): + # Check numpy masked arrays with hard masks -- from GH24574 + data = ma.masked_all((3,), dtype=float).harden_mask() + result = Series(data) + expected = Series([np.nan, np.nan, np.nan]) + tm.assert_series_equal(result, expected) + + def test_series_ctor_plus_datetimeindex(self): + rng = date_range("20090415", "20090519", freq="B") + data = dict.fromkeys(rng, 1) + + result = Series(data, index=rng) + assert result.index.is_(rng) + + def test_constructor_default_index(self): + s = Series([0, 1, 2]) + tm.assert_index_equal(s.index, Index(range(3)), exact=True) + + @pytest.mark.parametrize( + "input", + [ + [1, 2, 3], + (1, 2, 3), + list(range(3)), + Categorical(["a", "b", "a"]), + (i for i in range(3)), + (x for x in range(3)), + ], + ) + def test_constructor_index_mismatch(self, input): + # GH 19342 + # test that construction of a Series with an index of different length + # raises an error + msg = r"Length of values \(3\) does not match length of index \(4\)" + with pytest.raises(ValueError, match=msg): + Series(input, index=np.arange(4)) + + def test_constructor_numpy_scalar(self): + # GH 19342 + # construction with a numpy scalar + # should not raise + result = Series(np.array(100), index=np.arange(4), dtype="int64") + expected = Series(100, index=np.arange(4), dtype="int64") + tm.assert_series_equal(result, expected) + + def test_constructor_broadcast_list(self): + # GH 19342 + # construction with single-element container and index + # should raise + msg = r"Length of values \(1\) does not match length of index \(3\)" + with pytest.raises(ValueError, match=msg): + Series(["foo"], index=["a", "b", "c"]) + + def test_constructor_corner(self): + df = DataFrame(range(5), index=date_range("2020-01-01", periods=5)) + objs = [df, df] + s = Series(objs, index=[0, 1]) + assert isinstance(s, Series) + + def test_constructor_sanitize(self): + s = Series(np.array([1.0, 1.0, 8.0]), dtype="i8") + assert s.dtype == np.dtype("i8") + + msg = r"Cannot convert non-finite values \(NA or inf\) to integer" + with pytest.raises(IntCastingNaNError, match=msg): + Series(np.array([1.0, 1.0, np.nan]), copy=True, dtype="i8") + + def test_constructor_copy(self): + # GH15125 + # test dtype parameter has no side effects on copy=True + for data in [[1.0], np.array([1.0])]: + x = Series(data) + y = Series(x, copy=True, dtype=float) + + # copy=True maintains original data in Series + tm.assert_series_equal(x, y) + + # changes to origin of copy does not affect the copy + x[0] = 2.0 + assert not x.equals(y) + assert x[0] == 2.0 + assert y[0] == 1.0 + + @pytest.mark.parametrize( + "index", + [ + date_range("20170101", periods=3, tz="US/Eastern"), + date_range("20170101", periods=3), + timedelta_range("1 day", periods=3), + period_range("2012Q1", periods=3, freq="Q"), + Index(list("abc")), + Index([1, 2, 3]), + RangeIndex(0, 3), + ], + ids=lambda x: type(x).__name__, + ) + def test_constructor_limit_copies(self, index): + # GH 17449 + # limit copies of input + s = Series(index) + + # we make 1 copy; this is just a smoke test here + assert s._mgr.blocks[0].values is not index + + def test_constructor_shallow_copy(self): + # constructing a Series from Series with copy=False should still + # give a "shallow" copy (share data, not attributes) + # https://github.com/pandas-dev/pandas/issues/49523 + s = Series([1, 2, 3]) + s_orig = s.copy() + s2 = Series(s) + assert s2._mgr is not s._mgr + # Overwriting index of s2 doesn't change s + s2.index = ["a", "b", "c"] + tm.assert_series_equal(s, s_orig) + + def test_constructor_pass_none(self): + s = Series(None, index=range(5)) + assert s.dtype == np.float64 + + s = Series(None, index=range(5), dtype=object) + assert s.dtype == np.object_ + + # GH 7431 + # inference on the index + s = Series(index=np.array([None])) + expected = Series(index=Index([None])) + tm.assert_series_equal(s, expected) + + def test_constructor_pass_nan_nat(self): + # GH 13467 + exp = Series([np.nan, np.nan], dtype=np.float64) + assert exp.dtype == np.float64 + tm.assert_series_equal(Series([np.nan, np.nan]), exp) + tm.assert_series_equal(Series(np.array([np.nan, np.nan])), exp) + + exp = Series([NaT, NaT]) + assert exp.dtype == "datetime64[s]" + tm.assert_series_equal(Series([NaT, NaT]), exp) + tm.assert_series_equal(Series(np.array([NaT, NaT])), exp) + + tm.assert_series_equal(Series([NaT, np.nan]), exp) + tm.assert_series_equal(Series(np.array([NaT, np.nan])), exp) + + tm.assert_series_equal(Series([np.nan, NaT]), exp) + tm.assert_series_equal(Series(np.array([np.nan, NaT])), exp) + + def test_constructor_cast(self): + msg = "could not convert string to float" + with pytest.raises(ValueError, match=msg): + Series(["a", "b", "c"], dtype=float) + + def test_constructor_signed_int_overflow_raises(self): + # GH#41734 disallow silent overflow, enforced in 2.0 + if np_version_gt2: + msg = "The elements provided in the data cannot all be casted to the dtype" + err = OverflowError + else: + msg = "Values are too large to be losslessly converted" + err = ValueError + with pytest.raises(err, match=msg): + Series([1, 200, 923442], dtype="int8") + + with pytest.raises(err, match=msg): + Series([1, 200, 923442], dtype="uint8") + + @pytest.mark.parametrize( + "values", + [ + np.array([1], dtype=np.uint16), + np.array([1], dtype=np.uint32), + np.array([1], dtype=np.uint64), + [np.uint16(1)], + [np.uint32(1)], + [np.uint64(1)], + ], + ) + def test_constructor_numpy_uints(self, values): + # GH#47294 + value = values[0] + result = Series(values) + + assert result[0].dtype == value.dtype + assert result[0] == value + + def test_constructor_unsigned_dtype_overflow(self, any_unsigned_int_numpy_dtype): + # see gh-15832 + if np_version_gt2: + msg = ( + f"The elements provided in the data cannot " + f"all be casted to the dtype {any_unsigned_int_numpy_dtype}" + ) + else: + msg = "Trying to coerce negative values to unsigned integers" + with pytest.raises(OverflowError, match=msg): + Series([-1], dtype=any_unsigned_int_numpy_dtype) + + def test_constructor_floating_data_int_dtype(self, frame_or_series): + # GH#40110 + arr = np.random.default_rng(2).standard_normal(2) + + # Long-standing behavior (for Series, new in 2.0 for DataFrame) + # has been to ignore the dtype on these; + # not clear if this is what we want long-term + # expected = frame_or_series(arr) + + # GH#49599 as of 2.0 we raise instead of silently retaining float dtype + msg = "Trying to coerce float values to integer" + with pytest.raises(ValueError, match=msg): + frame_or_series(arr, dtype="i8") + + with pytest.raises(ValueError, match=msg): + frame_or_series(list(arr), dtype="i8") + + # pre-2.0, when we had NaNs, we silently ignored the integer dtype + arr[0] = np.nan + # expected = frame_or_series(arr) + + msg = r"Cannot convert non-finite values \(NA or inf\) to integer" + with pytest.raises(IntCastingNaNError, match=msg): + frame_or_series(arr, dtype="i8") + + exc = IntCastingNaNError + if frame_or_series is Series: + # TODO: try to align these + exc = ValueError + msg = "cannot convert float NaN to integer" + with pytest.raises(exc, match=msg): + # same behavior if we pass list instead of the ndarray + frame_or_series(list(arr), dtype="i8") + + # float array that can be losslessly cast to integers + arr = np.array([1.0, 2.0], dtype="float64") + expected = frame_or_series(arr.astype("i8")) + + obj = frame_or_series(arr, dtype="i8") + tm.assert_equal(obj, expected) + + obj = frame_or_series(list(arr), dtype="i8") + tm.assert_equal(obj, expected) + + def test_constructor_coerce_float_fail(self, any_int_numpy_dtype): + # see gh-15832 + # Updated: make sure we treat this list the same as we would treat + # the equivalent ndarray + # GH#49599 pre-2.0 we silently retained float dtype, in 2.0 we raise + vals = [1, 2, 3.5] + + msg = "Trying to coerce float values to integer" + with pytest.raises(ValueError, match=msg): + Series(vals, dtype=any_int_numpy_dtype) + with pytest.raises(ValueError, match=msg): + Series(np.array(vals), dtype=any_int_numpy_dtype) + + def test_constructor_coerce_float_valid(self, float_numpy_dtype): + s = Series([1, 2, 3.5], dtype=float_numpy_dtype) + expected = Series([1, 2, 3.5]).astype(float_numpy_dtype) + tm.assert_series_equal(s, expected) + + def test_constructor_invalid_coerce_ints_with_float_nan(self, any_int_numpy_dtype): + # GH 22585 + # Updated: make sure we treat this list the same as we would treat the + # equivalent ndarray + vals = [1, 2, np.nan] + # pre-2.0 this would return with a float dtype, in 2.0 we raise + + msg = "cannot convert float NaN to integer" + with pytest.raises(ValueError, match=msg): + Series(vals, dtype=any_int_numpy_dtype) + msg = r"Cannot convert non-finite values \(NA or inf\) to integer" + with pytest.raises(IntCastingNaNError, match=msg): + Series(np.array(vals), dtype=any_int_numpy_dtype) + + def test_constructor_dtype_no_cast(self): + # see gh-1572 + s = Series([1, 2, 3]) + s2 = Series(s, dtype=np.int64) + + s2[1] = 5 + assert s[1] == 2 + + def test_constructor_datelike_coercion(self): + # GH 9477 + # incorrectly inferring on dateimelike looking when object dtype is + # specified + s = Series([Timestamp("20130101"), "NOV"], dtype=object) + assert s.iloc[0] == Timestamp("20130101") + assert s.iloc[1] == "NOV" + assert s.dtype == object + + def test_constructor_datelike_coercion2(self): + # the dtype was being reset on the slicing and re-inferred to datetime + # even thought the blocks are mixed + belly = "216 3T19".split() + wing1 = "2T15 4H19".split() + wing2 = "416 4T20".split() + mat = pd.to_datetime("2016-01-22 2019-09-07".split()) + df = DataFrame({"wing1": wing1, "wing2": wing2, "mat": mat}, index=belly) + + result = df.loc["3T19"] + assert result.dtype == object + result = df.loc["216"] + assert result.dtype == object + + def test_constructor_mixed_int_and_timestamp(self, frame_or_series): + # specifically Timestamp with nanos, not datetimes + objs = [Timestamp(9), 10, NaT._value] + result = frame_or_series(objs, dtype="M8[ns]") + + expected = frame_or_series([Timestamp(9), Timestamp(10), NaT]) + tm.assert_equal(result, expected) + + def test_constructor_datetimes_with_nulls(self): + # gh-15869 + for arr in [ + np.array([None, None, None, None, datetime.now(), None]), + np.array([None, None, datetime.now(), None]), + ]: + result = Series(arr) + assert result.dtype == "M8[us]" + + def test_constructor_dtype_datetime64(self): + s = Series(iNaT, dtype="M8[ns]", index=range(5)) + assert isna(s).all() + + # in theory this should be all nulls, but since + # we are not specifying a dtype is ambiguous + s = Series(iNaT, index=range(5)) + assert not isna(s).all() + + s = Series(np.nan, dtype="M8[ns]", index=range(5)) + assert isna(s).all() + + s = Series([datetime(2001, 1, 2, 0, 0), iNaT], dtype="M8[ns]") + assert isna(s[1]) + assert s.dtype == "M8[ns]" + + s = Series([datetime(2001, 1, 2, 0, 0), np.nan], dtype="M8[ns]") + assert isna(s[1]) + assert s.dtype == "M8[ns]" + + def test_constructor_dtype_datetime64_10(self): + # GH3416 + pydates = [datetime(2013, 1, 1), datetime(2013, 1, 2), datetime(2013, 1, 3)] + dates = [np.datetime64(x) for x in pydates] + + ser = Series(dates) + assert ser.dtype == "M8[us]" + + ser.iloc[0] = np.nan + assert ser.dtype == "M8[us]" + + # GH3414 related + expected = Series(pydates, dtype="datetime64[ms]") + + result = Series(Series(dates).astype(np.int64) / 1000, dtype="M8[ms]") + tm.assert_series_equal(result, expected) + + result = Series(dates, dtype="datetime64[ms]") + tm.assert_series_equal(result, expected) + + expected = Series( + [NaT, datetime(2013, 1, 2), datetime(2013, 1, 3)], dtype="datetime64[ns]" + ) + result = Series([np.nan, *dates[1:]], dtype="datetime64[ns]") + tm.assert_series_equal(result, expected) + + def test_constructor_dtype_datetime64_11(self): + pydates = [datetime(2013, 1, 1), datetime(2013, 1, 2), datetime(2013, 1, 3)] + dates = [np.datetime64(x) for x in pydates] + + dts = Series(dates, dtype="datetime64[ns]") + + # valid astype + dts.astype("int64") + + # invalid casting + msg = r"Converting from datetime64\[ns\] to int32 is not supported" + with pytest.raises(TypeError, match=msg): + dts.astype("int32") + + # ints are ok + # we test with np.int64 to get similar results on + # windows / 32-bit platforms + result = Series(dts, dtype=np.int64) + expected = Series(dts.astype(np.int64)) + tm.assert_series_equal(result, expected) + + def test_constructor_dtype_datetime64_9(self): + # invalid dates can be help as object + result = Series([datetime(2, 1, 1)]) + assert result[0] == datetime(2, 1, 1, 0, 0) + + result = Series([datetime(3000, 1, 1)]) + assert result[0] == datetime(3000, 1, 1, 0, 0) + + def test_constructor_dtype_datetime64_8(self): + # don't mix types + result = Series([Timestamp("20130101"), 1], index=["a", "b"]) + assert result["a"] == Timestamp("20130101") + assert result["b"] == 1 + + def test_constructor_dtype_datetime64_7(self): + # GH6529 + # coerce datetime64 non-ns properly + dates = date_range("01-Jan-2015", "01-Dec-2015", freq="ME") + values2 = dates.view(np.ndarray).astype("datetime64[ns]") + expected = Series(values2, index=dates) + + for unit in ["s", "D", "ms", "us", "ns"]: + dtype = np.dtype(f"M8[{unit}]") + values1 = dates.view(np.ndarray).astype(dtype) + result = Series(values1, dates) + if unit == "D": + # for unit="D" we cast to nearest-supported reso, i.e. "s" + dtype = np.dtype("M8[s]") + assert result.dtype == dtype + tm.assert_series_equal(result, expected.astype(dtype)) + + # GH 13876 + # coerce to non-ns to object properly + expected = Series(values2, index=dates, dtype=object) + for dtype in ["s", "D", "ms", "us", "ns"]: + values1 = dates.view(np.ndarray).astype(f"M8[{dtype}]") + result = Series(values1, index=dates, dtype=object) + tm.assert_series_equal(result, expected) + + # leave datetime.date alone + dates2 = np.array([d.date() for d in dates.to_pydatetime()], dtype=object) + series1 = Series(dates2, dates) + tm.assert_numpy_array_equal(series1.values, dates2) + assert series1.dtype == object + + def test_constructor_dtype_datetime64_6(self): + # as of 2.0, these no longer infer datetime64 based on the strings, + # matching the Index behavior + + ser = Series([None, NaT, "2013-08-05 15:30:00.000001"]) + assert ser.dtype == object + + ser = Series([np.nan, NaT, "2013-08-05 15:30:00.000001"]) + assert ser.dtype == object + + ser = Series([NaT, None, "2013-08-05 15:30:00.000001"]) + assert ser.dtype == object + + ser = Series([NaT, np.nan, "2013-08-05 15:30:00.000001"]) + assert ser.dtype == object + + def test_constructor_dtype_datetime64_5(self): + # tz-aware (UTC and other tz's) + # GH 8411 + dr = date_range("20130101", periods=3) + assert Series(dr).iloc[0].tz is None + dr = date_range("20130101", periods=3, tz="UTC") + assert str(Series(dr).iloc[0].tz) == "UTC" + dr = date_range("20130101", periods=3, tz="US/Eastern") + assert str(Series(dr).iloc[0].tz) == "US/Eastern" + + def test_constructor_dtype_datetime64_4(self): + # non-convertible + ser = Series([1479596223000, -1479590, NaT]) + assert ser.dtype == "object" + assert ser[2] is NaT + assert "NaT" in str(ser) + + def test_constructor_dtype_datetime64_3(self): + # if we passed a NaT it remains + ser = Series([datetime(2010, 1, 1), datetime(2, 1, 1), NaT]) + assert ser.dtype == "M8[us]" + assert ser[2] is NaT + assert "NaT" in str(ser) + + def test_constructor_dtype_datetime64_2(self): + # if we passed a nan it remains + ser = Series([datetime(2010, 1, 1), datetime(2, 1, 1), np.nan]) + assert ser.dtype == "M8[us]" + assert ser[2] is NaT + assert "NaT" in str(ser) + + def test_constructor_with_datetime_tz(self): + # 8260 + # support datetime64 with tz + + dr = date_range("20130101", periods=3, tz="US/Eastern", unit="ns") + s = Series(dr) + assert s.dtype.name == "datetime64[ns, US/Eastern]" + assert s.dtype == "datetime64[ns, US/Eastern]" + assert isinstance(s.dtype, DatetimeTZDtype) + assert "datetime64[ns, US/Eastern]" in str(s) + + # export + result = s.values + assert isinstance(result, np.ndarray) + assert result.dtype == "datetime64[ns]" + + exp = DatetimeIndex(result) + exp = exp.tz_localize("UTC").tz_convert(tz=s.dt.tz) + tm.assert_index_equal(dr, exp) + + # indexing + result = s.iloc[0] + assert result == Timestamp("2013-01-01 00:00:00-0500", tz="US/Eastern") + result = s[0] + assert result == Timestamp("2013-01-01 00:00:00-0500", tz="US/Eastern") + + result = s[Series([True, True, False], index=s.index)] + tm.assert_series_equal(result, s[0:2]) + + result = s.iloc[0:1] + tm.assert_series_equal(result, Series(dr[0:1])) + + # concat + result = pd.concat([s.iloc[0:1], s.iloc[1:]]) + tm.assert_series_equal(result, s) + + # short str + assert "datetime64[ns, US/Eastern]" in str(s) + + # formatting with NaT + result = s.shift() + assert "datetime64[ns, US/Eastern]" in str(result) + assert "NaT" in str(result) + + result = DatetimeIndex(s, freq="infer") + tm.assert_index_equal(result, dr) + + def test_constructor_with_datetime_tz5(self): + # long str + ser = Series(date_range("20130101", periods=1000, tz="US/Eastern", unit="ns")) + assert "datetime64[ns, US/Eastern]" in str(ser) + + def test_constructor_with_datetime_tz4(self): + # inference + ser = Series( + [ + Timestamp("2013-01-01 13:00:00-0800", tz="US/Pacific").as_unit("s"), + Timestamp("2013-01-02 14:00:00-0800", tz="US/Pacific").as_unit("s"), + ] + ) + assert ser.dtype == "datetime64[s, US/Pacific]" + assert lib.infer_dtype(ser, skipna=True) == "datetime64" + + def test_constructor_with_datetime_tz3(self): + ser = Series( + [ + Timestamp("2013-01-01 13:00:00-0800", tz="US/Pacific"), + Timestamp("2013-01-02 14:00:00-0800", tz="US/Eastern"), + ] + ) + assert ser.dtype == "object" + assert lib.infer_dtype(ser, skipna=True) == "datetime" + + def test_constructor_with_datetime_tz2(self): + # with all NaT + ser = Series(NaT, index=[0, 1], dtype="datetime64[ns, US/Eastern]") + dti = DatetimeIndex(["NaT", "NaT"], tz="US/Eastern").as_unit("ns") + expected = Series(dti) + tm.assert_series_equal(ser, expected) + + def test_constructor_no_partial_datetime_casting(self): + # GH#40111 + vals = [ + "nan", + Timestamp("1990-01-01"), + "2015-03-14T16:15:14.123-08:00", + "2019-03-04T21:56:32.620-07:00", + None, + ] + ser = Series(vals) + assert all(ser[i] is vals[i] for i in range(len(vals))) + + @pytest.mark.parametrize("arr_dtype", [np.int64, np.float64]) + @pytest.mark.parametrize("kind", ["M", "m"]) + @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s", "h", "m", "D"]) + def test_construction_to_datetimelike_unit(self, arr_dtype, kind, unit): + # tests all units + # gh-19223 + # TODO: GH#19223 was about .astype, doesn't belong here + dtype = f"{kind}8[{unit}]" + arr = np.array([1, 2, 3], dtype=arr_dtype) + ser = Series(arr) + result = ser.astype(dtype) + + expected = Series(arr.astype(dtype)) + + if unit in ["ns", "us", "ms", "s"]: + assert result.dtype == dtype + assert expected.dtype == dtype + else: + # Otherwise we cast to nearest-supported unit, i.e. seconds + assert result.dtype == f"{kind}8[s]" + assert expected.dtype == f"{kind}8[s]" + + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("arg", ["2013-01-01 00:00:00", NaT, np.nan, None]) + def test_constructor_with_naive_string_and_datetimetz_dtype(self, arg): + # GH 17415: With naive string + result = Series([arg], dtype="datetime64[ns, CET]") + expected = Series([Timestamp(arg)], dtype="M8[ns]").dt.tz_localize("CET") + tm.assert_series_equal(result, expected) + + def test_constructor_datetime64_bigendian(self): + # GH#30976 + ms = np.datetime64(1, "ms") + arr = np.array([np.datetime64(1, "ms")], dtype=">M8[ms]") + + result = Series(arr) + expected = Series([Timestamp(ms)]).astype("M8[ms]") + assert expected.dtype == "M8[ms]" + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("interval_constructor", [IntervalIndex, IntervalArray]) + def test_construction_interval(self, interval_constructor): + # construction from interval & array of intervals + intervals = interval_constructor.from_breaks(np.arange(3), closed="right") + result = Series(intervals) + assert result.dtype == "interval[int64, right]" + tm.assert_index_equal(Index(result.values), Index(intervals)) + + @pytest.mark.parametrize( + "data_constructor", [list, np.array], ids=["list", "ndarray[object]"] + ) + def test_constructor_infer_interval(self, data_constructor): + # GH 23563: consistent closed results in interval dtype + data = [Interval(0, 1), Interval(0, 2), None] + result = Series(data_constructor(data)) + expected = Series(IntervalArray(data)) + assert result.dtype == "interval[float64, right]" + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "data_constructor", [list, np.array], ids=["list", "ndarray[object]"] + ) + def test_constructor_interval_mixed_closed(self, data_constructor): + # GH 23563: mixed closed results in object dtype (not interval dtype) + data = [Interval(0, 1, closed="both"), Interval(0, 2, closed="neither")] + result = Series(data_constructor(data)) + assert result.dtype == object + assert result.tolist() == data + + def test_construction_consistency(self): + # make sure that we are not re-localizing upon construction + # GH 14928 + ser = Series(date_range("20130101", periods=3, tz="US/Eastern")) + + result = Series(ser, dtype=ser.dtype) + tm.assert_series_equal(result, ser) + + result = Series(ser.dt.tz_convert("UTC"), dtype=ser.dtype) + tm.assert_series_equal(result, ser) + + # Pre-2.0 dt64 values were treated as utc, which was inconsistent + # with DatetimeIndex, which treats them as wall times, see GH#33401 + result = Series(ser.values, dtype=ser.dtype) + expected = Series(ser.values).dt.tz_localize(ser.dtype.tz) + tm.assert_series_equal(result, expected) + + with tm.assert_produces_warning(None): + # one suggested alternative to the deprecated (changed in 2.0) usage + middle = Series(ser.values).dt.tz_localize("UTC") + result = middle.dt.tz_convert(ser.dtype.tz) + tm.assert_series_equal(result, ser) + + with tm.assert_produces_warning(None): + # the other suggested alternative to the deprecated usage + result = Series(ser.values.view("int64"), dtype=ser.dtype) + tm.assert_series_equal(result, ser) + + @pytest.mark.parametrize( + "data_constructor", [list, np.array], ids=["list", "ndarray[object]"] + ) + def test_constructor_infer_period(self, data_constructor): + data = [Period("2000", "D"), Period("2001", "D"), None] + result = Series(data_constructor(data)) + expected = Series(period_array(data)) + tm.assert_series_equal(result, expected) + assert result.dtype == "Period[D]" + + @pytest.mark.xfail(reason="PeriodDtype Series not supported yet") + def test_construct_from_ints_including_iNaT_scalar_period_dtype(self): + series = Series([0, 1000, 2000, pd._libs.iNaT], dtype="period[D]") + + val = series[3] + assert isna(val) + + series[2] = val + assert isna(series[2]) + + def test_constructor_period_incompatible_frequency(self): + data = [Period("2000", "D"), Period("2001", "Y")] + result = Series(data) + assert result.dtype == object + assert result.tolist() == data + + def test_constructor_periodindex(self): + # GH7932 + # converting a PeriodIndex when put in a Series + + pi = period_range("20130101", periods=5, freq="D") + s = Series(pi) + assert s.dtype == "Period[D]" + expected = Series(pi.astype(object)) + assert expected.dtype == object + + def test_constructor_dict(self): + d = {"a": 0.0, "b": 1.0, "c": 2.0} + + result = Series(d) + expected = Series(d, index=sorted(d.keys())) + tm.assert_series_equal(result, expected) + + result = Series(d, index=["b", "c", "d", "a"]) + expected = Series([1, 2, np.nan, 0], index=["b", "c", "d", "a"]) + tm.assert_series_equal(result, expected) + + pidx = period_range("2020-01-01", periods=10, freq="D") + d = {pidx[0]: 0, pidx[1]: 1} + result = Series(d, index=pidx) + expected = Series(np.nan, pidx, dtype=np.float64) + expected.iloc[0] = 0 + expected.iloc[1] = 1 + tm.assert_series_equal(result, expected) + + def test_constructor_dict_list_value_explicit_dtype(self): + # GH 18625 + d = {"a": [[2], [3], [4]]} + result = Series(d, index=["a"], dtype="object") + expected = Series(d, index=["a"]) + tm.assert_series_equal(result, expected) + + def test_constructor_dict_order(self): + # GH19018 + # initialization ordering: by insertion order + d = {"b": 1, "a": 0, "c": 2} + result = Series(d) + expected = Series([1, 0, 2], index=list("bac")) + tm.assert_series_equal(result, expected) + + def test_constructor_dict_extension(self, ea_scalar_and_dtype): + ea_scalar, ea_dtype = ea_scalar_and_dtype + d = {"a": ea_scalar} + result = Series(d, index=["a"]) + expected = Series(ea_scalar, index=["a"], dtype=ea_dtype) + + assert result.dtype == ea_dtype + + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("value", [2, np.nan, None, float("nan")]) + def test_constructor_dict_nan_key(self, value): + # GH 18480 + d = {1: "a", value: "b", float("nan"): "c", 4: "d"} + result = Series(d).sort_values() + expected = Series(["a", "b", "c", "d"], index=[1, value, np.nan, 4]) + tm.assert_series_equal(result, expected) + + # MultiIndex: + d = {(1, 1): "a", (2, np.nan): "b", (3, value): "c"} + result = Series(d).sort_values() + expected = Series( + ["a", "b", "c"], index=Index([(1, 1), (2, np.nan), (3, value)]) + ) + tm.assert_series_equal(result, expected) + + def test_constructor_dict_datetime64_index(self): + # GH 9456 + + dates_as_str = ["1984-02-19", "1988-11-06", "1989-12-03", "1990-03-15"] + values = [42544017.198965244, 1234565, 40512335.181958228, -1] + + def create_data(constructor): + return dict(zip((constructor(x) for x in dates_as_str), values)) + + data_datetime64 = create_data(np.datetime64) + data_datetime = create_data(lambda x: datetime.strptime(x, "%Y-%m-%d")) + data_Timestamp = create_data(Timestamp) + + expected = Series(values, (Timestamp(x) for x in dates_as_str)) + + result_datetime64 = Series(data_datetime64) + result_datetime = Series(data_datetime) + result_Timestamp = Series(data_Timestamp) + + tm.assert_series_equal( + result_datetime64, expected.set_axis(expected.index.as_unit("s")) + ) + tm.assert_series_equal(result_datetime, expected) + tm.assert_series_equal(result_Timestamp, expected) + + def test_constructor_dict_tuple_indexer(self): + # GH 12948 + data = {(1, 1, None): -1.0} + result = Series(data) + expected = Series( + -1.0, index=MultiIndex(levels=[[1], [1], [np.nan]], codes=[[0], [0], [-1]]) + ) + tm.assert_series_equal(result, expected) + + def test_constructor_mapping(self, non_dict_mapping_subclass): + # GH 29788 + ndm = non_dict_mapping_subclass({3: "three"}) + result = Series(ndm) + expected = Series(["three"], index=[3]) + + tm.assert_series_equal(result, expected) + + def test_constructor_list_of_tuples(self): + data = [(1, 1), (2, 2), (2, 3)] + s = Series(data) + assert list(s) == data + + def test_constructor_tuple_of_tuples(self): + data = ((1, 1), (2, 2), (2, 3)) + s = Series(data) + assert tuple(s) == data + + @pytest.mark.parametrize( + "data, expected_values, expected_index", + [ + ({(1, 2): 3, (None, 5): 6}, [3, 6], [(1, 2), (None, 5)]), + ({(1,): 3, (4, 5): 6}, [3, 6], [(1, None), (4, 5)]), + ], + ) + def test_constructor_dict_of_tuples(self, data, expected_values, expected_index): + # GH 60695 + result = Series(data).sort_values() + expected = Series(expected_values, index=MultiIndex.from_tuples(expected_index)) + tm.assert_series_equal(result, expected) + + # https://github.com/pandas-dev/pandas/issues/22698 + @pytest.mark.filterwarnings("ignore:elementwise comparison:FutureWarning") + def test_fromDict(self, using_infer_string): + data = {"a": 0, "b": 1, "c": 2, "d": 3} + + series = Series(data) + tm.assert_is_sorted(series.index) + + data = {"a": 0, "b": "1", "c": "2", "d": datetime.now()} + series = Series(data) + assert series.dtype == np.object_ + + data = {"a": 0, "b": "1", "c": "2", "d": "3"} + series = Series(data) + assert series.dtype == np.object_ if not using_infer_string else "str" + + data = {"a": "0", "b": "1"} + series = Series(data, dtype=float) + assert series.dtype == np.float64 + + def test_fromValue(self, datetime_series, using_infer_string): + nans = Series(np.nan, index=datetime_series.index, dtype=np.float64) + assert nans.dtype == np.float64 + assert len(nans) == len(datetime_series) + + strings = Series("foo", index=datetime_series.index) + assert strings.dtype == np.object_ if not using_infer_string else "str" + assert len(strings) == len(datetime_series) + + d = datetime.now() + dates = Series(d, index=datetime_series.index) + assert dates.dtype == "M8[us]" + assert len(dates) == len(datetime_series) + + # GH12336 + # Test construction of categorical series from value + categorical = Series(0, index=datetime_series.index, dtype="category") + expected = Series(0, index=datetime_series.index).astype("category") + assert categorical.dtype == "category" + assert len(categorical) == len(datetime_series) + tm.assert_series_equal(categorical, expected) + + def test_constructor_dtype_timedelta64(self): + # basic + td = Series([timedelta(days=i) for i in range(3)]) + assert td.dtype == "timedelta64[us]" + + td = Series([timedelta(days=1)]) + assert td.dtype == "timedelta64[us]" + + td = Series([timedelta(days=1), timedelta(days=2), np.timedelta64(1, "s")]) + + assert td.dtype == "timedelta64[us]" + + # mixed with NaT + td = Series([timedelta(days=1), NaT], dtype="m8[ns]") + assert td.dtype == "timedelta64[ns]" + + td = Series([timedelta(days=1), np.nan], dtype="m8[ns]") + assert td.dtype == "timedelta64[ns]" + + td = Series([np.timedelta64(300000000, "ns"), NaT], dtype="m8[ns]") + assert td.dtype == "timedelta64[ns]" + + # improved inference + # GH5689 + td = Series([np.timedelta64(300000000, "ns"), NaT]) + assert td.dtype == "timedelta64[ns]" + + # because iNaT is int, not coerced to timedelta + td = Series([np.timedelta64(300000000, "ns"), iNaT]) + assert td.dtype == "object" + + td = Series([np.timedelta64(300000000, "ns"), np.nan]) + assert td.dtype == "timedelta64[ns]" + + td = Series([NaT, np.timedelta64(300000000, "ns")]) + assert td.dtype == "timedelta64[ns]" + + td = Series([np.timedelta64(1, "s")]) + assert td.dtype == "timedelta64[s]" + + # valid astype + td.astype("int64") + + # invalid casting + msg = r"Converting from timedelta64\[s\] to int32 is not supported" + with pytest.raises(TypeError, match=msg): + td.astype("int32") + + # this is an invalid casting + msg = "|".join( + [ + "Could not convert object to NumPy timedelta", + "Could not convert 'foo' to NumPy timedelta", + ] + ) + with pytest.raises(ValueError, match=msg): + Series([timedelta(days=1), "foo"], dtype="m8[ns]") + + # leave as object here + td = Series([timedelta(days=i) for i in range(3)] + ["foo"]) + assert td.dtype == "object" + + # as of 2.0, these no longer infer timedelta64 based on the strings, + # matching Index behavior + ser = Series([None, NaT, "1 Day"]) + assert ser.dtype == object + + ser = Series([np.nan, NaT, "1 Day"]) + assert ser.dtype == object + + ser = Series([NaT, None, "1 Day"]) + assert ser.dtype == object + + ser = Series([NaT, np.nan, "1 Day"]) + assert ser.dtype == object + + # GH 16406 + def test_constructor_mixed_tz(self): + s = Series([Timestamp("20130101"), Timestamp("20130101", tz="US/Eastern")]) + expected = Series( + [Timestamp("20130101"), Timestamp("20130101", tz="US/Eastern")], + dtype="object", + ) + tm.assert_series_equal(s, expected) + + def test_NaT_scalar(self): + series = Series([0, 1000, 2000, iNaT], dtype="M8[ns]") + + val = series[3] + assert isna(val) + + series[2] = val + assert isna(series[2]) + + def test_NaT_cast(self): + # GH10747 + result = Series([np.nan]).astype("M8[ns]") + expected = Series([NaT], dtype="M8[ns]") + tm.assert_series_equal(result, expected) + + def test_constructor_name_hashable(self): + for n in [777, 777.0, "name", datetime(2001, 11, 11), (1,), "\u05d0"]: + for data in [[1, 2, 3], np.ones(3), {"a": 0, "b": 1}]: + s = Series(data, name=n) + assert s.name == n + + def test_constructor_name_unhashable(self): + msg = r"Series\.name must be a hashable type" + for n in [["name_list"], np.ones(2), {1: 2}]: + for data in [["name_list"], np.ones(2), {1: 2}]: + with pytest.raises(TypeError, match=msg): + Series(data, name=n) + + def test_auto_conversion(self): + series = Series(list(date_range("1/1/2000", periods=10, unit="ns"))) + assert series.dtype == "M8[ns]" + + def test_convert_non_ns(self): + # convert from a numpy array of non-ns timedelta64 + arr = np.array([1, 2, 3], dtype="timedelta64[s]") + ser = Series(arr) + assert ser.dtype == arr.dtype + + tdi = timedelta_range("00:00:01", periods=3, freq="s").as_unit("s") + expected = Series(tdi) + assert expected.dtype == arr.dtype + tm.assert_series_equal(ser, expected) + + # convert from a numpy array of non-ns datetime64 + arr = np.array( + ["2013-01-01", "2013-01-02", "2013-01-03"], dtype="datetime64[D]" + ) + ser = Series(arr) + expected = Series(date_range("20130101", periods=3, freq="D"), dtype="M8[s]") + assert expected.dtype == "M8[s]" + tm.assert_series_equal(ser, expected) + + arr = np.array( + ["2013-01-01 00:00:01", "2013-01-01 00:00:02", "2013-01-01 00:00:03"], + dtype="datetime64[s]", + ) + ser = Series(arr) + expected = Series( + date_range("20130101 00:00:01", periods=3, freq="s"), dtype="M8[s]" + ) + assert expected.dtype == "M8[s]" + tm.assert_series_equal(ser, expected) + + @pytest.mark.parametrize( + "index", + [ + date_range("1/1/2000", periods=10), + timedelta_range("1 day", periods=10), + period_range("2000-Q1", periods=10, freq="Q"), + ], + ids=lambda x: type(x).__name__, + ) + def test_constructor_cant_cast_datetimelike(self, index): + # floats are not ok + # strip Index to convert PeriodIndex -> Period + # We don't care whether the error message says + # PeriodIndex or PeriodArray + msg = f"Cannot cast {type(index).__name__.rstrip('Index')}.*? to " + + with pytest.raises(TypeError, match=msg): + Series(index, dtype=float) + + # ints are ok + # we test with np.int64 to get similar results on + # windows / 32-bit platforms + result = Series(index, dtype=np.int64) + expected = Series(index.astype(np.int64)) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "index", + [ + date_range("1/1/2000", periods=10), + timedelta_range("1 day", periods=10), + period_range("2000-Q1", periods=10, freq="Q"), + ], + ids=lambda x: type(x).__name__, + ) + def test_constructor_cast_object(self, index): + s = Series(index, dtype=object) + exp = Series(index).astype(object) + tm.assert_series_equal(s, exp) + + s = Series(Index(index, dtype=object), dtype=object) + exp = Series(index).astype(object) + tm.assert_series_equal(s, exp) + + s = Series(index.astype(object), dtype=object) + exp = Series(index).astype(object) + tm.assert_series_equal(s, exp) + + @pytest.mark.parametrize("dtype", [np.datetime64, np.timedelta64]) + def test_constructor_generic_timestamp_no_frequency(self, dtype, request): + # see gh-15524, gh-15987 + msg = "dtype has no unit. Please pass in" + + if np.dtype(dtype).name not in ["timedelta64", "datetime64"]: + mark = pytest.mark.xfail(reason="GH#33890 Is assigned ns unit") + request.applymarker(mark) + + with pytest.raises(ValueError, match=msg): + Series([], dtype=dtype) + + @pytest.mark.parametrize("unit", ["ps", "as", "fs", "Y", "M", "W", "D", "h", "m"]) + @pytest.mark.parametrize("kind", ["m", "M"]) + def test_constructor_generic_timestamp_bad_frequency(self, kind, unit): + # see gh-15524, gh-15987 + # as of 2.0 we raise on any non-supported unit rather than silently + # cast to nanos; previously we only raised for frequencies higher + # than ns + dtype = f"{kind}8[{unit}]" + + msg = "dtype=.* is not supported. Supported resolutions are" + with pytest.raises(TypeError, match=msg): + Series([], dtype=dtype) + + with pytest.raises(TypeError, match=msg): + # pre-2.0 the DataFrame cast raised but the Series case did not + DataFrame([[0]], dtype=dtype) + + @pytest.mark.parametrize("dtype", [None, "uint8", "category"]) + def test_constructor_range_dtype(self, dtype): + # GH 16804 + expected = Series([0, 1, 2, 3, 4], dtype=dtype or "int64") + result = Series(range(5), dtype=dtype) + tm.assert_series_equal(result, expected) + + def test_constructor_range_overflows(self): + # GH#30173 range objects that overflow int64 + rng = range(2**63, 2**63 + 4) + ser = Series(rng) + expected = Series(list(rng)) + tm.assert_series_equal(ser, expected) + assert list(ser) == list(rng) + assert ser.dtype == np.uint64 + + rng2 = range(2**63 + 4, 2**63, -1) + ser2 = Series(rng2) + expected2 = Series(list(rng2)) + tm.assert_series_equal(ser2, expected2) + assert list(ser2) == list(rng2) + assert ser2.dtype == np.uint64 + + rng3 = range(-(2**63), -(2**63) - 4, -1) + ser3 = Series(rng3) + expected3 = Series(list(rng3)) + tm.assert_series_equal(ser3, expected3) + assert list(ser3) == list(rng3) + assert ser3.dtype == object + + rng4 = range(2**73, 2**73 + 4) + ser4 = Series(rng4) + expected4 = Series(list(rng4)) + tm.assert_series_equal(ser4, expected4) + assert list(ser4) == list(rng4) + assert ser4.dtype == object + + def test_constructor_tz_mixed_data(self): + # GH 13051 + dt_list = [ + Timestamp("2016-05-01 02:03:37"), + Timestamp("2016-04-30 19:03:37-0700", tz="US/Pacific"), + ] + result = Series(dt_list) + expected = Series(dt_list, dtype=object) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("pydt", [True, False]) + def test_constructor_data_aware_dtype_naive(self, tz_aware_fixture, pydt): + # GH#25843, GH#41555, GH#33401 + tz = tz_aware_fixture + ts = Timestamp("2019", tz=tz) + if pydt: + ts = ts.to_pydatetime() + + msg = ( + "Cannot convert timezone-aware data to timezone-naive dtype. " + r"Use pd.Series\(values\).dt.tz_localize\(None\) instead." + ) + with pytest.raises(ValueError, match=msg): + Series([ts], dtype="datetime64[ns]") + + with pytest.raises(ValueError, match=msg): + Series(np.array([ts], dtype=object), dtype="datetime64[ns]") + + with pytest.raises(ValueError, match=msg): + Series({0: ts}, dtype="datetime64[ns]") + + msg = "Cannot unbox tzaware Timestamp to tznaive dtype" + with pytest.raises(TypeError, match=msg): + Series(ts, index=[0], dtype="datetime64[ns]") + + def test_constructor_datetime64(self): + rng = date_range("1/1/2000 00:00:00", "1/1/2000 1:59:50", freq="10s") + dates = np.asarray(rng) + + series = Series(dates) + assert np.issubdtype(series.dtype, np.dtype("M8[ns]")) + + def test_constructor_datetimelike_scalar_to_string_dtype( + self, nullable_string_dtype + ): + # https://github.com/pandas-dev/pandas/pull/33846 + result = Series("M", index=[1, 2, 3], dtype=nullable_string_dtype) + expected = Series(["M", "M", "M"], index=[1, 2, 3], dtype=nullable_string_dtype) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("box", [lambda x: x, np.datetime64]) + def test_constructor_sparse_datetime64(self, box): + # https://github.com/pandas-dev/pandas/issues/35762 + values = [box("2012-01-01"), box("2013-01-01")] + dtype = pd.SparseDtype("datetime64[ns]") + result = Series(values, dtype=dtype) + arr = pd.arrays.SparseArray(values, dtype=dtype) + expected = Series(arr) + tm.assert_series_equal(result, expected) + + def test_construction_from_ordered_collection(self): + # https://github.com/pandas-dev/pandas/issues/36044 + result = Series({"a": 1, "b": 2}.keys()) + expected = Series(["a", "b"]) + tm.assert_series_equal(result, expected) + + result = Series({"a": 1, "b": 2}.values()) + expected = Series([1, 2]) + tm.assert_series_equal(result, expected) + + def test_construction_from_large_int_scalar_no_overflow(self): + # https://github.com/pandas-dev/pandas/issues/36291 + n = 1_000_000_000_000_000_000_000 + result = Series(n, index=[0]) + expected = Series(n) + tm.assert_series_equal(result, expected) + + def test_constructor_list_of_periods_infers_period_dtype(self): + series = Series(list(period_range("2000-01-01", periods=10, freq="D"))) + assert series.dtype == "Period[D]" + + series = Series( + [Period("2011-01-01", freq="D"), Period("2011-02-01", freq="D")] + ) + assert series.dtype == "Period[D]" + + def test_constructor_subclass_dict(self, dict_subclass): + data = dict_subclass((x, 10.0 * x) for x in range(10)) + series = Series(data) + expected = Series(dict(data.items())) + tm.assert_series_equal(series, expected) + + def test_constructor_ordereddict(self): + # GH3283 + data = OrderedDict( + (f"col{i}", np.random.default_rng(2).random()) for i in range(12) + ) + + series = Series(data) + expected = Series(list(data.values()), list(data.keys())) + tm.assert_series_equal(series, expected) + + # Test with subclass + class A(OrderedDict): + pass + + series = Series(A(data)) + tm.assert_series_equal(series, expected) + + @pytest.mark.parametrize( + "data, expected_index_multi", + [ + ({("a", "a"): 0.0, ("b", "a"): 1.0, ("b", "c"): 2.0}, True), + ({("a",): 0.0, ("a", "b"): 1.0}, True), + ({"z": 111.0, ("a", "a"): 0.0, ("b", "a"): 1.0, ("b", "c"): 2.0}, False), + ], + ) + def test_constructor_dict_multiindex(self, data, expected_index_multi): + # GH#60695 + result = Series(data) + + if expected_index_multi: + expected = Series( + list(data.values()), + index=MultiIndex.from_tuples(list(data.keys())), + ) + tm.assert_series_equal(result, expected) + else: + expected = Series( + list(data.values()), + index=Index(list(data.keys())), + ) + tm.assert_series_equal(result, expected) + + def test_constructor_dict_multiindex_reindex_flat(self): + # construction involves reindexing with a MultiIndex corner case + data = {("i", "i"): 0, ("i", "j"): 1, ("j", "i"): 2, "j": np.nan} + expected = Series(data) + + result = Series(expected[:-1].to_dict(), index=expected.index) + tm.assert_series_equal(result, expected) + + def test_constructor_dict_timedelta_index(self): + # GH #12169 : Resample category data with timedelta index + # construct Series from dict as data and TimedeltaIndex as index + # will result NaN in result Series data + expected = Series( + data=["A", "B", "C"], index=pd.to_timedelta([0, 10, 20], unit="s") + ) + + result = Series( + data={ + pd.to_timedelta(0, unit="s"): "A", + pd.to_timedelta(10, unit="s"): "B", + pd.to_timedelta(20, unit="s"): "C", + }, + index=pd.to_timedelta([0, 10, 20], unit="s"), + ) + tm.assert_series_equal(result, expected) + + def test_constructor_infer_index_tz(self): + values = [188.5, 328.25] + tzinfo = tzoffset(None, 7200) + index = [ + datetime(2012, 5, 11, 11, tzinfo=tzinfo), + datetime(2012, 5, 11, 12, tzinfo=tzinfo), + ] + series = Series(data=values, index=index) + + assert series.index.tz == tzinfo + + # it works! GH#2443 + repr(series.index[0]) + + def test_constructor_with_pandas_dtype(self): + # going through 2D->1D path + vals = [(1,), (2,), (3,)] + ser = Series(vals) + dtype = ser.array.dtype # NumpyEADtype + ser2 = Series(vals, dtype=dtype) + tm.assert_series_equal(ser, ser2) + + def test_constructor_int_dtype_missing_values(self): + # GH#43017 + result = Series(index=[0], dtype="int64") + expected = Series(np.nan, index=[0], dtype="float64") + tm.assert_series_equal(result, expected) + + def test_constructor_bool_dtype_missing_values(self): + # GH#43018 + result = Series(index=[0], dtype="bool") + expected = Series(True, index=[0], dtype="bool") + tm.assert_series_equal(result, expected) + + def test_constructor_int64_dtype(self, any_int_dtype): + # GH#44923 + result = Series(["0", "1", "2"], dtype=any_int_dtype) + expected = Series([0, 1, 2], dtype=any_int_dtype) + tm.assert_series_equal(result, expected) + + def test_constructor_raise_on_lossy_conversion_of_strings(self): + # GH#44923 + if not np_version_gt2: + raises = pytest.raises( + ValueError, match="string values cannot be losslessly cast to int8" + ) + else: + raises = pytest.raises( + OverflowError, match="The elements provided in the data" + ) + with raises: + Series(["128"], dtype="int8") + + def test_constructor_dtype_timedelta_alternative_construct(self): + # GH#35465 + result = Series([1000000, 200000, 3000000], dtype="timedelta64[us]") + expected = Series(pd.to_timedelta([1000000, 200000, 3000000], unit="us")) + tm.assert_series_equal(result, expected) + + def test_constructor_dtype_timedelta_ns_s_astype_int64(self): + # GH#35465 + result = Series([1000000, 200000, 3000000], dtype="timedelta64[ns]").astype( + "int64" + ) + expected = Series([1000000, 200000, 3000000], dtype="timedelta64[s]").astype( + "int64" + ) + tm.assert_series_equal(result, expected) + + @pytest.mark.filterwarnings( + "ignore:elementwise comparison failed:DeprecationWarning" + ) + @pytest.mark.parametrize("func", [Series, DataFrame, Index, pd.array]) + def test_constructor_mismatched_null_nullable_dtype( + self, func, any_numeric_ea_dtype + ): + # GH#44514 + msg = "|".join( + [ + "cannot safely cast non-equivalent object", + r"int\(\) argument must be a string, a bytes-like object " + "or a (real )?number", + r"Cannot cast array data from dtype\('O'\) to dtype\('float64'\) " + "according to the rule 'safe'", + "object cannot be converted to a FloatingDtype", + "'values' contains non-numeric NA", + ] + ) + + for null in [*tm.NP_NAT_OBJECTS, NaT]: + with pytest.raises(TypeError, match=msg): + func([null, 1.0, 3.0], dtype=any_numeric_ea_dtype) + + def test_series_constructor_ea_int_from_bool(self): + # GH#42137 + result = Series([True, False, True, pd.NA], dtype="Int64") + expected = Series([1, 0, 1, pd.NA], dtype="Int64") + tm.assert_series_equal(result, expected) + + result = Series([True, False, True], dtype="Int64") + expected = Series([1, 0, 1], dtype="Int64") + tm.assert_series_equal(result, expected) + + def test_series_constructor_ea_int_from_string_bool(self): + # GH#42137 + with pytest.raises(ValueError, match="invalid literal"): + Series(["True", "False", "True", pd.NA], dtype="Int64") + + @pytest.mark.parametrize("val", [1, 1.0]) + def test_series_constructor_overflow_uint_ea(self, val): + # GH#38798 + max_val = np.iinfo(np.uint64).max - 1 + result = Series([max_val, val], dtype="UInt64") + expected = Series(np.array([max_val, 1], dtype="uint64"), dtype="UInt64") + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("val", [1, 1.0]) + def test_series_constructor_overflow_uint_ea_with_na(self, val): + # GH#38798 + max_val = np.iinfo(np.uint64).max - 1 + result = Series([max_val, val, pd.NA], dtype="UInt64") + expected = Series( + IntegerArray( + np.array([max_val, 1, 0], dtype="uint64"), + np.array([0, 0, 1], dtype=np.bool_), + ) + ) + tm.assert_series_equal(result, expected) + + def test_series_constructor_overflow_uint_with_nan(self): + # GH#38798 + max_val = np.iinfo(np.uint64).max - 1 + result = Series([max_val, pd.NA], dtype="UInt64") + expected = Series( + IntegerArray( + np.array([max_val, 1], dtype="uint64"), + np.array([0, 1], dtype=np.bool_), + ) + ) + tm.assert_series_equal(result, expected) + + def test_series_constructor_ea_all_na(self): + # GH#38798 + result = Series([pd.NA, pd.NA], dtype="UInt64") + expected = Series( + IntegerArray( + np.array([1, 1], dtype="uint64"), + np.array([1, 1], dtype=np.bool_), + ) + ) + tm.assert_series_equal(result, expected) + + def test_series_from_index_dtype_equal_does_not_copy(self): + # GH#52008 + idx = Index([1, 2, 3]) + expected = idx.copy(deep=True) + ser = Series(idx, dtype="int64") + ser.iloc[0] = 100 + tm.assert_index_equal(idx, expected) + + def test_series_string_inference(self): + # GH#54430 + with pd.option_context("future.infer_string", True): + ser = Series(["a", "b"]) + dtype = pd.StringDtype("pyarrow" if HAS_PYARROW else "python", na_value=np.nan) + expected = Series(["a", "b"], dtype=dtype) + tm.assert_series_equal(ser, expected) + + expected = Series(["a", 1], dtype="object") + with pd.option_context("future.infer_string", True): + ser = Series(["a", 1]) + tm.assert_series_equal(ser, expected) + + @pytest.mark.parametrize("na_value", [None, np.nan, pd.NA]) + def test_series_string_with_na_inference(self, na_value): + # GH#54430 + with pd.option_context("future.infer_string", True): + ser = Series(["a", na_value]) + dtype = pd.StringDtype("pyarrow" if HAS_PYARROW else "python", na_value=np.nan) + expected = Series(["a", None], dtype=dtype) + tm.assert_series_equal(ser, expected) + + def test_series_string_inference_scalar(self): + # GH#54430 + with pd.option_context("future.infer_string", True): + ser = Series("a", index=[1]) + dtype = pd.StringDtype("pyarrow" if HAS_PYARROW else "python", na_value=np.nan) + expected = Series("a", index=[1], dtype=dtype) + tm.assert_series_equal(ser, expected) + + def test_series_string_inference_array_string_dtype(self): + # GH#54496 + with pd.option_context("future.infer_string", True): + ser = Series(np.array(["a", "b"])) + dtype = pd.StringDtype("pyarrow" if HAS_PYARROW else "python", na_value=np.nan) + expected = Series(["a", "b"], dtype=dtype) + tm.assert_series_equal(ser, expected) + + def test_series_string_inference_storage_definition(self): + # https://github.com/pandas-dev/pandas/issues/54793 + # but after PDEP-14 (string dtype), it was decided to keep dtype="string" + # returning the NA string dtype, so expected is changed from + # "string[pyarrow_numpy]" to "string[python]" + expected = Series( + ["a", "b"], dtype="string[pyarrow]" if HAS_PYARROW else "string[python]" + ) + with pd.option_context("future.infer_string", True): + result = Series(["a", "b"], dtype="string") + tm.assert_series_equal(result, expected) + + expected = Series(["a", "b"], dtype=pd.StringDtype(na_value=np.nan)) + with pd.option_context("future.infer_string", True): + result = Series(["a", "b"], dtype="str") + tm.assert_series_equal(result, expected) + + def test_series_constructor_infer_string_scalar(self): + # GH#55537 + with pd.option_context("future.infer_string", True): + ser = Series("a", index=[1, 2], dtype="string[python]") + expected = Series(["a", "a"], index=[1, 2], dtype="string[python]") + tm.assert_series_equal(ser, expected) + assert ser.dtype.storage == "python" + + def test_series_string_inference_na_first(self): + # GH#55655 + with pd.option_context("future.infer_string", True): + result = Series([pd.NA, "b"]) + dtype = pd.StringDtype("pyarrow" if HAS_PYARROW else "python", na_value=np.nan) + expected = Series([None, "b"], dtype=dtype) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize("klass", [Series, Index]) + def test_inference_on_pandas_objects(self, klass): + # GH#56012 + obj = klass([Timestamp("2019-12-31")], dtype=object) + # This doesn't do inference + result = Series(obj) + assert result.dtype == np.object_ + + +class TestSeriesConstructorIndexCoercion: + def test_series_constructor_datetimelike_index_coercion(self): + idx = date_range("2020-01-01", periods=5) + ser = Series( + np.random.default_rng(2).standard_normal(len(idx)), idx.astype(object) + ) + # as of 2.0, we no longer silently cast the object-dtype index + # to DatetimeIndex GH#39307, GH#23598 + assert not isinstance(ser.index, DatetimeIndex) + + @pytest.mark.parametrize("container", [None, np.array, Series, Index]) + @pytest.mark.parametrize("data", [1.0, range(4)]) + def test_series_constructor_infer_multiindex(self, container, data): + indexes = [["a", "a", "b", "b"], ["x", "y", "x", "y"]] + if container is not None: + indexes = [container(ind) for ind in indexes] + + multi = Series(data, index=indexes) + assert isinstance(multi.index, MultiIndex) + + # TODO: make this not cast to object in pandas 3.0 + @pytest.mark.skipif( + not np_version_gt2, reason="StringDType only available in numpy 2 and above" + ) + @pytest.mark.parametrize( + "data", + [ + ["a", "b", "c"], + ["a", "b", np.nan], + ], + ) + def test_np_string_array_object_cast(self, data): + from numpy.dtypes import StringDType + + arr = np.array(data, dtype=StringDType()) + res = Series(arr) + assert res.dtype == np.object_ + + if data[-1] is np.nan: + # as of GH#62522 the comparison op for `res==data` casts data + # using sanitize_array, which casts to 'str' dtype, which does not + # consider string 'nan' to be equal to np.nan, + # (which apparently numpy does? weird.) + assert (res.iloc[:-1] == data[:-1]).all() + assert res.iloc[-1] == "nan" + else: + assert (res == data).all() + + +class TestSeriesConstructorInternals: + def test_constructor_no_pandas_array(self): + ser = Series([1, 2, 3]) + result = Series(ser.array) + tm.assert_series_equal(ser, result) + assert isinstance(result._mgr.blocks[0], NumpyBlock) + assert result._mgr.blocks[0].is_numeric + + def test_from_array(self): + result = Series(pd.array(["1h", "2h"], dtype="timedelta64[ns]")) + assert result._mgr.blocks[0].is_extension is False + + result = Series(pd.array(["2015"], dtype="datetime64[ns]")) + assert result._mgr.blocks[0].is_extension is False + + def test_from_list_dtype(self): + result = Series(["1h", "2h"], dtype="timedelta64[ns]") + assert result._mgr.blocks[0].is_extension is False + + result = Series(["2015"], dtype="datetime64[ns]") + assert result._mgr.blocks[0].is_extension is False + + +def test_constructor(rand_series_with_duplicate_datetimeindex): + dups = rand_series_with_duplicate_datetimeindex + assert isinstance(dups, Series) + assert isinstance(dups.index, DatetimeIndex) + + +@pytest.mark.parametrize( + "input_dict,expected", + [ + ({0: 0}, np.array([[0]], dtype=np.int64)), + ({"a": "a"}, np.array([["a"]], dtype=object)), + ({1: 1}, np.array([[1]], dtype=np.int64)), + ], +) +def test_numpy_array(input_dict, expected): + result = np.array([Series(input_dict)]) + tm.assert_numpy_array_equal(result, expected) + + +def test_index_ordered_dict_keys(): + # GH 22077 + + param_index = OrderedDict( + [ + ((("a", "b"), ("c", "d")), 1), + ((("a", None), ("c", "d")), 2), + ] + ) + series = Series([1, 2], index=param_index.keys()) + expected = Series( + [1, 2], + index=MultiIndex.from_tuples( + [(("a", "b"), ("c", "d")), (("a", None), ("c", "d"))] + ), + ) + tm.assert_series_equal(series, expected) + + +@pytest.mark.parametrize( + "input_list", + [ + [1, complex("nan"), 2], + [1 + 1j, complex("nan"), 2 + 2j], + ], +) +def test_series_with_complex_nan(input_list): + # GH#53627 + ser = Series(input_list) + result = Series(ser.array) + assert ser.dtype == "complex128" + tm.assert_series_equal(ser, result) + + +def test_dict_keys_rangeindex(): + result = Series({0: 1, 1: 2}) + expected = Series([1, 2], index=RangeIndex(2)) + tm.assert_series_equal(result, expected, check_index_type=True) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/test_cumulative.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/test_cumulative.py new file mode 100644 index 0000000000000000000000000000000000000000..db83cf1112e7452df3328dd52b2ebe8a6232161c --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/test_cumulative.py @@ -0,0 +1,284 @@ +""" +Tests for Series cumulative operations. + +See also +-------- +tests.frame.test_cumulative +""" + +import re + +import numpy as np +import pytest + +import pandas as pd +import pandas._testing as tm + +methods = { + "cumsum": np.cumsum, + "cumprod": np.cumprod, + "cummin": np.minimum.accumulate, + "cummax": np.maximum.accumulate, +} + + +class TestSeriesCumulativeOps: + @pytest.mark.parametrize("func", [np.cumsum, np.cumprod]) + def test_datetime_series(self, datetime_series, func): + tm.assert_numpy_array_equal( + func(datetime_series).values, + func(np.array(datetime_series)), + check_dtype=True, + ) + + # with missing values + ts = datetime_series.copy() + ts[::2] = np.nan + + result = func(ts)[1::2] + expected = func(np.array(ts.dropna())) + + tm.assert_numpy_array_equal(result.values, expected, check_dtype=False) + + @pytest.mark.parametrize("method", ["cummin", "cummax"]) + def test_cummin_cummax(self, datetime_series, method): + ufunc = methods[method] + + result = getattr(datetime_series, method)().values + expected = ufunc(np.array(datetime_series)) + + tm.assert_numpy_array_equal(result, expected) + ts = datetime_series.copy() + ts[::2] = np.nan + result = getattr(ts, method)()[1::2] + expected = ufunc(ts.dropna()) + + result.index = result.index._with_freq(None) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "ts", + [ + pd.Timedelta(0), + pd.Timestamp("1999-12-31"), + pd.Timestamp("1999-12-31").tz_localize("US/Pacific"), + ], + ) + @pytest.mark.parametrize( + "method, skipna, exp_tdi", + [ + ["cummax", True, ["NaT", "2 days", "NaT", "2 days", "NaT", "3 days"]], + ["cummin", True, ["NaT", "2 days", "NaT", "1 days", "NaT", "1 days"]], + [ + "cummax", + False, + ["NaT", "NaT", "NaT", "NaT", "NaT", "NaT"], + ], + [ + "cummin", + False, + ["NaT", "NaT", "NaT", "NaT", "NaT", "NaT"], + ], + ], + ) + def test_cummin_cummax_datetimelike(self, ts, method, skipna, exp_tdi): + # with ts==pd.Timedelta(0), we are testing td64; with naive Timestamp + # we are testing datetime64[ns]; with Timestamp[US/Pacific] + # we are testing dt64tz + tdi = pd.to_timedelta(["NaT", "2 days", "NaT", "1 days", "NaT", "3 days"]) + ser = pd.Series(tdi + ts) + + exp_tdi = pd.to_timedelta(exp_tdi) + expected = pd.Series(exp_tdi + ts) + result = getattr(ser, method)(skipna=skipna) + tm.assert_series_equal(expected, result) + + def test_cumsum_datetimelike(self): + # GH#57956 + df = pd.DataFrame( + [ + [pd.Timedelta(0), pd.Timedelta(days=1)], + [pd.Timedelta(days=2), pd.NaT], + [pd.Timedelta(hours=-6), pd.Timedelta(hours=12)], + ] + ) + result = df.cumsum() + expected = pd.DataFrame( + [ + [pd.Timedelta(0), pd.Timedelta(days=1)], + [pd.Timedelta(days=2), pd.NaT], + [pd.Timedelta(days=1, hours=18), pd.Timedelta(days=1, hours=12)], + ] + ) + tm.assert_frame_equal(result, expected) + + @pytest.mark.parametrize( + "func, exp", + [ + ("cummin", "2012-1-1"), + ("cummax", "2012-1-2"), + ], + ) + def test_cummin_cummax_period(self, func, exp): + # GH#28385 + ser = pd.Series( + [pd.Period("2012-1-1", freq="D"), pd.NaT, pd.Period("2012-1-2", freq="D")] + ) + result = getattr(ser, func)(skipna=False) + expected = pd.Series([pd.Period("2012-1-1", freq="D"), pd.NaT, pd.NaT]) + tm.assert_series_equal(result, expected) + + result = getattr(ser, func)(skipna=True) + exp = pd.Period(exp, freq="D") + expected = pd.Series([pd.Period("2012-1-1", freq="D"), pd.NaT, exp]) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "arg", + [ + [False, False, False, True, True, False, False], + [False, False, False, False, False, False, False], + ], + ) + @pytest.mark.parametrize( + "func", [lambda x: x, lambda x: ~x], ids=["identity", "inverse"] + ) + @pytest.mark.parametrize("method", methods.keys()) + def test_cummethods_bool(self, arg, func, method): + # GH#6270 + # checking Series method vs the ufunc applied to the values + + ser = func(pd.Series(arg)) + ufunc = methods[method] + + exp_vals = ufunc(ser.values) + expected = pd.Series(exp_vals) + + result = getattr(ser, method)() + + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "method, expected", + [ + ["cumsum", pd.Series([0, 1, np.nan, 1], dtype=object)], + ["cumprod", pd.Series([False, 0, np.nan, 0])], + ["cummin", pd.Series([False, False, np.nan, False])], + ["cummax", pd.Series([False, True, np.nan, True])], + ], + ) + def test_cummethods_bool_in_object_dtype(self, method, expected): + ser = pd.Series([False, True, np.nan, False]) + result = getattr(ser, method)() + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "method, order", + [ + ["cummax", "abc"], + ["cummin", "cba"], + ], + ) + def test_cummax_cummin_on_ordered_categorical(self, method, order): + # GH#52335 + cat = pd.CategoricalDtype(list(order), ordered=True) + ser = pd.Series( + list("ababcab"), + dtype=cat, + ) + result = getattr(ser, method)() + expected = pd.Series( + list("abbbccc"), + dtype=cat, + ) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "skip, exp", + [ + [True, ["a", np.nan, "b", "b", "c"]], + [False, ["a", np.nan, np.nan, np.nan, np.nan]], + ], + ) + @pytest.mark.parametrize( + "method, order", + [ + ["cummax", "abc"], + ["cummin", "cba"], + ], + ) + def test_cummax_cummin_ordered_categorical_nan(self, skip, exp, method, order): + # GH#52335 + cat = pd.CategoricalDtype(list(order), ordered=True) + ser = pd.Series( + ["a", np.nan, "b", "a", "c"], + dtype=cat, + ) + result = getattr(ser, method)(skipna=skip) + expected = pd.Series( + exp, + dtype=cat, + ) + tm.assert_series_equal( + result, + expected, + ) + + def test_cumprod_timedelta(self): + # GH#48111 + ser = pd.Series([pd.Timedelta(days=1), pd.Timedelta(days=3)]) + with pytest.raises(TypeError, match="cumprod not supported for Timedelta"): + ser.cumprod() + + @pytest.mark.parametrize( + "data, op, skipna, expected_data", + [ + ([], "cumsum", True, []), + ([], "cumsum", False, []), + (["x", "z", "y"], "cumsum", True, ["x", "xz", "xzy"]), + (["x", "z", "y"], "cumsum", False, ["x", "xz", "xzy"]), + (["x", pd.NA, "y"], "cumsum", True, ["x", pd.NA, "xy"]), + (["x", pd.NA, "y"], "cumsum", False, ["x", pd.NA, pd.NA]), + ([pd.NA, "x", "y"], "cumsum", True, [pd.NA, "x", "xy"]), + ([pd.NA, "x", "y"], "cumsum", False, [pd.NA, pd.NA, pd.NA]), + ([pd.NA, pd.NA, pd.NA], "cumsum", True, [pd.NA, pd.NA, pd.NA]), + ([pd.NA, pd.NA, pd.NA], "cumsum", False, [pd.NA, pd.NA, pd.NA]), + ([], "cummin", True, []), + ([], "cummin", False, []), + (["y", "z", "x"], "cummin", True, ["y", "y", "x"]), + (["y", "z", "x"], "cummin", False, ["y", "y", "x"]), + (["y", pd.NA, "x"], "cummin", True, ["y", pd.NA, "x"]), + (["y", pd.NA, "x"], "cummin", False, ["y", pd.NA, pd.NA]), + ([pd.NA, "y", "x"], "cummin", True, [pd.NA, "y", "x"]), + ([pd.NA, "y", "x"], "cummin", False, [pd.NA, pd.NA, pd.NA]), + ([pd.NA, pd.NA, pd.NA], "cummin", True, [pd.NA, pd.NA, pd.NA]), + ([pd.NA, pd.NA, pd.NA], "cummin", False, [pd.NA, pd.NA, pd.NA]), + ([], "cummax", True, []), + ([], "cummax", False, []), + (["x", "z", "y"], "cummax", True, ["x", "z", "z"]), + (["x", "z", "y"], "cummax", False, ["x", "z", "z"]), + (["x", pd.NA, "y"], "cummax", True, ["x", pd.NA, "y"]), + (["x", pd.NA, "y"], "cummax", False, ["x", pd.NA, pd.NA]), + ([pd.NA, "x", "y"], "cummax", True, [pd.NA, "x", "y"]), + ([pd.NA, "x", "y"], "cummax", False, [pd.NA, pd.NA, pd.NA]), + ([pd.NA, pd.NA, pd.NA], "cummax", True, [pd.NA, pd.NA, pd.NA]), + ([pd.NA, pd.NA, pd.NA], "cummax", False, [pd.NA, pd.NA, pd.NA]), + ], + ) + def test_cum_methods_ea_strings( + self, string_dtype_no_object, data, op, skipna, expected_data + ): + # https://github.com/pandas-dev/pandas/pull/60633 - pyarrow + # https://github.com/pandas-dev/pandas/pull/60938 - Python + ser = pd.Series(data, dtype=string_dtype_no_object) + method = getattr(ser, op) + expected = pd.Series(expected_data, dtype=string_dtype_no_object) + result = method(skipna=skipna) + tm.assert_series_equal(result, expected) + + def test_cumprod_pyarrow_strings(self, pyarrow_string_dtype, skipna): + # https://github.com/pandas-dev/pandas/pull/60633 + ser = pd.Series(list("xyz"), dtype=pyarrow_string_dtype) + msg = re.escape(f"operation 'cumprod' not supported for dtype '{ser.dtype}'") + with pytest.raises(TypeError, match=msg): + ser.cumprod(skipna=skipna) diff --git a/.venv/lib/python3.13/site-packages/pandas/tests/series/test_formats.py b/.venv/lib/python3.13/site-packages/pandas/tests/series/test_formats.py new file mode 100644 index 0000000000000000000000000000000000000000..76c8914e60b76c71a39604c9fde1f8e731fc7e8e --- /dev/null +++ b/.venv/lib/python3.13/site-packages/pandas/tests/series/test_formats.py @@ -0,0 +1,592 @@ +from datetime import ( + datetime, + timedelta, +) + +import numpy as np +import pytest + +import pandas as pd +from pandas import ( + Categorical, + DataFrame, + Index, + Series, + date_range, + option_context, + period_range, + timedelta_range, +) + + +class TestSeriesRepr: + def test_multilevel_name_print_0(self): + # GH#55415 None does not get printed, but 0 does + # (matching DataFrame and flat index behavior) + mi = pd.MultiIndex.from_product([range(2, 3), range(3, 4)], names=[0, None]) + ser = Series(1.5, index=mi) + + res = repr(ser) + expected = "0 \n2 3 1.5\ndtype: float64" + assert res == expected + + def test_multilevel_name_print(self, lexsorted_two_level_string_multiindex): + index = lexsorted_two_level_string_multiindex + ser = Series(range(len(index)), index=index, name="sth") + expected = [ + "first second", + "foo one 0", + " two 1", + " three 2", + "bar one 3", + " two 4", + "baz two 5", + " three 6", + "qux one 7", + " two 8", + " three 9", + "Name: sth, dtype: int64", + ] + expected = "\n".join(expected) + assert repr(ser) == expected + + def test_small_name_printing(self): + # Test small Series. + s = Series([0, 1, 2]) + + s.name = "test" + assert "Name: test" in repr(s) + + s.name = None + assert "Name:" not in repr(s) + + def test_big_name_printing(self): + # Test big Series (diff code path). + s = Series(range(1000)) + + s.name = "test" + assert "Name: test" in repr(s) + + s.name = None + assert "Name:" not in repr(s) + + def test_empty_name_printing(self): + s = Series(index=date_range("20010101", "20020101"), name="test", dtype=object) + assert "Name: test" in repr(s) + + @pytest.mark.parametrize("args", [(), (0, -1)]) + def test_float_range(self, args): + str( + Series( + np.random.default_rng(2).standard_normal(1000), + index=np.arange(1000, *args), + ) + ) + + def test_empty_object(self): + # empty + str(Series(dtype=object)) + + def test_string(self, string_series): + str(string_series) + str(string_series.astype(int)) + + # with NaNs + string_series[5:7] = np.nan + str(string_series) + + def test_object(self, object_series): + str(object_series) + + def test_datetime(self, datetime_series): + str(datetime_series) + # with Nones + ots = datetime_series.astype("O") + ots[::2] = None + repr(ots) + + @pytest.mark.parametrize( + "name", + [ + "", + 1, + 1.2, + "foo", + "\u03b1\u03b2\u03b3", + "loooooooooooooooooooooooooooooooooooooooooooooooooooong", + ("foo", "bar", "baz"), + (1, 2), + ("foo", 1, 2.3), + ("\u03b1", "\u03b2", "\u03b3"), + ("\u03b1", "bar"), + ], + ) + def test_various_names(self, name, string_series): + # various names + string_series.name = name + repr(string_series) + + def test_tuple_name(self): + biggie = Series( + np.random.default_rng(2).standard_normal(1000), + index=np.arange(1000), + name=("foo", "bar", "baz"), + ) + repr(biggie) + + @pytest.mark.parametrize("arg", [100, 1001]) + def test_tidy_repr_name_0(self, arg): + # tidy repr + ser = Series(np.random.default_rng(2).standard_normal(arg), name=0) + rep_str = repr(ser) + assert "Name: 0" in rep_str + + def test_newline(self, any_string_dtype): + ser = Series( + ["a\n\r\tb"], + name="a\n\r\td", + index=Index(["a\n\r\tf"], dtype=any_string_dtype), + dtype=any_string_dtype, + ) + assert "\t" not in repr(ser) + assert "\r" not in repr(ser) + assert "a\n" not in repr(ser) + + @pytest.mark.parametrize( + "name, expected", + [ + ["foo", "Series([], Name: foo, dtype: int64)"], + [None, "Series([], dtype: int64)"], + ], + ) + def test_empty_int64(self, name, expected): + # with empty series (#4651) + s = Series([], dtype=np.int64, name=name) + assert repr(s) == expected + + def test_repr_bool_fails(self, capsys): + s = Series( + [ + DataFrame(np.random.default_rng(2).standard_normal((2, 2))) + for i in range(5) + ] + ) + + # It works (with no Cython exception barf)! + repr(s) + + captured = capsys.readouterr() + assert captured.err == "" + + def test_repr_name_iterable_indexable(self): + s = Series([1, 2, 3], name=np.int64(3)) + + # it works! + repr(s) + + s.name = ("\u05d0",) * 2 + repr(s) + + def test_repr_max_rows(self): + # GH 6863 + with option_context("display.max_rows", None): + str(Series(range(1001))) # should not raise exception + + def test_unicode_string_with_unicode(self): + df = Series(["\u05d0"], name="\u05d1") + str(df) + + ser = Series(["\u03c3"] * 10) + repr(ser) + + ser2 = Series(["\u05d0"] * 1000) + ser2.name = "title1" + repr(ser2) + + def test_str_to_bytes_raises(self): + # GH 26447 + df = Series(["abc"], name="abc") + msg = "^'str' object cannot be interpreted as an integer$" + with pytest.raises(TypeError, match=msg): + bytes(df) + + def test_timeseries_repr_object_dtype(self): + index = Index( + [datetime(2000, 1, 1) + timedelta(i) for i in range(1000)], dtype=object + ) + ts = Series(np.random.default_rng(2).standard_normal(len(index)), index) + repr(ts) + + ts = Series( + np.arange(20, dtype=np.float64), index=date_range("2020-01-01", periods=20) + ) + assert repr(ts).splitlines()[-1].startswith("Freq:") + + ts2 = ts.iloc[np.random.default_rng(2).integers(0, len(ts) - 1, 400)] + repr(ts2).splitlines()[-1] + + def test_latex_repr(self): + pytest.importorskip("jinja2") # uses Styler implementation + result = r"""\begin{tabular}{ll} +\toprule + & 0 \\ +\midrule +0 & $\alpha$ \\ +1 & b \\ +2 & c \\ +\bottomrule +\end{tabular} +""" + with option_context( + "styler.format.escape", None, "styler.render.repr", "latex" + ): + s = Series([r"$\alpha$", "b", "c"]) + assert result == s._repr_latex_() + + assert s._repr_latex_() is None + + def test_index_repr_in_frame_with_nan(self): + # see gh-25061 + i = Index([1, np.nan]) + s = Series([1, 2], index=i) + exp = """1.0 1\nNaN 2\ndtype: int64""" + + assert repr(s) == exp + + def test_series_repr_nat(self): + series = Series([0, 1000, 2000, pd.NaT._value], dtype="M8[ns]") + + result = repr(series) + expected = ( + "0 1970-01-01 00:00:00.000000\n" + "1 1970-01-01 00:00:00.000001\n" + "2 1970-01-01 00:00:00.000002\n" + "3 NaT\n" + "dtype: datetime64[ns]" + ) + assert result == expected + + def test_float_repr(self): + # GH#35603 + # check float format when cast to object + ser = Series([1.0]).astype(object) + expected = "0 1.0\ndtype: object" + assert repr(ser) == expected + + def test_different_null_objects(self): + # GH#45263 + ser = Series([1, 2, 3, 4], [True, None, np.nan, pd.NaT]) + result = repr(ser) + expected = "True 1\nNone 2\nNaN 3\nNaT 4\ndtype: int64" + assert result == expected + + def test_2d_extension_type(self): + # GH#33770 + + # Define a stub extension type with just enough code to run Series.__repr__() + class DtypeStub(pd.api.extensions.ExtensionDtype): + @property + def type(self): + return np.ndarray + + @property + def name(self): + return "DtypeStub" + + class ExtTypeStub(pd.api.extensions.ExtensionArray): + def __len__(self) -> int: + return 2 + + def __getitem__(self, ix): + return [ix == 1, ix == 0] + + @property + def dtype(self): + return DtypeStub() + + series = Series(ExtTypeStub(), copy=False) + res = repr(series) # This line crashed before GH#33770 was fixed. + expected = "\n".join( + ["0 [False True]", "1 [True False]", "dtype: DtypeStub"] + ) + assert res == expected + + +class TestCategoricalRepr: + def test_categorical_repr_unicode(self): + # see gh-21002 + + class County: + name = "San Sebastián" + state = "PR" + + def __repr__(self) -> str: + return self.name + ", " + self.state + + cat = Categorical([County() for _ in range(61)]) + idx = Index(cat) + ser = idx.to_series() + + repr(ser) + str(ser) + + def test_categorical_repr(self, using_infer_string): + a = Series(Categorical([1, 2, 3, 4])) + exp = ( + "0 1\n1 2\n2 3\n3 4\n" + "dtype: category\nCategories (4, int64): [1, 2, 3, 4]" + ) + + assert exp == a.__str__() + + a = Series(Categorical(["a", "b"] * 25)) + exp = ( + "0 a\n1 b\n" + " ..\n" + "48 a\n49 b\n" + "Length: 50, dtype: category\nCategories (2, object): ['a', 'b']" + ) + if using_infer_string: + exp = exp.replace("object", "str") + with option_context("display.max_rows", 5): + assert exp == repr(a) + + levs = list("abcdefghijklmnopqrstuvwxyz") + a = Series(Categorical(["a", "b"], categories=levs, ordered=True)) + exp = ( + "0 a\n1 b\n" + "dtype: category\n" + "Categories (26, object): ['a' < 'b' < 'c' < 'd' ... " + "'w' < 'x' < 'y' < 'z']" + ) + if using_infer_string: + exp = exp.replace("object", "str") + assert exp == a.__str__() + + def test_categorical_series_repr(self): + s = Series(Categorical([1, 2, 3])) + exp = """0 1 +1 2 +2 3 +dtype: category +Categories (3, int64): [1, 2, 3]""" + + assert repr(s) == exp + + s = Series(Categorical(np.arange(10))) + exp = f"""0 0 +1 1 +2 2 +3 3 +4 4 +5 5 +6 6 +7 7 +8 8 +9 9 +dtype: category +Categories (10, {np.dtype(int)}): [0, 1, 2, 3, ..., 6, 7, 8, 9]""" + + assert repr(s) == exp + + def test_categorical_series_repr_ordered(self): + s = Series(Categorical([1, 2, 3], ordered=True)) + exp = """0 1 +1 2 +2 3 +dtype: category +Categories (3, int64): [1 < 2 < 3]""" + + assert repr(s) == exp + + s = Series(Categorical(np.arange(10), ordered=True)) + exp = f"""0 0 +1 1 +2 2 +3 3 +4 4 +5 5 +6 6 +7 7 +8 8 +9 9 +dtype: category +Categories (10, {np.dtype(int)}): [0 < 1 < 2 < 3 ... 6 < 7 < 8 < 9]""" + + assert repr(s) == exp + + def test_categorical_series_repr_datetime(self): + idx = date_range("2011-01-01 09:00", freq="h", periods=5, unit="ns") + s = Series(Categorical(idx)) + exp = """0 2011-01-01 09:00:00 +1 2011-01-01 10:00:00 +2 2011-01-01 11:00:00 +3 2011-01-01 12:00:00 +4 2011-01-01 13:00:00 +dtype: category +Categories (5, datetime64[ns]): [2011-01-01 09:00:00, 2011-01-01 10:00:00, 2011-01-01 11:00:00, + 2011-01-01 12:00:00, 2011-01-01 13:00:00]""" # noqa: E501 + + assert repr(s) == exp + + idx = date_range( + "2011-01-01 09:00", freq="h", periods=5, tz="US/Eastern", unit="ns" + ) + s = Series(Categorical(idx)) + exp = """0 2011-01-01 09:00:00-05:00 +1 2011-01-01 10:00:00-05:00 +2 2011-01-01 11:00:00-05:00 +3 2011-01-01 12:00:00-05:00 +4 2011-01-01 13:00:00-05:00 +dtype: category +Categories (5, datetime64[ns, US/Eastern]): [2011-01-01 09:00:00-05:00, 2011-01-01 10:00:00-05:00, + 2011-01-01 11:00:00-05:00, 2011-01-01 12:00:00-05:00, + 2011-01-01 13:00:00-05:00]""" # noqa: E501 + + assert repr(s) == exp + + def test_categorical_series_repr_datetime_ordered(self): + idx = date_range("2011-01-01 09:00", freq="h", periods=5, unit="ns") + s = Series(Categorical(idx, ordered=True)) + exp = """0 2011-01-01 09:00:00 +1 2011-01-01 10:00:00 +2 2011-01-01 11:00:00 +3 2011-01-01 12:00:00 +4 2011-01-01 13:00:00 +dtype: category +Categories (5, datetime64[ns]): [2011-01-01 09:00:00 < 2011-01-01 10:00:00 < 2011-01-01 11:00:00 < + 2011-01-01 12:00:00 < 2011-01-01 13:00:00]""" # noqa: E501 + + assert repr(s) == exp + + idx = date_range( + "2011-01-01 09:00", freq="h", periods=5, tz="US/Eastern", unit="ns" + ) + s = Series(Categorical(idx, ordered=True)) + exp = """0 2011-01-01 09:00:00-05:00 +1 2011-01-01 10:00:00-05:00 +2 2011-01-01 11:00:00-05:00 +3 2011-01-01 12:00:00-05:00 +4 2011-01-01 13:00:00-05:00 +dtype: category +Categories (5, datetime64[ns, US/Eastern]): [2011-01-01 09:00:00-05:00 < 2011-01-01 10:00:00-05:00 < + 2011-01-01 11:00:00-05:00 < 2011-01-01 12:00:00-05:00 < + 2011-01-01 13:00:00-05:00]""" # noqa: E501 + + assert repr(s) == exp + + def test_categorical_series_repr_period(self): + idx = period_range("2011-01-01 09:00", freq="h", periods=5) + s = Series(Categorical(idx)) + exp = """0 2011-01-01 09:00 +1 2011-01-01 10:00 +2 2011-01-01 11:00 +3 2011-01-01 12:00 +4 2011-01-01 13:00 +dtype: category +Categories (5, period[h]): [2011-01-01 09:00, 2011-01-01 10:00, 2011-01-01 11:00, 2011-01-01 12:00, + 2011-01-01 13:00]""" # noqa: E501 + + assert repr(s) == exp + + idx = period_range("2011-01", freq="M", periods=5) + s = Series(Categorical(idx)) + exp = """0 2011-01 +1 2011-02 +2 2011-03 +3 2011-04 +4 2011-05 +dtype: category +Categories (5, period[M]): [2011-01, 2011-02, 2011-03, 2011-04, 2011-05]""" + + assert repr(s) == exp + + def test_categorical_series_repr_period_ordered(self): + idx = period_range("2011-01-01 09:00", freq="h", periods=5) + s = Series(Categorical(idx, ordered=True)) + exp = """0 2011-01-01 09:00 +1 2011-01-01 10:00 +2 2011-01-01 11:00 +3 2011-01-01 12:00 +4 2011-01-01 13:00 +dtype: category +Categories (5, period[h]): [2011-01-01 09:00 < 2011-01-01 10:00 < 2011-01-01 11:00 < 2011-01-01 12:00 < + 2011-01-01 13:00]""" # noqa: E501 + + assert repr(s) == exp + + idx = period_range("2011-01", freq="M", periods=5) + s = Series(Categorical(idx, ordered=True)) + exp = """0 2011-01 +1 2011-02 +2 2011-03 +3 2011-04 +4 2011-05 +dtype: category +Categories (5, period[M]): [2011-01 < 2011-02 < 2011-03 < 2011-04 < 2011-05]""" + + assert repr(s) == exp + + def test_categorical_series_repr_timedelta(self): + idx = timedelta_range("1 days", periods=5) + s = Series(Categorical(idx)) + exp = """0 1 days +1 2 days +2 3 days +3 4 days +4 5 days +dtype: category +Categories (5, timedelta64[us]): [1 days, 2 days, 3 days, 4 days, 5 days]""" + + assert repr(s) == exp + + idx = timedelta_range("1 hours", periods=10) + s = Series(Categorical(idx)) + exp = """0 0 days 01:00:00 +1 1 days 01:00:00 +2 2 days 01:00:00 +3 3 days 01:00:00 +4 4 days 01:00:00 +5 5 days 01:00:00 +6 6 days 01:00:00 +7 7 days 01:00:00 +8 8 days 01:00:00 +9 9 days 01:00:00 +dtype: category +Categories (10, timedelta64[us]): [0 days 01:00:00, 1 days 01:00:00, 2 days 01:00:00, + 3 days 01:00:00, ..., 6 days 01:00:00, 7 days 01:00:00, + 8 days 01:00:00, 9 days 01:00:00]""" # noqa: E501 + + assert repr(s) == exp + + def test_categorical_series_repr_timedelta_ordered(self): + idx = timedelta_range("1 days", periods=5) + s = Series(Categorical(idx, ordered=True)) + exp = """0 1 days +1 2 days +2 3 days +3 4 days +4 5 days +dtype: category +Categories (5, timedelta64[us]): [1 days < 2 days < 3 days < 4 days < 5 days]""" + + assert repr(s) == exp + + idx = timedelta_range("1 hours", periods=10) + s = Series(Categorical(idx, ordered=True)) + exp = """0 0 days 01:00:00 +1 1 days 01:00:00 +2 2 days 01:00:00 +3 3 days 01:00:00 +4 4 days 01:00:00 +5 5 days 01:00:00 +6 6 days 01:00:00 +7 7 days 01:00:00 +8 8 days 01:00:00 +9 9 days 01:00:00 +dtype: category +Categories (10, timedelta64[us]): [0 days 01:00:00 < 1 days 01:00:00 < 2 days 01:00:00 < + 3 days 01:00:00 ... 6 days 01:00:00 < 7 days 01:00:00 < + 8 days 01:00:00 < 9 days 01:00:00]""" # noqa: E501 + + assert repr(s) == exp