Skip to content

Commit 9a3e1ef

Browse files
jbrockmendeljreback
authored andcommitted
TST: Fix xfails for non-box maybe_promote on integer dtypes (#28864)
1 parent 9743154 commit 9a3e1ef

File tree

2 files changed

+63
-24
lines changed

2 files changed

+63
-24
lines changed

pandas/core/dtypes/cast.py

+52-3
Original file line numberDiff line numberDiff line change
@@ -424,9 +424,58 @@ def maybe_promote(dtype, fill_value=np.nan):
424424
dtype = np.dtype(np.object_)
425425
elif issubclass(dtype.type, np.integer):
426426
# upcast to prevent overflow
427-
arr = np.asarray(fill_value)
428-
if arr != arr.astype(dtype):
429-
dtype = arr.dtype
427+
mst = np.min_scalar_type(fill_value)
428+
if mst > dtype:
429+
# np.dtype ordering considers:
430+
# int[n] < int[2*n]
431+
# uint[n] < uint[2*n]
432+
# u?int[n] < object_
433+
dtype = mst
434+
435+
elif np.can_cast(fill_value, dtype):
436+
pass
437+
438+
elif dtype.kind == "u" and mst.kind == "i":
439+
dtype = np.promote_types(dtype, mst)
440+
if dtype.kind == "f":
441+
# Case where we disagree with numpy
442+
dtype = np.dtype(np.object_)
443+
444+
elif dtype.kind == "i" and mst.kind == "u":
445+
446+
if fill_value > np.iinfo(np.int64).max:
447+
# object is the only way to represent fill_value and keep
448+
# the range allowed by the given dtype
449+
dtype = np.dtype(np.object_)
450+
451+
elif mst.itemsize < dtype.itemsize:
452+
pass
453+
454+
elif dtype.itemsize == mst.itemsize:
455+
# We never cast signed to unsigned because that loses
456+
# parts of the original range, so find the smallest signed
457+
# integer that can hold all of `mst`.
458+
ndt = {
459+
np.int64: np.object_,
460+
np.int32: np.int64,
461+
np.int16: np.int32,
462+
np.int8: np.int16,
463+
}[dtype.type]
464+
dtype = np.dtype(ndt)
465+
466+
else:
467+
# bump to signed integer dtype that holds all of `mst` range
468+
# Note: we have to use itemsize because some (windows)
469+
# builds don't satisfiy e.g. np.uint32 == np.uint32
470+
ndt = {
471+
4: np.int64,
472+
2: np.int32,
473+
1: np.int16, # TODO: Test for this case
474+
}[mst.itemsize]
475+
dtype = np.dtype(ndt)
476+
477+
fill_value = dtype.type(fill_value)
478+
430479
elif issubclass(dtype.type, np.floating):
431480
# check if we can cast
432481
if _check_lossless_cast(fill_value, dtype):

pandas/tests/dtypes/cast/test_promote.py

+11-21
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,17 @@ def _assert_match(result_fill_value, expected_fill_value):
151151
# GH#23982/25425 require the same type in addition to equality/NA-ness
152152
res_type = type(result_fill_value)
153153
ex_type = type(expected_fill_value)
154-
assert res_type == ex_type
154+
if res_type.__name__ == "uint64":
155+
# No idea why, but these (sometimes) do not compare as equal
156+
assert ex_type.__name__ == "uint64"
157+
elif res_type.__name__ == "ulonglong":
158+
# On some builds we get this instead of np.uint64
159+
# Note: cant check res_type.dtype.itemsize directly on numpy 1.18
160+
assert res_type(0).itemsize == 8
161+
assert ex_type == res_type or ex_type == np.uint64
162+
else:
163+
# On some builds, type comparison fails, e.g. np.int32 != np.int32
164+
assert res_type == ex_type or res_type.__name__ == ex_type.__name__
155165

156166
match_value = result_fill_value == expected_fill_value
157167

@@ -275,26 +285,6 @@ def test_maybe_promote_int_with_int(dtype, fill_value, expected_dtype, box):
275285
expected_dtype = np.dtype(expected_dtype)
276286
boxed, box_dtype = box # read from parametrized fixture
277287

278-
if not boxed:
279-
if expected_dtype == object:
280-
pytest.xfail("overflow error")
281-
if expected_dtype == "int32":
282-
pytest.xfail("always upcasts to platform int")
283-
if dtype == "int8" and expected_dtype == "int16":
284-
pytest.xfail("casts to int32 instead of int16")
285-
if (
286-
issubclass(dtype.type, np.unsignedinteger)
287-
and np.iinfo(dtype).max < fill_value <= np.iinfo("int64").max
288-
):
289-
pytest.xfail("falsely casts to signed")
290-
if (dtype, expected_dtype) in [
291-
("uint8", "int16"),
292-
("uint32", "int64"),
293-
] and fill_value != np.iinfo("int32").min - 1:
294-
pytest.xfail("casts to int32 instead of int8/int16")
295-
# this following xfail is "only" a consequence of the - now strictly
296-
# enforced - principle that maybe_promote_with_scalar always casts
297-
pytest.xfail("wrong return type of fill_value")
298288
if boxed:
299289
if expected_dtype != object:
300290
pytest.xfail("falsely casts to object")

0 commit comments

Comments
 (0)