Самый быстрый способ сравнить строку и предыдущую строку в Pandas dataframe с миллионами строк

я ищу решения для ускорения функции, которую я написал, чтобы выполнить цикл через фрейм данных 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 ' изменилось с предыдущей строки, а также является ли разница в the'Time' значения больше 1. Он также смотрит на первое значение в массивах, хранящихся в 'Col1' и 'Col2' и обновления 'newcol3' и 'newcol4 ' если эти значения изменились с предыдущей строки.

вот псевдо-код для того, что я делаю в настоящее время (так как я упростил проблему, я не тестировал это, но это очень похоже на то, что я на самом деле делаю в IPython notebook):

 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 может увеличить скорость функций, но у меня нет опыта работы с ним (и я новичок как в pandas, так и в python). Можно ли передать две строки фрейма данных в качестве аргументов функции, а затем использовать Cython для ее ускорения или необходимо создать новые столбцы с помощью"diff" значения в них так, что функция только считывает и записывает в одну строку фрейма данных за раз, чтобы извлечь выгоду из использования Cython? Любые другие трюки скорости были бы очень признательны!

(относительно использования .Лок, сравнил я .линия контроля. ,Мот и .ix, и этот был немного быстрее, так что это единственная причина, по которой я использую это в настоящее время)

(кроме того, мой User столбец на самом деле unicode не int, что может быть проблематичным для быстрого сравнения)

3 ответов


Я думал о том же, что и Энди, только с groupby добавил, И я думаю, что это дополняет ответ Энди. Добавление groupby просто будет иметь эффект размещения NaN в первой строке, когда вы делаете diff или shift. (Обратите внимание, что это не попытка точного ответа, просто набросать некоторые основные методы.)

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

в дополнение к точке Энди о хранении объектов обратите внимание, что я сделал здесь, чтобы извлечь первый элемент столбец списка (и добавьте также сдвинутую версию). Делая это так, вам нужно только сделать дорогостоящую экстракцию один раз, и после этого можно придерживаться стандартных методов панд.


используйте pandas (конструкции) и векторизуйте свой код, т. е. не используйте для циклов, вместо этого используйте функции 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% случаев использования, но см. повышение производительности раздел документов советы.


в вашей проблеме кажется, что вы хотите перебирать строки попарно. Первое, что вы можете сделать, это что-то вроде этого:

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 напрямую, вам все равно нужно будет использовать .Лок или .Мот с индексами.

Если iterrows все еще слишком медленный, я предлагаю сделать что-то вроде этого:

  • создайте столбец user_id из имен unicode с помощью pd.уникальный (пользователь) и сопоставление имени со словарем к целочисленным идентификаторам.

  • создайте Дельта-фрейм данных: в сдвинутый фрейм данных со столбцом user_id и time вы вычитаете исходный фрейм данных.

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

Если user_id > 0, это означает, что пользователь изменился в двух последовательных строках. Столбец времени можно фильтровать непосредственно с помощью delta[delta ['time' > 1]] С этой дельты таблицы данных запись построчная изменения. Вы можете использовать его маску для обновления столбцов, которые вам нужны от вас оригинал фрейм данных.