Замените pandas groupby и примените для увеличения производительности - PullRequest
3 голосов
/ 21 февраля 2020

Я использую pandas groupby и применяю к go из DataFrame, содержащего 150 миллионов строк со следующими столбцами:

Id  Created     Item    Stock   Price
1   2019-01-01  Item 1  200     10
1   2019-01-01  Item 2  100     15
2   2019-01-01  Item 1  200     10

К списку из 2,2 миллиона записей, который выглядит следующим образом :

[{
  "Id": 1,
  "Created": "2019-01-01",
  "Items": [
    {"Item":"Item 1", "Stock": 200, "Price": 10},
    {"Item":"Item 2", "Stock": 100, "Price": 5}
    ]
},
{
  "Id": 2,
  "Created": "2019-01-01",
  "Items": [
    {"Item":"Item 1", "Stock": 200, "Price": 10}
    ]
}]

В основном используя эту строку кода:

df.groupby(['Id', 'Created']).apply(lambda x: x[['Item', 'Stock', 'Price']].to_dict(orient='records'))

Это занимает довольно много времени, и, насколько я понимаю, подобные операции тяжелы для pandas. Есть ли не pandas способ выполнить sh такой же, но с большей производительностью?

Редактировать : операция занимает 55 минут, я использую ScriptProcessor в AWS, что позвольте мне указать количество энергии, которое я хочу.

Редактировать 2 : Итак, с помощью решения artonas я подхожу ближе: вот что мне удается произвести сейчас:

defaultdict(<function __main__.<lambda>()>,
            {'1': defaultdict(list,
                         {'Id': '1',
                          'Created':'2019-01-01',
                          'Items': [{'Item': Item2, 'Stock': 100, 'Price': 15},
                                    {'Item': Item1, 'Stock': 200, 'Price': 10}]
                         })
            },
           {'2': defaultdict(list,
                         {'Id': '2',
                          'Created':'2019-01-01',
                          'Items': [{'Item': Item1, 'Stock': 200, 'Price': 10}]
                         })
            },

Но как сделать go из вышесказанного, к этому?

[{
  "Id": 1,
  "Created": "2019-01-01",
  "Items": [
    {"Item":"Item 1", "Stock": 200, "Price": 10},
    {"Item":"Item 2", "Stock": 100, "Price": 5}
    ]
},
{
  "Id": 2,
  "Created": "2019-01-01",
  "Items": [
    {"Item":"Item 1", "Stock": 200, "Price": 10}
    ]
}]

По сути, меня интересует только часть после "defaultdict (list") для всех записей. Мне нужно, чтобы он был в списке это не зависит от Id в качестве ключа.

Редактировать 3 : Последнее обновление, содержащее результаты для моего набора производственных данных. С принятым ответом, предоставленным artona, мне удалось go от 55 минут до 7 (!) Минут. И без каких-либо серьезных изменений в моем коде. Решение, предоставленное Phung Duy Phong, заняло у меня от 55 минут до 17, что тоже неплохо.

Ответы [ 3 ]

1 голос
/ 21 февраля 2020

Если фрейм данных правильно отсортирован, то есть здесь все строки для одной пары (Id, Created) являются последовательными, вы можете просто выполнить их итерацию. Но поскольку итерация фрейма данных стоит дорого, поскольку pandas должен создавать новую серию для каждой строки, я бы напрямую итерировал базовые numpy массивы.

Код мог бы быть:

records = []
Id = None

for i in range(len(df)):
    if df['Id'].values[i] != Id or df['Created'].values[i] != created:
        items = []
        Id = df['Id'].values[i]
        created = df['Created'].values[i]
        records.append({'Id': Id, 'Created': created,
                'Items': items})

    items.append({x: df[x].values[i]
              for x in ['Item', 'Stock', 'Price']})

Если данные изначально не отсортированы, вы можете попробовать отсортировать кадр с помощью pandas, а затем использовать приведенный выше код

1 голос
/ 21 февраля 2020

Использовать collection.defaultdict и itertuples. Итерации по строке только один раз.

In [105]: %timeit df.groupby(['Id', 'Created']).apply(lambda x: x[['Item', 'Stock', 'Price']].to_dict(orient='records'))
10.1 s ± 44.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [107]:from collections import defaultdict
     ...:def create_dict():
     ...:     dict_ids = defaultdict(lambda : defaultdict(list))
     ...:     for row in df.itertuples():
     ...:          dict_ids[row.Id][row.Created].append({"Item": row.Item, "Stock": row.Stock, "Price": row.Price})
     ...:     list_of_dicts = [{"Id":key_id, "Created":key_created, "Items": values} for key_id, value_id in dict_ids.items() for key_created, values in value_id.items()]
     ...:     return list_of_dicts

In [108]: %timeit create_dict()
4.58 s ± 417 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
0 голосов
/ 21 февраля 2020

Попробуйте следующее:

df['Items'] = df.loc[:, ['X', 'Y', 'Z']].to_dict(orient='records')
df.groupby(['ID', 'CREATED'])['Items'].apply(list).reset_index().to_dict(orient='records')
...