我想找到一种更有效的方法(就峰值内存使用量和可能的时间而言)来完成熊猫groupby.ngroup
的工作,以便在处理大型数据集时不遇到内存问题(我请提供以下原因,说明为什么本专栏对我有用。以小数据集为例。我可以使用groupby.ngroup
轻松完成此任务。
import pandas as pd
import numpy as np
df = pd.DataFrame(np.array(
[[0, 1, 92],
[0, 0, 39],
[0, 0, 32],
[1, 0, 44],
[1, 1, 50],
[0, 1, 11],
[0, 0, 14]]), columns=['male', 'edu', 'wage'])
df['group_id'] = df.groupby(['male', 'edu']).ngroup()
df
male edu wage group_id
0 0 1 92 1
1 0 0 39 0
2 0 0 32 0
3 1 0 44 2
4 1 1 50 3
5 0 1 11 1
6 0 0 14 0
但是,当我开始使用更大的数据集时,N=100,000,000
的内存使用量和计算时间便爆炸了,并且groupby中的内存使用量与数据帧的内存使用量之比几乎是{的三倍{1}}。见下文。
N=100,000
我为什么对这个组标识符感兴趣?因为我想使用from memory_profiler import memory_usage
import time
N_values = [10**k for k in range(4, 9)]
stats = pd.DataFrame(index=N_values, dtype=float, columns=['time', 'basemem', 'groupby_mem'])
for N in N_values:
df = pd.DataFrame(
np.hstack([np.random.randint(0, 2, (N, 2)), np.random.normal(5, 1, (N, 1))]),
columns=['male', 'edu', 'wage']
)
def groupby_ngroup():
df.groupby(['male', 'edu']).ngroup()
def foo():
pass
basemem = max(memory_usage(proc=foo))
tic = time.time()
mem = max(memory_usage(proc=groupby_ngroup))
toc = time.time() - tic
stats.loc[N, 'basemem'] = basemem
stats.loc[N, 'groupby_mem'] = mem
stats.loc[N, 'time'] = toc
stats['mem_ratio'] = stats.eval('groupby_mem/basemem')
stats
time basemem groupby_mem mem_ratio
10000 0.037834 104.781250 105.359375 1.005517
100000 0.051785 108.187500 113.125000 1.045638
1000000 0.143642 128.156250 182.437500 1.423555
10000000 0.644650 334.148438 820.183594 2.454549
100000000 6.074531 2422.585938 7095.437500 2.928869
方法创建利用熊猫的groupby
函数(例如groupby.mean
)的列,而不是.map
,这需要大量的内存和时间。此外,groupby.transform
方法可以与.map
数据帧一起使用,因为dask
当前不支持dask
。有了.transform
的专栏,我可以简单地执行"group_id"
和means = df.groupby(['group_id'])['wage'].mean()
来完成df['mean_wage'] = df['group_id'].map(means)
的工作。
答案 0 :(得分:1)
不使用ngroup
,而是编写我们自己的函数来创建group_id
列怎么样?
这是一个似乎具有更好性能的代码段:
from memory_profiler import memory_usage
import time
import pandas as pd
import numpy as np
N_values = [10**k for k in range(4, 9)]
stats = pd.DataFrame(index=N_values, dtype=float, columns=['time', 'basemem', 'groupby_mem'])
for N in N_values:
df = pd.DataFrame(
np.hstack([np.random.randint(0, 2, (N, 2)), np.random.normal(5, 1, (N, 1))]),
columns=['male', 'edu', 'wage']
)
def groupby_ngroup():
#df.groupby(['male', 'edu']).ngroup()
df['group_id'] = 2*df.male + df.edu
def foo():
pass
basemem = max(memory_usage(proc=foo))
tic = time.time()
mem = max(memory_usage(proc=groupby_ngroup))
toc = time.time() - tic
stats.loc[N, 'basemem'] = basemem
stats.loc[N, 'groupby_mem'] = mem
stats.loc[N, 'time'] = toc
stats['mem_ratio'] = stats.eval('groupby_mem/basemem')
stats
time basemem groupby_mem mem_ratio
10000 0.117921 2370.792969 79.761719 0.033643
100000 0.026921 84.265625 84.324219 1.000695
1000000 0.067960 130.101562 130.101562 1.000000
10000000 0.220024 308.378906 536.140625 1.738577
100000000 0.751135 2367.187500 3651.171875 1.542409
从本质上讲,我们使用列为数字的事实并将其视为二进制数。 group_id
是十进制等效项。
将其缩放为三列可获得相似的结果。为此,将数据帧初始化替换为以下内容:
df = pd.DataFrame(
np.hstack([np.random.randint(0, 2, (N, 3)), np.random.normal(5, 1, (N, 1))]),
columns=['male', 'edu','random1', 'wage']
)
和group_id函数用于:
def groupby_ngroup():
df['group_id'] = 4*df.male + 2*df.edu + df.random1
以下是该测试的结果:
time basemem groupby_mem mem_ratio
10000 0.050006 78.906250 78.980469 1.000941
100000 0.033699 85.007812 86.339844 1.015670
1000000 0.066184 147.378906 147.378906 1.000000
10000000 0.322198 422.039062 691.179688 1.637715
100000000 1.233054 3167.921875 5183.183594 1.636146
答案 1 :(得分:0)
让我们尝试使用hash
list(map(hash,df.to_records().tolist()))
[4686582722376372986, 3632587615391525059, 2578593961740479157, -48845846747569345, 2044051356115000853, -583388452461625474, -1637380652526859201]
答案 2 :(得分:0)
对于groupby变量具有未知模式的groupby,看来groupby.ngroup
可能会和它一样好。但是,如果您的groupby变量都是类别变量,例如采用值0,1,2,3....
,那么我们可以从@saurjog
给出的解决方案中获得启发。
要生成组ID,我们可以构建一个数值表达式,用于计算groupby变量的特殊总和。考虑以下功能
def gen_groupby_numexpr(cols, numcats):
txt = [cols[0]]
k = numcats[0]
for c,k_ in zip(cols[1:], numcats[1:]):
txt.append('{}*{}'.format(k, c))
k = k*k_
return ' + '.join(txt)
def ngroup_cat(df, by, numcats):
'''
by : list
the categorical (0,1,2,3...) groupby column names
numcats : list
the number of unique values for each column in "by"
'''
expr = gen_groupby_numexpr(by, numcats)
return df.eval(expr)
函数gen_groupby_numexpr
生成数值表达式,ngroup_cat
生成by
中具有唯一值计数numcats
的groupby变量的组ID。因此,请考虑以下符合我们用例的数据集。它包含3个分类变量,我们将使用它们来构成分组依据,其中两个在{0,1}
中使用值,而另一个在{0,1,2}
中使用值。
df2 = pd.DataFrame(np.hstack([np.random.randint(0, 2, (100, 2)),
np.random.randint(0, 3, (100, 1)),
np.random.randint(0, 20, (100, 1))]),
columns=['male', 'mar', 'edu', 'wage'])
如果我们生成数字表达式,我们将得到:
'male + 2*mar + 4*edu'
总而言之,我们可以使用
生成组ID。df2['group_id'] = ngroup_cat(df2, ['male', 'mar', 'edu'], [2, 2, 3])
从中我们获得2*2*3=12
个唯一的组ID:
df2[['male', 'mar', 'edu', 'group_id']].drop_duplicates().sort_values(['group_id'])
male mar edu group_id
1 0 0 0 0
13 1 0 0 1
8 0 1 0 2
10 1 1 0 3
4 0 0 1 4
12 1 0 1 5
2 0 1 1 6
6 1 1 1 7
7 0 0 2 8
5 1 0 2 9
44 0 1 2 10
0 1 1 2 11
当我将上述解决方案与groupby.ngroup
进行比较时,它在N=10,000,000
的数据集上运行的速度快将近3倍,并且使用的额外内存大大减少。
现在,我们可以估算这些groupby平均值,然后将它们映射回整个数据帧以完成变换工作。对于使用transform
还是groupby
而言,我计算出一些混合结果的基准,然后map
更快,内存占用更少。如果您正在计算包含多个变量的组的均值,那么我认为后者会更有效。此外,后者也可以在尚不支持dask
的{{1}}中完成。