Pandas 两个数据帧的向量化而不是循环
Pandas vectorization instead of loop for two dataframes
我有 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 的答案,只是因为我可以理解它是如何工作的。
最有效的答案是
对于给定的 dffinal 行,使用 select 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 的新副本。没有数据样本无法对此进行测试,但我认为这会起作用。
此答案基于 , except that it uses itertuples
instead of iterrows
. itertuples
is generally safer than iterrows
, because it preserves dtypes correctly. See the "Notes" section of the DataFrame.iterrows
文档。
它也是自包含的,因为它可以自上而下执行而无需 copy/paste 数据等
请注意,我遍历 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
我能想到的 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
这是解决您的问题的一种方法
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
我有 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
最有效的答案是
对于给定的 dffinal 行,使用 select 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 的新副本。没有数据样本无法对此进行测试,但我认为这会起作用。
此答案基于 itertuples
instead of iterrows
. itertuples
is generally safer than iterrows
, because it preserves dtypes correctly. See the "Notes" section of the DataFrame.iterrows
文档。
它也是自包含的,因为它可以自上而下执行而无需 copy/paste 数据等
请注意,我遍历 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
我能想到的 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
这是解决您的问题的一种方法
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