考虑以下示例
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
谢谢!
答案 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
的方式。
以下是orig
上orig
和alt
的比较基准:
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。
这会检查两个函数orig
和orig
是否产生相同的结果(但顺序不同):
alt