cuDF 与 Pandas 的对比#

cuDF 是一个与 Pandas API 高度匹配的 DataFrame 库,但直接使用时,它不是 Pandas 的完全替代品。cuDF 和 Pandas 在 API 和行为上存在一些差异。本页记录了 cuDF 与 Pandas 之间的相似之处和差异。

从 v23.10.01 版本开始,cuDF 还提供了一种 pandas 加速模式(cudf.pandas),它支持 100% 的 pandas API,并且无需任何代码更改即可在 GPU 上加速 pandas 代码。请参阅cudf.pandas 文档

支持的操作#

cuDF 支持许多与 Pandas 相同的数据结构和操作。这包括 SeriesDataFrameIndex 以及它们上面的操作,例如一元和二元操作、索引、过滤、连接、合并、分组和窗口操作等等。

检查我们是否支持特定的 Pandas API 的最佳方法是搜索我们的API 文档

数据类型#

cuDF 支持 Pandas 中许多常用的数据类型,包括数值、日期时间、时间戳、字符串和分类数据类型。此外,我们还支持用于十进制、列表和“struct”值的特殊数据类型。有关详情,请参阅数据类型一节。

请注意,我们不支持像 Pandas 的 ExtensionDtype 这样的自定义数据类型。

空值(或“缺失”值)#

与 Pandas 不同,cuDF 中的所有数据类型都可为空,这意味着它们可以包含缺失值(由 cudf.NA 表示)。

>>> s = cudf.Series([1, 2, cudf.NA])
>>> s
0       1
1       2
2    <NA>
dtype: int64

在任何情况下,空值都不会被强制转换为 NaN;请对比 cuDF 和 Pandas 的行为如下

>>> s = cudf.Series([1, 2, cudf.NA], dtype="category")
>>> s
0       1
1       2
2    <NA>
dtype: category
Categories (2, int64): [1, 2]

>>> s = pd.Series([1, 2, pd.NA], dtype="category")
>>> s
0      1
1      2
2    NaN
dtype: category
Categories (2, int64): [1, 2]

有关详情,请参阅缺失数据文档。

迭代#

不支持对 cuDF SeriesDataFrameIndex 进行迭代。这是因为对驻留在 GPU 上的数据进行迭代会产生极差的性能,因为 GPU 针对高度并行操作进行了优化,而不是顺序操作。

在绝大多数情况下,可以避免迭代,并使用现有函数或方法完成相同的任务。如果您确实需要迭代,请使用 .to_arrow().to_pandas() 将数据从 GPU 复制到 CPU,然后使用 .from_arrow().from_pandas() 将结果复制回 GPU。

结果排序#

在 Pandas 中,join(或 merge)、value_countsgroupby 操作对返回结果中的行顺序提供了一定的保证。在 Pandas 的 join 中,join 键的顺序(取决于执行的 join 风格)默认要么被保留,要么按字典序排序。groupby 对分组键进行排序,并保留每组内的行顺序。在某些情况下,在 Pandas 中禁用此选项可以获得更好的性能。

相比之下,cuDF 的默认行为是以非确定性顺序返回行,以最大限度地提高性能。请对比下面从 Pandas 和 cuDF 获得的结果

>>> import cupy as cp
>>> cp.random.seed(0)
>>> import cudf
>>> df = cudf.DataFrame({'a': cp.random.randint(0, 1000, 1000), 'b': range(1000)})
>>> df.groupby("a").mean().head()
         b
a
29   193.0
803  915.0
5    138.0
583  300.0
418  613.0
>>> df.to_pandas().groupby("a").mean().head()
            b
a
0   70.000000
1  356.333333
2  770.000000
3  838.000000
4  342.000000

在大多数情况下,DataFrame 的行是通过索引标签而不是位置来访问的,因此返回行的顺序并不重要。但是,如果您要求结果以可预测的(排序的)顺序返回,您可以显式传递 sort=True 选项,或者在尝试使用 sort=False 匹配 Pandas 行为时启用 mode.pandas_compatible 选项。

>>> df.groupby("a", sort=True).mean().head()
         b
a
0   70.000000
1  356.333333
2  770.000000
3  838.000000
4  342.000000

>>> cudf.set_option("mode.pandas_compatible", True)
>>> df.groupby("a").mean().head()
            b
a
0   70.000000
1  356.333333
2  770.000000
3  838.000000
4  342.000000

浮点计算#

cuDF 利用 GPU 并行执行操作。这意味着操作的顺序并不总是确定性的。这会影响浮点操作的确定性,因为浮点算术是非结合性的,即 a + b 不等于 b + a

例如,当 s 是一个浮点数 Series 时,s.sum() 不保证产生与 Pandas 完全相同的结果,也不保证每次运行产生相同的结果。如果您需要比较浮点结果,通常应使用 cudf.testing 模块中提供的函数,这些函数允许您在所需精度范围内比较值。

列名#

与 Pandas 不同,cuDF 不支持重复的列名。最好使用唯一的字符串作为列名。

将带有非字符串列名的 DataFrame 写入 Parquet#

当 DataFrame 包含非字符串列名时,pandas 会在写入 Parquet 文件之前将每个列名强制转换为 strcudf 默认会在此操作尝试时引发错误。但是,为了实现与 pandas 类似的行为,您可以启用 mode.pandas_compatible 选项,这将使 cudf 像 pandas 一样将列名强制转换为 str

>>> import cudf
>>> df = cudf.DataFrame({1: [1, 2, 3], "1": ["a", "b", "c"]})
>>> df.to_parquet("df.parquet")

Traceback (most recent call last):
ValueError: Writing a Parquet file requires string column names
>>> cudf.set_option("mode.pandas_compatible", True)
>>> df.to_parquet("df.parquet")

UserWarning: The DataFrame has column names of non-string type. They will be converted to strings on write.

无真正的 "object" 数据类型#

在 Pandas 和 NumPy 中,"object" 数据类型用于任意 Python 对象的集合。例如,在 Pandas 中,您可以执行以下操作

>>> import pandas as pd
>>> s = pd.Series(["a", 1, [1, 2, 3]])
0            a
1            1
2    [1, 2, 3]
dtype: object

为了与 Pandas 兼容,cuDF 将字符串的数据类型报告为 "object",但我们不支持存储或操作任意 Python 对象的集合。

.apply() 函数限制#

Pandas 中的 .apply() 函数接受一个用户定义函数 (UDF),该函数可以包含应用于 SeriesDataFrame 的每个值,或者在分组的情况下应用于每组的任意操作。cuDF 也支持 .apply(),但它依赖于 Numba 来 JIT 编译 UDF 并在 GPU 上执行。这可能非常快,但对 UDF 中允许的操作有一些限制。有关详情,请参阅UDF文档。