考虑下面的例子

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


谢谢!

最佳答案

使用NumPy / Pandas时,提高速度的关键通常是将矢量化函数应用于可能的最大数组/ NDFrames。您的原始代码运行缓慢的主要原因是因为它为每一行调用一次expand_onerow。这些行很小,您有数百万行。为了使其更快,我们需要找到一种方法来表示应用于整个DataFrame或至少应用于整个列的函数的计算。这样往往会获得结果,因为花在快速C或Fortran代码上的时间更多,而在慢速Python代码上的时间更少。

在这种情况下,可以通过复制data并将整个DataFrame的索引移位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)


请注意,alt具有一个(基本上)具有4次迭代的Python循环。 orig具有Python循环(以列表理解的形式)和len(df)迭代。减少函数调用并将矢量化函数应用于更大的类似数组的对象是altorig相比获得速度的方式。



这是比较origaltdata上的基准:

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


因此,在367行DataFrame上,altorig快500倍以上。对于中小型DataFrame,随着len(data)变大,速度优势趋于增长,因为alt的Python循环仍将具有4次迭代,而orig的循环变长。但是,在某个时候,对于非常大的DataFrame,我希望速度优势会以某个恒定因子达到顶峰-我不知道它会有多大,除非它应该大于500倍。



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

result = alt(data)
expected = orig(data)
result = result.reset_index().sort_values(by=['index','value']).reset_index(drop=True)
expected = expected.reset_index().sort_values(by=['index','value']).reset_index(drop=True)
assert expected.equals(result)

关于python - 如何在行级加快重新索引?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/49662187/

10-11 16:15