一种高效的内存创建列的方法,该列指示一组列中值的唯一组合

2024-10-01 00:25:14 发布

您现在位置:Python中文网/ 问答频道 /正文

我想找到一种更有效的方法(在峰值内存使用率和可能的时间方面)来完成panda的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

但是当我开始使用更大的数据集时,内存使用和计算时间会爆炸,groupby中的内存使用与数据帧内存使用的比率在N=100,000,000N=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

为什么我对这个组标识符感兴趣?因为我想创建使用pandas groupby函数的列,比如使用.map方法的groupby.mean,而不是占用大量内存和时间的groupby.transform。此外,.map方法可用于dask数据帧,因为dask当前不支持.transform。对于"group_id"列,我可以简单地执行means = df.groupby(['group_id'])['wage'].mean()df['mean_wage'] = df['group_id'].map(means)来执行transform的工作。你知道吗


Tags: 数据内存iddftimestatsnpgroup
3条回答

不使用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']        
    )

组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

让我们尝试使用hash

list(map(hash,df.to_records().tolist()))
[4686582722376372986, 3632587615391525059, 2578593961740479157, -48845846747569345, 2044051356115000853, -583388452461625474, -1637380652526859201]

对于groupby中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_catby中具有唯一值计数numcats的groupby变量生成组id。因此,考虑以下与我们的用例匹配的数据集。它包含3个分类变量,我们将使用它们来形成groupby,其中两个变量在{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'

总之,我们可以用

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倍,并且使用的额外内存明显减少。你知道吗

现在我们可以通过方法来估计这些组,然后将它们映射回整个数据帧来进行转换工作。我计算了一些关于使用transform还是groupby的基准测试,结果不一,那么map更快,占用的内存更少。如果你计算的是多变量组的平均数,那么我认为后者更有效。此外,后者也可以在dask中完成,其中transform还不受支持。你知道吗

相关问题 更多 >