Метод groupby
pandas велик, когда элементы/строки объекта Series
/DataFrame
принадлежат к одной группе. Но у меня есть ситуация, когда каждая строка может принадлежать нулевой, одной или нескольким группам.
Пример с некоторыми гипотетическими данными:
+--------+-------+----------------------+
| Item | Count | Tags |
+--------+-------+----------------------+
| Apple | 5 | ['fruit', 'red'] |
| Tomato | 10 | ['vegetable', 'red'] |
| Potato | 3 | [] |
| Orange | 20 | ['fruit'] |
+--------+-------+----------------------+
Согласно столбцу "Теги", Apple и Tomato принадлежат к двум группам: картофель не принадлежит ни к одной группе, а к одному принадлежит апельсин. Таким образом, группировка по тегам и суммирование счетчика для каждого тега должны давать:
+-----------+-------+
| Tag | Count |
+-----------+-------+
| fruit | 25 |
| red | 15 |
| vegetable | 10 |
+-----------+-------+
Как можно выполнить эту операцию?
'Count'
по длине 'Tags'
df.Count.repeat(df.Tags.str.len()).groupby(np.concatenate(df.Tags)).sum()
fruit 25
red 15
vegetable 10
Name: Count, dtype: int64
numpy.bincount
и pandas.factorize
i, r = pd.factorize(np.concatenate(df.Tags))
c = np.bincount(i, df.Count.repeat(df.Tags.str.len()))
pd.Series(c.astype(df.Count.dtype), r)
fruit 25
red 15
vegetable 10
dtype: int64
from collections import defaultdict
import pandas as pd
counts = [5, 10, 3, 20]
tags = [['fruit', 'red'], ['vegetable', 'red'], [], ['fruit']]
d = defaultdict(int)
for c, T in zip(counts, tags):
for t in T:
d[t] += c
print(pd.Series(d))
print()
print(pd.DataFrame([*d.items()], columns=['Tag', 'Count']))
fruit 25
red 15
vegetable 10
dtype: int64
Tag Count
0 fruit 25
1 red 15
2 vegetable 10
Я решил эту проблему, написав функцию, которую я назвал groupby_many
. Он работает как DataFrame
объектами Series
и с DataFrame
:
import numpy as np
import pandas as pd
def groupby_many(data, groups):
"""
Groups a Series or DataFrame object where each row can belong to many groups.
Parameters
----------
data : Series or DataFrame
The data to group
groups : iterable of iterables
For each row in data, the groups that row belongs to.
A row can belong to zero, one, or multiple groups.
Returns
-------
A GroupBy object
"""
pairs = [(i, g) for (i, gg) in enumerate(groups) for g in gg]
row, group = zip(*pairs)
return data.iloc[list(row)].groupby(list(group))
Он работает, создавая версию данных, где каждая строка дублируется n раз, где n - количество групп, к которым принадлежит строка. Каждая строка в этой версии относится только к одной группе, поэтому теперь ее можно обрабатывать обычной groupby
.
Чтобы увидеть это в действии на выборку данных в вопросе:
>>> df = pd.DataFrame.from_dict({
'Item': ["Apple", "Tomato", "Potato", "Orange"],
'Count': [5, 10, 3, 20],
'Tags': [['fruit', 'red'], ['vegetable', 'red'], [], ['fruit']]})
>>> df = df.set_index('Item')
>>> print(df)
Count Tags
Item
Apple 5 [fruit, red]
Tomato 10 [vegetable, red]
Potato 3 []
Orange 20 [fruit]
>>> result = groupby_many(df, df['Tags']).sum()
>>> print(result)
Count
fruit 25
red 15
vegetable 10