考虑下面的例子
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)
迭代。减少函数调用并将矢量化函数应用于更大的类似数组的对象是alt
与orig
相比获得速度的方式。这是比较
orig
和alt
在data
上的基准: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上,
alt
比orig
快500倍以上。对于中小型DataFrame,随着len(data)
变大,速度优势趋于增长,因为alt
的Python循环仍将具有4次迭代,而orig
的循环变长。但是,在某个时候,对于非常大的DataFrame,我希望速度优势会以某个恒定因子达到顶峰-我不知道它会有多大,除非它应该大于500倍。这将检查两个函数
orig
和alt
是否产生相同的结果(但顺序不同):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/