2015-04-04 73 views
10

我正在寻找解决方案来加速我写入的函数,以循环访问熊猫数据框并比较当前行和前一行之间的列值。在熊猫数据框中比较行和前一行的行数百万行的最快方法

作为一个例子,这是我的问题的一个简化版本:

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'值的差异是否大于1.它还查看存储在'Col1'和'Col2'中的数组中的第一个值,并更新了'newcol3'和' newcol4',如果这些值自上一行以来已更改。

下面是我在做什么目前的伪代码(因为我已经简化了问题,我没有测试过这一点,但它非常类似于我居然在IPython的笔记本电脑做):

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; 

我现在需要将这个函数应用到数百万行,它不可能很慢,所以我试图找出加速它的最佳方法。我听说Cython可以提高函数的速度,但我没有经验(我对熊猫和Python都是新手)。是否可以将两行数据框作为参数传递给函数,然后使用Cython加速它,或者需要创建新的列,其中包含“diff”值,以便该函数只读取和写入一个为了从使用Cython中受益,每次都要放入一行数据帧?任何其他速度技巧将不胜感激!

(使用的.loc方面,我比较的.loc,.iloc和.IX,这一次是稍快所以这是我使用的是目前的唯一原因)

(另外,我User在列现实是unicode不是整数,这可能是快速比较的问题)

+1

百万行,为什么不使用Python可以轻松连接到的专用数据库,如MySQL或SQLlite?关系数据库可以运行复杂的SQL查询,并通过if/then逻辑对由索引连接的行比较进行比较。它们旨在为数百万行进行扩展。即使可以设置触发器,以便任何用户更改,可以更新特定的列。 – Parfait 2015-04-04 13:42:49

回答

10

我一直在想和Andy一样,只是加了groupby,我认为这是对Andy的回答的补充。只要你做diffshift,添加groupby就会产生将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 

作为后续安迪的观点有关存储的对象,请注意,我在这里做的,提取第一列表列的元素(也添加一个移位的版本)。这样做,你只需要做一次昂贵的提取,一次可以坚持标准的熊猫方法。

+0

非常感谢(JohnE&@Andy),我实现了两个解决方案,groupby和提取Col1的第一个元素特别有用,现在花费大约3分钟运行整个数据集 - 非常高兴! :) – AdO 2015-04-05 22:22:39

0

在你的问题,你好像你想遍历行成对。你可以做的第一件事情是这样的:

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(用户),您的Unicode名称的user_id列,并映射名称用字典整数ID 。

  • 创建一个增量数据帧:将一个带有user_id和time列的移位数据帧减去原始数据帧。

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

如果user_id说明> 0,它意味着用户在两个连续行改变。时间列可以用delta [delta ['time'> 1]]直接过滤] 使用此delta数据帧,您可以逐行记录更改。您可以使用它作为掩码来更新您原始数据帧所需的列。

8

使用熊猫(构造)和向量化你的代码,即不使用循环,而是使用熊猫/ numpy函数。

“newcol1”和基于“用户”是否自前一行改变,在“时间”值之差也是否大于1

计算这些“newcol2”另:

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的提示。

相关问题