熊猫矢量化而不是两个数据帧的循环

Posted

技术标签:

【中文标题】熊猫矢量化而不是两个数据帧的循环【英文标题】:Pandas vectorization instead of loop for two dataframes 【发布时间】:2021-09-12 17:47:48 【问题描述】:

我有 2 个数据框。 我的主要数据框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 easy method 的答案,只是因为我无法理解它是如何工作的。

最有效的答案是fast but complex

【问题讨论】:

如果您可以以 CSV 或 JSON 格式发布数据,这将大有帮助,这样人们就可以轻松加载并测试他们的答案。固定宽度不太理想。 还有 - diffinal 是如何定义的? @shadowtalker 抱歉,我试图根据这个***.com/a/20159305/15542251 指南来解决这个问题。不确定我是否理解正确。 diffinal 只是我的第一个数据框 请参阅include a minimal data frame,了解如何在代码中包含数据框。让他人轻松帮助您。 那只是一个支持项。请继续使用intro tour 中的on topic 和how to ask。 【参考方案1】:

可能很难避免使用您为给定的 dffinal 行选择 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 的新副本。没有数据样本就无法对此进行测试,但我认为这会奏效。

【讨论】:

谢谢,显然在我的情况下无法使用矢量化。我尝试了您的代码,但我收到此错误“[Index(['LCH'], dtype='object')] 均不在 [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']我用过这段代码 请注意,itertuples 应该比 iterrows 更快,并且可能更“dtype-safe”。 @shadowtalker 你能给我看看例子吗,我以前从未尝试过 itertuples @BogdanTitomir 我编辑了代码,我没有仔细阅读你如何计算 LCH 和 L#,我认为它现在应该可以工作了 谢谢,您的代码运行良好,显着提高了我的代码性能。我选择@shadowtalker 答案是正确的,因为它稍微快一点。不幸的是,我只能选择一个正确的答案【参考方案2】:

此答案基于https://***.com/a/68197271/2954547,只是它使用itertuples 而不是iterrowsitertuples 通常比iterrows 更安全,因为它正确地保留了数据类型。请参阅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

【讨论】:

直到这周我才真正花太多时间在这里回答问题,但肯定会窃取 StringIO 技术,将问题的打印语句输出到我的笔记本中。也感谢你展示这个例子。我对优化的怀疑是 iterrows 与 itertuples 不会有很大的不同,因为绝大多数计算都是从 df2 中选择正确的行,但是看到这个实现很酷! 好点。 itertuples 的主要好处不是速度而是数据类型安全。见pandas.pydata.org/pandas-docs/stable/reference/api/…的“备注”部分 我怎样才能添加一个新行df_final.at[i, 'SumPosNeg']=,它需要等于所有正och 值的总和除以所有负och 值或sum(och>0)/sum(och<0) 的总和 即新列结果应等于 2.0/(-2.1)=-0.95。我从最终结果数据框中得到了 2.0 和 2.1。 @BogdanTitomir 花一些时间了解我和 Clay 的答案可能是值得的,这样您就可以根据需要添加自己的扩展或修改。【参考方案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 ram .或者我做错了什么 @BogdanTitomir 是的,我不建议对大数据帧使用交叉连接(它会占用大量空间),可以在重置索引之前仅选择相关列(cols 和 och)并尝试 我觉得这个方法对我的大脑来说太复杂了:) 不过还是谢谢你!【参考方案4】:

这是解决问题的一种方法

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

【讨论】:

非常感谢,代码完成工作并且比我的原始代码快得多,但比其他答案慢一点。代码看起来非常复杂,我希望它比其他答案更快。但也许我做错了什么并损坏了代码:) @BogdanTitomir 不确定这个问题,但在我的测试中我发现这比5-6x 更快。顺便问一下数据框df1df2的形状是什么? 测试样本 df1 有 12,000 行。 df2 有 450,000 谢谢,原来是最快的方法。我选择了这个作为正确答案,但可能我会选择其他方法,因为我觉得这个方法太复杂了。 很高兴它对你有用。尽管可以进行更多优化,但这只会使代码更加复杂。

以上是关于熊猫矢量化而不是两个数据帧的循环的主要内容,如果未能解决你的问题,请参考以下文章

在熊猫数据框中矢量化条件赋值

如何矢量化这个熊猫操作?

CMake如何验证循环是不是自动矢量化

我如何在熊猫中对这个操作进行矢量化?

用矢量化方法替换python for循环以丢弃丢失的数据

GCC:两个相似循环之间的向量化差异