Группировать строки серии Pandas или DataFrame, когда строки могут принадлежать нескольким группам

1

Метод 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 |
+-----------+-------+

Как можно выполнить эту операцию?

  • 0
    Спасибо @asynts, я случайно использовал старую версию исходной таблицы. Отредактировано, чтобы исправить.
Теги:
pandas
pandas-groupby

2 ответа

2

взорвите столбец '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
  • 0
    Хорошо, я не знал о функции повтора. Колонка, по которой я группировал в действительности, не была строкой, но похоже, что ее можно адаптировать.
  • 0
    Я просто предупреждаю, что эти решения несколько специфичны для простого примера в вопросе. Я попытался сделать мой groupby_many более общим, чтобы вы могли использовать его, если значения для группировки не являются столбцом в DataFrame, и, поскольку он возвращает объект GroupBy, вы можете объединять несколько столбцов DataFrame.
Показать ещё 1 комментарий
1

Я решил эту проблему, написав функцию, которую я назвал 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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню