首页 > 解决方案 > python cuDF groupby 适用于有序数据

问题描述

我有一些有序数据,其中有事件层次结构。每列是一个事件的唯一 id,相对于层次结构中它上面的事件。类似于每天的数字在一个月中是唯一的,而每个月的数字在一年中是唯一的。我想让最低级别在最高级别中是唯一的,例如通过从 1 到 365 编号使一年中的每一天都是唯一的。我的用例并不特定于天、月和年。

前:

| ID | EVENT_1 | EVENT_2 | EVENT_3 |
| -- | ------- | ------- | ------- |
|  1 |       1 |       1 |       1 |
|  1 |       1 |       1 |       2 |
|  1 |       1 |       1 |       3 |
|  1 |       1 |       2 |       1 |
|  1 |       1 |       2 |       2 |
|  1 |       1 |       3 |       1 |
|  1 |       1 |       3 |       2 |
|  1 |       2 |       1 |       1 |
|  1 |       2 |       1 |       2 |

后:

| ID | EVENT_1 | EVENT_2 | EVENT_3 | EVENT_3A |
| -- | ------- | ------- | ------- | -------- |
|  1 |       1 |       1 |       1 |        1 |
|  1 |       1 |       1 |       2 |        2 |
|  1 |       1 |       1 |       3 |        3 |
|  1 |       1 |       2 |       1 |        4 |
|  1 |       1 |       2 |       2 |        5 |
|  1 |       1 |       3 |       1 |        6 |
|  1 |       1 |       3 |       2 |        7 |
|  1 |       2 |       1 |       1 |        1 |
|  1 |       2 |       1 |       2 |        2 |

目标是获得一个列,其中对于每个 id,都有一个 EVENT_3A,使得 EVENT_3A 是 EVENT_3 相对于 EVENT_1 发生的顺序(好像没有 EVENT_2)。此外,还有许多 ID 必须单独计算。现在我正在 CPU 上执行此操作,但需要很长时间,所以我想切换到 GPU 上执行此操作。

我的主要想法是做一个groupby('ID').apply_grouped()orgroupby('ID').agg()但我不知道在apply_grouped()oragg()函数中放什么。我之前在 CPU 上使用 dask 执行此操作,但它更直观,因为分组的 DataFrame 直接传递给apply()函数。似乎在 cuDF 中我必须通过 incols 并且我无法弄清楚如何将它们视为 DataFrame。

大约有 5,000 个 ID,因此理想情况下,每个分组的 ID 都将由 GPU 中的一个核心处理,但我不确定它是否可以这样工作,因为我是 GPU 编程的新手。

任何建议或解决方案都有帮助,谢谢。

标签: pythondata-sciencecudf

解决方案


目标是获得一个列,其中对于每个 id,都有一个 EVENT_3A,使得 EVENT_3A 是 EVENT_3 相对于 EVENT_1 发生的顺序(好像没有 EVENT_2)。

您所描述的是一个 groupby 累积计数操作,其键为 [ID, EVENT_1]。它尚未在 cuDF 中实现,因此您需要使用用户定义的函数。例如:

您的设置:

import cudf
from numba import cuda
import numpy as np
​
data = {
    "ID":[1,1,1,1,1,1,1,1,1],
    "EVENT_1":[1,1,1,1,1,1,1,2,2,],
    "EVENT_2":[1,1,1,2,2,3,3,1,1],
    "EVENT_3":[1,2,3,1,2,1,2,1,2]
}

​
gdf = cudf.DataFrame(data)
print(gdf)
   ID  EVENT_1  EVENT_2  EVENT_3
0   1        1        1        1
1   1        1        1        2
2   1        1        1        3
3   1        1        2        1
4   1        1        2        2
5   1        1        3        1
6   1        1        3        2
7   1        2        1        1
8   1        2        1        2

我们可以而且应该apply_grouped在这里使用。我鼓励您查看文档以完全了解这里发生了什么,但在较高级别上,我们可以使用组内线程索引作为该行的索引作为计数。我们传递EVENT_3列,因此我们确保列名和函数参数匹配。

def cumcount(EVENT_3, cumcount):
    for i in range(cuda.threadIdx.x, len(EVENT_3), cuda.blockDim.x):
        cumcount[i] = i + 1 # since your exmaple counts start with 1 rather than 0


results = gdf.groupby(["ID", "EVENT_1"]).apply_grouped(cumcount,
                               incols=['EVENT_3'],
                               outcols=dict(cumcount=np.int32))

print(results.sort_index()) # get the original row order, for demonstration
   ID  EVENT_1  EVENT_2  EVENT_3  cumcount
0   1        1        1        1         1
1   1        1        1        2         2
2   1        1        1        3         3
3   1        1        2        1         4
4   1        1        2        2         5
5   1        1        3        1         6
6   1        1        3        2         7
7   1        2        1        1         1
8   1        2        1        2         2

作为健全性检查,您可以证明这些结果与更大数据上的 pandas 匹配。

n_ids = 5000
n_rows = 10000000
​
df = pd.DataFrame({
    "ID": np.random.choice(range(n_ids), n_rows),
    "EVENT_1": np.random.choice(range(500), n_rows),
    "EVENT_2": np.random.choice(range(500), n_rows),
    "EVENT_3": np.random.choice(range(n_ids), n_rows)
})

gdf = cudf.from_pandas(df)
results = gdf.groupby(["ID", "EVENT_1"]).apply_grouped(cumcount,
                               incols=['EVENT_3'],
                               outcols=dict(cumcount=np.int32))
results = results.sort_index()

pdf_res = df.groupby(["ID", "EVENT_1"]).EVENT_3.cumcount() + 1
print(pdf_res.astype("int32").equals(results['cumcount'].to_pandas()))
True

请注意,df.groupby([ID, EVENT_1]).EVENT_3.cumcount() + 1如果您有 < 100 万行和合理数量的组,在 pandas 中使用可能会非常快,因为 groupby cumcount 相当有效。话虽如此,cuDF UDF在规模上会更快


推荐阅读