У меня есть dataframe, как это:
index customerID item_tag orderID Amount
0 23 A 1 34.50
1 55 B 2 11.22
2 23 A 3 9.34
3 55 D 4 123.44
4 55 F 5 231.40
У меня также есть список, содержащий item_tags:
my_list = ['A', 'B', 'D']
Теперь я хочу проверить, сколько элементов из my_list
было заказано каждым клиентом. Например, для клиента 23 это число будет = 1, так как заказчик 23 будет указывать только элемент, помеченный как A, но не B или D. Клиент 55, однако, заказал элементы B и D, поэтому эта переменная индикатора будет равна 2, так как две типы элементов из my_list
присутствуют в его заказах. (он также заказал элемент F, но этот элемент не находится в my_list
).
До сих пор я пробовал groupby([customerId, item_tag], as_index = False).count()
, но для этого требуется создать новый фреймворк данных (может быть, не обязательно?), А затем использовать оператор if для каждого элемента в списке, но я подозреваю, что есть более элегантный способ. Я не мог найти ни одного, ни Google, ни здесь. У моей DataFrame есть миллион строк, поэтому я ищу наиболее эффективное решение.
В результате я хочу, чтобы dataframe выглядел следующим образом:
index customerID if_A if_B if_D sum_in_list
0 23 1 0 0 1
1 55 0 1 1 2
Здесь одним из способов использования get_dummies
+ groupby
, вы получаете счет бесплатно:
res = pd.get_dummies(df[['customerID', 'item_tag']], columns=['item_tag'])\
.groupby(['customerID'], as_index=False).sum()
print(res)
customerID item_tag_A item_tag_B item_tag_D item_tag_F
0 23 2 0 0 0
1 55 0 1 1 1
Есть несколько дополнительных шагов, если вы хотите получить двоичный результат и ограничить определенные теги:
L = ['A', 'B', 'D']
df_filtered = df.loc[df['item_tag'].isin(L), ['customerID', 'item_tag']]
res = pd.get_dummies(df_filtered, columns=['item_tag'])\
.groupby(['customerID']).any().astype(int).reset_index()
res['total_count'] = res.iloc[:, 1:].sum(axis=1)
print(res)
customerID item_tag_A item_tag_B item_tag_D total_count
0 23 1 0 0 1
1 55 0 1 1 2
Это фильтрованная кросс-таблица, и мы можем увидеть несколько вариантов их выполнения здесь, в ответ на вопрос № 9
crosstab
и clip_upper
pd.crosstab(df.customerID, df.item_tag).clip_upper()[my_list]
item_tag A B D
customerID
23 1 0 0
55 0 1 1
Добавьте assign
чтобы получить суммирование при использовании lambda
чтобы сохранить его в строке
pd.crosstab(df.customerID, df.item_tag).clip_upper(1)[my_list].assign(
Total=lambda d: d.sum(1))
item_tag A B D Total
customerID
23 1 0 0 1
55 0 1 1 2
pandas.Series
Интересная альтернатива с построением нового объекта серии. Я item_tag
его таким образом, чтобы поместить item_tag
на первом уровне MultiIndex, оставив удобнее использовать loc
и нарезать теги, которые меня волнуют.
s = pd.Series(1, set(zip(df.item_tag, df.customerID)))
s.loc[my_list].unstack(0, fill_value=0).assign(
Total=lambda d: d.sum(1))
A B D Total
23 1 0 0 1
55 0 1 1 2
Мое решение отфильтровывает нежелательные продукты, а затем группирует:
wanted = df[df['item_tag'].isin(my_list)]
wanted.groupby(['customerID', 'item_tag'])\
.count().unstack()['Amount'].fillna(0).astype(int)
#item_tag A B D
#customerID
#23 2 0 0
#55 0 1 1