最快的方法来比较pandas数据帧中的行和上一行以及数百万行

时间:2015-04-04 13:14:23

标签: python performance pandas bigdata cython

我正在寻找解决方案来加速我编写的函数,以循环遍历pandas数据帧并比较当前行和上一行之间的列值。

例如,这是我的问题的简化版本:

   User  Time                 Col1  newcol1  newcol2  newcol3  newcol4
0     1     6     [cat, dog, goat]        0        0        0        0
1     1     6         [cat, sheep]        0        0        0        0
2     1    12        [sheep, goat]        0        0        0        0
3     2     3          [cat, lion]        0        0        0        0
4     2     5  [fish, goat, lemur]        0        0        0        0
5     3     9           [cat, dog]        0        0        0        0
6     4     4          [dog, goat]        0        0        0        0
7     4    11                [cat]        0        0        0        0

目前我有一个循环的函数并计算' newcol1'和' newcol2'根据' User'自上一行以来已发生变化,以及Time' Col1'值大于1.它还会查看存储在' Col2'中的数组中的第一个值。和' newcol3'和更新' newcol4'和' def myJFunc(df): ... #initialize jnum counter ... jnum = 0; ... #loop through each row of dataframe (not including the first/zeroeth) ... for i in range(1,len(df)): ... #has user changed? ... if df.User.loc[i] == df.User.loc[i-1]: ... #has time increased by more than 1 (hour)? ... if abs(df.Time.loc[i]-df.Time.loc[i-1])>1: ... #update new columns ... df['newcol2'].loc[i-1] = 1; ... df['newcol1'].loc[i] = 1; ... #increase jnum ... jnum += 1; ... #has content changed? ... if df.Col1.loc[i][0] != df.Col1.loc[i-1][0]: ... #record this change ... df['newcol4'].loc[i-1] = [df.Col1.loc[i-1][0], df.Col2.loc[i][0]]; ... #different user? ... elif df.User.loc[i] != df.User.loc[i-1]: ... #update new columns ... df['newcol1'].loc[i] = 1; ... df['newcol2'].loc[i-1] = 1; ... #store jnum elsewhere (code not included here) and reset jnum ... jnum = 1; '如果这些值自上一行以来发生了变化。

这是我目前正在做的伪代码(因为我已经简化了我没有测试过的问题,但它与我和我的非常相似) #39;实际上是在ipython笔记本中做的):

diff

我现在需要将此功能应用于数百万行并且速度非常慢,因此我试图找出加速它的最佳方法。我听说Cython可以提高功能的速度,但我没有经验(而且我对pandas和python都是新手)。是否可以将两行数据帧作为参数传递给函数,然后使用Cython加速它,或者是否有必要使用" User"来创建新列。它们中的值使得函数一次只读取和写入数据帧的一行,以便从使用Cython中受益?任何其他速度技巧将不胜感激!

(关于使用.loc,我比较.loc,.iloc和.ix,这个比较快,所以这是我目前使用它的唯一原因)

(另外,我的{{1}}列实际上是unicode而不是int,这对于快速比较可能会有问题)

3 个答案:

答案 0 :(得分:13)

我和Andy一样思考,只是添加groupby,我认为这是对Andy回答的补充。添加groupby只会在您执行diffshift时将NaN放在第一行。 (请注意,这不是一个精确答案的尝试,只是为了勾勒出一些基本技术。)

df['time_diff'] = df.groupby('User')['Time'].diff()

df['Col1_0'] = df['Col1'].apply( lambda x: x[0] )

df['Col1_0_prev'] = df.groupby('User')['Col1_0'].shift()

   User  Time                 Col1  time_diff Col1_0 Col1_0_prev
0     1     6     [cat, dog, goat]        NaN    cat         NaN
1     1     6         [cat, sheep]          0    cat         cat
2     1    12        [sheep, goat]          6  sheep         cat
3     2     3          [cat, lion]        NaN    cat         NaN
4     2     5  [fish, goat, lemur]          2   fish         cat
5     3     9           [cat, dog]        NaN    cat         NaN
6     4     4          [dog, goat]        NaN    dog         NaN
7     4    11                [cat]          7    cat         dog

作为Andy关于存储对象的观点的后续,请注意我在这里所做的是提取列表列的第一个元素(并添加移位版本)。像这样做你只需要进行一次昂贵的提取,然后就可以坚持标准的熊猫方法了。

答案 1 :(得分:8)

使用pandas(构造)并对代码进行矢量化,即不要使用for循环,而是使用pandas / numpy函数。

  

'newcol1'和'newcol2'基于“用户”自上一行以来是否发生了变化,以及“时间”值的差异是否大于1。

单独计算:

df['newcol1'] = df['User'].shift() == df['User']
df.ix[0, 'newcol1'] = True # possibly tweak the first row??

df['newcol1'] = (df['Time'].shift() - df['Time']).abs() > 1

我不清楚Col1的用途,但是列中的一般python对象不能很好地扩展(你不能使用快速路径,内容分散在内存中)。大部分时间你都可以使用别的东西......


Cython是最后一个选项,在99%的用例中不需要,但请参阅enhancing performance section of the docs了解提示。

答案 2 :(得分:1)

在您的问题中,您似乎想要成对地遍历行。你能做的第一件事就是这样:

from itertools import tee, izip
def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return izip(a, b)

for (idx1, row1), (idx2, row2) in pairwise(df.iterrows()):
    # you stuff

但是你不能直接修改row1和row2,你仍然需要在索引中使用.loc或.iloc。

如果iterrows仍然太慢,我建议做这样的事情:

  • 使用pd.unique(User)从您的unicode名称创建user_id列,并将名称与字典映射到整数ID。

  • 创建增量数据框:使用user_id和time列替换原始数据框的移位数据框。

    df[[col1, ..]].shift() - df[[col1, ..]])
    

如果user_id> 0,表示用户连续两次更改。时间列可以直接使用delta [delta [' time' > 1]] 使用此delta数据帧,您可以逐行记录更改。您可以使用它作为掩码来更新原始数据帧所需的列。