Skip to content

Commit b8cce91

Browse files
authored
BUG: do not replace all nulls with "NaN"-string in Series index (#45283)
1 parent a474af5 commit b8cce91

File tree

7 files changed

+59
-2
lines changed

7 files changed

+59
-2
lines changed

doc/source/whatsnew/v1.4.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,7 @@ Other
10501050
- Bug in :meth:`Series.replace` raising ``ValueError`` when using ``regex=True`` with a :class:`Series` containing ``np.nan`` values (:issue:`43344`)
10511051
- Bug in :meth:`DataFrame.to_records` where an incorrect ``n`` was used when missing names were replaced by ``level_n`` (:issue:`44818`)
10521052
- Bug in :meth:`DataFrame.eval` where ``resolvers`` argument was overriding the default resolvers (:issue:`34966`)
1053+
- :meth:`Series.__repr__` and :meth:`DataFrame.__repr__` no longer replace all null-values in indexes with "NaN" but use their real string-representations. "NaN" is used only for ``float("nan")`` (:issue:`45263`)
10531054

10541055
.. ---------------------------------------------------------------------------
10551056

pandas/_libs/missing.pyi

+1
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ def checknull(val: object, inf_as_na: bool = ...) -> bool: ...
1414
def isnaobj(arr: np.ndarray, inf_as_na: bool = ...) -> npt.NDArray[np.bool_]: ...
1515
def isnaobj2d(arr: np.ndarray, inf_as_na: bool = ...) -> npt.NDArray[np.bool_]: ...
1616
def is_numeric_na(values: np.ndarray) -> npt.NDArray[np.bool_]: ...
17+
def is_float_nan(values: np.ndarray) -> npt.NDArray[np.bool_]: ...

pandas/_libs/missing.pyx

+25
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,31 @@ cdef bint checknull_with_nat_and_na(object obj):
248248
return checknull_with_nat(obj) or obj is C_NA
249249

250250

251+
@cython.wraparound(False)
252+
@cython.boundscheck(False)
253+
def is_float_nan(values: ndarray) -> ndarray:
254+
"""
255+
True for elements which correspond to a float nan
256+
257+
Returns
258+
-------
259+
ndarray[bool]
260+
"""
261+
cdef:
262+
ndarray[uint8_t] result
263+
Py_ssize_t i, N
264+
object val
265+
266+
N = len(values)
267+
result = np.zeros(N, dtype=np.uint8)
268+
269+
for i in range(N):
270+
val = values[i]
271+
if util.is_nan(val):
272+
result[i] = True
273+
return result.view(bool)
274+
275+
251276
@cython.wraparound(False)
252277
@cython.boundscheck(False)
253278
def is_numeric_na(values: ndarray) -> ndarray:

pandas/core/indexes/base.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
is_datetime_array,
3434
no_default,
3535
)
36+
from pandas._libs.missing import is_float_nan
3637
from pandas._libs.tslibs import (
3738
IncompatibleFrequency,
3839
OutOfBoundsDatetime,
@@ -1394,7 +1395,7 @@ def _format_with_header(self, header: list[str_t], na_rep: str_t) -> list[str_t]
13941395
result = [pprint_thing(x, escape_chars=("\t", "\r", "\n")) for x in values]
13951396

13961397
# could have nans
1397-
mask = isna(values)
1398+
mask = is_float_nan(values)
13981399
if mask.any():
13991400
result_arr = np.array(result)
14001401
result_arr[mask] = na_rep

pandas/tests/frame/test_repr_info.py

+21
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,27 @@ def test_repr_with_mi_nat(self, float_string_frame):
6868
expected = " X\nNaT a 1\n2013-01-01 b 2"
6969
assert result == expected
7070

71+
def test_repr_with_different_nulls(self):
72+
# GH45263
73+
df = DataFrame([1, 2, 3, 4], [True, None, np.nan, NaT])
74+
result = repr(df)
75+
expected = """ 0
76+
True 1
77+
None 2
78+
NaN 3
79+
NaT 4"""
80+
assert result == expected
81+
82+
def test_repr_with_different_nulls_cols(self):
83+
# GH45263
84+
d = {np.nan: [1, 2], None: [3, 4], NaT: [6, 7], True: [8, 9]}
85+
df = DataFrame(data=d)
86+
result = repr(df)
87+
expected = """ NaN None NaT True
88+
0 1 3 6 8
89+
1 2 4 7 9"""
90+
assert result == expected
91+
7192
def test_multiindex_na_repr(self):
7293
# only an issue with long columns
7394
df3 = DataFrame(

pandas/tests/indexes/test_base.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,8 @@ def test_format_missing(self, vals, nulls_fixture):
658658
index = Index(vals)
659659

660660
formatted = index.format()
661-
expected = [str(index[0]), str(index[1]), str(index[2]), "NaN"]
661+
null_repr = "NaN" if isinstance(nulls_fixture, float) else str(nulls_fixture)
662+
expected = [str(index[0]), str(index[1]), str(index[2]), null_repr]
662663

663664
assert formatted == expected
664665
assert index[3] is nulls_fixture

pandas/tests/series/test_repr.py

+7
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,13 @@ def test_float_repr(self):
258258
expected = "0 1.0\ndtype: object"
259259
assert repr(ser) == expected
260260

261+
def test_different_null_objects(self):
262+
# GH#45263
263+
ser = Series([1, 2, 3, 4], [True, None, np.nan, pd.NaT])
264+
result = repr(ser)
265+
expected = "True 1\nNone 2\nNaN 3\nNaT 4\ndtype: int64"
266+
assert result == expected
267+
261268

262269
class TestCategoricalRepr:
263270
def test_categorical_repr_unicode(self):

0 commit comments

Comments
 (0)