python cuDF groupby применить с упорядоченными данными

У меня есть упорядоченные данные, в которых есть иерархия событий. Каждый столбец представляет собой уникальный идентификатор события по отношению к событию над ним в иерархии. Это похоже на то, как каждый номер дня уникален в месяце, а каждый номер месяца уникален в году. Я хочу, чтобы самый низкий уровень был уникальным в пределах самого высокого уровня, например, чтобы каждый день был уникальным в году с помощью нумерации от 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 |

Цель состоит в том, чтобы получить столбец, в котором для каждого идентификатора существует EVENT_3A, так что EVENT_3A - это порядок, в котором EVENT_3 происходит по отношению к EVENT_1 (как если бы не было EVENT_2). Кроме того, существует множество идентификаторов, для которых это необходимо вычислять независимо. Сейчас я делаю это на процессоре, но это занимает много времени, поэтому я хотел бы переключиться на выполнение этого на графическом процессоре.

Моя основная идея - сделать groupby('ID').apply_grouped() или groupby('ID').agg(), но я не знаю, что добавить в функции apply_grouped() или agg(). Раньше я делал это с помощью dask на ЦП, но это было более интуитивно понятно, потому что сгруппированный DataFrame передавался непосредственно в функцию apply(). Кажется, что в cuDF мне нужно передать incols, и я не могу понять, как рассматривать их как DataFrame.

Существует около 5000 идентификаторов, поэтому в идеале каждый сгруппированный идентификатор будет обрабатываться ядром в графическом процессоре, но я не уверен, может ли это работать так, поскольку я новичок в программировании для графического процессора.

Любые предложения или решения полезны, спасибо.


person Kyle    schedule 29.11.2020    source источник


Ответы (1)


Цель состоит в том, чтобы получить столбец, в котором для каждого идентификатора существует EVENT_3A, так что EVENT_3A - это порядок, в котором EVENT_3 происходит по отношению к EVENT_1 (как если бы не было EVENT_2).

Вы описываете операцию группового кумулятивного подсчета с ключами [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

В качестве проверки вы можете доказать, что эти результаты соответствуют пандам на больших данных.

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 в пандах, вероятно, будет довольно быстрым, если у вас есть ‹1 миллион строк и разумное количество групп, поскольку groupby cumcount довольно эффективен. С учетом сказанного, cuDF UDF будет намного быстрее масштабироваться.

person Nick Becker    schedule 30.11.2020
comment
Большое спасибо, это сработало отлично и привело к огромному увеличению скорости - person Kyle; 30.11.2020