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

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

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

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

Ответ 1

Я думал так же, как Энди, только с 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.

Ответ 2

Используйте pandas (конструкции) и векторизовать свой код, т.е. не использовать для циклов, вместо этого используйте pandas/numpy.

'newcol1' и 'newcol2' в зависимости от того, изменился ли "Пользователь" с предыдущей строки, а также отличается ли разница в значениях "Time" больше 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% случаев использования, но см. повышение производительности в документах. для подсказок.

Ответ 3

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

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 все еще слишком медленный, я предлагаю сделать что-то вроде этого:

  • Создайте столбец user_id из ваших юникод-имен, используя pd.unique(User) и сопоставьте имя со словарем с целыми идентификаторами.

  • Создайте дельта-фрейм данных: сдвинутому фрейму данных с помощью user_id и столбца времени вы выберете исходный фрейм.

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

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