Группировать строки серии Pandas или DataFrame, когда строки могут принадлежать нескольким группам - PullRequest
0 голосов
/ 30 августа 2018

Метод панд groupby хорош, когда элементы / строки объекта Series / DataFrame принадлежат к одной группе. Но у меня есть ситуация, когда каждая строка может принадлежать нулю, одной или нескольким группам.

Пример с некоторыми гипотетическими данными:

+--------+-------+----------------------+
| Item   | Count | Tags                 |
+--------+-------+----------------------+
| Apple  |     5 | ['fruit', 'red']     |
| Tomato |    10 | ['vegetable', 'red'] |
| Potato |     3 | []                   |
| Orange |    20 | ['fruit']            |
+--------+-------+----------------------+

Согласно столбцу «Теги», и «Яблоко» и «Помидор» принадлежат к двум группам, «Картофель» не принадлежит ни к одной группе, а апельсин - к одной. Таким образом, группировка по тегам и суммирование количества для каждого тега должно дать:

+-----------+-------+
| Tag       | Count |
+-----------+-------+
| fruit     |    25 |
| red       |    15 |
| vegetable |    10 |
+-----------+-------+

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

Ответы [ 2 ]

0 голосов
/ 30 августа 2018

взорвать ваш '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 голосов
/ 30 августа 2018

Я решил эту проблему, написав функцию, которую я назвал groupby_many. Работает на объектах 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...