pandas goupby方法对sum()的速度慢且效率低

0kjbasz6  于 2023-06-28  发布在  Go
关注(0)|答案(1)|浏览(147)

给出:

pandas DataFrame的合成数据集users vs. tokens,通过以下helper函数生成:

  1. import numpy as np
  2. import pandas as pd
  3. import string
  4. import random
  5. def get_df(nROWs:int=10, nCOLs:int=100, MIN=0.0, MAX=199.0):
  6. my_strings = string.printable
  7. df = pd.DataFrame(np.random.uniform(low=MIN, high=MAX, size=(nROWs, nCOLs)).astype("float16"), columns=list(map(lambda orig_string: "tk_"+orig_string, random.sample(my_strings, nCOLs))) )
  8. df["user_ip"] = [f"u{random.randint(0, nROWs)}" for r in range(nROWs)]
  9. return df

目标:

我想对分组用户的每列值求和,

我的低效解决方案:

考虑如下的小 Dataframe :

  1. df1 = get_df(nROWs=3, nCOLs=5, MIN=0, MAX=10.0) # here `nCOLs` can't exceptionally go above 100, due to `len(string.printable)=100`
  2. df2 = get_df(nROWs=5, nCOLs=4, MIN=0, MAX=5.0)
  3. df3 = get_df(nROWs=7, nCOLs=9, MIN=0, MAX=1.0)

并首先沿着axis=0连接它们:

  1. df_c = pd.concat([df1, df2, df3,], axis=0)


,那么.groupby()方法对于这个小尺寸很好:

  1. d = dict()
  2. for n, g in df_c.groupby("user_ip"):
  3. d[n] = g.loc[:, g.columns!="user_ip"].sum()
  4. df_res = pd.DataFrame.from_dict(d, orient="index").astype("float16")


问题:
假设我在一台超级计算机上有足够的内存资源,并且考虑到真实的的 Dataframe 大小为15e+5 x 45e+3或更高,它是超级慢的,因为for循环内的每个执行大约需要10 ~ 15 sec

  1. df1 = get_df(nROWs=int(15e+5), nCOLs=100, MIN=0, MAX=200.0) # here `nCOLs` can't exceptionally go above 100, due to `len(string.printable)=100`
  2. df2 = get_df(nROWs=int(3e+6), nCOLs=76, MIN=0, MAX=100.0)
  3. df3 = get_df(nROWs=int(1e+3), nCOLs=88, MIN=0, MAX=0.9)

我想知道是否有一个更好,更有效的解决方案来处理大规模的数据。
干杯

qhhrdooz

qhhrdooz1#

您正在使用for循环。这对效率来说是一个“不”字。当然,您只是在组上迭代,而不是在所有行上迭代。如果您只有几个组(在组的平均大小之前,组的数量至少可以忽略不计),这将不是问题。
但那不是你的案子。按user_ipuser_ip分组,这两个值是在等于行数的范围内的随机值。这意味着组数与行数相同63.2%(即统计信息:N个N进制值上的分布中的唯一值的数量趋向于N的63.2%)。
因此,您可以完全跳过groupby和pandas .sum,只需使用简单的纯Python代码迭代 Dataframe 的所有行,每次递增d[userip],这只会慢一点(甚至可能更快)。当然,你有1/0.632=58%的迭代要做,但它们也更简单)。我并不建议使用这种方法。恰恰相反。只是要提到这样一个事实,即您所做的与臭名昭著的“只迭代行”一样低效,这是众所周知的最低效的方法
您必须找到删除所有for循环的方法(get_df中也有一个for循环,它可以很容易地被np.random.randint(0,nRows, nRows)替换,即使这不是您所关心的)。
例如,(只是一个快速的提议,你必须检查它是否有效,也许还需要适应它)

  1. dfg = df.groupby('user_ip').sum()
  2. df_res = pd.DataFrame(dfg.values.sum(axis=1), dfg.index)

相关问题