Фильтровать одновременно по разным значениям строк Панды - PullRequest
0 голосов
/ 27 июня 2018

У меня огромный массив данных с product_id и их property_id. Обратите внимание, что для каждого свойства начинается новый индекс. Мне нужно фильтровать одновременно по различным значениям property_id для каждого product_id. Есть ли способ сделать это быстро?

out_df

product_id  property_id
0   3588    1
1   3588    2
2   3588    5
3   3589    1
4   3589    3
5   3589    5
6   3590    1
7   3590    2
8   3590    5

Например, вы хотите отфильтровать для каждого product_id два свойства, которые присваиваются различным строкам, таким как out_df.loc[(out_df['property_id'] == 1) & (out_df['property_id'] == 2)], но вместо него). Мне нужно что-то подобное, но работает одновременно для всех строк каждого столбца product_id.

Я знаю, что это можно сделать через groupby в списки

3587    [2, 1, 5]
3588    [1, 3, 5]
3590    [1, 2, 5]

и поиск пересечений внутри списков.

gp_df.apply(lambda r: {1, 2} < (set(r['property_id'])), axis=1)  

Но это требует времени, и в то же время обычная фильтрация Pandas значительно оптимизируется по скорости (поверьте в использование некоторых хитрых прямых и обратных индексов внутри того, что делают поисковые системы, такие как ElasticSearch, Sphinx и т. Д.).

Ожидаемый результат : где оба {1 и 2} имеют.

3587    [2, 1, 5]
3590    [1, 2, 5]

Ответы [ 3 ]

0 голосов
/ 27 июня 2018

Самым простым является использование GroupBy.transform со сравнительными наборами:

s = {1, 2}
a = df[df.groupby('product_id')['property_id'].transform(lambda r: s < set(r))]
print (a)
   product_id  property_id
0        3588            1
1        3588            2
2        3588            5
6        3590            1
7        3590            2
8        3590            5

Другое решение - фильтровать только значения наборов, сначала удаляя дубликаты:

df1 = df[df['property_id'].isin(s) & ~df.duplicated(['product_id', 'property_id'])]

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

f, u = df1['product_id'].factorize()
ids = df1.loc[np.bincount(f)[f] == len(s), 'product_id'].unique()

Последний фильтр всех строк с product_id по условию:

a = df[df['product_id'].isin(ids)]
print (a)
   product_id  property_id
0        3588            1
1        3588            2
2        3588            5
6        3590            1
7        3590            2
8        3590            5
0 голосов
/ 27 июня 2018

Поскольку это не только функциональный вопрос, но и производительность, я бы выбрал такой подход пересечения:

df = pd.DataFrame({'product_id': [3588, 3588, 3588, 3589, 3589, 3589, 3590, 3590,3590], 
                   'property_id': [1, 2, 5, 1, 3, 5, 1, 2, 5]})

df = df.set_index(['property_id'])

print("The full DataFrame:")
print(df)

start = time()

for i in range(1000):
    s1 = df.loc[(1), 'product_id']
    s2 = df.loc[(2), 'product_id']

    s_done = pd.Series(list(set(s1).intersection(set(s2))))

print("Overlapping product_id's")
print(time()-start)

Итерация 1000 раз занимает на моем ThinkPad T450s 0,93 секунды . Я взял на себя смелость протестировать два предложения @ jezrael, и они приходят в 2.11 и 2.00 секунды, групповой подход, хотя и мудрый в разработке программного обеспечения, более элегантный.

В зависимости от размера набора данных и важности производительности, вы также можете переключиться на более простые типы данных, такие как классические словари, и получить дополнительную скорость.


Блокнот Jupyter можно найти здесь: pandas_fast_lookup_using_intersection.ipynb

0 голосов
/ 27 июня 2018

ты имеешь в виду что-то подобное?

result = out_df.loc[out_df['property_id'].isin([1,2]), :]

Если хотите, вы можете удалить дубликаты на основе product_id ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...