группирование панд DataFrame с заранее определенными группами - PullRequest
0 голосов
/ 07 марта 2019

Мне интересно, как эффективно сделать что-то вроде groupby, когда у меня есть предопределенные группы, и элементы могут принадлежать нескольким группам одновременно.

Предположим, у меня есть следующее DataFrame:

df = pd.DataFrame({'value': [0, 2, 4]}, index=['A', 'B', 'C'])
   value
A      0
B      2
C      4

и у меня есть следующие предопределенные группы, которые могут перекрываться и иметь другой размер:

groups = {'group 1': ['A', 'B'],
          'group 2': ['A', 'B', 'C']}

Теперь я хочу выполнить функцию на DataFrame группы.Например, я хочу вычислить среднее значение value для каждой группы.

Я думал о создании промежуточного "расширенного" DataFrame, на котором я мог бы запустить groupby:

intermediate_df = pd.DataFrame(columns=['id', 'group', 'value'])
intermediate_df['value'] = intermediate_df['value'].astype(float)

for group, members in groups.items():
    for id_ in members:
        row = pd.Series([id_, group, df.at[id_, 'value']],
                        index=['id', 'group', 'value'])
        intermediate_df = intermediate_df.append(row, ignore_index=True)
  id    group  value
0  A  group 1    0.0
1  B  group 1    2.0
2  A  group 2    0.0
3  B  group 2    2.0
4  C  group 2    4.0

Затем я мог бы сделать

intermediate_df.groupby('group').mean()

, который дал бы мне желаемый результат:

         value
group         
group 1    1.0
group 2    2.0

Конечно, способ создания этого промежуточного звена DataFrame абсолютно неэффективен.Что будет эффективным решением для моей проблемы?

Ответы [ 3 ]

2 голосов
/ 07 марта 2019

Вы можете создать свой intermediate_df с помощью Pandas.concat и понимания списка:

intermediate_df = pd.concat([df.loc[v].assign(group=k) for k, v in groups.items()])

[OUT]

   value    group
A      0  group 1
B      2  group 1
A      0  group 2
C      4  group 2
1 голос
/ 07 марта 2019

Изменить попытку для неравных групп:

pd.DataFrame().from_dict(groups, orient='index').T.stack().map(df.squeeze()).mean(level=1)

Вы также можете сделать это так:

pd.DataFrame(groups).stack().map(df.squeeze()).mean(level=1)

Выход:

group 1    1
group 2    2
dtype: int64
0 голосов
/ 08 марта 2019

Основываясь на предыдущих ответах, я использую понимание списка для intermediate_df

intermediate_df = pd.DataFrame([[group, id_] for group, members in groups.items() for id_ in members], 
                               columns=['group', 'id']).merge(df, left_on='id', right_index=True)

Это, кажется, самое быстрое решение по сравнению с другими ответами:

n=10000
m=1000
df = pd.DataFrame({'value': np.random.normal(size=n)}, index=np.arange(n).astype(str))
groups = {str(i): list(df.sample(5).index) for i in range(m)}
%%timeit
intermediate_df = pd.concat([df.loc[members].assign(group=group) for group, members in groups.items()])
intermediate_df.groupby('group').mean()

948 ms ± 63.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
pd.DataFrame(groups).stack().map(df.squeeze()).mean(level=1)

42.4 ms ± 183 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
intermediate_df = pd.DataFrame([[group, id_] for group, members in groups.items() for id_ in members], 
                               columns=['group', 'id']).merge(df, left_on='id', right_index=True)
intermediate_df.groupby('group').mean()

6.13 ms ± 50.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...