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 中常用的数据类型,包括数值、日期时间、时间戳、字符串和分类数据类型。此外,我们还支持用于小数、列表和“结构”值的特殊数据类型。详情请参阅 数据类型 部分。

请注意,我们不支持像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中,连接键的顺序(取决于执行的特定连接类型)默认情况下要么保留,要么按字典顺序排序。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 选项,或者在尝试与 Pandas 行为匹配时启用 mode.pandas_compatible 选项,并使用 sort=False

>>> 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是一个浮点数系列时,s.sum()不能保证与Pandas产生相同的结果,也不能保证每次运行都产生相同的结果。如果你需要比较浮点数结果,通常应该使用cudf.testing模块中提供的函数来进行比较,这些函数允许你比较到所需的精度。

列名#

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

将DataFrame写入Parquet文件时使用非字符串列名#

当DataFrame的列名不是字符串时,pandas在写入Parquet文件之前会将每个列名转换为str。默认情况下,如果尝试这样做,cudf会引发错误。然而,为了实现与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中允许的操作施加了一些限制。有关详细信息,请参阅UDFs的文档。