diff --git a/python/cudf/cudf/core/column/lists.py b/python/cudf/cudf/core/column/lists.py index f1457d63f3ed..fa51ef4714da 100644 --- a/python/cudf/cudf/core/column/lists.py +++ b/python/cudf/cudf/core/column/lists.py @@ -178,6 +178,14 @@ def as_string_column(self, dtype: DtypeObj) -> StringColumn: ), self._string_separators, ) + # format_list_column converts top-level nulls to the na_rep + # string ("None"). Re-apply the original null mask so that + # top-level nulls remain as actual nulls, matching pandas. + if self.null_count > 0: + plc_column = plc_column.with_mask( + self.plc_column.null_mask(), + self.null_count, + ) return cast( "cudf.core.column.string.StringColumn", ColumnBase.create(plc_column, dtype), diff --git a/python/cudf/cudf/core/column/timedelta.py b/python/cudf/cudf/core/column/timedelta.py index dd16ffcf1b42..89eaa3d8f8bc 100644 --- a/python/cudf/cudf/core/column/timedelta.py +++ b/python/cudf/cudf/core/column/timedelta.py @@ -253,30 +253,25 @@ def as_string_column(self, dtype: DtypeObj) -> StringColumn: ) if isinstance(dtype, np.dtype) and dtype.kind == "U": dtype = np.dtype("object") - if cudf.get_option("mode.pandas_compatible"): - components = self.components - has_hours = components["hours"].any() - has_minutes = components["minutes"].any() - has_seconds = components["seconds"].any() - has_millis = ( - self.time_unit in {"ns", "us", "ms"} - and components["milliseconds"].any() - ) - has_micros = ( - self.time_unit in {"ns", "us"} and self.microseconds.any() - ) - has_nanos = self.time_unit == "ns" and self.nanoseconds.any() - - has_subday = has_hours or has_minutes or has_seconds - has_fraction = has_millis or has_micros or has_nanos - return self._as_string_pandas_compat( - dtype, - has_subday=has_subday, - has_fraction=has_fraction, - has_nanos=has_nanos, - ) - - return self.strftime("%D days %H:%M:%S", dtype=dtype) + components = self.components + has_hours = components["hours"].any() + has_minutes = components["minutes"].any() + has_seconds = components["seconds"].any() + has_millis = ( + self.time_unit in {"ns", "us", "ms"} + and components["milliseconds"].any() + ) + has_micros = self.time_unit in {"ns", "us"} and self.microseconds.any() + has_nanos = self.time_unit == "ns" and self.nanoseconds.any() + + has_subday = has_hours or has_minutes or has_seconds + has_fraction = has_millis or has_micros or has_nanos + return self._as_string_pandas_compat( + dtype, + has_subday=has_subday, + has_fraction=has_fraction, + has_nanos=has_nanos, + ) def _as_string_pandas_compat( self, @@ -287,10 +282,6 @@ def _as_string_pandas_compat( has_nanos: bool, ) -> StringColumn: """Convert to string using pandas-compatible formatting.""" - nat_scalar = pa_scalar_to_plc_scalar( - pa.scalar("NaT", type=pa.string()) - ) - if not (has_subday or has_fraction): fmt = "%D days" else: @@ -347,9 +338,6 @@ def _as_string_pandas_compat( r"\1", ) - # Fill nulls with "NaT". - plc_result = plc.replace.replace_nulls(plc_result, nat_scalar) - return cast( cudf.core.column.string.StringColumn, ColumnBase.create(plc_result, dtype), diff --git a/python/cudf/cudf/tests/dataframe/test_repr.py b/python/cudf/cudf/tests/dataframe/test_repr.py index 7110bc2ad273..9de95938d80d 100644 --- a/python/cudf/cudf/tests/dataframe/test_repr.py +++ b/python/cudf/cudf/tests/dataframe/test_repr.py @@ -251,29 +251,54 @@ def test_dataframe_null_index_repr(df): @pytest.mark.parametrize( - "df,expected_repr", + "df", [ - ( - lambda: cudf.DataFrame( - { - "a": cudf.Series( - [1000000, 200000, 3000000], dtype="timedelta64[s]" - ) - } - ), - textwrap.dedent( - """ - a - 0 11 days 13:46:40 - 1 2 days 07:33:20 - 2 34 days 17:20:00 - """ - ), + lambda: cudf.DataFrame( + { + "a": cudf.Series( + [1000000, 200000, 3000000], dtype="timedelta64[s]" + ) + } ), - ( - lambda: cudf.DataFrame( - { - "a": cudf.Series( + lambda: cudf.DataFrame( + { + "a": cudf.Series( + [ + 136457654, + None, + 245345345, + 223432411, + None, + 3634548734, + 23234, + ], + dtype="timedelta64[s]", + ), + "b": [10, 11, 22, 33, 44, 55, 66], + } + ), + lambda: cudf.DataFrame( + { + "a": cudf.Series( + [ + 136457654, + None, + 245345345, + 223432411, + None, + 3634548734, + 23234, + ], + dtype="timedelta64[s]", + index=["a", "b", "c", "d", "e", "f", "g"], + ) + } + ), + lambda: cudf.DataFrame( + { + "a": cudf.Series( + [1, 2, 3, 4, 5, 6, 7], + index=cudf.Index( [ 136457654, None, @@ -283,28 +308,16 @@ def test_dataframe_null_index_repr(df): 3634548734, 23234, ], - dtype="timedelta64[s]", + dtype="timedelta64[ms]", ), - "b": [10, 11, 22, 33, 44, 55, 66], - } - ), - textwrap.dedent( - """ - a b - 0 1579 days 08:54:14 10 - 1 NaT 11 - 2 2839 days 15:29:05 22 - 3 2586 days 00:33:31 33 - 4 NaT 44 - 5 42066 days 12:52:14 55 - 6 0 days 06:27:14 66 - """ - ), + ) + } ), - ( - lambda: cudf.DataFrame( - { - "a": cudf.Series( + lambda: cudf.DataFrame( + { + "a": cudf.Series( + ["a", "f", "q", "e", "w", "e", "t"], + index=cudf.Index( [ 136457654, None, @@ -314,96 +327,17 @@ def test_dataframe_null_index_repr(df): 3634548734, 23234, ], - dtype="timedelta64[s]", - index=["a", "b", "c", "d", "e", "f", "g"], - ) - } - ), - textwrap.dedent( - """ - a - a 1579 days 08:54:14 - b NaT - c 2839 days 15:29:05 - d 2586 days 00:33:31 - e NaT - f 42066 days 12:52:14 - g 0 days 06:27:14 - """ - ), - ), - ( - lambda: cudf.DataFrame( - { - "a": cudf.Series( - [1, 2, 3, 4, 5, 6, 7], - index=cudf.Index( - [ - 136457654, - None, - 245345345, - 223432411, - None, - 3634548734, - 23234, - ], - dtype="timedelta64[ms]", - ), - ) - } - ), - textwrap.dedent( - """ - a - 1 days 13:54:17.654 1 - NaT 2 - 2 days 20:09:05.345 3 - 2 days 14:03:52.411 4 - NaT 5 - 42 days 01:35:48.734 6 - 0 days 00:00:23.234 7 - """ - ), - ), - ( - lambda: cudf.DataFrame( - { - "a": cudf.Series( - ["a", "f", "q", "e", "w", "e", "t"], - index=cudf.Index( - [ - 136457654, - None, - 245345345, - 223432411, - None, - 3634548734, - 23234, - ], - dtype="timedelta64[ns]", - ), - ) - } - ), - textwrap.dedent( - """ - a - 0 days 00:00:00.136457654 a - NaT f - 0 days 00:00:00.245345345 q - 0 days 00:00:00.223432411 e - NaT w - 0 days 00:00:03.634548734 e - 0 days 00:00:00.000023234 t - """ - ), + dtype="timedelta64[ns]", + ), + ) + } ), ], ) -def test_timedelta_dataframe_repr(df, expected_repr): - actual_repr = repr(df()) - - assert actual_repr.split() == expected_repr.split() +def test_timedelta_dataframe_repr(df): + gdf = df() + pdf = gdf.to_pandas() + assert repr(gdf).split() == repr(pdf).split() def test_categorical_dataframe_with_nan_repr(): diff --git a/python/cudf/cudf/tests/series/methods/test_astype.py b/python/cudf/cudf/tests/series/methods/test_astype.py index 6c98f838dc92..dbc35f8d92e3 100644 --- a/python/cudf/cudf/tests/series/methods/test_astype.py +++ b/python/cudf/cudf/tests/series/methods/test_astype.py @@ -248,131 +248,37 @@ def test_timedelta_datetime_cast_invalid(): @pytest.mark.parametrize( - "sr_data, sr_dtype, exp_data, exp_dtype", + "sr_data, sr_dtype", [ - [ - [1, 2, 3], - "timedelta64[ns]", - [ - "0 days 00:00:00.000000001", - "0 days 00:00:00.000000002", - "0 days 00:00:00.000000003", - ], - None, - ], - [ - [1000000, 200000, 3000000], - "timedelta64[ms]", - ["0 days 00:16:40", "0 days 00:03:20", "0 days 00:50:00"], - None, - ], - [ - [1000000, 200000, 3000000], - "timedelta64[s]", - ["11 days 13:46:40", "2 days 07:33:20", "34 days 17:20:00"], - None, - ], - [ - [None, None, None, None, None], - "timedelta64[us]", - [None, None, None, None, None], - "str", - ], - [ - [ - 136457654, - None, - 245345345, - 223432411, - None, - 3634548734, - 23234, - ], + ([1, 2, 3], "timedelta64[ns]"), + ([1000000, 200000, 3000000], "timedelta64[ms]"), + ([1000000, 200000, 3000000], "timedelta64[s]"), + ([None, None, None, None, None], "timedelta64[us]"), + ( + [136457654, None, 245345345, 223432411, None, 3634548734, 23234], "timedelta64[us]", - [ - "0 days 00:02:16.457654", - None, - "0 days 00:04:05.345345", - "0 days 00:03:43.432411", - None, - "0 days 01:00:34.548734", - "0 days 00:00:00.023234", - ], - None, - ], - [ - [ - 136457654, - None, - 245345345, - 223432411, - None, - 3634548734, - 23234, - ], + ), + ( + [136457654, None, 245345345, 223432411, None, 3634548734, 23234], "timedelta64[ms]", - [ - "1 days 13:54:17.654", - None, - "2 days 20:09:05.345", - "2 days 14:03:52.411", - None, - "42 days 01:35:48.734", - "0 days 00:00:23.234", - ], - None, - ], - [ - [ - 136457654, - None, - 245345345, - 223432411, - None, - 3634548734, - 23234, - ], + ), + ( + [136457654, None, 245345345, 223432411, None, 3634548734, 23234], "timedelta64[s]", - [ - "1579 days 08:54:14", - None, - "2839 days 15:29:05", - "2586 days 00:33:31", - None, - "42066 days 12:52:14", - "0 days 06:27:14", - ], - None, - ], - [ - [ - 136457654, - None, - 245345345, - 223432411, - None, - 3634548734, - 23234, - ], + ), + ( + [136457654, None, 245345345, 223432411, None, 3634548734, 23234], "timedelta64[ns]", - [ - "0 days 00:00:00.136457654", - None, - "0 days 00:00:00.245345345", - "0 days 00:00:00.223432411", - None, - "0 days 00:00:03.634548734", - "0 days 00:00:00.000023234", - ], - None, - ], + ), ], ) -def test_timedelta_str_roundtrip(sr_data, sr_dtype, exp_data, exp_dtype): +def test_timedelta_str_roundtrip(sr_data, sr_dtype): gsr = cudf.Series(sr_data, dtype=sr_dtype) + psr = gsr.to_pandas() + actual_series = gsr.astype("str") + expected_series = psr.astype("str") - expected_series = cudf.Series(exp_data, dtype=exp_dtype) assert_eq(expected_series, actual_series) assert_eq(gsr, actual_series.astype(gsr.dtype)) @@ -545,7 +451,7 @@ def test_string_timstamp_typecast_to_different_datetime_resolutions( pd_sr = pd.Series(data) gdf_sr = cudf.Series(pd_sr) - expect = pd_sr.values.astype(datetime_types_as_str) + expect = np.array(data).astype(datetime_types_as_str) got = gdf_sr.astype(datetime_types_as_str).to_numpy() np.testing.assert_equal(expect, got) diff --git a/python/cudf/cudf/tests/series/methods/test_describe.py b/python/cudf/cudf/tests/series/methods/test_describe.py index bab5ba3aaa7e..b831bf4d4b96 100644 --- a/python/cudf/cudf/tests/series/methods/test_describe.py +++ b/python/cudf/cudf/tests/series/methods/test_describe.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION. # SPDX-License-Identifier: Apache-2.0 import pandas as pd @@ -17,13 +17,7 @@ def test_series_describe_numeric(numeric_types_as_str): assert_eq(expected, actual, check_dtype=True) -def test_series_describe_temporal(temporal_types_as_str, request): - if "ms" in temporal_types_as_str: - request.applymarker( - pytest.mark.xfail( - reason=f"string formatting of {temporal_types_as_str} incorrect in cuDF" - ) - ) +def test_series_describe_temporal(temporal_types_as_str): gs = cudf.Series([0, 1, 2, 3, 1, 2, 3], dtype=temporal_types_as_str) ps = gs.to_pandas() diff --git a/python/cudf/cudf/tests/series/test_repr.py b/python/cudf/cudf/tests/series/test_repr.py index 64e54d8e82ed..1fa83e9dbf47 100644 --- a/python/cudf/cudf/tests/series/test_repr.py +++ b/python/cudf/cudf/tests/series/test_repr.py @@ -16,7 +16,7 @@ def test_null_series(nrows, all_supported_types_as_str, request): request.applymarker( pytest.mark.xfail( - all_supported_types_as_str in {"bool", "timedelta64[ms]"}, + all_supported_types_as_str in {"bool"}, reason=f"cuDF repr doesn't match pandas repr for {all_supported_types_as_str}", ) ) @@ -76,7 +76,7 @@ def test_float_series(x): @pytest.mark.parametrize( - "sr", + "psr", [ pd.Series([1, 2, 3], index=[10, 20, None]), pd.Series([1, None, 3], name="a", index=[None, "a", "b"]), @@ -114,13 +114,11 @@ def test_float_series(x): ).set_index(["a", "v"])["p"], ], ) -def test_series_null_index_repr(sr): - psr = sr +def test_series_null_index_repr(psr): gsr = cudf.from_pandas(psr) - expected_repr = repr(psr).replace("NaN", "") + expected_repr = repr(psr) actual_repr = repr(gsr) - assert expected_repr.split() == actual_repr.split() @@ -165,288 +163,95 @@ def test_timedelta_series_s_us_repr(data, dtype): @pytest.mark.parametrize( - "ser, expected_repr", + "ser", [ - ( - lambda: cudf.Series([], dtype="timedelta64[ns]"), - textwrap.dedent( - """ - Series([], dtype: timedelta64[ns]) - """ - ), - ), - ( - lambda: cudf.Series([], dtype="timedelta64[ms]"), - textwrap.dedent( - """ - Series([], dtype: timedelta64[ms]) - """ - ), - ), - ( - lambda: cudf.Series( - [1000000, 200000, 3000000], dtype="timedelta64[ns]" - ), - textwrap.dedent( - """ - 0 0 days 00:00:00.001000 - 1 0 days 00:00:00.000200 - 2 0 days 00:00:00.003000 - dtype: timedelta64[ns] - """ - ), - ), - ( - lambda: cudf.Series( - [1000000, 200000, 3000000], dtype="timedelta64[ms]" - ), - textwrap.dedent( - """ - 0 0 days 00:16:40 - 1 0 days 00:03:20 - 2 0 days 00:50:00 - dtype: timedelta64[ms] - """ - ), + lambda: cudf.Series([], dtype="timedelta64[ns]"), + lambda: cudf.Series([], dtype="timedelta64[ms]"), + lambda: cudf.Series( + [1000000, 200000, 3000000], dtype="timedelta64[ns]" ), - ( - lambda: cudf.Series( - [1000000, 200000, None], dtype="timedelta64[ns]" - ), - textwrap.dedent( - """ - 0 0 days 00:00:00.001000000 - 1 0 days 00:00:00.000200000 - 2 NaT - dtype: timedelta64[ns] - """ - ), + lambda: cudf.Series( + [1000000, 200000, 3000000], dtype="timedelta64[ms]" ), - ( - lambda: cudf.Series( - [1000000, 200000, None], dtype="timedelta64[ms]" - ), - textwrap.dedent( - """ - 0 0 days 00:16:40 - 1 0 days 00:03:20 - 2 NaT - dtype: timedelta64[ms] - """ - ), + lambda: cudf.Series([1000000, 200000, None], dtype="timedelta64[ns]"), + lambda: cudf.Series([1000000, 200000, None], dtype="timedelta64[ms]"), + lambda: cudf.Series( + [None, None, None, None, None], dtype="timedelta64[ns]" ), - ( - lambda: cudf.Series( - [None, None, None, None, None], dtype="timedelta64[ns]" - ), - textwrap.dedent( - """ - 0 NaT - 1 NaT - 2 NaT - 3 NaT - 4 NaT - dtype: timedelta64[ns] - """ - ), + lambda: cudf.Series( + [None, None, None, None, None], dtype="timedelta64[ms]" ), - ( - lambda: cudf.Series( - [None, None, None, None, None], dtype="timedelta64[ms]" - ), - textwrap.dedent( - """ - 0 NaT - 1 NaT - 2 NaT - 3 NaT - 4 NaT - dtype: timedelta64[ms] - """ - ), + lambda: cudf.Series( + [12, 12, 22, 343, 4353534, 435342], dtype="timedelta64[ns]" ), - ( - lambda: cudf.Series( - [12, 12, 22, 343, 4353534, 435342], dtype="timedelta64[ns]" - ), - textwrap.dedent( - """ - 0 0 days 00:00:00.000000012 - 1 0 days 00:00:00.000000012 - 2 0 days 00:00:00.000000022 - 3 0 days 00:00:00.000000343 - 4 0 days 00:00:00.004353534 - 5 0 days 00:00:00.000435342 - dtype: timedelta64[ns] - """ - ), + lambda: cudf.Series( + [12, 12, 22, 343, 4353534, 435342], dtype="timedelta64[ms]" ), - ( - lambda: cudf.Series( - [12, 12, 22, 343, 4353534, 435342], dtype="timedelta64[ms]" - ), - textwrap.dedent( - """ - 0 0 days 00:00:00.012000 - 1 0 days 00:00:00.012000 - 2 0 days 00:00:00.022000 - 3 0 days 00:00:00.343000 - 4 0 days 01:12:33.534000 - 5 0 days 00:07:15.342000 - dtype: timedelta64[ms] - """ - ), + lambda: cudf.Series( + [1.321, 1132.324, 23223231.11, 233.41, 0.2434, 332, 323], + dtype="timedelta64[ns]", ), - ( - lambda: cudf.Series( - [1.321, 1132.324, 23223231.11, 233.41, 0.2434, 332, 323], - dtype="timedelta64[ns]", - ), - textwrap.dedent( - """ - 0 0 days 00:00:00.000000001 - 1 0 days 00:00:00.000001132 - 2 0 days 00:00:00.023223231 - 3 0 days 00:00:00.000000233 - 4 0 days 00:00:00 - 5 0 days 00:00:00.000000332 - 6 0 days 00:00:00.000000323 - dtype: timedelta64[ns] - """ - ), + lambda: cudf.Series( + [1.321, 1132.324, 23223231.11, 233.41, 0.2434, 332, 323], + dtype="timedelta64[ms]", ), - ( - lambda: cudf.Series( - [1.321, 1132.324, 23223231.11, 233.41, 0.2434, 332, 323], - dtype="timedelta64[ms]", - ), - textwrap.dedent( - """ - 0 0 days 00:00:00.001000 - 1 0 days 00:00:01.132000 - 2 0 days 06:27:03.231000 - 3 0 days 00:00:00.233000 - 4 0 days 00:00:00 - 5 0 days 00:00:00.332000 - 6 0 days 00:00:00.323000 - dtype: timedelta64[ms] - """ - ), + lambda: cudf.Series( + [ + 13645765432432, + 134736784, + 245345345, + 223432411, + 999992343241, + 3634548734, + 23234, + ], + dtype="timedelta64[ms]", ), - ( - lambda: cudf.Series( - [ - 13645765432432, - 134736784, - 245345345, - 223432411, - 999992343241, - 3634548734, - 23234, - ], - dtype="timedelta64[ms]", - ), - textwrap.dedent( - """ - 0 157937 days 02:23:52.432000 - 1 1 days 13:25:36.784000 - 2 2 days 20:09:05.345000 - 3 2 days 14:03:52.411000 - 4 11573 days 23:39:03.241000 - 5 42 days 01:35:48.734000 - 6 0 days 00:00:23.234000 - dtype: timedelta64[ms] - """ - ), + lambda: cudf.Series( + [ + 13645765432432, + 134736784, + 245345345, + 223432411, + 999992343241, + 3634548734, + 23234, + ], + dtype="timedelta64[ns]", ), - ( - lambda: cudf.Series( - [ - 13645765432432, - 134736784, - 245345345, - 223432411, - 999992343241, - 3634548734, - 23234, - ], - dtype="timedelta64[ns]", - ), - textwrap.dedent( - """ - 0 0 days 03:47:25.765432432 - 1 0 days 00:00:00.134736784 - 2 0 days 00:00:00.245345345 - 3 0 days 00:00:00.223432411 - 4 0 days 00:16:39.992343241 - 5 0 days 00:00:03.634548734 - 6 0 days 00:00:00.000023234 - dtype: timedelta64[ns] - """ - ), + lambda: cudf.Series( + [ + 13645765432432, + 134736784, + 245345345, + 223432411, + 999992343241, + 3634548734, + 23234, + ], + dtype="timedelta64[ms]", + name="abc", ), - ( - lambda: cudf.Series( - [ - 13645765432432, - 134736784, - 245345345, - 223432411, - 999992343241, - 3634548734, - 23234, - ], - dtype="timedelta64[ms]", - name="abc", - ), - textwrap.dedent( - """ - 0 157937 days 02:23:52.432000 - 1 1 days 13:25:36.784000 - 2 2 days 20:09:05.345000 - 3 2 days 14:03:52.411000 - 4 11573 days 23:39:03.241000 - 5 42 days 01:35:48.734000 - 6 0 days 00:00:23.234000 - Name: abc, dtype: timedelta64[ms] - """ - ), - ), - ( - lambda: cudf.Series( - [ - 13645765432432, - 134736784, - 245345345, - 223432411, - 999992343241, - 3634548734, - 23234, - ], - dtype="timedelta64[ns]", - index=["a", "b", "z", "x", "y", "l", "m"], - name="hello", - ), - textwrap.dedent( - """ - a 0 days 03:47:25.765432432 - b 0 days 00:00:00.134736784 - z 0 days 00:00:00.245345345 - x 0 days 00:00:00.223432411 - y 0 days 00:16:39.992343241 - l 0 days 00:00:03.634548734 - m 0 days 00:00:00.000023234 - Name: hello, dtype: timedelta64[ns] - """ - ), + lambda: cudf.Series( + [ + 13645765432432, + 134736784, + 245345345, + 223432411, + 999992343241, + 3634548734, + 23234, + ], + dtype="timedelta64[ns]", + index=["a", "b", "z", "x", "y", "l", "m"], + name="hello", ), ], ) -def test_timedelta_series_ns_ms_repr(ser, expected_repr): - expected = expected_repr - actual = repr(ser()) - - assert expected.split() == actual.split() +def test_timedelta_series_ns_ms_repr(ser): + gsr = ser() + psr = gsr.to_pandas() + assert repr(psr).split() == repr(gsr).split() def test_categorical_series_with_nan_repr():