эффективные группы pandas применяют два кадра данных вместе с tqdm - PullRequest
0 голосов
/ 13 октября 2018

Метод 1 (ясно, но очень медленно)

product_ids = df1.product_id.unique()
store_ids= df1.store_id.unique()

with tqdm(total=product_ids.shape[0]*store_ids.shape[0]) as t:
    for product_id in product_ids:
        p1 = df1.loc[(df1.product_id==product_id)]
        p2 = df2.loc[(df2.product_id==product_id)]
        for store_id in store_ids:
            df11 = p1.loc[(p1.store_id==store_id)]
            df22 = p2.loc[(p2.store_id==store_id)]
            train_predict(df11, df22)
            t.update()

Метод 2 (быстро, но мне не нравится)

df1 = df1.reset_index()
df2 = df2.reset_index().set_index(['store_id', 'product_id'])

def _reduce(df_orderitems):
    MIN_ORDERITEMS_COUNT = 30

    store_id = df_orderitems.store_id.iloc[0]
    product_id = df_orderitems.product_id.iloc[0]

    try:
        ## !!!! here refer to global df2, I don't like  !!!!!
        df_stockquantitylog = df2.loc[(store_id, product_id)]
        ## !!!! here refer to global df2, I don't like  !!!!!   
    except KeyError:
        logger.info('## df_orderitems shape:%s , cannot find (%s, %s)' % (df_orderitems.shape, store_id, product_id) )
        return

    train_predict(df_orderitems, df_stockquantitylog)

tqdm.pandas()
df1.groupby(['store_id', 'product_id']).progress_apply(_reduce)

Мне нужен tqdm, чтобы показать индикатор выполнения, но метод 1очень медленно (я думаю, из-за неэффективной печати).Метод 2 с патчем tqdm pandas, другой ключевой момент, на мой взгляд, groupby.apply.Но я не могу понять, как сделать метод 1 таким же быстрым, как метод 2.

Примечание:

df1.shape[0] != df2.shape[0], не может объединиться.Они сбрасываются из базы данных.Например, может быть 10 строк с одинаковыми store_id A и product_id B в df1 и 100 строк с одинаковыми store_id A и product_id B в df2.Они не могут быть объединены перед правильной обработкой:

Нужно:

  1. сначала выбрать по store_id и product_id (в каждом df1 и df2)
  2. Вы не можетеприсоединиться без выбора.Я должен применить другое агрегирование с df1[(df1.store_id==A)&(df1.product_id==B)]) и df2[(df2.store_id==A)&(df2.product_id==B)]), чтобы дать им одинаковый DatatimeIndex для объединения, потому что некоторые столбцы метаданных должны агрегироваться по дате.Вы не можете сделать это без выбора, потому что разные комбинации store_id и product_id имеют повторяющиеся даты.
  3. Тогда оба результата являются сливаемыми (присоединяемыми)
  4. модель поезда

1 Ответ

0 голосов
/ 13 октября 2018

Итак, если ваша единственная проблема с методом 2 заключается в том, что вы не хотите смотреть на глобальный df2, почему бы не передать его в качестве второго параметра вашей функции?Например.

def _reduce(df_orderitems, df_): ...

Но я бы не советовал делать точно ни один из описанных здесь методов.

Итерирование по фрейму данных, как в методе 1, никогда не будет таким быстрымиспользуя apply, потому что apply внутренне оптимизирован с использованием Cython.На самом деле, это будет очень медленно (как вы нашли). Здесь - хорошее объяснение того, почему и ваши варианты для ускорения операций.

Ваш вопрос немного расплывчат, почему иначе вам не нравится метод 2, но я бы хотелсделать две вещи, если бы я был на вашем месте.

  1. Я бы использовал SQL-подобные операции в пандах для объединения двух фреймов данных.

У вас есть перекрывающиеся столбцы ('store_id' и 'product_id'), которые появляются как в df1, так и в df2, так что я бы сделал в пандах SQL-стиль join, чтобы объединить два кадра данных.Таким образом, вам не придется иметь дело с битами индексации, которые вы сейчас делаете.

Давайте сначала создадим фиктивные данные, которые, я думаю, представляют вашу ситуацию:

  df1 = pd.DataFrame({"store_id": ['A','A','A','B','B'],
                      "product_id": [0, 1, 2, 1, 0],
                      "record_number": [0, 1, 2, 3, 4], 
                      "data": [21, 22, 28, 26, 25]})

  df2 = pd.DataFrame({"store_id":['A','A','A','B','B', 'B'],
                      "product_id": [0, 1, 2, 0, 1, 2], 
                      "more_data":[35, 39, 36, 33, 37, 32]})

Затем вы можете использовать функцию join для объединения двух фреймов данных в стиле SQL на перекрывающихся друг с другомстолбцы (join использует индексы кадра данных).Это отображает данные из df2 на данные в df1, чтобы создать новый объединенный фрейм данных.( Подробно описано в pandas docs )

 merged = df1.join(df2.set_index(['store_id','product_id']), 
                   how='left', 
                   on=['store_id','product_id'], 
                   rsuffix='_df2_data')

Что дает вам

    store_id  product_id  record_number  product_data  more_product_data
  0        A           0              0            21                 35
  1        A           1              1            22                 39
  2        A           2              2            28                 36
  3        B           1              3            26                 37
  4        B           0              4            25                 33

В зависимости от полноты данных, вы, вероятно, захотите проверить наличие NaN

Вы также можете фильтровать, преобразовывать и т. Д. Ваши данные по мере необходимости (если есть другие этапы на этапе обработки)

Тогда я бы использовал apply (progress_apply), чтобы выполнить шаг прогнозирования

Вы можете либо создать новую функцию, либо использовать лямбда-функцию, в зависимости от вашей ситуации

def train_predict(a, b): 
    return a + b 

def predict_outcome(df_row):     
    return train_predict(df_row[['product_data']].values[0], 
                         df_row[['more_product_data']].values[0])

tqdm.pandas(desc='predict the outcome')
merged['prediction'] = merged.progress_apply(lambda x:  train_predict(x['product_data'],x['more_product_data']),
                                             axis='columns')

# or 

tqdm.pandas(desc='predict the outcome')
merged['prediction'] = merged.progress_apply(predict_outcome, axis='columns')

(аргумент axis='columns' указывает apply перебирать строки df)

...