使用Pandas向量化代替循环处理两个数据框。

4

我有两个数据框。 我的主要数据框是 dffinal

        date  id  och  och1  och2  och3  cch1  LCH  L#
0  3/27/2020   1 -2.1     3     3     1     5  NaN NaN
1   4/9/2020   2  2.0     1     2     1     3  NaN NaN

我的第二个数据框是 df2

        date  och  cch  och1  och2  och3  cch1
0  5/30/2012 -0.7 -0.7     3    -1     1    56
1  9/16/2013  0.9 -1.0     6     4     3     7
2  9/26/2013  2.5  5.4     2     3     2     4
3  8/26/2016  0.1 -0.7     4     3     5    10

我有这个循环

for i in dffinal.index:    
    df3=df2.copy()
    
    df3 = df3[df3['och1'] >dffinal['och1'].iloc[i]]
    df3 = df3[df3['och2'] >dffinal['och2'].iloc[i]]
    df3 = df3[df3['och3'] >dffinal['och3'].iloc[i]]    
    
    df3 = df3[df3['cch1'] >dffinal['cch1'].iloc[i]]     
    
    dffinal['LCH'][i] =df3["och"].mean()
    dffinal['L#'][i] =len(df3.index)

从我的代码中可以看出,LCH和L#的值是根据上述条件从df2(df3)获得的。

这段代码可以正常工作,但速度非常慢。我发现可以使用pandas向量化来提高效率。然而,我无法弄清如何在我的情况下实现它。

这是我的期望结果。

        date  id  och  och1  och2  och3  cch1       LCH   L#
0  3/27/2020   1 -2.1     3     3     1     5  0.900000  1.0
1   4/9/2020   2  2.0     1     2     1     3  1.166667  3.0

如果您能帮助我提高代码的效率,我将非常感激。

正确答案

我个人使用@shadowtalker的答案简单方法,因为我能够理解它的工作原理。

最有效的答案是快速但复杂


1
如果您能以CSV或JSON格式发布数据,那将非常有帮助,这样人们就可以轻松加载并测试他们的答案。固定宽度不太理想。 - shadowtalker
另外,diffinal是如何定义的? - shadowtalker
@shadowtalker 抱歉,我是根据这个指南 https://dev59.com/O2Ij5IYBdhLWcg3wk182#20159305 来尝试解决问题的。不确定我是否正确理解了你的意思。diffinal 只是我的第一个数据框。 - Bogdan Titomir
1
请参见 include a minimal data frame 以了解如何在代码中包含数据框。让他人帮助您变得更加容易。 - Prune
@Prune,谢谢,下次我会把这个作为我的主要指南来发布问题。 - Bogdan Titomir
2
这只是一个支持项目。请继续使用 主题相关提问方式 中的内容,来自 入门指南 - Prune
4个回答

3

我所能想到的,不用循环而只使用Pandas方法的方式,是在重置索引后进行交叉连接并与df.all(1)进行比较。

cols = ['och1','och2','och3','cch1']
u = df2.reset_index().assign(k=1).merge(
    dffinal.reset_index().assign(k=1),on='k',suffixes=('','_y'))
#for new Version of pandas there is a how='cross' included now

dffinal['NewLCH'] = (u[u[cols].gt(u[[f"{i}_y" for i in cols]].to_numpy()).all(1)]
                     .groupby("index_y")['och'].mean())

print(dffinal)

        date  id  och  och1  och2  och3  cch1  LCH  L#    NewLCH
0  3/27/2020   1 -2.1     3     3     1     5  NaN NaN  0.900000
1   4/9/2020   2  2.0     1     2     1     3  NaN NaN  1.166667

我尝试了你的代码,但是出现了错误,提示“无法为形状为(5358055840,)和数据类型int64的数组分配39.9 GiB”,我理解这意味着该代码需要40 GiB的内存。或者我做错了什么。 - Bogdan Titomir
1
@BogdanTitomir 是的,我不建议在大数据框中使用交叉连接(它会占用很多空间),可以在重置索引之前仅选择相关列(cols和och)并尝试。 - anky
1
我觉得这个方法对我的大脑来说太复杂了:) 但无论如何还是谢谢你! - Bogdan Titomir

3

使用现有逻辑从df2中选择行子集可能非常难以避免迭代,但您应该能够使用以下方法加速迭代方法(希望速度可以快很多)。

注意:如果您反复访问正在迭代的数据框的行,请使用.iterrows,这样您就可以更简单(和更快速)地获取内容。

for i,row in dffinal.iterrows():
    och_array = df2.loc[(df3['och1'] >row['och1']) &\
          (df2['och2'] >row['och2']) &\
          (df2['och3'] >row['och3']) &\   
          (df2['cch1'] >row['cch1']),'och'].values
    dffinal.at[i,'LCH'] = och_array.mean()
    dffinal.at[i,'L#'] = len(och_array)

这样做可以避免在dffinal中查找,避免多次创建df的新副本。如果没有数据样本,无法测试,但我认为这将有效。

谢谢,显然在我的情况下无法使用向量化。我尝试了你的代码,但是出现了这个错误“None of [Index(['LCH'], dtype='object')] are in the [columns]”。我使用了以下代码:for i,row in dffinal.iterrows(): df_stats = df2.loc[(df2['och1'] >row['och1']) & (df2['och2'] >row['och2']) & (df2['och3'] >row['och3']) & (df2['cch1'] >row['cch1']),['LCH']].mean() dffinal.at[i,'LCH'] = df_stats['LCH'] - Bogdan Titomir
1
请注意,itertuples 应该比 iterrows 更快,并且可能更加“dtype-safe”。 - shadowtalker
@shadowtalker,你能给我展示一下例子吗?我以前从未尝试过itertuples。 - Bogdan Titomir
1
@BogdanTitomir 我修改了代码,我没有仔细阅读你如何计算LCH和L#,我认为现在应该可以工作了。 - Clay Shwery
1
谢谢,你的代码完美地运行了,并且显著提高了我的代码性能。我选择@shadowtalker的答案作为正确答案,仅仅是因为它稍微快一些。不幸的是,我只能选择一个答案作为正确答案。 - Bogdan Titomir

3
本答案基于 https://dev59.com/lMHqa4cB1Zd3GeqPv0Pd#68197271 ,但使用了itertuples代替iterrows。通常情况下,itertuplesiterrows更安全,因为它可以正确地保留dtypes。请参阅DataFrame.iterrows文档中的“注”部分。
此外,它是自包含的,可以从上到下执行,无需复制/粘贴数据等操作。
请注意,我遍历的是df1.itertuples而不是df_final.itertuples永远不要改变你正在遍历的内容,也永远不要遍历你正在改变的内容。原位修改DataFrame是一种改变。
import io

import pandas as pd


data1_txt = """
     date  id  och  och1  och2  och3  cch1  LCH  L#
3/27/2020   1 -2.1     3     3     1     5  NaN NaN
4/9/2020   2  2.0     1     2     1     3  NaN NaN
"""

data2_txt = """
     date  och  cch  och1  och2  och3  cch1
5/30/2012 -0.7 -0.7     3    -1     1    56
9/16/2013  0.9 -1.0     6     4     3     7
9/26/2013  2.5  5.4     2     3     2     4
8/26/2016  0.1 -0.7     4     3     5    10
"""

df1 = pd.read_fwf(io.StringIO(data1_txt), index_col='id')
df2 = pd.read_fwf(io.StringIO(data2_txt))

df_final = df1.copy()

for row in df1.itertuples():
    row_mask = (
        (df2['och1'] > row.och1) &
        (df2['och2'] > row.och2) &
        (df2['och3'] > row.och3) &
        (df2['cch1'] > row.cch1)
    )
    och_vals = df2.loc[row_mask, 'och']
    i = row.Index
    df_final.at[i, 'LCH'] = och_vals.mean()
    df_final.at[i, 'L#'] = len(och_vals)

print(df_final)

输出结果是:
         date  och  och1  och2  och3  cch1  LCH  L#       LCH   L#
id                                                                
1   3/27/2020 -2.1     3     3     1     5  NaN NaN  0.900000  1.0
2    4/9/2020  2.0     1     2     1     3  NaN NaN  1.166667  3.0

1
我之前在这里回答问题的时间并不多,但是这周我一定会使用StringIO技术将打印语句输出从问题中转移到我的笔记本上。还要感谢您展示了这个例子。我对于优化的猜测是,iterrows与itertuples之间的差异不会很大,因为大部分计算都在从df2中选择正确的行,但是看到这个实现方式还是很酷的! - Clay Shwery
2
很好。itertuples 的主要优点不在于速度,而是保证数据类型安全。请参阅 https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.iterrows.html 中的“注意事项”部分。 - shadowtalker
我该如何添加一个新行 df_final.at[i, 'SumPosNeg']=,其值应等于所有正的 och 值之和除以所有负的 och 值之和或者 sum(och>0)/sum(och<0) - Bogdan Titomir
新列的结果应该等于2.0/(-2.1)=-0.95。我从最终结果数据框中得到了2.0和2.1。 - Bogdan Titomir
@BogdanTitomir,花些时间理解我的和Clay的答案可能是值得的,这样你就可以根据需要添加自己的扩展或修改。 - shadowtalker
@shadowtalker,谢谢,我已经搞定了。你的代码是最容易理解的,我决定采用你的解决方案。 - Bogdan Titomir

2

这里有一种解决问题的方法

def fast(A, B):
    for a in A:
        m = (B[:, 1:] > a[1:]).all(1)
        yield B[m, 0].mean(), m.sum()

c = ['och', 'och1', 'och2', 'och3', 'cch1']
df1[['LCH', 'L#']] = list(fast(df1[c].to_numpy(), df2[c].to_numpy()))

        date  id  och  och1  och2  och3  cch1       LCH  L#
0  3/27/2020   1 -2.1     3     3     1     5  0.900000   1
1   4/9/2020   2  2.0     1     2     1     3  1.166667   3

非常感谢,这段代码完成了任务,而且比我的原始代码快得多,但令人惊讶的是比其他答案稍微慢了一点。这段代码看起来非常复杂,我本来以为它会比其他答案更快。但也许是我做错了什么,弄坏了代码 :) - Bogdan Titomir
1
@BogdanTitomir 不确定问题是什么,但在我的测试中,我发现这个速度要快 5-6 倍。顺便问一下,数据框 df1df2 的形状是什么? - Shubham Sharma
测试样本 df1 共有 12,000 行。df2 共有 450,000 行。 - Bogdan Titomir
1
谢谢,原来这是最快的方法。我选择了这个作为正确答案,但可能我会选择其他方法,因为这个方法对我来说感觉太复杂了。 - Bogdan Titomir
1
很高兴它对你有用。虽然还有一些优化可能是可行的,但那只会让代码更加复杂。 - Shubham Sharma
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接