【问题标题】:How to speed up groupby().sum() on a dask dataframe with 5 millions of rows and 500 thousands of groups?如何在具有 500 万行和 50 万组的 dask 数据帧上加速 groupby().sum()?
【发布时间】:2022-01-15 18:05:54
【问题描述】:

我有一个数据框

  • 500 万行。
  • 一列group_id,其唯一元素数为500.000。
  • 成千上万的其他列名为 var1var2 等。var1var2、...中的每一个都只包含 0 和 1。

我想按group_id 分组,然后总结它们。为了获得更好的性能,我使用 dask。但是,这种简单的聚合速度仍然很慢。

The time spent on a dataframe with 10 columns is 6.285385847091675 seconds
The time spent on a dataframe with 100 columns is 64.9060411453247 seconds
The time spent on a dataframe with 200 columns is 150.6109869480133 seconds
The time spent on a dataframe with 300 columns is 235.77087807655334 seconds

我的真实数据集包含多达 30.000 列。我已经阅读了@Divakar 关于使用 numpy 的答案(12)。但是,前一个线程是关于计数的,而后者是关于对列求和的。

您能否详细说明一些加快这种聚合的方法?

import numpy as np
import pandas as pd
import os, time
from multiprocessing import dummy
import dask.dataframe as dd

core = os.cpu_count()
P = dummy.Pool(processes = core)

n_docs = 500000
n_rows = n_docs * 10
data = {}

def create_col(i):
    name = 'var' + str(i)
    data[name] = np.random.randint(0, 2, n_rows)

n_cols = 300
P.map(create_col, range(1, n_cols + 1))
df = pd.DataFrame(data, dtype = 'int8')
df.insert(0, 'group_id', np.random.randint(1, n_docs + 1, n_rows))
df = dd.from_pandas(df, npartitions = 3 * core) 

start = time.time()
df.groupby('group_id').sum().compute()
end = time.time()
print('The time spent on a dataframe with {} columns is'.format(n_cols), end - start, 'seconds')

【问题讨论】:

  • 真正的数据集从哪里来?
  • @JeffUK 它是通过将pd.get_dummies 应用于具有 25 列的原始数据框而生成的。
  • group_id 来自 0 到 499999?
  • @dankal444 范围从 1 到 500.000。

标签: python pandas numpy pandas-groupby


【解决方案1】:

(我在原始答案中误解了 OP,所以清除所有内容)。

我得到了改善:

  • 切换到 numpy
  • 对组和数据使用相同的 dtype (np.int32)
  • 在并行模式下使用 numba'
import numba as nb
@nb.njit('int32[:, :](int32[:, :], int_)', parallel=True)
def count_groups2(group_and_data, n_groups):
    n_cols = group_and_data.shape[1] - 1
    counts = np.zeros((n_groups, n_cols), dtype=np.int32)
    for idx in nb.prange(len(group_and_data)):
        row = group_and_data[idx]
        counts[row[0]] += row[1:]
    return counts

df = pd.DataFrame(data, dtype='int32')
group_id = np.random.randint(1, n_docs + 1, n_rows, dtype=np.int32)
df.insert(0, 'group_id', group_id)

# switching to numpy (line below) is costly
# it would be faster to work with numpy alone (no pandas)
group_and_data = df.values
count_groups2(group_and_data)
op_method(df)

    72         1    1439807.0 1439807.0      7.0      group_and_data = df.values
    73         1    1341527.0 1341527.0      6.5      count_groups2(group_and_data, n_groups=500_000)
    74         1   12043334.0 12043334.0     58.5      op_method(df)

【讨论】:

  • 这太棒了!!!在您的代码中,有一个循环for idx in range(len(group_ids))。我的电脑有 28 个内核和 56 个线程。并行for idx in range(len(group_ids))有修改吗?
  • @Akira 抱歉,faster4 只是我忘记更改的旧名称,我猜你已经让它工作了,但无论如何更新了答案
  • @Akira 它在分析函数中占用的时间百分比。我使用line_profiler 以这种形式给出结果:Line # Hits Time Per Hit % Time Line Contents
  • 将您的代码应用到我的真实数据集将计算时间从 1 小时 20 分钟 54 秒减少到 7 分钟 22 秒。我的 28 核 56 线程 CPU 是fully utilized。这是非常令人印象深刻的。再次感谢您。
  • 很高兴听到这个消息。谢谢你让我知道:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-02-21
  • 1970-01-01
  • 2022-07-22
  • 1970-01-01
  • 2021-12-04
  • 2018-12-21
  • 1970-01-01
相关资源
最近更新 更多