From d93ffd056f3c4b5306562b5766a8ec5feb424686 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Fri, 27 Oct 2023 14:53:40 -0700 Subject: [PATCH 01/27] DEPS: Test NEP 50 --- ci/run_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/run_tests.sh b/ci/run_tests.sh index 6a70ea1df3e71..b3c2b745d7147 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -11,7 +11,7 @@ echo PYTHONHASHSEED=$PYTHONHASHSEED COVERAGE="-s --cov=pandas --cov-report=xml --cov-append --cov-config=pyproject.toml" # TODO: Support NEP 50 and remove NPY_PROMOTION_STATE -PYTEST_CMD="NPY_PROMOTION_STATE=legacy MESONPY_EDITABLE_VERBOSE=1 PYTHONDEVMODE=1 PYTHONWARNDEFAULTENCODING=1 pytest -r fEs -n $PYTEST_WORKERS --dist=loadfile $TEST_ARGS $COVERAGE $PYTEST_TARGET" +PYTEST_CMD="NPY_PROMOTION_STATE=weak MESONPY_EDITABLE_VERBOSE=1 PYTHONDEVMODE=1 PYTHONWARNDEFAULTENCODING=1 pytest -v -r fEs -n 0 --dist=loadfile $TEST_ARGS $COVERAGE $PYTEST_TARGET" if [[ "$PATTERN" ]]; then PYTEST_CMD="$PYTEST_CMD -m \"$PATTERN\"" From 4ec19f50cff712483c2204cfae683b17104d0b01 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Fri, 27 Oct 2023 18:06:40 -0700 Subject: [PATCH 02/27] Use Python floats in test_maybe_promote_float_with_float --- pandas/tests/dtypes/cast/test_promote.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pandas/tests/dtypes/cast/test_promote.py b/pandas/tests/dtypes/cast/test_promote.py index 1becf3b9843b7..021107724bef7 100644 --- a/pandas/tests/dtypes/cast/test_promote.py +++ b/pandas/tests/dtypes/cast/test_promote.py @@ -229,24 +229,24 @@ def test_maybe_promote_float_with_int(float_numpy_dtype, any_int_numpy_dtype): [ # float filled with float ("float32", 1, "float32"), - ("float32", np.finfo("float32").max * 1.1, "float64"), + ("float32", float(np.finfo("float32").max) * 1.1, "float64"), ("float64", 1, "float64"), - ("float64", np.finfo("float32").max * 1.1, "float64"), + ("float64", float(np.finfo("float32").max) * 1.1, "float64"), # complex filled with float ("complex64", 1, "complex64"), - ("complex64", np.finfo("float32").max * 1.1, "complex128"), + ("complex64", float(np.finfo("float32").max) * 1.1, "complex128"), ("complex128", 1, "complex128"), - ("complex128", np.finfo("float32").max * 1.1, "complex128"), + ("complex128", float(np.finfo("float32").max) * 1.1, "complex128"), # float filled with complex ("float32", 1 + 1j, "complex64"), - ("float32", np.finfo("float32").max * (1.1 + 1j), "complex128"), + ("float32", float(np.finfo("float32").max) * (1.1 + 1j), "complex128"), ("float64", 1 + 1j, "complex128"), - ("float64", np.finfo("float32").max * (1.1 + 1j), "complex128"), + ("float64", float(np.finfo("float32").max) * (1.1 + 1j), "complex128"), # complex filled with complex ("complex64", 1 + 1j, "complex64"), - ("complex64", np.finfo("float32").max * (1.1 + 1j), "complex128"), + ("complex64", float(np.finfo("float32").max) * (1.1 + 1j), "complex128"), ("complex128", 1 + 1j, "complex128"), - ("complex128", np.finfo("float32").max * (1.1 + 1j), "complex128"), + ("complex128", float(np.finfo("float32").max) * (1.1 + 1j), "complex128"), ], ) def test_maybe_promote_float_with_float(dtype, fill_value, expected_dtype): From 0dd702c5d4b1cd3297facbbf4f21ab119015a363 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Fri, 27 Oct 2023 18:10:41 -0700 Subject: [PATCH 03/27] Refactor test_to_html_multiindex to allow tests to collect --- pandas/tests/io/formats/test_to_html.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/tests/io/formats/test_to_html.py b/pandas/tests/io/formats/test_to_html.py index 38a2bb52930e3..07cb002b00598 100644 --- a/pandas/tests/io/formats/test_to_html.py +++ b/pandas/tests/io/formats/test_to_html.py @@ -416,15 +416,15 @@ def test_to_html_columns_arg(float_frame): "columns,justify,expected", [ ( - MultiIndex.from_tuples( - list(zip(np.arange(2).repeat(2), np.mod(range(4), 2))), + MultiIndex.from_arrays( + [np.arange(2).repeat(2), np.mod(range(4), 2)], names=["CL0", "CL1"], ), "left", "multiindex_1", ), ( - MultiIndex.from_tuples(list(zip(range(4), np.mod(range(4), 2)))), + MultiIndex.from_arrays([np.arange(4), np.mod(range(4), 2)]), "right", "multiindex_2", ), From b8e3eaa8dbda19b13341462eb09b075e787bc688 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Mon, 30 Oct 2023 10:44:29 -0700 Subject: [PATCH 04/27] Supress deprecationwarning for now --- .github/workflows/unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index d00dfc87a0584..127ae8826eea6 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -80,7 +80,7 @@ jobs: - name: "Numpy Dev" env_file: actions-311-numpydev.yaml pattern: "not slow and not network and not single_cpu" - test_args: "-W error::DeprecationWarning -W error::FutureWarning" + test_args: "-W error::FutureWarning" - name: "Pyarrow Nightly" env_file: actions-311-pyarrownightly.yaml pattern: "not slow and not network and not single_cpu" From 1ce02526bd54f1ae528506f108a6b954eeda1e35 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Mon, 30 Oct 2023 12:01:49 -0700 Subject: [PATCH 05/27] Use old invocation --- ci/run_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/run_tests.sh b/ci/run_tests.sh index b3c2b745d7147..7ce57794bf134 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -11,7 +11,7 @@ echo PYTHONHASHSEED=$PYTHONHASHSEED COVERAGE="-s --cov=pandas --cov-report=xml --cov-append --cov-config=pyproject.toml" # TODO: Support NEP 50 and remove NPY_PROMOTION_STATE -PYTEST_CMD="NPY_PROMOTION_STATE=weak MESONPY_EDITABLE_VERBOSE=1 PYTHONDEVMODE=1 PYTHONWARNDEFAULTENCODING=1 pytest -v -r fEs -n 0 --dist=loadfile $TEST_ARGS $COVERAGE $PYTEST_TARGET" +PYTEST_CMD="NPY_PROMOTION_STATE=weak MESONPY_EDITABLE_VERBOSE=1 PYTHONDEVMODE=1 PYTHONWARNDEFAULTENCODING=1 pytest -r fEs -n $PYTEST_WORKERS --dist=loadfile $TEST_ARGS $COVERAGE $PYTEST_TARGET" if [[ "$PATTERN" ]]; then PYTEST_CMD="$PYTEST_CMD -m \"$PATTERN\"" From a039851b7543fabea1bd63778c771aa8f9e74b74 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:50:21 -0700 Subject: [PATCH 06/27] Use Python ints in _range.py functions --- pandas/core/arrays/_ranges.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pandas/core/arrays/_ranges.py b/pandas/core/arrays/_ranges.py index 5f0409188e5e3..e6fe905a35326 100644 --- a/pandas/core/arrays/_ranges.py +++ b/pandas/core/arrays/_ranges.py @@ -54,8 +54,8 @@ def generate_regular_range( iend = end._value if end is not None else None freq.nanos # raises if non-fixed frequency td = Timedelta(freq) - b: int | np.int64 | np.uint64 - e: int | np.int64 | np.uint64 + b: int + e: int try: td = td.as_unit( # pyright: ignore[reportGeneralTypeIssues] unit, round_ok=False @@ -98,7 +98,7 @@ def generate_regular_range( def _generate_range_overflow_safe( endpoint: int, periods: int, stride: int, side: str = "start" -) -> np.int64 | np.uint64: +) -> int: """ Calculate the second endpoint for passing to np.arange, checking to avoid an integer overflow. Catch OverflowError and re-raise @@ -117,7 +117,7 @@ def _generate_range_overflow_safe( Returns ------- - other_end : np.int64 | np.uint64 + other_end : int Raises ------ @@ -165,7 +165,7 @@ def _generate_range_overflow_safe( def _generate_range_overflow_safe_signed( endpoint: int, periods: int, stride: int, side: str -) -> np.int64 | np.uint64: +) -> int: """ A special case for _generate_range_overflow_safe where `periods * stride` can be calculated without overflowing int64 bounds. @@ -183,7 +183,7 @@ def _generate_range_overflow_safe_signed( # Putting this into a DatetimeArray/TimedeltaArray # would incorrectly be interpreted as NaT raise OverflowError - return result + return int(result) except (FloatingPointError, OverflowError): # with endpoint negative and addend positive we risk # FloatingPointError; with reversed signed we risk OverflowError @@ -202,7 +202,7 @@ def _generate_range_overflow_safe_signed( i64max = np.uint64(i8max) assert uresult > i64max if uresult <= i64max + np.uint64(stride): - return uresult + return int(uresult) raise OutOfBoundsDatetime( f"Cannot generate range with {side}={endpoint} and periods={periods}" From 45d3d51bd160579e8e5e86ba2a414ecd7d3076bd Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:55:30 -0700 Subject: [PATCH 07/27] Address test_constructor --- pandas/tests/indexes/numeric/test_numeric.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pandas/tests/indexes/numeric/test_numeric.py b/pandas/tests/indexes/numeric/test_numeric.py index 944e215ee17bd..f9bf7d89b9cdf 100644 --- a/pandas/tests/indexes/numeric/test_numeric.py +++ b/pandas/tests/indexes/numeric/test_numeric.py @@ -352,11 +352,13 @@ def test_constructor(self, dtype): arr = index.values.copy() new_index = index_cls(arr, copy=True) tm.assert_index_equal(new_index, index, exact=True) - val = arr[0] + 3000 + val = int(arr[0]) + 3000 # this should not change index - arr[0] = val - assert new_index[0] != val + if dtype != np.int8: + # NEP 50 won't allow assignment that would overflow + arr[0] = val + assert new_index[0] != val if dtype == np.int64: # pass list, coerce fine From 7569ea0c5a616a05ee9910c74dab733a886c2ed1 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:57:59 -0700 Subject: [PATCH 08/27] Fix test_constructor_coercion_signed_to_unsigned --- pandas/tests/indexes/numeric/test_numeric.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pandas/tests/indexes/numeric/test_numeric.py b/pandas/tests/indexes/numeric/test_numeric.py index f9bf7d89b9cdf..d559e548713ec 100644 --- a/pandas/tests/indexes/numeric/test_numeric.py +++ b/pandas/tests/indexes/numeric/test_numeric.py @@ -407,8 +407,13 @@ def test_constructor_coercion_signed_to_unsigned( any_unsigned_int_numpy_dtype, ): # see gh-15832 - msg = "Trying to coerce negative values to unsigned integers" - + msg = "|".join( + [ + "Trying to coerce negative values to unsigned integers", + "The elements provided in the data cannot all be casted " + "to the dtype uint8", + ] + ) with pytest.raises(OverflowError, match=msg): Index([-1], dtype=any_unsigned_int_numpy_dtype) From 1f947243386df2b5775026b4af18062cae44265d Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:58:23 -0700 Subject: [PATCH 09/27] Fix test_constructor_coercion_signed_to_unsigned --- pandas/tests/indexes/numeric/test_numeric.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/tests/indexes/numeric/test_numeric.py b/pandas/tests/indexes/numeric/test_numeric.py index d559e548713ec..a16b68a57dcc7 100644 --- a/pandas/tests/indexes/numeric/test_numeric.py +++ b/pandas/tests/indexes/numeric/test_numeric.py @@ -410,8 +410,7 @@ def test_constructor_coercion_signed_to_unsigned( msg = "|".join( [ "Trying to coerce negative values to unsigned integers", - "The elements provided in the data cannot all be casted " - "to the dtype uint8", + "The elements provided in the data cannot all be casted", ] ) with pytest.raises(OverflowError, match=msg): From 6fb901c19040d37d78a8a91cb48ed6f1cd547bd1 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Mon, 30 Oct 2023 15:18:45 -0700 Subject: [PATCH 10/27] Cast numpy scalars as python scalars before arith ops --- pandas/core/ops/array_ops.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pandas/core/ops/array_ops.py b/pandas/core/ops/array_ops.py index b39930da9f711..8924f76957064 100644 --- a/pandas/core/ops/array_ops.py +++ b/pandas/core/ops/array_ops.py @@ -574,6 +574,14 @@ def maybe_prepare_scalar_for_op(obj, shape: Shape): # np.timedelta64(3, 'D') / 2 == np.timedelta64(1, 'D') return Timedelta(obj) + # We want NumPy numeric scalars to behave like Python scalars + # post NEP 50 + elif isinstance(obj, np.integer): + return int(obj) + + elif isinstance(obj, np.floating): + return float(obj) + return obj From 707543c6a0101d91a829768666ded395b0f22f0f Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Mon, 30 Oct 2023 15:30:40 -0700 Subject: [PATCH 11/27] add xfail reason to TestCoercionFloat32 --- pandas/tests/series/indexing/test_setitem.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/tests/series/indexing/test_setitem.py b/pandas/tests/series/indexing/test_setitem.py index 5fcd3a19dcaa4..208a02a7bcfcf 100644 --- a/pandas/tests/series/indexing/test_setitem.py +++ b/pandas/tests/series/indexing/test_setitem.py @@ -1409,6 +1409,7 @@ def obj(self): np.float32, None, marks=pytest.mark.xfail( + np._get_promotion_state() != "weak", reason="np.float32(1.1) ends up as 1.100000023841858, so " "np_can_hold_element raises and we cast to float64", ), From 560f42d9792386748cc2b770345df09488cda188 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Fri, 3 Nov 2023 19:47:28 -0400 Subject: [PATCH 12/27] only set promotion state for numpy > 2.0 --- .github/workflows/unit-tests.yml | 1 + ci/run_tests.sh | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 07dcc96a0213d..d2d48577ba0cf 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -96,6 +96,7 @@ jobs: TEST_ARGS: ${{ matrix.test_args || '' }} PYTEST_WORKERS: 'auto' PYTEST_TARGET: ${{ matrix.pytest_target || 'pandas' }} + NPY_PROMOTION_STATE: ${{ matrix.env_file == 'actions-311-numpydev.yaml' && 'legacy' || 'weak' }} concurrency: # https://github.community/t/concurrecy-not-work-for-push/183068/7 group: ${{ github.event_name == 'push' && github.run_number || github.ref }}-${{ matrix.env_file }}-${{ matrix.pattern }}-${{ matrix.extra_apt || '' }}-${{ matrix.pandas_copy_on_write || '' }} diff --git a/ci/run_tests.sh b/ci/run_tests.sh index 7ce57794bf134..48ef21686a26f 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -10,8 +10,7 @@ echo PYTHONHASHSEED=$PYTHONHASHSEED COVERAGE="-s --cov=pandas --cov-report=xml --cov-append --cov-config=pyproject.toml" -# TODO: Support NEP 50 and remove NPY_PROMOTION_STATE -PYTEST_CMD="NPY_PROMOTION_STATE=weak MESONPY_EDITABLE_VERBOSE=1 PYTHONDEVMODE=1 PYTHONWARNDEFAULTENCODING=1 pytest -r fEs -n $PYTEST_WORKERS --dist=loadfile $TEST_ARGS $COVERAGE $PYTEST_TARGET" +PYTEST_CMD="MESONPY_EDITABLE_VERBOSE=1 PYTHONDEVMODE=1 PYTHONWARNDEFAULTENCODING=1 pytest -r fEs -n $PYTEST_WORKERS --dist=loadfile $TEST_ARGS $COVERAGE $PYTEST_TARGET" if [[ "$PATTERN" ]]; then PYTEST_CMD="$PYTEST_CMD -m \"$PATTERN\"" From d5daa3902d9f252e8c3cebea349b5fdd6264ae8b Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Fri, 3 Nov 2023 22:05:05 -0400 Subject: [PATCH 13/27] order was backwards --- .github/workflows/unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index d2d48577ba0cf..7a90ff0ad157f 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -96,7 +96,7 @@ jobs: TEST_ARGS: ${{ matrix.test_args || '' }} PYTEST_WORKERS: 'auto' PYTEST_TARGET: ${{ matrix.pytest_target || 'pandas' }} - NPY_PROMOTION_STATE: ${{ matrix.env_file == 'actions-311-numpydev.yaml' && 'legacy' || 'weak' }} + NPY_PROMOTION_STATE: ${{ matrix.env_file == 'actions-311-numpydev.yaml' && 'weak' || 'legacy' }} concurrency: # https://github.community/t/concurrecy-not-work-for-push/183068/7 group: ${{ github.event_name == 'push' && github.run_number || github.ref }}-${{ matrix.env_file }}-${{ matrix.pattern }}-${{ matrix.extra_apt || '' }}-${{ matrix.pandas_copy_on_write || '' }} From 9df7f0f8dc6a1a43934816d0b6c1d998b1912357 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:03:58 -0800 Subject: [PATCH 14/27] Version promotion state call --- pandas/tests/series/indexing/test_setitem.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/tests/series/indexing/test_setitem.py b/pandas/tests/series/indexing/test_setitem.py index 3d30a3aa1de93..a6a36f80ad494 100644 --- a/pandas/tests/series/indexing/test_setitem.py +++ b/pandas/tests/series/indexing/test_setitem.py @@ -6,6 +6,7 @@ import numpy as np import pytest +from pandas.compat.numpy import np_version_gte1p24 from pandas.errors import IndexingError from pandas.core.dtypes.common import is_list_like @@ -1409,7 +1410,7 @@ def obj(self): np.float32, None, marks=pytest.mark.xfail( - np._get_promotion_state() != "weak", + np_version_gte1p24 and np._get_promotion_state() != "weak", reason="np.float32(1.1) ends up as 1.100000023841858, so " "np_can_hold_element raises and we cast to float64", ), From 97ebfd6ba7e0435206ccd1b345cd9667d222be4a Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Sun, 26 Nov 2023 15:51:06 -0500 Subject: [PATCH 15/27] fix timedelta tests --- pandas/_libs/tslibs/timedeltas.pyx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 5852a7d95d994..fd1199985d703 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -2060,6 +2060,12 @@ class Timedelta(_Timedelta): # integers or floats if util.is_nan(other): return NaT + # We want NumPy numeric scalars to behave like Python scalars + # post NEP 50 + if isinstance(other, np.integer): + other = int(other) + if isinstance(other, np.floating): + other = float(other) return Timedelta._from_value_and_reso( (self._value/ other), self._creso ) From db470bc78f57154b6d58af7852e4245ad96486dd Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Sun, 26 Nov 2023 21:52:49 -0500 Subject: [PATCH 16/27] go for green --- pandas/_libs/tslibs/timedeltas.pyx | 6 ++++ pandas/core/dtypes/cast.py | 39 +++++++++++++++++++++--- pandas/tests/indexing/test_coercion.py | 6 ++++ pandas/tests/indexing/test_loc.py | 11 ++++++- pandas/tests/series/test_constructors.py | 20 +++++++++--- 5 files changed, 72 insertions(+), 10 deletions(-) diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index fd1199985d703..c3c61a50e9af7 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -2120,6 +2120,12 @@ class Timedelta(_Timedelta): elif is_integer_object(other) or is_float_object(other): if util.is_nan(other): return NaT + # We want NumPy numeric scalars to behave like Python scalars + # post NEP 50 + if isinstance(other, np.integer): + other = int(other) + if isinstance(other, np.floating): + other = float(other) return type(self)._from_value_and_reso(self._value// other, self._creso) elif is_array(other): diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index 0ad652bd5aa64..e50178d7358fe 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -40,6 +40,7 @@ is_supported_unit, ) from pandas._libs.tslibs.timedeltas import array_to_timedelta64 +from pandas.compat.numpy import np_version_gt2 from pandas.errors import ( IntCastingNaNError, LossySetitemError, @@ -1318,6 +1319,30 @@ def find_result_type(left_dtype: DtypeObj, right: Any) -> DtypeObj: # which will make us upcast too far. if lib.is_float(right) and right.is_integer() and left_dtype.kind != "f": right = int(right) + # After NEP 50, numpy won't inspect Python scalars + # TODO: do we need to recreate numpy's inspection logic for floats too + # (this breaks some tests) + if isinstance(right, int) and not isinstance(right, np.integer): + # This gives an unsigned type by default + # (if our number is positive) + + # If our left dtype is unsigned, we might not want this since + # this might give us 1 dtype too big, though. + # W should check if the corresponding int dtype (e.g. int64 for uint64) + # can hold the number by a little hack where + # we take the negative of the number + # If the dtype is the same size, then we will use that + right_dtype = np.min_scalar_type(right) + if right > 0 and not np.issubdtype(left_dtype, np.unsignedinteger): + maybe_right_dtype = np.min_scalar_type(-right) + if right_dtype.itemsize == maybe_right_dtype.itemsize: + # It can fit in the corresponding int version + right = maybe_right_dtype + else: + right = right_dtype + else: + right = right_dtype + new_dtype = np.result_type(left_dtype, right) elif is_valid_na_for_dtype(right, left_dtype): @@ -1623,11 +1648,13 @@ def maybe_cast_to_integer_array(arr: list | np.ndarray, dtype: np.dtype) -> np.n with warnings.catch_warnings(): # We already disallow dtype=uint w/ negative numbers # (test_constructor_coercion_signed_to_unsigned) so safe to ignore. - warnings.filterwarnings( - "ignore", - "NumPy will stop allowing conversion of out-of-bound Python int", - DeprecationWarning, - ) + if not np_version_gt2: + warnings.filterwarnings( + "ignore", + "NumPy will stop allowing conversion of " + "out-of-bound Python int", + DeprecationWarning, + ) casted = np.array(arr, dtype=dtype, copy=False) else: with warnings.catch_warnings(): @@ -1664,6 +1691,7 @@ def maybe_cast_to_integer_array(arr: list | np.ndarray, dtype: np.dtype) -> np.n raise ValueError(f"string values cannot be losslessly cast to {dtype}") if dtype.kind == "u" and (arr < 0).any(): + # TODO: can this be hit anymore after numpy 2.0? raise OverflowError("Trying to coerce negative values to unsigned integers") if arr.dtype.kind == "f": @@ -1676,6 +1704,7 @@ def maybe_cast_to_integer_array(arr: list | np.ndarray, dtype: np.dtype) -> np.n raise ValueError("Trying to coerce float values to integers") if casted.dtype < arr.dtype: + # TODO: Can this path be hit anymore with numpy > 2 # GH#41734 e.g. [1, 200, 923442] and dtype="int8" -> overflows raise ValueError( f"Values are too large to be losslessly converted to {dtype}. " diff --git a/pandas/tests/indexing/test_coercion.py b/pandas/tests/indexing/test_coercion.py index c743166030048..f19b313f38346 100644 --- a/pandas/tests/indexing/test_coercion.py +++ b/pandas/tests/indexing/test_coercion.py @@ -224,6 +224,8 @@ def test_insert_int_index( "insert, coerced_val, coerced_dtype", [ (1, 1.0, None), + # When float_numpy_dtype=float32, this is not the case + # see the correction below (1.1, 1.1, np.float64), (False, False, object), # GH#36319 ("x", "x", object), @@ -236,6 +238,10 @@ def test_insert_float_index( obj = pd.Index([1.0, 2.0, 3.0, 4.0], dtype=dtype) coerced_dtype = coerced_dtype if coerced_dtype is not None else dtype + if dtype == "float32" and coerced_val == 1.1: + # Hack, in the 2nd test case, since 1.1 can be losslessly cast to float32 + # the expected dtype will be float32 if the original dtype was float32 + coerced_dtype = np.float32 exp = pd.Index([1.0, coerced_val, 2.0, 3.0, 4.0], dtype=coerced_dtype) self._assert_insert_conversion(obj, insert, exp, coerced_dtype) diff --git a/pandas/tests/indexing/test_loc.py b/pandas/tests/indexing/test_loc.py index 96fd3f4e6fca0..3891d49b0c891 100644 --- a/pandas/tests/indexing/test_loc.py +++ b/pandas/tests/indexing/test_loc.py @@ -13,6 +13,7 @@ import pytest from pandas._libs import index as libindex +from pandas.compat.numpy import np_version_gt2 from pandas.errors import IndexingError import pandas.util._test_decorators as td @@ -2995,7 +2996,15 @@ def test_loc_setitem_uint8_upcast(value): with tm.assert_produces_warning(FutureWarning, match="item of incompatible dtype"): df.loc[2, "col1"] = value # value that can't be held in uint8 - expected = DataFrame([1, 2, 300, 4], columns=["col1"], dtype="uint16") + if np_version_gt2 and isinstance(value, np.int16): + # Note, result type of uint16 + int16 is int16 + # in numpy < 2, though, numpy would inspect the + # value and see that it could fit in an uint16, resulting in a uint16 + dtype = "int16" + else: + dtype = "uint16" + + expected = DataFrame([1, 2, 300, 4], columns=["col1"], dtype=dtype) tm.assert_frame_equal(df, expected) diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 587c73cc535d8..3e1baea4367cd 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -14,6 +14,7 @@ iNaT, lib, ) +from pandas.compat.numpy import np_version_gt2 from pandas.errors import IntCastingNaNError import pandas.util._test_decorators as td @@ -774,11 +775,16 @@ def test_constructor_cast(self): def test_constructor_signed_int_overflow_raises(self): # GH#41734 disallow silent overflow, enforced in 2.0 - msg = "Values are too large to be losslessly converted" - with pytest.raises(ValueError, match=msg): + 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 = OverflowError + with pytest.raises(err, match=msg): Series([1, 200, 923442], dtype="int8") - with pytest.raises(ValueError, match=msg): + with pytest.raises(err, match=msg): Series([1, 200, 923442], dtype="uint8") @pytest.mark.parametrize( @@ -802,7 +808,13 @@ def test_constructor_numpy_uints(self, values): def test_constructor_unsigned_dtype_overflow(self, any_unsigned_int_numpy_dtype): # see gh-15832 - msg = "Trying to coerce negative values to unsigned integers" + 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) From 9a0dec0caa415b6a628beb8617eab57cd17fce77 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Mon, 27 Nov 2023 18:45:51 -0500 Subject: [PATCH 17/27] fix non npdev too? --- pandas/tests/indexing/test_coercion.py | 3 ++- pandas/tests/series/test_constructors.py | 11 +++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/pandas/tests/indexing/test_coercion.py b/pandas/tests/indexing/test_coercion.py index f19b313f38346..3102f76ba41f7 100644 --- a/pandas/tests/indexing/test_coercion.py +++ b/pandas/tests/indexing/test_coercion.py @@ -13,6 +13,7 @@ IS64, is_platform_windows, ) +from pandas.compat.numpy import np_version_gt2 import pandas as pd import pandas._testing as tm @@ -238,7 +239,7 @@ def test_insert_float_index( obj = pd.Index([1.0, 2.0, 3.0, 4.0], dtype=dtype) coerced_dtype = coerced_dtype if coerced_dtype is not None else dtype - if dtype == "float32" and coerced_val == 1.1: + if np_version_gt2 and dtype == "float32" and coerced_val == 1.1: # Hack, in the 2nd test case, since 1.1 can be losslessly cast to float32 # the expected dtype will be float32 if the original dtype was float32 coerced_dtype = np.float32 diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 326c144b1e207..ea5e30084b272 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -775,16 +775,11 @@ def test_constructor_cast(self): 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 = OverflowError - with pytest.raises(err, match=msg): + msg = "The elements provided in the data cannot all be casted to the dtype" + with pytest.raises(OverflowError, match=msg): Series([1, 200, 923442], dtype="int8") - with pytest.raises(err, match=msg): + with pytest.raises(OverflowError, match=msg): Series([1, 200, 923442], dtype="uint8") @pytest.mark.parametrize( From a12cad9216e788d9e21c33ab3e9d31e5ac417758 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:42:24 -0500 Subject: [PATCH 18/27] fixes --- pandas/tests/series/test_constructors.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index ea5e30084b272..6344fa0912ed8 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -775,11 +775,16 @@ def test_constructor_cast(self): def test_constructor_signed_int_overflow_raises(self): # GH#41734 disallow silent overflow, enforced in 2.0 - msg = "The elements provided in the data cannot all be casted to the dtype" - with pytest.raises(OverflowError, match=msg): + 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(OverflowError, match=msg): + with pytest.raises(err, match=msg): Series([1, 200, 923442], dtype="uint8") @pytest.mark.parametrize( @@ -1176,10 +1181,9 @@ def test_constructor_with_datetime_tz3(self): 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) + s = Series(NaT, index=[0, 1], dtype="datetime64[ns, US/Eastern]") + expected = Series(DatetimeIndex(["NaT", "NaT"], tz="US/Eastern")) + tm.assert_series_equal(s, expected) def test_constructor_no_partial_datetime_casting(self): # GH#40111 From 1cd5acd5bf2d12b80344494c30885f6a2d54ed00 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Mon, 4 Dec 2023 16:58:59 -0500 Subject: [PATCH 19/27] adjust xfail condition --- pandas/tests/series/indexing/test_setitem.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pandas/tests/series/indexing/test_setitem.py b/pandas/tests/series/indexing/test_setitem.py index 4e6e75024e490..23137f0975fb1 100644 --- a/pandas/tests/series/indexing/test_setitem.py +++ b/pandas/tests/series/indexing/test_setitem.py @@ -1441,7 +1441,10 @@ def obj(self): np.float32, None, marks=pytest.mark.xfail( - np_version_gte1p24 and np._get_promotion_state() != "weak", + ( + not np_version_gte1p24 + or (np_version_gte1p24 and np._get_promotion_state() != "weak") + ), reason="np.float32(1.1) ends up as 1.100000023841858, so " "np_can_hold_element raises and we cast to float64", ), From 42c8fff208ca4a334b8a7b9e2fe59715ab53450d Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Mon, 4 Dec 2023 17:19:43 -0500 Subject: [PATCH 20/27] go for green --- pandas/core/dtypes/cast.py | 5 ++++- pyproject.toml | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index 42c020c5b81cf..ab9090c5693f3 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -1333,7 +1333,10 @@ def find_result_type(left_dtype: DtypeObj, right: Any) -> DtypeObj: right_dtype = np.min_scalar_type(right) if right > 0 and not np.issubdtype(left_dtype, np.unsignedinteger): maybe_right_dtype = np.min_scalar_type(-right) - if right_dtype.itemsize == maybe_right_dtype.itemsize: + if ( + maybe_right_dtype != np.dtype("O") + and right_dtype.itemsize == maybe_right_dtype.itemsize + ): # It can fit in the corresponding int version right = maybe_right_dtype else: diff --git a/pyproject.toml b/pyproject.toml index f6ba25f448540..8036825399b61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,9 +30,9 @@ authors = [ license = {file = 'LICENSE'} requires-python = '>=3.9' dependencies = [ - "numpy>=1.22.4,<2; python_version<'3.11'", - "numpy>=1.23.2,<2; python_version=='3.11'", - "numpy>=1.26.0,<2; python_version>='3.12'", + "numpy>=1.22.4; python_version<'3.11'", + "numpy>=1.23.2; python_version=='3.11'", + "numpy>=1.26.0; python_version>='3.12'", "python-dateutil>=2.8.2", "pytz>=2020.1", "tzdata>=2022.7" From f79bc66644cd01139beccfd07921cc7e75ca1cf5 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Tue, 19 Dec 2023 20:14:24 -0800 Subject: [PATCH 21/27] add tests --- pandas/core/dtypes/cast.py | 14 +++++--- pandas/tests/dtypes/test_inference.py | 48 +++++++++++++++++++++++++++ pandas/tests/indexing/test_loc.py | 2 +- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index 3833379b75b72..6f1c9a8d79c1b 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -1322,14 +1322,18 @@ def find_result_type(left_dtype: DtypeObj, right: Any) -> DtypeObj: # This gives an unsigned type by default # (if our number is positive) - # If our left dtype is unsigned, we might not want this since + # If our left dtype is signed, we might not want this since # this might give us 1 dtype too big, though. - # W should check if the corresponding int dtype (e.g. int64 for uint64) - # can hold the number by a little hack where - # we take the negative of the number + # We should check if the corresponding int dtype (e.g. int64 for uint64) + # can hold the number by a little hack where we ask numpy for the min + # type of the negative of the number (which can't return an unsigned) # If the dtype is the same size, then we will use that right_dtype = np.min_scalar_type(right) - if right > 0 and not np.issubdtype(left_dtype, np.unsignedinteger): + if right == 0: + # Special case 0, our trick will not work for it since + # np.min_scalar_type(-0) will still give something unsigned + right = left_dtype + elif right > 0 and not np.issubdtype(left_dtype, np.unsignedinteger): maybe_right_dtype = np.min_scalar_type(-right) if ( maybe_right_dtype != np.dtype("O") diff --git a/pandas/tests/dtypes/test_inference.py b/pandas/tests/dtypes/test_inference.py index ff2cfc1278331..597a9760c33bf 100644 --- a/pandas/tests/dtypes/test_inference.py +++ b/pandas/tests/dtypes/test_inference.py @@ -33,8 +33,10 @@ missing as libmissing, ops as libops, ) +from pandas.compat.numpy import np_version_gt2 from pandas.core.dtypes import inference +from pandas.core.dtypes.cast import find_result_type from pandas.core.dtypes.common import ( ensure_int32, is_bool, @@ -1995,3 +1997,49 @@ def test_ensure_int32(): values = np.arange(10, dtype=np.int64) result = ensure_int32(values) assert result.dtype == np.int32 + + +@pytest.mark.parametrize( + "right,result", + [ + (0, np.uint8), + (300, np.uint16), + # For floats, we just upcast directly to float64 instead of trying to + # find a smaller floating dtype + (300.0, np.uint16), # for integer floats, we convert them to ints + (300.1, np.float64), + (np.int16(300), np.int16 if np_version_gt2 else np.uint16), + ], +) +def test_find_result_type_uint_int(right, result): + left_dtype = np.dtype("uint8") + assert find_result_type(left_dtype, right) == result + + +@pytest.mark.parametrize( + "right,result", + [ + (0, np.int8), + (300, np.int16), + # For floats, we just upcast directly to float64 instead of trying to + # find a smaller floating dtype + (300.0, np.int16), # for integer floats, we convert them to ints + (300.1, np.float64), + (np.int16(300), np.int16), + ], +) +def test_find_result_type_int_int(right, result): + left_dtype = np.dtype("int8") + assert find_result_type(left_dtype, right) == result + + +@pytest.mark.parametrize( + "right,result", + [ + (300.0, np.float64), + (np.float32(300), np.float32 if np_version_gt2 else np.float16), + ], +) +def test_find_result_type_floats(right, result): + left_dtype = np.dtype("float16") + assert find_result_type(left_dtype, right) == result diff --git a/pandas/tests/indexing/test_loc.py b/pandas/tests/indexing/test_loc.py index 83a10ce4a0fdb..fb0adc56c401b 100644 --- a/pandas/tests/indexing/test_loc.py +++ b/pandas/tests/indexing/test_loc.py @@ -3022,7 +3022,7 @@ def test_loc_setitem_uint8_upcast(value): df.loc[2, "col1"] = value # value that can't be held in uint8 if np_version_gt2 and isinstance(value, np.int16): - # Note, result type of uint16 + int16 is int16 + # Note, result type of uint8 + int16 is int16 # in numpy < 2, though, numpy would inspect the # value and see that it could fit in an uint16, resulting in a uint16 dtype = "int16" From d7ac45284103be0a22ead2d3c9964568313df1b4 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Tue, 19 Dec 2023 20:15:41 -0800 Subject: [PATCH 22/27] add negative numbers test --- pandas/tests/dtypes/test_inference.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pandas/tests/dtypes/test_inference.py b/pandas/tests/dtypes/test_inference.py index 597a9760c33bf..1e95b77ef9792 100644 --- a/pandas/tests/dtypes/test_inference.py +++ b/pandas/tests/dtypes/test_inference.py @@ -2003,6 +2003,7 @@ def test_ensure_int32(): "right,result", [ (0, np.uint8), + (-1, np.int16), (300, np.uint16), # For floats, we just upcast directly to float64 instead of trying to # find a smaller floating dtype @@ -2020,6 +2021,7 @@ def test_find_result_type_uint_int(right, result): "right,result", [ (0, np.int8), + (-1, np.int8), (300, np.int16), # For floats, we just upcast directly to float64 instead of trying to # find a smaller floating dtype From 0d80c1c9037aeff945fa1986c0179b41b6f5238f Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Wed, 20 Dec 2023 14:15:35 -0800 Subject: [PATCH 23/27] updates --- pandas/_libs/tslibs/timedeltas.pyx | 4 ++-- pandas/tests/dtypes/test_inference.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 4d7839e93f0c3..75c1d50d0b9e2 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -2122,9 +2122,9 @@ class Timedelta(_Timedelta): return NaT # We want NumPy numeric scalars to behave like Python scalars # post NEP 50 - if isinstance(other, np.integer): + if isinstance(other, cnp.integer): other = int(other) - if isinstance(other, np.floating): + if isinstance(other, cnp.floating): other = float(other) return type(self)._from_value_and_reso(self._value// other, self._creso) diff --git a/pandas/tests/dtypes/test_inference.py b/pandas/tests/dtypes/test_inference.py index 1e95b77ef9792..49eb06c299886 100644 --- a/pandas/tests/dtypes/test_inference.py +++ b/pandas/tests/dtypes/test_inference.py @@ -2039,7 +2039,7 @@ def test_find_result_type_int_int(right, result): "right,result", [ (300.0, np.float64), - (np.float32(300), np.float32 if np_version_gt2 else np.float16), + (np.float32(300), np.float32), ], ) def test_find_result_type_floats(right, result): From 399bdf06474a6921c2f1c8086e4b29c1f3362e46 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Wed, 20 Dec 2023 14:18:21 -0800 Subject: [PATCH 24/27] fix accidental changes --- pandas/tests/series/test_constructors.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index eb376b3af3781..63efca61f98fe 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -1180,9 +1180,10 @@ def test_constructor_with_datetime_tz3(self): def test_constructor_with_datetime_tz2(self): # with all NaT - s = Series(NaT, index=[0, 1], dtype="datetime64[ns, US/Eastern]") - expected = Series(DatetimeIndex(["NaT", "NaT"], tz="US/Eastern")) - tm.assert_series_equal(s, expected) + 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 From b50c6de8f335d40eb3c218fde67ccc54bfa78bc7 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Wed, 20 Dec 2023 15:56:24 -0800 Subject: [PATCH 25/27] more --- pandas/_libs/tslibs/timedeltas.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 75c1d50d0b9e2..f6c69cf6d3875 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -2062,9 +2062,9 @@ class Timedelta(_Timedelta): return NaT # We want NumPy numeric scalars to behave like Python scalars # post NEP 50 - if isinstance(other, np.integer): + if isinstance(other, cnp.integer): other = int(other) - if isinstance(other, np.floating): + if isinstance(other, cnp.floating): other = float(other) return Timedelta._from_value_and_reso( (self._value/ other), self._creso From 79f4dde6f545f382a5065a4c36e392957a362ad1 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Thu, 21 Dec 2023 11:10:42 -0800 Subject: [PATCH 26/27] simplify --- pandas/core/dtypes/cast.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index 6f1c9a8d79c1b..018d6df346492 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -1323,26 +1323,20 @@ def find_result_type(left_dtype: DtypeObj, right: Any) -> DtypeObj: # (if our number is positive) # If our left dtype is signed, we might not want this since - # this might give us 1 dtype too big, though. + # this might give us 1 dtype too big # We should check if the corresponding int dtype (e.g. int64 for uint64) - # can hold the number by a little hack where we ask numpy for the min - # type of the negative of the number (which can't return an unsigned) - # If the dtype is the same size, then we will use that + # can hold the number right_dtype = np.min_scalar_type(right) if right == 0: - # Special case 0, our trick will not work for it since - # np.min_scalar_type(-0) will still give something unsigned + # Special case 0 right = left_dtype - elif right > 0 and not np.issubdtype(left_dtype, np.unsignedinteger): - maybe_right_dtype = np.min_scalar_type(-right) - if ( - maybe_right_dtype != np.dtype("O") - and right_dtype.itemsize == maybe_right_dtype.itemsize - ): - # It can fit in the corresponding int version - right = maybe_right_dtype - else: - right = right_dtype + elif ( + not np.issubdtype(left_dtype, np.unsignedinteger) + and right > 0 + and right <= 2 ** (8 * right_dtype.itemsize - 1) - 1 + ): + # If left dtype isn't unsigned, check if it fits in the signed dtype + right = np.dtype(f"i{right_dtype.itemsize}") else: right = right_dtype From 9ce828b16f4c43e73c40c09984d59f4d1ff95c85 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Thu, 21 Dec 2023 11:41:15 -0800 Subject: [PATCH 27/27] linter --- pandas/core/dtypes/cast.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index 018d6df346492..7a088bf84c48e 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -1332,8 +1332,7 @@ def find_result_type(left_dtype: DtypeObj, right: Any) -> DtypeObj: right = left_dtype elif ( not np.issubdtype(left_dtype, np.unsignedinteger) - and right > 0 - and right <= 2 ** (8 * right_dtype.itemsize - 1) - 1 + and 0 < right <= 2 ** (8 * right_dtype.itemsize - 1) - 1 ): # If left dtype isn't unsigned, check if it fits in the signed dtype right = np.dtype(f"i{right_dtype.itemsize}")