Заполните столбец группы последним значением последней группы

1

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

TOUR_ID  ID    PAGE_ID     CREATED DATE         AVAILABILITY    

T_1      ID1      P1      2018-07-03 19:10:19     AVAILABLE     
T_1      ID1      P1      2018-07-03 19:10:20     AVAILABLE     
T_1      ID1      P2      2018-07-03 19:12:33     AVAILABLE     
T_1      ID2      P3      2018-07-03 19:13:34     AVAILABLE 
T_1      ID2      P3      2018-07-03 19:13:35     NOT AVAILABLE     
T_1      ID2      P4      2018-07-03 19:16:24     AVAILABLE     

T_2      ID3      P4      2018-07-03 18:23:19     AVAILABLE       
T_2      ID3      P4      2018-07-03 18:23:20     NOT AVAILABLE   
T_2      ID1      P1      2018-07-03 19:10:21     NOT AVAILABLE     
T_2      ID2      P3      2018-07-03 19:13:37     NOT AVAILABLE 
T_2      ID2      P3      2018-07-03 19:13:38     NOT AVAILABLE     
T_2      ID3      P5      2018-07-03 20:56:33     AVAILABLE       
T_2      ID3      P5      2018-07-03 20:56:34     NOT AVAILABLE   
T_2      ID3      P5      2018-07-03 22:56:35     AVAILABLE       
T_2      ID3      P6      2018-07-03 22:57:20     NOT AVAILABLE   
T_2      ID3      P7      2018-07-03 22:58:35     AVAILABLE       
T_2      ID4      P8      2018-07-03 22:59:00     AVAILABLE     
T_2      ID1      P1      2018-07-03 23:12:00     AVAILABLE     
T_2      ID1      P3      2018-07-03 23:32:00     AVAILABLE         

В каждой группе (Tour_ID, ID, Page_ID) мне нужно создать столбец с последним значением предыдущей группы. Кроме того, в первый раз, когда tour_ID или ID меняются, я получаю NaNs, потому что для этой комбинации нет никаких предыдущих значений.

Результат должен выглядеть следующим образом:

TOUR_ID   ID    PAGE_ID     CREATED DATE         AVAILABILITY   PREVIOUS AVAILABILITY    

T_1      ID1      P1      2018-07-03 19:10:19     AVAILABLE            NaN     
T_1      ID1      P1      2018-07-03 19:10:20     AVAILABLE            NaN
T_1      ID1      P2      2018-07-03 19:12:33     AVAILABLE         AVAILABLE
T_1      ID2      P3      2018-07-03 19:13:34     AVAILABLE            NaN
T_1      ID2      P3      2018-07-03 19:13:35     NOT_AVAILABLE        NaN
T_1      ID2      P4      2018-07-03 19:16:24     AVAILABLE       NOT_AVAILABLE       

T_2      ID3      P4      2018-07-03 18:23:19     AVAILABLE            NaN
T_2      ID3      P4      2018-07-03 18:23:20     NOT AVAILABLE        NaN
T_2      ID1      P1      2018-07-03 19:10:21     NOT AVAILABLE        NaN
T_2      ID2      P3      2018-07-03 19:13:37     NOT AVAILABLE        NaN
T_2      ID2      P3      2018-07-03 19:13:38     NOT AVAILABLE        NaN
T_2      ID3      P5      2018-07-03 20:56:33     AVAILABLE       NOT AVAILABLE
T_2      ID3      P5      2018-07-03 20:56:34     NOT AVAILABLE   NOT AVAILABLE
T_2      ID3      P5      2018-07-03 22:56:35     AVAILABLE       NOT AVAILABLE
T_2      ID3      P6      2018-07-03 22:57:20     NOT AVAILABLE     AVAILABLE
T_2      ID3      P7      2018-07-03 22:58:35     AVAILABLE       NOT AVAILABLE
T_2      ID4      P8      2018-07-03 22:59:00     AVAILABLE            NaN
T_2      ID1      P1      2018-07-03 23:12:00     AVAILABLE            NaN
T_2      ID1      P3      2018-07-03 23:32:00     AVAILABLE         AVAILABLE

У меня есть код, который работает, но он не масштабируется (блок данных имеет около 900 000). Любая помощь в улучшении производительности кода была бы должным образом оценена.

Вот что у меня есть до сих пор:

for current_op in df.TOUR_ID.unique():    
    dummy = df[df.TOUR_ID == current_op].ID.unique()

    for current_ID in dummy:
        dummy_m = df[(df.TOUR_ID == current_op) & (df.ID == current_ID)].PAGE_ID.unique()

        for current_page in dummy_m:
            mask = (df.TOUR_ID == current_op) & (df.ID == current_ID) & (df.PAGE_ID == current_page)
            indexes = mask.reset_index().rename(columns ={0:'Bool'})
            ind = indexes.index[indexes['Bool'] == True].tolist()[0]

            if (ind == 0) | ((current_page == dummy_m[0])):
                df.loc[mask,'Previous_availability'] = np.nan
            else:
                previous_aval = df.AVAILABILITY.loc[indexes['index'].loc[ind-1]]

                df.loc[mask, 'Previous_availability'] = previous_aval

Примечание: NaN в конечном итоге будет сброшен

-- Редактировать

Ниже приведен код для создания фрейма данных:

 import pandas as pd 
 import numpy as np
 df = pd.DataFrame([['T_1','ID1','P1','2018-07-03 19:10:19', 'AVAILABLE'],
               ['T_1','ID1','P1','2018-07-03 19:10:20', 'AVAILABLE'],
               ['T_1','ID1','P2','2018-07-03 19:12:33', 'AVAILABLE'],

               ['T_1','ID2','P3','2018-07-03 19:13:34', 'AVAILABLE'],
               ['T_1','ID2','P3','2018-07-03 19:13:35', 'NOT AVAILABLE'],
               ['T_1','ID2','P4','2018-07-03 19:16:24', 'AVAILABLE'],

               ['T_2','ID3','P4','2018-07-03 18:23:19', 'AVAILABLE'],
               ['T_2','ID3','P4','2018-07-03 18:23:20', 'NOT AVAILABLE'],
               ['T_2','ID1','P1','2018-07-03 19:10:21', 'NOT AVAILABLE'],
               ['T_2','ID2','P3','2018-07-03 19:13:36', 'NOT AVAILABLE'],
               ['T_2','ID2','P3','2018-07-03 19:13:37', 'NOT AVAILABLE'],
               ['T_2','ID3','P5','2018-07-03 20:56:33', 'AVAILABLE'],
               ['T_2','ID3','P5','2018-07-03 20:56:34', 'NOT AVAILABLE'],
               ['T_2','ID3','P5','2018-07-03 22:56:35', 'AVAILABLE'],
               ['T_2','ID3','P6','2018-07-03 22:57:20', 'NOT AVAILABLE'],
               ['T_2','ID3','P7','2018-07-03 22:58:35', 'AVAILABLE'],
               ['T_2','ID4','P8','2018-07-03 22:59:00', 'AVAILABLE'],
               ['T_2','ID1','P1','2018-07-03 23:12:00', 'AVAILABLE'],
               ['T_2','ID1','P3','2018-07-03 23:32:00', 'AVAILABLE']

              ], columns=['TOUR_ID','ID','PAGE_ID','CREATED DATE', 'AVAILABILITY'])
  • 1
    Как последняя строка NAN?
  • 0
    ID4 не имеет предыдущих значений, поэтому он должен быть NaN
Показать ещё 4 комментария
Теги:
pandas
performance

2 ответа

1
Лучший ответ

Это был довольно важный руководитель, но здесь один из способов решить эту проблему:

df = pd.read_csv('test.tsv').set_index(['TOUR_ID', 'ID', 'PAGE_ID'])

Получить последнюю строку каждой группы, сдвинуть вперед на один:

shifted = df.groupby(['TOUR_ID', 'ID', 'PAGE_ID']).last().shift(1).reset_index()

Теперь нас интересуют строки, где мы видим изменения в PAGE_ID но не в ID, поэтому мы строим логическую маску:

change = shifted != shifted.shift(1)
mask = np.array(change.PAGE_ID & ~change.ID & ~change.TOUR_ID)

Наконец, мы применяем маску и объединяемся для создания нового столбца:

shifted.set_index(['TOUR_ID', 'ID', 'PAGE_ID'], inplace=True)

shifted[~mask] = np.nan

result = df.join(shifted['AVAILABILITY'], rsuffix='LAST')
  • 0
    Привет Андрей. Большое спасибо за вашу помощь, это спокойное блестящее решение. Просто добавлю одну деталь: меня также интересуют изменения в ID. Фрейм данных, который я использовал в качестве примера, является упрощенной версией того, что у меня под рукой. В частности, идентификатор и PAGE_ID могут повторяться для другого идентификатора TOUR_ID. (например, ID2 - P3, может появиться в экземплярах T_2). Поскольку для этих экземпляров нет предварительных данных, их следует установить в NaN. Есть ли способ интегрировать это в ваше решение?
  • 0
    @MiloVentimiglia Другими словами, нас интересуют случаи, когда мы не наблюдаем изменения в TOUR_ID , в дополнение к ID ? Добавление ~change.TOUR_ID к конструкции маски должно учитывать это. Я отредактировал свой ответ соответственно.
Показать ещё 2 комментария
0

Хорошо, вот мой удар.

1) Создать вспомогательную серию P_INT (целая часть PAGE_ID)

2) Создайте вспомогательный DataFrame df_last_availability с помощью MultiIndex ['TOUR_ID', 'ID', 'P_INT']

3) Смещение P_INT на 1

4) Сбросьте индекс исходного df, чтобы он соответствовал df_last_availability. Отсюда вы можете легко объединить (используя левое соединение) 2 DataFrames на индекс.

5) Последние прикованные методы - это просто очистка, чтобы вернуть рамку данных обратно в исходную форму - то есть передать вспомогательное поле и вернуть индекс обратно к нему.

df['P_INT'] = df.PAGE_ID.str.extract('(\d+)').astype(int)
df_last_availability = df.groupby(['TOUR_ID', 'ID', 'P_INT']).last()
df['P_INT'] = df.P_INT - 1

(df.set_index(['TOUR_ID', 'ID', 'P_INT'])
.merge(df_last_availability[['AVAILABILITY']], how='left',
       left_index=True, right_index=True, suffixes=('', '_PREV'))
.reset_index()    
.drop(['P_INT'], axis=1))

Ещё вопросы

Сообщество Overcoder
Наверх
Меню