如何加快行级别的重新索引?

时间:2018-04-05 00:02:07

标签: python pandas

考虑以下示例

import pandas as pd
import numpy as np

myidx = pd.date_range('2016-01-01','2017-01-01')
data = pd.DataFrame({'value' : xrange(len(myidx))}, index = myidx)

data.head()
Out[16]: 
            value
2016-01-01      0
2016-01-02      1
2016-01-03      2
2016-01-04      3
2016-01-05      4

此问题与expanding each row in a dataframe

有关

我绝对需要提高直观上非常简单的东西的性能:我需要“放大”数据框,以便每个索引值“放大”几天(2天前,2天后)。

要执行此任务,我有以下功能

def expand_onerow(df, ndaysback = 2, nhdaysfwd = 2):
    new_index = pd.date_range(pd.to_datetime(df.index[0]) - pd.Timedelta(days=ndaysback),
                              pd.to_datetime(df.index[0]) + pd.Timedelta(days=nhdaysfwd),
                              freq='D')
    newdf = df.reindex(index=new_index, method='nearest')     #New df with expanded index
    return newdf

现在使用iterrows或(假设)更快的itertuples会导致效果不佳。

%timeit pd.concat([expand_onerow(data.loc[[x],:], ndaysback = 2, nhdaysfwd = 2) for x ,_ in data.iterrows()])
1 loop, best of 3: 574 ms per loop

%timeit pd.concat([expand_onerow(data.loc[[x.Index],:], ndaysback = 2, nhdaysfwd = 2) for x in data.itertuples()])
1 loop, best of 3: 643 ms per loop

如何加快最终数据帧的生成?我的实际数据帧中有数百万个obs,并且索引日期不一定是连续的,因为它们在此示例中。

最终数据框

head(10)

Out[21]: 
            value
2015-12-30      0
2015-12-31      0
2016-01-01      0
2016-01-02      0
2016-01-03      0
2015-12-31      1
2016-01-01      1
2016-01-02      1
2016-01-03      1
2016-01-04      1

谢谢!

1 个答案:

答案 0 :(得分:3)

使用NumPy / Pandas时,速度的关键通常是将矢量化函数应用于最大的数组/ NDFrame。原始代码速度慢的主要原因是因为它为每行调用{em} {em} {/ 1>} 。行很小,你有数百万。为了加快速度,我们需要找到一种方法来根据应用于整个DataFrames或至少整个列的函数来表达计算。这样可以在快速C或Fortran代码中花费更多时间,在较慢的Python代码中花费更少时间来实现结果。

在这种情况下,可以通过复制expand_onerow并将整个DataFrame 的索引移位data天来获得结果:

i

然后连接移位的副本:

new = df.copy()
new.index = df.index + pd.Timedelta(days=i)
dfs.append(new)
pd.concat(dfs)

请注意import pandas as pd import numpy as np myidx = pd.date_range('2016-01-01','2017-01-01') data = pd.DataFrame({'value' : range(len(myidx))}, index = myidx) def expand_onerow(df, ndaysback = 2, nhdaysfwd = 2): new_index = pd.date_range(pd.to_datetime(df.index[0]) - pd.Timedelta(days=ndaysback), pd.to_datetime(df.index[0]) + pd.Timedelta(days=nhdaysfwd), freq='D') newdf = df.reindex(index=new_index, method='nearest') #New df with expanded index return newdf def orig(df, ndaysback=2, ndaysfwd=2): return pd.concat([expand_onerow(data.loc[[x],:], ndaysback = ndaysback, nhdaysfwd = ndaysfwd) for x ,_ in data.iterrows()]) def alt(df, ndaysback=2, ndaysfwd=2): dfs = [df] for i in range(-ndaysback, ndaysfwd+1): if i != 0: new = df.copy() new.index = df.index + pd.Timedelta(days=i) # you could instead use # new = df.set_index(df.index + pd.Timedelta(days=i)) # but it made the timeit result a bit slower dfs.append(new) return pd.concat(dfs) 有一个带有(基本上)4次迭代的Python循环。 alt有一个带有orig次迭代的Python循环(以列表推导的形式)。减少函数调用并将向量化函数应用于更大的类似数组的对象是len(df)获得速度超过alt的方式。

以下是origorigalt的比较基准:

data

因此,{36}行DataFrame上的In [40]: %timeit orig(data) 1 loop, best of 3: 1.15 s per loop In [76]: %timeit alt(data) 100 loops, best of 3: 2.22 ms per loop In [77]: 1150/2.22 Out[77]: 518.018018018018 alt快500倍。对于中小型DataFrame,速度优势会随着orig变大而增长,因为len(data)的Python循环仍然会有4次迭代,而alt& #39; s循环变长。然而,在某些时候,对于非常大的数据框架,我希望速度优势能够达到某个恒定因素 - 我不知道它有多大,除了它应该大于500x。

这会检查两个函数origorig是否产生相同的结果(但顺序不同):

alt