Breaking changes for pandas 2 in cuDF 24.04+#

In release 24.04 and later, cuDF requires pandas 2, following the announcement in RAPIDS Support Notice 36. Migrating to pandas 2 comes with a number of API and behavior changes, documented below. The changes to support pandas 2 affect both cudf and cudf.pandas (cuDF pandas accelerator mode). For more details, refer to the pandas 2.0 changelog.

Removed DataFrame.append & Series.append, use cudf.concat instead.#

DataFrame.append & Series.append deprecations are enforced by removing these two APIs. Instead, please use cudf.concat.

Old behavior:


In [37]: s = cudf.Series([1, 2, 3])

In [38]: p = cudf.Series([10, 20, 30])

In [39]: s.append(p)
Out[39]:
0     1
1     2
2     3
0    10
1    20
2    30
dtype: int64

New behavior:

In [40]: cudf.concat([s, p])
Out[40]:
0     1
1     2
2     3
0    10
1    20
2    30
dtype: int64

Removed various numeric Index sub-classes, use cudf.Index#

Float32Index, Float64Index, GenericIndex, Int8Index, Int16Index, Int32Index, Int64Index, StringIndex, UInt8Index, UInt16Index, UInt32Index, UInt64Index have all been removed, use cudf.Index directly with a dtype to construct the index instead.

Old behavior:

In [35]: cudf.Int8Index([0, 1, 2])
Out[35]: Int8Index([0, 1, 2], dtype='int8')

New behavior:

In [36]: cudf.Index([0, 1, 2], dtype='int8')
Out[36]: Index([0, 1, 2], dtype='int8')

Change in bitwise operation results#

Bitwise operations between two objects with different indexes will now not result in boolean results.

Old behavior:

In [1]: import cudf

In [2]: import numpy as np

In [3]: s = cudf.Series([1, 2, 3])

In [4]: p = cudf.Series([10, 11, 12], index=[2, 1, 10])

In [5]: np.bitwise_or(s, p)
Out[5]:
0      True
1      True
2      True
10    False
dtype: bool

New behavior:

In [5]: np.bitwise_or(s, p)
Out[5]:
0     <NA>
1       11
2       11
10    <NA>
dtype: int64

ufuncs will perform re-indexing#

Performing a numpy ufunc operation on two objects with mismatching index will result in re-indexing:

Old behavior:

In [1]: import cudf

In [2]: df = cudf.DataFrame({"a": [1, 2, 3]}, index=[0, 2, 3])

In [3]: df1 = cudf.DataFrame({"a": [1, 2, 3]}, index=[10, 20, 3])

In [4]: import numpy as np

In [6]: np.add(df, df1)
Out[6]:
   a
0  2
2  4
3  6

New behavior:

In [6]: np.add(df, df1)
Out[6]:
       a
0   <NA>
2   <NA>
3      6
10  <NA>
20  <NA>

DataFrame vs Series comparisons need to have matching index#

Going forward any comparison between DataFrame & Series objects will need to have matching axes, i.e., the column names of DataFrame should match index of Series:

Old behavior:

In [1]: import cudf

In [2]: df = cudf.DataFrame({'a':range(0, 5), 'b':range(10, 15)})

In [3]: df
Out[3]:
   a   b
0  0  10
1  1  11
2  2  12
3  3  13
4  4  14

In [4]: s = cudf.Series([1, 2, 3])

In [6]: df == s
Out[6]:
       a      b      0      1      2
0  False  False  False  False  False
1  False  False  False  False  False
2  False  False  False  False  False
3  False  False  False  False  False
4  False  False  False  False  False

New behavior:

In [5]: df == s

ValueError: Can only compare DataFrame & Series objects whose columns & index are same respectively, please reindex.

In [8]: s = cudf.Series([1, 2], index=['a', 'b'])
# Create a series with matching Index to that of `df.columns` and then compare.

In [9]: df == s
Out[9]:
       a      b
0  False  False
1   True  False
2  False  False
3  False  False
4  False  False

Series.rank#

Series.rank will now throw an error for non-numeric data when numeric_only=True is passed:

Old behavior:

In [4]: s = cudf.Series(["a", "b", "c"])
   ...: s.rank(numeric_only=True)
Out[4]: Series([], dtype: float64)

New behavior:

In [4]: s = cudf.Series(["a", "b", "c"])
   ...: s.rank(numeric_only=True)
TypeError: Series.rank does not allow numeric_only=True with non-numeric dtype.

Value counts sets the results name to count/proportion#

In past versions, when running Series.value_counts(), the result would inherit the original object’s name, and the result index would be nameless. This would cause confusion when resetting the index, and the column names would not correspond with the column values. Now, the result name will be 'count' (or 'proportion' if normalize=True).

Old behavior:

In [3]: cudf.Series(['quetzal', 'quetzal', 'elk'], name='animal').value_counts()

Out[3]:
quetzal    2
elk        1
Name: animal, dtype: int64

New behavior:

In [3]: pd.Series(['quetzal', 'quetzal', 'elk'], name='animal').value_counts()

Out[3]:
animal
quetzal    2
elk        1
Name: count, dtype: int64

DataFrame.describe will include datetime data by default#

Previously by default (i.e., datetime_is_numeric=False) describe would not return datetime data. Now this parameter is inoperative will always include datetime columns.

Old behavior:

In [4]: df = cudf.DataFrame(
   ...:             {
   ...:                 "int_data": [1, 2, 3],
   ...:                 "str_data": ["hello", "world", "hello"],
   ...:                 "float_data": [0.3234, 0.23432, 0.0],
   ...:                 "timedelta_data": cudf.Series(
   ...:                     [1, 2, 1], dtype="timedelta64[ns]"
   ...:                 ),
   ...:                 "datetime_data": cudf.Series(
   ...:                     [1, 2, 1], dtype="datetime64[ns]"
   ...:                 ),
   ...:             }
   ...:         )
   ...:

In [5]: df
Out[5]:
   int_data str_data  float_data            timedelta_data                 datetime_data
0         1    hello     0.32340 0 days 00:00:00.000000001 1970-01-01 00:00:00.000000001
1         2    world     0.23432 0 days 00:00:00.000000002 1970-01-01 00:00:00.000000002
2         3    hello     0.00000 0 days 00:00:00.000000001 1970-01-01 00:00:00.000000001

In [6]: df.describe()
Out[6]:
       int_data  float_data             timedelta_data
count       3.0    3.000000                          3
mean        2.0    0.185907  0 days 00:00:00.000000001
std         1.0    0.167047            0 days 00:00:00
min         1.0    0.000000  0 days 00:00:00.000000001
25%         1.5    0.117160  0 days 00:00:00.000000001
50%         2.0    0.234320  0 days 00:00:00.000000001
75%         2.5    0.278860  0 days 00:00:00.000000001
max         3.0    0.323400  0 days 00:00:00.000000002

New behavior:

In [6]: df.describe()
Out[6]:
       int_data  float_data             timedelta_data                  datetime_data
count       3.0    3.000000                          3                              3
mean        2.0    0.185907  0 days 00:00:00.000000001  1970-01-01 00:00:00.000000001
min         1.0    0.000000  0 days 00:00:00.000000001  1970-01-01 00:00:00.000000001
25%         1.5    0.117160  0 days 00:00:00.000000001  1970-01-01 00:00:00.000000001
50%         2.0    0.234320  0 days 00:00:00.000000001  1970-01-01 00:00:00.000000001
75%         2.5    0.278860  0 days 00:00:00.000000001  1970-01-01 00:00:00.000000001
max         3.0    0.323400  0 days 00:00:00.000000002  1970-01-01 00:00:00.000000002
std         1.0    0.167047            0 days 00:00:00                           <NA>

Converting a datetime string with Z to timezone-naive dtype is not allowed.#

Previously when a date that had Z at the trailing end was allowed to be type-casted to datetime64 type, now that will raise an error.

Old behavior:

In [11]: s = cudf.Series(np.datetime_as_string(np.arange("2002-10-27T04:30", 10 * 60, 1, dtype="M8[m]"),timezone="UTC"))

In [12]: s
Out[12]:
0      2002-10-27T04:30Z
1      2002-10-27T04:31Z
2      2002-10-27T04:32Z
3      2002-10-27T04:33Z
4      2002-10-27T04:34Z
             ...
595    2002-10-27T14:25Z
596    2002-10-27T14:26Z
597    2002-10-27T14:27Z
598    2002-10-27T14:28Z
599    2002-10-27T14:29Z
Length: 600, dtype: object

In [13]: s.astype('datetime64[ns]')
Out[13]:
0     2002-10-27 04:30:00
1     2002-10-27 04:31:00
2     2002-10-27 04:32:00
3     2002-10-27 04:33:00
4     2002-10-27 04:34:00
              ...
595   2002-10-27 14:25:00
596   2002-10-27 14:26:00
597   2002-10-27 14:27:00
598   2002-10-27 14:28:00
599   2002-10-27 14:29:00
Length: 600, dtype: datetime64[ns]

New behavior:

In [13]: s.astype('datetime64[ns]')

*** NotImplementedError: cuDF does not yet support timezone-aware datetimes casting

Datetime & Timedelta reduction operations will preserve their time resolutions.#

Previously reduction operations on datetime64 & timedelta64 types used to result in lower-resolution results. Now the original resolution is preserved:

Old behavior:

In [14]: sr = cudf.Series([10, None, 100, None, None], dtype='datetime64[us]')

In [15]: sr
Out[15]:
0    1970-01-01 00:00:00.000010
1                          <NA>
2    1970-01-01 00:00:00.000100
3                          <NA>
4                          <NA>
dtype: datetime64[us]

In [16]: sr.std()
Out[16]: Timedelta('0 days 00:00:00.000063639')

New behavior:

In [16]: sr.std()
Out[16]: Timedelta('0 days 00:00:00.000063')

get_dummies default return type is changed from int8 to bool#

The default return values of get_dummies will be boolean instead of int8

Old behavior:

In [2]: s = cudf.Series([1, 2, 10, 11, None])

In [6]: cudf.get_dummies(s)
Out[6]:
   1   2   10  11
0   1   0   0   0
1   0   1   0   0
2   0   0   1   0
3   0   0   0   1
4   0   0   0   0

New behavior:

In [3]: cudf.get_dummies(s)
Out[3]:
      1      2      10     11
0   True  False  False  False
1  False   True  False  False
2  False  False   True  False
3  False  False  False   True
4  False  False  False  False

reset_index will name columns as None when name=None#

reset_index used to name columns as 0 or self.name if name=None. Now, passing name=None will name the column as None exactly.

Old behavior:

In [2]: s = cudf.Series([1, 2, 3])

In [4]: s.reset_index(name=None)
Out[4]:
   index  0
0      0  1
1      1  2
2      2  3

New behavior:

In [7]: s.reset_index(name=None)
Out[7]:
   index  None
0      0     1
1      1     2
2      2     3

Fixed an issue where duration components were being incorrectly calculated#

Old behavior:

In [18]: sr = cudf.Series([136457654736252, 134736784364431, 245345345545332, 223432411, 2343241, 3634548734, 23234], dtype='timedelta64[ms]')

In [19]: sr
Out[19]:
0    1579371 days 00:05:36.252
1    1559453 days 12:32:44.431
2    2839645 days 04:52:25.332
3          2 days 14:03:52.411
4          0 days 00:39:03.241
5         42 days 01:35:48.734
6          0 days 00:00:23.234
dtype: timedelta64[ms]

In [21]: sr.dt.components
Out[21]:
    days  hours  minutes  seconds  milliseconds  microseconds  nanoseconds
0  84843      3        3       40           285           138          688
1  64925     15       30       48           464           138          688
2  64093     10       23        7           107           828          992
3      2     14        3       52           411             0            0
4      0      0       39        3           241             0            0
5     42      1       35       48           734             0            0
6      0      0        0       23           234             0            0

New behavior:

In [21]: sr.dt.components
Out[21]:
      days  hours  minutes  seconds  milliseconds  microseconds  nanoseconds
0  1579371      0        5       36           252             0            0
1  1559453     12       32       44           431             0            0
2  2839645      4       52       25           332             0            0
3        2     14        3       52           411             0            0
4        0      0       39        3           241             0            0
5       42      1       35       48           734             0            0
6        0      0        0       23           234             0            0

fillna on datetime/timedelta with a lower-resolution scalar will now type-cast the series#

Previously, when fillna was performed with a higher-resolution scalar than the series, the resulting resolution would have been cast to the higher resolution. Now the original resolution is preserved.

Old behavior:

In [22]: sr = cudf.Series([1000000, 200000, None], dtype='timedelta64[s]')

In [23]: sr
Out[23]:
0    11 days 13:46:40
1     2 days 07:33:20
2                <NA>
dtype: timedelta64[s]

In [24]: sr.fillna(np.timedelta64(1,'ms'))
Out[24]:
0       11 days 13:46:40
1        2 days 07:33:20
2    0 days 00:00:00.001
dtype: timedelta64[ms]

New behavior:

In [24]: sr.fillna(np.timedelta64(1,'ms'))
Out[24]:
0    11 days 13:46:40
1     2 days 07:33:20
2     0 days 00:00:00
dtype: timedelta64[s]

Groupby.nth & Groupby.dtypes will have the grouped column in result#

Previously, Groupby.nth & Groupby.dtypes would set the grouped columns as Index object. Now the new behavior will actually preserve the original objects Index and return the grouped columns too as part of the result.

Old behavior:

In [31]: df = cudf.DataFrame(
    ...:         {
    ...:             "a": [1, 1, 1, 2, 3],
    ...:             "b": [1, 2, 2, 2, 1],
    ...:             "c": [1, 2, None, 4, 5],
    ...:             "d": ["a", "b", "c", "d", "e"],
    ...:         }
    ...:     )
    ...:

In [32]: df
Out[32]:
   a  b     c  d
0  1  1     1  a
1  1  2     2  b
2  1  2  <NA>  c
3  2  2     4  d
4  3  1     5  e

In [33]: df.groupby('a').nth(1)
Out[33]:
   b  c  d
a
1  2  2  b

In [34]: df.groupby('a').dtypes
Out[34]:
       b      c       d
a
1  int64  int64  object
2  int64  int64  object
3  int64  int64  object

New behavior:

In [33]: df.groupby('a').nth(1)
Out[33]:
   a  b    c  d
1  1  2  2.0  b

In [34]: df.groupby('a').dtypes
Out[34]:
       a      b      c       d
a
1  int64  int64  int64  object
2  int64  int64  int64  object
3  int64  int64  int64  object