加速大熊猫数据框中的iloc解决方案

时间:2015-10-17 08:22:48

标签: python pandas dataframe

我有以下DataFrame

dates = pd.date_range('20150101', periods=4)
df = pd.DataFrame({'A' : [5,10,3,4]}, index = dates)

df.loc[:,'B'] = 0
df.loc[:,'C'] = 0
df.iloc[0,1]  = 10
df.iloc[0,2]  = 3

print df

Out[69]:

             A   B  C
2015-01-01   5  10  3
2015-01-02  10   0  0
2015-01-03   3   0  0
2015-01-04   4   0  0

我想为列BC

实现以下逻辑
  • B(k+1) = B(k) - A(k+1)
  • C(k+1) = B(k) + A(k+1)

我可以使用以下代码执行此操作:

for i in range (1, df.shape[0]): 
        df.iloc[i,1] = df.iloc[i-1,1] - df.iloc[i,0] 
        df.iloc[i,2] = df.iloc[i-1,1] + df.iloc[i,0] 
print df

这给出了:

             A   B   C
2015-01-01   5  10   3
2015-01-02  10   0  20
2015-01-03   3  -3   3
2015-01-04   4  -7   1

我正在寻找的答案。问题是当我将其应用于具有大型数据集的DataFrame时,它运行缓慢。非常慢。有没有更好的方法来实现这一目标?

5 个答案:

答案 0 :(得分:6)

向量化的技巧是将所有内容重写为cumsums。

In [11]: x = df["A"].shift(-1).cumsum().shift().fillna(0)

In [12]: x
Out[12]:
2015-01-01     0
2015-01-02    10
2015-01-03    13
2015-01-04    17
Name: A, dtype: float64

In [13]: df["B"].cumsum() - x
Out[13]:
2015-01-01    10
2015-01-02     0
2015-01-03    -3
2015-01-04    -7
dtype: float64

In [14]: df["B"].cumsum() - x + 2 * df["A"]
Out[14]:
2015-01-01    20
2015-01-02    20
2015-01-03     3
2015-01-04     1
dtype: float64

注意:第一个值是特殊情况,因此您必须将其调整为3。

答案 1 :(得分:2)

像这样的递归事件可能难以矢量化。 numba通常会很好地处理它们 - 如果您需要重新分发代码,cython可能是更好的选择,因为它会产生没有额外依赖关系的常规c扩展。

In [88]: import numba

In [89]: @numba.jit(nopython=True)
    ...: def logic(a, b, c):
    ...:     N = len(a)
    ...:     out = np.zeros((N, 2), dtype=np.int64)
    ...:     for i in range(N):
    ...:         if i == 0:
    ...:             out[i, 0] = b[i]
    ...:             out[i, 1] = c[i]
    ...:         else:
    ...:             out[i, 0] = out[i-1,0] - a[i]
    ...:             out[i, 1] = out[i-1,0] + a[i]
    ...:     return out

In [90]: logic(df.A.values, df.B.values, df.C.values)
Out[90]: 
array([[10,  3],
       [ 0, 20],
       [-3,  3],
       [-7,  1]], dtype=int64)

In [91]: df[['A','B']] = logic(df.A.values, df.B.values, df.C.values)

编辑: 如其他答案所示,这个问题实际上可以进行矢量化,你应该使用它。

答案 2 :(得分:1)

基本上只有你的答案没有for循环:

df['B'].iloc[1:] = df['B'].iloc[:-1].values - df['A'].iloc[1:].values
df['C'].iloc[1:] = df['B'].iloc[:-1].values + df['A'].iloc[1:].values

我不知道性能问题,但我想如果没有循环它会更快。

答案 3 :(得分:1)

完整的解决方案:

df1 = df[:1]
df['B'] = df['B'].shift().cumsum()[1:] - df['A'][1:].cumsum()
df[:1] = df1
df['C'] = df['B'].shift() + df['A']
df[:1] = df1
df

            A   B   C
2015-01-01  5   10  3
2015-01-02  10  0   20
2015-01-03  3   -3  3
2015-01-04  4   -7  1

答案 4 :(得分:0)

您可以提取numpy中的基础数组,并获得更快,更简单的解决方案。在这里,我们可以获得130倍的加速比。

dates = pd.date_range('20150101', periods=10000)
df = pd.DataFrame({'A' : np.random.rand(10000)}, index = dates)

df.loc[:,'B'] = 0
df.loc[:,'C'] = 0
df.iloc[0,1]  = 10
df.iloc[0,2]  = 3

旧的,慢速的熊猫版本:

%%time
for i in range (1, df.shape[0]): 
        df.iloc[i,1] = df.iloc[i-1,1] - df.iloc[i,0] 
        df.iloc[i,2] = df.iloc[i-1,1] + df.iloc[i,0]


CPU times: user 6.02 s, sys: 3.33 ms, total: 6.02 s
Wall time: 5.98 s

更快,更好的版本:

%%time
arr_a = df['A'].to_numpy()
arr_b = df['B'].to_numpy()
arr_c = df['C'].to_numpy()
for i in range(1, df.shape[0]):
    arr_b[i] = arr_b[i - 1] - arr_a[i]
    arr_c[i] = arr_b[i - 1] + arr_a[i]


CPU times: user 47.6 ms, sys: 23 µs, total: 47.6 ms
Wall time: 46 ms

numpy数组引用了原始内存,因此更改numpy数组也会修改数据帧。

熊猫索引缓慢。他们希望您使用矢量化运算,但是对于这样的问题,很难获得3位博士学位的要求才能了解熊猫需要哪些组,合并和索引的集合。拔出底层数组并直接进行修改会更简单,更快捷。