【问题标题】:Improve performance of a for loop comparing pandas dataframe rows提高比较 pandas 数据帧行的 for 循环的性能
【发布时间】:2018-06-27 15:49:20
【问题描述】:

我遇到了 Python/Pandas 的性能问题。 我有一个 for 循环比较 Pandas DataFrame 中的后续行:

for i in range(1, N):
    if df.column_A.iloc[i] == df.column_A.iloc[i-1]:
        if df.column_B.iloc[i] == 'START' and df.column_B.iloc[i-1] == 'STOP':
            df.time.iloc[i] = df.time.iloc[i] - df.time.iloc[i-1]

运行正常,但速度极慢。 我的数据框有大约 1M 行,我想知道是否有某种方法可以提高性能。 我读过关于矢量化的文章,但我不知道从哪里开始。

【问题讨论】:

  • 你能发布一个可测试的输入片段吗?
  • 你能生成一些具有预期输出的测试数据吗?
  • 你能想出一个minimal reproducible example吗?
  • 这是一些测试数据:df = pd.DataFrame( [['A', 'START', 3], ['A', 'STOP', 6], ['B', 'STOP', 2], ['C', 'STOP', 1], ['C', 'START', 9], ['C', 'STOP', 7]], columns=['column_A', 'column_B', 'time'] )

标签: python performance pandas


【解决方案1】:

我认为你可以使用shiftmask

mask = ((df.column_A == df.column_A.shift()) 
        & (df.column_B == 'START') & (df.column_B.shift() == 'STOP'))
df.loc[mask, 'time'] -= df.time.shift().loc[mask]

掩码选择'column_A'中的值等于前一个值的行(由shift获得)并且'column_B'等于'START'并且上一行'STOP'。使用loc 允许您通过在列时间中使用相同的掩码删除前一行的值(再次shift)来通过mask 在列“时间”中更改所有选定行的值

编辑:举个例子:

df = pd.DataFrame({'column_A': [0,1,1,2,1,2,2], 'column_B': ['START', 'STOP', 'START','STOP', 'START','STOP', 'START'], 'time':range(7)})
   column_A column_B  time
0         0    START     0
1         1     STOP     1
2         1    START     2
3         2     STOP     3
4         1    START     4
5         2     STOP     5
6         2    START     6

所以这里第 2 行和第 6 行满足您的条件,因为前一行在 column_A 中具有相同的值,并且在 column_B 中获得“START”,而前一行具有“STOP”。

运行代码后你会得到df:

   column_A column_B  time
0         0    START   0.0
1         1     STOP   1.0
2         1    START   1.0
3         2     STOP   3.0
4         1    START   4.0
5         2     STOP   5.0
6         2    START   1.0

第 2 行的时间值为 1(最初是 2 减去前第 1 行的值),第 6 行相同(6 - 5)

编辑时间比较让我们创建一个 3000 行的 df

df = pd.DataFrame( [['A', 'START', 3], ['A', 'STOP', 6], ['B', 'STOP', 2], 
                    ['C', 'STOP', 1], ['C', 'START', 9], ['C', 'STOP', 7]],
                   columns=['column_A', 'column_B', 'time'] )
df = pd.concat([df]*500)
df.shape
Out[16]: (3000, 3)

现在用两种方法创建两个函数:

# original method
def espogian (df):
    N = df.shape[0]
    for i in range(1, N):
        if df.column_A.iloc[i] == df.column_A.iloc[i-1]:
            if df.column_B.iloc[i] == 'START' and df.column_B.iloc[i-1] == 'STOP':
                df.time.iloc[i] = df.time.iloc[i] - df.time.iloc[i-1]
    return df
# mine
def ben(df):
    mask = ((df.column_A == df.column_A.shift()) 
        & (df.column_B == 'START') & (df.column_B.shift() == 'STOP'))
    df.loc[mask, 'time'] -= df.time.shift().loc[mask]
    return df

然后运行timeit:

%timeit espogian (df)
1 loop, best of 3: 8.71 s per loop

%timeit ben (df)
100 loops, best of 3: 4.79 ms per loop

# verify they are equal
df1 = espogian (df)
df2 = ben (df)
(df1==df2).all()
Out[24]: 
column_A    True
column_B    True
time        True

【讨论】:

  • 谢谢。我已经根据我的数据测试了您的代码,但实际上比我的示例要慢
  • @espogian 在您的 1M 行数据上速度较慢吗?我刚刚在 3000 行的 df 上添加了一些时间比较,它显然更快
  • 使用 timeit 我收集了不同的结果。我仍在试图理解为什么。但是,我的真正目标是将效率提高一个数量级。不知道有没有可能
  • @espogian 顺便说一句,你的代码中的 N 是什么,我以为是 df.shape[0] 但也许我错了?
  • 我已经修改了我的代码,现在可以正常工作了!我的错误,这是一个 dtype 不匹配!感谢您的宝贵帮助,这段代码确实提高了很多性能!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-08
  • 1970-01-01
  • 2021-10-10
相关资源
最近更新 更多