在pandas数据框中,查找每一行两个列列表中哪一个为真的最快方法是什么?

5

我希望以最快的方式完成以下操作:

我们有一个 pd.DataFrame:

df = pd.DataFrame({
    'High': [1.3,1.2,1.1],
    'Low': [1.3,1.2,1.1],
    'High1': [1.1, 1.1, 1.1],
    'High2': [1.2, 1.2, 1.2],
    'High3': [1.3, 1.3, 1.3],
    'Low1': [1.3, 1.3, 1.3],
    'Low2': [1.2, 1.2, 1.2],
    'Low3': [1.1, 1.1, 1.1]})

那看起来像:

In [4]: df
Out[4]:
   High  High1  High2  High3  Low  Low1  Low2  Low3
0   1.3    1.1    1.2    1.3  1.3   1.3   1.2   1.1
1   1.2    1.1    1.2    1.3  1.2   1.3   1.2   1.1
2   1.1    1.1    1.2    1.3  1.1   1.3   1.2   1.1

我想知道High1、High2和High3中哪一个浮点数首次大于等于High值。如果没有,应该为np.nan。
同样的,对于Low1、Low2和Low3的值,但在这种情况下,哪一个是首次小于或等于High值。如果没有,应该为np.nan。
最后,我需要知道哪一个先出现,Low还是High。
解决此问题的一种奇怪且不太高效的方法是:
df['LowIs'] = np.nan
df['HighIs'] = np.nan

for i in range(1,4):
    df['LowIs'] = np.where((np.isnan(df['LowIs'])) & (
        df['Low'] >= df['Low'+str(i)]), i, df['LowIs'])
    df['HighIs'] = np.where((np.isnan(df['HighIs'])) & (
        df['High'] <= df['High'+str(i)]), i, df['HighIs'])

df['IsFirst'] = np.where(
    df.LowIs < df.HighIs,
    'Low',
    np.where(df.LowIs > df.HighIs, 'High', 'None')
)

这使我得到了:
In [8]: df
Out[8]:
   High  High1  High2  High3  Low  Low1  Low2  Low3  LowIs  HighIs IsFirst
0   1.3    1.1    1.2    1.3  1.3   1.3   1.2   1.1    1.0     3.0     Low
1   1.2    1.1    1.2    1.3  1.2   1.3   1.2   1.1    2.0     2.0    None
2   1.1    1.1    1.2    1.3  1.1   1.3   1.2   1.1    3.0     1.0    High

我需要反复进行许多次迭代,其中High/Low值会有所不同,因此在执行此操作时性能至关重要。

如果High1、High2、High3和Low1、Low2、Low3可以放在一个单独的数据框中,并进行转置,或者放在一个字典中,那么我不介意。因此,为了以最佳性能准备数据的过程可能会很慢且笨拙。

我尝试过一种解决方案,但是无法以向量化方式完成,并且似乎速度也很慢:

df.loc[(df.index == 0), 'HighIs'] = np.where(
    df.loc[(df.index == 0), ['High1', 'High2', 'High3']] >= 1.3
)[1][0] + 1

因此,检查第一行中哪个列为真,然后查看np.where()的索引号。

期待任何建议,并希望学到新的东西! :)


这最终是你想要解决的问题,还是你在解决不同问题的解决方案中遇到的问题?我只是问一下,因为这似乎是可能的 x-y 问题 - wflynny
3个回答

2

如果我理解问题正确,这是一个半向量化的版本:

df = pd.DataFrame({
    'High': [1.3,1.7,1.1],
    'Low': [1.3,1.2,1.1],
    'High1': [1.1, 1.1, 1.1],
    'High2': [1.2, 1.2, 1.2],
    'High3': [1.3, 1.3, 1.3],
    'Low1': [1.3, 1.3, 1.3],
    'Low2': [1.2, 1.2, 1.2],
    'Low3': [1.1, 1.1, 1.1]})

highs = ['High{:d}'.format(x) for x in range(0,4)]

for h in highs[::-1]:
    mask = df['High'] <= df[h]
    df.loc[mask, 'FirstHigh'] = h

生成:

   High  High1  High2  High3  Low  Low1  Low2  Low3 FirstHigh
0   1.3    1.1    1.2    1.3  1.3   1.3   1.2   1.1     High3
1   1.7    1.1    1.2    1.3  1.2   1.3   1.2   1.1       NaN
2   1.1    1.1    1.2    1.3  1.1   1.3   1.2   1.1     High1

解释: 关键在于我们以相反的顺序遍历列。也就是说,我们从High3开始,检查它是否大于High,并相应地设置FirstHigh。然后我们移动到High2。如果这个值也更大,我们就简单地覆盖前一个结果,否则它将保持不变。由于我们以这种相反的顺序迭代,结果是第一个较高的列将成为最终结果。


我计时了一下,你的原始版本比我的稍微快一点。所以就忘记我写的一切吧。 - Aske Doerge
1
是的,它可以运行,但速度较慢。还是谢谢你的建议! - Marco

2

测试您的高n列与高列:

a = df.iloc[:,1:4].ge(df.High, axis=0)

a
Out[67]: 
   High1  High2  High3
0  False  False   True
1  False  False  False
2   True   True   True

现在将False替换为np.nan,并要求最小或最大值的列索引(因为所有值都是True或np.nan,所以无所谓):

a.replace(False, np.nan).idxmax(1)

0    High3
1      NaN
2    High1

使用le作为比较运算符的低列也遵循同样的原则。

谢谢,它的工作速度比我的方法稍快,这是我想出的最终代码:<pre> a = df.iloc[:,1:4].rename(columns={&#39;High1&#39;: 1, &#39;High2&#39;: 2, &#39;High3&#39;: 3}).ge(df.High, axis=0) b = df.iloc[:,5:8].rename(columns={&#39;Low1&#39;: 1, &#39;Low2&#39;: 2, &#39;Low3&#39;: 3}).le(df.Low, axis=0) df[&#39;HighIs&#39;] = a.replace(False, np.nan).idxmax(1) df[&#39;LowIs&#39;] = b.replace(False, np.nan).idxmax(1) `df['IsFirst'] = np.where(df.LowIs < df.HighIs, 'Low', np.where(df.LowIs > df.HighIs, 'High', 'None'))`` - Marco

1
这里提供了一种矢量化方法,使用 NumPy广播 -
a = df.values
out1 = (a[:,1:4] >= a[:,0,None]).argmax(1)+1
out2 = (a[:,5:8] <= a[:,4,None]).argmax(1)+1
df['LowIs'] = out2
df['HighIs'] = out1
df['IsFirst'] = np.where(out1!=out2,np.where(out1 > out2, 'Low', 'High'),None)

示例输出 -

In [195]: df
Out[195]: 
   High  High1  High2  High3  Low  Low1  Low2  Low3  LowIs  HighIs IsFirst
0   1.3    1.1    1.2    1.3  1.3   1.3   1.2   1.1      1       3     Low
1   1.2    1.1    1.2    1.3  1.2   1.3   1.2   1.1      2       2    None
2   1.1    1.1    1.2    1.3  1.1   1.3   1.2   1.1      3       1    High

谢谢,这是目前表现最佳的解决方案,速度比我的方法快了大约3倍!也许我应该考虑只使用numpy来完成部分工作,而不使用pandas... - Marco
@Marco 这就是正确的方法!根据我的经验,在进行数字计算并寻求性能时,值得考虑使用 NumPy。 - Divakar

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