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

相關問題