При использовании groupby (), как я могу создать DataFrame с новым столбцом, содержащим индекс номера группы, аналогично dplyr::group_indices в R. Например, если у меня есть

>>> df=pd.DataFrame({'a':[1,1,1,2,2,2],'b':[1,1,2,1,1,2]})
>>> df
   a  b
0  1  1
1  1  1
2  1  2
3  2  1
4  2  1
5  2  2

Как я могу получить DataFrame как

   a  b  idx
0  1  1  1
1  1  1  1
2  1  2  2
3  2  1  3
4  2  1  3
5  2  2  4

(порядок индексов idx не имеет значения)

27
user2667066 11 Янв 2017 в 18:27

6 ответов

Лучший ответ

Вот краткий способ использования drop_duplicates и merge для получения уникального идентификатора.

group_vars = ['a','b']
df.merge( df.drop_duplicates( group_vars ).reset_index(), on=group_vars )

   a  b  index
0  1  1      0
1  1  1      0
2  1  2      2
3  2  1      3
4  2  1      3
5  2  2      5

Идентификатор в этом случае идет 0,2,3,5 (только остаток от исходного индекса), но его можно легко изменить на 0,1,2,3 с дополнительным reset_index(drop=True).

Обновление . Более новые версии панд (0.20.2) предлагают более простой способ сделать это с помощью метода ngroup, как отмечено в комментарии к вышеупомянутому вопросу @Constantino и последующим ответом от @CalumYou . Я оставлю это здесь как альтернативный подход, но ngroup кажется лучшим способом сделать это в большинстве случаев.

15
JohnE 31 Авг 2019 в 13:47

Вот решение, использующее ngroup из комментарий выше от Константино, для тех, кто все еще ищет эту функцию (эквивалент dplyr::group_indices в R, если вы пытались гуглить с такими ключевыми словами, как я). Это также примерно на 25% быстрее, чем решение, полученное с помощью maxliving в зависимости от моего времени.

>>> import pandas as pd
>>> df = pd.DataFrame({'a':[1,1,1,2,2,2],'b':[1,1,2,1,1,2]})
>>> df['idx'] = df.groupby(['a', 'b']).ngroup()
>>> df
   a  b  idx
0  1  1    0
1  1  1    0
2  1  2    1
3  2  1    2
4  2  1    2
5  2  2    3

>>> %timeit df['idx'] = create_index_usingduplicated(df, grouping_cols=['a', 'b'])
1.83 ms ± 67.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit df['idx'] = df.groupby(['a', 'b']).ngroup()
1.38 ms ± 30 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
15
Calum You 13 Июл 2018 в 17:13

Простой способ сделать это - объединить столбцы группировки (чтобы каждая комбинация их значений представляла собой уникальный элемент), а затем преобразовать его в pandas Категориальный и сохраняйте только его метки:

df['idx'] = pd.Categorical(df['a'].astype(str) + '_' + df['b'].astype(str)).codes
df

    a   b   idx
0   1   1   0
1   1   1   0
2   1   2   1
3   2   1   2
4   2   1   2
5   2   2   3

Изменить: изменил свойства labels на codes, так как первый кажется устаревшим

Edit2: добавлен разделитель в соответствии с предложением Authman Apatira

15
foglerit 16 Авг 2018 в 18:47

Я не уверен, что это такая тривиальная проблема. Вот несколько запутанное решение, которое сначала сортирует группирующие столбцы, а затем проверяет, отличается ли каждая строка от предыдущей строки и, если это так, накапливается на 1. Проверьте еще раз ниже для ответа со строковыми данными.

df.sort_values(['a', 'b']).diff().fillna(0).ne(0).any(1).cumsum().add(1)

Выход

0    1
1    1
2    2
3    3
4    3
5    4
dtype: int64

Итак, разбив это на шаги, давайте посмотрим на вывод df.sort_values(['a', 'b']).diff().fillna(0), который проверяет, отличается ли каждая строка от предыдущей. Любая ненулевая запись указывает на новую группу.

     a    b
0  0.0  0.0
1  0.0  0.0
2  0.0  1.0
3  1.0 -1.0
4  0.0  0.0
5  0.0  1.0

Новая группа должна иметь только один отдельный столбец, поэтому .ne(0).any(1) проверяет это - не равно 0 для любого из столбцов. А потом просто накопительная сумма, чтобы отслеживать группы.

Ответ для столбцов в виде строк

#create fake data and sort it
df=pd.DataFrame({'a':list('aabbaccdc'),'b':list('aabaacddd')})
df1 = df.sort_values(['a', 'b'])

Вывод df1

   a  b
0  a  a
1  a  a
4  a  a
3  b  a
2  b  b
5  c  c
6  c  d
8  c  d
7  d  d

Используйте аналогичный подход, проверив, изменилась ли группа

df1.ne(df1.shift().bfill()).any(1).cumsum().add(1)

0    1
1    1
4    1
3    2
2    3
5    4
6    5
8    5
7    6
1
Ted Petrou 11 Янв 2017 в 16:03

Определенно, не самое простое решение, но вот что я бы сделал (комментарии в коде):

df=pd.DataFrame({'a':[1,1,1,2,2,2],'b':[1,1,2,1,1,2]})

#create a dummy grouper id by just joining desired rows
df["idx"] = df[["a","b"]].astype(str).apply(lambda x: "".join(x),axis=1)

print df

Это сгенерирует уникальный idx для каждой комбинации a и b.

   a  b idx
0  1  1  11
1  1  1  11
2  1  2  12
3  2  1  21
4  2  1  21
5  2  2  22

Но это все еще довольно глупый индекс (подумайте о некоторых более сложных значениях в столбцах a и b. Итак, давайте очистим индекс:

# create a dictionary of dummy group_ids and their index-wise representation
dict_idx = dict(enumerate(set(df["idx"])))

# switch keys and values, so you can use dict in .replace method
dict_idx = {y:x for x,y in dict_idx.iteritems()}

#replace values with the generated dict
df["idx"].replace(dict_idx,inplace=True)

print df

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

   a  b  idx
0  1  1    0
1  1  1    0
2  1  2    1
3  2  1    2
4  2  1    2
5  2  2    3
2
Marjan Moderc 11 Янв 2017 в 16:14

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

def create_index_usingduplicated(df, grouping_cols=['a', 'b']):
    df.sort_values(grouping_cols, inplace=True)
    # You could do the following three lines in one, I just thought 
    # this would be clearer as an explanation of what's going on:
    duplicated = df.duplicated(subset=grouping_cols, keep='first')
    new_group = ~duplicated
    return new_group.cumsum()

Сроки результаты:

a = np.random.randint(0, 1000, size=int(1e5))
b = np.random.randint(0, 1000, size=int(1e5))
df = pd.DataFrame({'a': a, 'b': b})

In [6]: %timeit df['idx'] = pd.Categorical(df['a'].astype(str) + df['b'].astype(str)).codes
1 loop, best of 3: 375 ms per loop

In [7]: %timeit df['idx'] = create_index_usingduplicated(df, grouping_cols=['a', 'b'])
100 loops, best of 3: 17.7 ms per loop
2
maxliving 13 Сен 2017 в 19:46