Skip to content

Commit 8d858f7

Browse files
jbrockmendeljreback
authored andcommitted
TST: un-xfail incorrectly xfailed tests for maybe_promote (#28564)
1 parent 182abe7 commit 8d858f7

File tree

2 files changed

+50
-63
lines changed

2 files changed

+50
-63
lines changed

pandas/core/dtypes/cast.py

+27-1
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ def maybe_promote(dtype, fill_value=np.nan):
358358
fill_value = NaT
359359
elif is_extension_array_dtype(dtype) and isna(fill_value):
360360
fill_value = dtype.na_value
361+
361362
elif is_float(fill_value):
362363
if issubclass(dtype.type, np.bool_):
363364
dtype = np.object_
@@ -366,6 +367,8 @@ def maybe_promote(dtype, fill_value=np.nan):
366367
elif is_bool(fill_value):
367368
if not issubclass(dtype.type, np.bool_):
368369
dtype = np.object_
370+
else:
371+
fill_value = np.bool_(fill_value)
369372
elif is_integer(fill_value):
370373
if issubclass(dtype.type, np.bool_):
371374
dtype = np.object_
@@ -374,6 +377,10 @@ def maybe_promote(dtype, fill_value=np.nan):
374377
arr = np.asarray(fill_value)
375378
if arr != arr.astype(dtype):
376379
dtype = arr.dtype
380+
elif issubclass(dtype.type, np.floating):
381+
# check if we can cast
382+
if _check_lossless_cast(fill_value, dtype):
383+
fill_value = dtype.type(fill_value)
377384
elif is_complex(fill_value):
378385
if issubclass(dtype.type, np.bool_):
379386
dtype = np.object_
@@ -398,12 +405,31 @@ def maybe_promote(dtype, fill_value=np.nan):
398405
pass
399406
elif is_datetime64tz_dtype(dtype):
400407
pass
401-
elif issubclass(np.dtype(dtype).type, str):
408+
elif issubclass(np.dtype(dtype).type, (bytes, str)):
402409
dtype = np.object_
403410

404411
return dtype, fill_value
405412

406413

414+
def _check_lossless_cast(value, dtype: np.dtype) -> bool:
415+
"""
416+
Check if we can cast the given value to the given dtype _losslesly_.
417+
418+
Parameters
419+
----------
420+
value : object
421+
dtype : np.dtype
422+
423+
Returns
424+
-------
425+
bool
426+
"""
427+
casted = dtype.type(value)
428+
if casted == value:
429+
return True
430+
return False
431+
432+
407433
def infer_dtype_from(val, pandas_dtype=False):
408434
"""
409435
interpret the dtype from a scalar or array. This is a convenience

pandas/tests/dtypes/cast/test_promote.py

+23-62
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
is_timedelta64_dtype,
2424
)
2525
from pandas.core.dtypes.dtypes import DatetimeTZDtype, PandasExtensionDtype
26+
from pandas.core.dtypes.missing import isna
2627

2728
import pandas as pd
2829

@@ -95,6 +96,7 @@ def _safe_dtype_assert(left_dtype, right_dtype):
9596
"""
9697
Compare two dtypes without raising TypeError.
9798
"""
99+
__tracebackhide__ = True
98100
if isinstance(right_dtype, PandasExtensionDtype):
99101
# switch order of equality check because numpy dtypes (e.g. if
100102
# left_dtype is np.object_) do not know some expected dtypes (e.g.
@@ -157,20 +159,17 @@ def _check_promote(
157159

158160
_safe_dtype_assert(result_dtype, expected_dtype)
159161

160-
# for equal values, also check type (relevant e.g. for int vs float, resp.
161-
# for different datetimes and timedeltas)
162-
match_value = (
163-
result_fill_value
164-
== expected_fill_value
165-
# disabled type check due to too many xfails; GH 23982/25425
166-
# and type(result_fill_value) == type(expected_fill_value)
167-
)
162+
# GH#23982/25425 require the same type in addition to equality/NA-ness
163+
res_type = type(result_fill_value)
164+
ex_type = type(expected_fill_value)
165+
assert res_type == ex_type
166+
167+
match_value = result_fill_value == expected_fill_value
168168

169+
# Note: type check above ensures that we have the _same_ NA value
169170
# for missing values, None == None and iNaT == iNaT (which is checked
170171
# through match_value above), but np.nan != np.nan and pd.NaT != pd.NaT
171-
match_missing = (result_fill_value is np.nan and expected_fill_value is np.nan) or (
172-
result_fill_value is NaT and expected_fill_value is NaT
173-
)
172+
match_missing = isna(result_fill_value) and isna(expected_fill_value)
174173

175174
assert match_value or match_missing
176175

@@ -251,7 +250,9 @@ def test_maybe_promote_bool_with_any(any_numpy_dtype_reduced, box):
251250

252251
if boxed and fill_dtype == bool:
253252
pytest.xfail("falsely upcasts to object")
254-
if boxed and box_dtype is None and is_datetime_or_timedelta_dtype(fill_dtype):
253+
if boxed and box_dtype is None and fill_dtype.kind == "M":
254+
pytest.xfail("wrongly casts fill_value")
255+
if boxed and box_dtype is None and fill_dtype.kind == "m":
255256
pytest.xfail("wrongly casts fill_value")
256257

257258
# create array of given dtype; casts "1" to correct dtype
@@ -282,7 +283,9 @@ def test_maybe_promote_any_with_bool(any_numpy_dtype_reduced, box):
282283
pytest.xfail("falsely upcasts to object")
283284
if boxed and dtype not in (str, object) and box_dtype is None:
284285
pytest.xfail("falsely upcasts to object")
285-
if not boxed and is_datetime_or_timedelta_dtype(dtype):
286+
if not boxed and dtype.kind == "M":
287+
pytest.xfail("raises error")
288+
if not boxed and dtype.kind == "m":
286289
pytest.xfail("raises error")
287290

288291
# filling anything but bool with bool casts to object
@@ -393,9 +396,6 @@ def test_maybe_promote_datetimetz_with_any_numpy_dtype(
393396
fill_dtype = np.dtype(any_numpy_dtype_reduced)
394397
boxed, box_dtype = box # read from parametrized fixture
395398

396-
if box_dtype != object:
397-
pytest.xfail("does not upcast correctly")
398-
399399
# create array of given dtype; casts "1" to correct dtype
400400
fill_value = np.array([1], dtype=fill_dtype)[0]
401401

@@ -430,8 +430,6 @@ def test_maybe_promote_datetimetz_with_datetimetz(
430430
pytest.xfail("Cannot process fill_value with this dtype, see GH 24310")
431431
if dtype.tz == fill_dtype.tz and boxed:
432432
pytest.xfail("falsely upcasts")
433-
if dtype.tz != fill_dtype.tz and not boxed:
434-
pytest.xfail("falsely upcasts")
435433

436434
# create array of given dtype; casts "1" to correct dtype
437435
fill_value = pd.Series([10 ** 9], dtype=fill_dtype)[0]
@@ -466,14 +464,10 @@ def test_maybe_promote_datetimetz_with_na(tz_aware_fixture, fill_value, box):
466464
dtype = DatetimeTZDtype(tz=tz_aware_fixture)
467465
boxed, box_dtype = box # read from parametrized fixture
468466

469-
if boxed and (
470-
box_dtype == object
471-
or (box_dtype is None and (fill_value is None or fill_value is NaT))
472-
):
473-
pytest.xfail("false upcasts to object")
474467
# takes the opinion that DatetimeTZ should have single na-marker
475468
# using iNaT would lead to errors elsewhere -> NaT
476469
if not boxed and fill_value == iNaT:
470+
# TODO: are we sure iNaT _should_ be cast to NaT?
477471
pytest.xfail("wrong missing value marker")
478472

479473
expected_dtype = dtype
@@ -509,8 +503,10 @@ def test_maybe_promote_any_numpy_dtype_with_datetimetz(
509503
fill_dtype = DatetimeTZDtype(tz=tz_aware_fixture)
510504
boxed, box_dtype = box # read from parametrized fixture
511505

512-
if is_datetime_or_timedelta_dtype(dtype) and not boxed:
506+
if dtype.kind == "m" and not boxed:
513507
pytest.xfail("raises error")
508+
elif dtype.kind == "M" and not boxed:
509+
pytest.xfail("Comes back as M8 instead of object")
514510

515511
fill_value = pd.Series([fill_value], dtype=fill_dtype)[0]
516512

@@ -566,19 +562,6 @@ def test_maybe_promote_any_with_timedelta64(
566562
else:
567563
if boxed and box_dtype is None and is_timedelta64_dtype(type(fill_value)):
568564
pytest.xfail("does not upcast correctly")
569-
if (
570-
not boxed
571-
and is_timedelta64_dtype(type(fill_value))
572-
and (
573-
is_integer_dtype(dtype)
574-
or is_float_dtype(dtype)
575-
or is_complex_dtype(dtype)
576-
or issubclass(dtype.type, np.bytes_)
577-
)
578-
):
579-
pytest.xfail("does not upcast correctly")
580-
if box_dtype == "td_dtype":
581-
pytest.xfail("falsely upcasts")
582565
if not boxed and is_datetime64_dtype(dtype):
583566
pytest.xfail("raises error")
584567

@@ -612,7 +595,9 @@ def test_maybe_promote_string_with_any(string_dtype, any_numpy_dtype_reduced, bo
612595
fill_dtype = np.dtype(any_numpy_dtype_reduced)
613596
boxed, box_dtype = box # read from parametrized fixture
614597

615-
if boxed and box_dtype is None and is_datetime_or_timedelta_dtype(fill_dtype):
598+
if boxed and box_dtype is None and fill_dtype.kind == "m":
599+
pytest.xfail("wrong missing value marker")
600+
if boxed and box_dtype is None and fill_dtype.kind == "M":
616601
pytest.xfail("wrong missing value marker")
617602

618603
# create array of given dtype; casts "1" to correct dtype
@@ -652,17 +637,6 @@ def test_maybe_promote_any_with_string(any_numpy_dtype_reduced, string_dtype, bo
652637

653638
if is_datetime_or_timedelta_dtype(dtype) and box_dtype != object:
654639
pytest.xfail("does not upcast or raises")
655-
if (
656-
boxed
657-
and box_dtype in (None, "str")
658-
and (
659-
is_integer_dtype(dtype)
660-
or is_float_dtype(dtype)
661-
or is_complex_dtype(dtype)
662-
or issubclass(dtype.type, np.bytes_)
663-
)
664-
):
665-
pytest.xfail("does not upcast correctly")
666640

667641
# create array of given dtype
668642
fill_value = "abc"
@@ -760,19 +734,6 @@ def test_maybe_promote_any_numpy_dtype_with_na(
760734
pytest.xfail("does not upcast to object")
761735
elif dtype == "uint64" and not boxed and fill_value == iNaT:
762736
pytest.xfail("does not upcast correctly")
763-
elif is_datetime_or_timedelta_dtype(dtype) and boxed:
764-
pytest.xfail("falsely upcasts to object")
765-
elif (
766-
boxed
767-
and (
768-
is_integer_dtype(dtype) or is_float_dtype(dtype) or is_complex_dtype(dtype)
769-
)
770-
and fill_value is not NaT
771-
and dtype != "uint64"
772-
):
773-
pytest.xfail("falsely upcasts to object")
774-
elif boxed and dtype == "uint64" and (fill_value is np.nan or fill_value is None):
775-
pytest.xfail("falsely upcasts to object")
776737
# below: opinionated that iNaT should be interpreted as missing value
777738
elif (
778739
not boxed

0 commit comments

Comments
 (0)