首页 > 解决方案 > 从 .apply() 更改为使用列表理解将一个数据帧与一列列表与另一个数据帧中的值进行比较的函数

问题描述

简单地说,我想把下面的代码改成一个不使用applyor的函数progress_apply,这样在 2000 万+行上执行性能不需要 4+ 小时。

d2['B'] = d2['C'].progress_apply(lambda x: [z for y in d1['B'] for z in y if x.startswith(z)])
d2['B'] = d2['B'].progress_apply(max)

完整问题如下:

我有两个数据框。第一个数据框有一列有 4 个类别(A、B、C、D),其中有四个不同的数字列表,我想与第二个数据框中的列进行比较,这不是第一个数据框中的列表,而是以第一个数据帧中的一个或多个值开头的单个值。因此,在执行一些列表推导以在第二个数据帧的新列中返回匹配值列表后,最后一步是获取每个列表每行的这些值的最大值:

d1 = pd.DataFrame({'A' : ['A', 'B', 'C', 'D'],
               'B' : [['84'], ['8420', '8421', '8422', '8423', '8424', '8425', '8426'], ['847', '8475'], ['8470', '8471']]})

    A   B
0   A   [84]
1   B   [8420, 8421, 8422, 8423, 8424, 8425, 8426]
2   C   [847, 8475]
3   D   [8470, 8471]

d2 = pd.DataFrame({'C' : [8420513, 8421513, 8426513, 8427513, 8470513, 8470000, 8475000]})

    C
0   8420513
1   8421513
2   8426513
3   8427513
4   8470513
5   8470000
6   8475000

我目前的代码是这样的:

from tqdm import tqdm, tqdm_notebook
tqdm_notebook().pandas()
d1 = pd.DataFrame({'A' : ['A', 'B', 'C', 'D'], 'B' : [['84'], ['8420', '8421', '8422', '8423', '8424', '8425', '8426'], ['847', '8475'], ['8470', '8471']]})
d2 = pd.DataFrame({'C' : [8420513, 8421513, 8426513, 8427513, 8470513, 8470000, 8475000]})
d2['C'] = d2['C'].astype(str)
d2['B'] = d2['C'].progress_apply(lambda x: [z for y in d1['B'] for z in y if x.startswith(z)])
d2['B'] = d2['B'].progress_apply(max)
d2

并成功返回此输出:

    C       B
0   8420513 8420
1   8421513 8421
2   8426513 8426
3   8427513 84
4   8470513 8470
5   8470000 8470
6   8475000 8475

问题在于tqdm进度条估计代码将需要4-5 小时才能在我的实际 DataFrame 上运行 2000 万行以上。我知道.apply应该避免这种情况,并且自定义函数可以更快,这样我就不必逐行进行。我通常可以更改apply为一个函数,但我正在为这个特定的函数而苦苦挣扎。我想我很遥远,但我会分享我尝试过的东西:

def func1(df, d2C, d1B):
    return df[[z for y in d1B for z in y if z in d2C]]


d2['B'] = func1(d2, d2['C'], d1['B'])
d2

使用此代码,我收到ValueError: Wrong number of items passed 0, placement implies 1并且仍然需要包含代码以获取每行每个列表的最大值。

标签: pythonpandasfunctionlambdalist-comprehension

解决方案


让我们尝试使用explode和正则表达式extract

d1e = d1['B'].explode()
regstr = '('+'|'.join(sorted(d1e)[::-1])+')'
d2['B'] = d2['C'].astype('str').str.extract(regstr)

输出:

         C     B
0  8420513  8420
1  8421513  8421
2  8426513  8426
3  8427513    84
4  8470513  8470
5  8470000  8470
6  8475000  8475

因为,.str 访问比列表理解慢

import re
regstr = '|'.join(sorted(d1e)[::-1])
d2['B'] = [re.match(regstr, i).group() for i in d2['C'].astype('str')]

时间:

from timeit import timeit
import re

d1 = pd.DataFrame({'A' : ['A', 'B', 'C', 'D'], 'B' : [['84'], ['8420', '8421', '8422', '8423', '8424', '8425', '8426'], ['847', '8475'], ['8470', '8471']]})
d2 = pd.DataFrame({'C' : [8420513, 8421513, 8426513, 8427513, 8470513, 8470000, 8475000]})
d2['C'] = d2['C'].astype(str)


def orig(d):
    d['B'] = d['C'].apply(lambda x: [z for y in d1['B'] for z in y if x.startswith(z)])
    d['B'] = d['B'].apply(max)
    return d
    
def comtorecords(d):
    d['B']=[max([z for y in d1.B for z in y if str(row[1]) .startswith(z)]) for row in d.to_records()]
    return d

def regxstracc(d):
    d1e = d1['B'].explode()
    regstr = '('+'|'.join(sorted(d1e)[::-1])+')'
    d['B'] = d['C'].astype('str').str.extract(regstr)
    return d

def regxcompre(d):
    regstr = '|'.join(sorted(d1e)[::-1])
    d['B'] = [re.match(regstr, i).group() for i in d['C'].astype('str')]
    return d


res = pd.DataFrame(
    index=[10, 30, 100, 300, 1000, 3000, 10000, 30000],
    columns='orig comtorecords regxstracc regxcompre'.split(),
    dtype=float
)

for i in res.index:
    d = pd.concat([d2]*i)
    for j in res.columns:
        stmt = '{}(d)'.format(j)
        setp = 'from __main__ import d, {}'.format(j)
        print(stmt, d.shape)
        res.at[i, j] = timeit(stmt, setp, number=100)

# res.groupby(res.columns.str[4:-1], axis=1).plot(loglog=True);
res.plot(loglog=True);

输出:

在此处输入图像描述


推荐阅读