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 相同的数据结构和操作。这包括 Series、DataFrame、Index 以及对它们的操作,如一元和二元操作、索引、过滤、连接、合并、分组和窗口操作等。
检查我们是否支持特定的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的Series、DataFrame或Index进行迭代。这是因为迭代驻留在GPU上的数据将产生极其差的性能,因为GPU是为高度并行操作而不是顺序操作优化的。
在绝大多数情况下,可以避免迭代并使用现有函数或方法来完成相同的任务。如果你绝对必须迭代,请使用.to_arrow()或.to_pandas()将数据从GPU复制到CPU,然后使用.from_arrow()或.from_pandas()将结果复制回GPU。
结果排序#
在Pandas中,join(或merge)、value_counts和groupby操作提供了关于返回结果中行顺序的某些保证。在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),该函数可以包含应用于Series、DataFrame或分组操作中每个值的任意操作。cuDF也支持.apply(),但它依赖于Numba来JIT编译UDF并在GPU上执行。这可以非常快,但对UDF中允许的操作施加了一些限制。有关详细信息,请参阅UDFs的文档。