Агрегирование с использованием произвольного приоритета в pandas

#python #pandas #pandas-groupby

#python #pandas #pandas-groupby

Вопрос:

Учитывая фрейм данных

   Column1 Column2  Column3
0       a     foo        1
1       a     bar        2
2       b     baz       12
3       b     foo        4
4       c     bar        6
5       c     foo        3
6       c     baz        7
7       d     foo        9
  

Я хотел бы сгруппировать по столбцу 1, используя произвольный порядок приоритета, значения которого следует сохранить из столбца 3.

Например, если порядок приоритета равен:

  1. база
  2. панель
  3. foo

тогда я бы ожидал, что результат будет отображаться как

          Column2
Column1         
a              2
b             12
c              7
d              9
  

при этом группа «a» сохраняет значение «bar», поскольку для группы «a» нет «baz», группа «b» сохраняет значение «baz» и так далее.

Какой самый элегантный способ сделать это? Прямо сейчас я применяю серию apply lambda для работы с каждым элементом, но это кажется неаккуратным.

РЕДАКТИРОВАТЬ: Что, если преценденция проходит через несколько столбцов?

Пример.

   Column1 Column2 Column3  Column4
0       a     foo    john        1
1       a     bar     jim        2
2       b     baz    jack       12
3       b     foo     jim        4
4       c     bar    john        6
5       c     foo    john        3
6       c     baz    jack        7
7       d     foo    jack        9
  

Если порядок приоритета как для Column2, так и для Column3 равен:

  1. джим
  2. база
  3. foo

тогда я бы ожидал, что результат будет отображаться как

         Column2  Column3
Column1                 
a           jim        2
b           jim        4
c           baz        7
d           foo        9
  

Ответ №1:

Вы можете попробовать использовать приведенную ниже логику с map затем groupby transform

 order = ['baz','bar','foo']
d = {v:k for k,v in dict(enumerate(order)).items()}
out = df.assign(k=df['Column2'].map(d))

print(df[out['k'].eq(out.groupby("Column1")['k'].transform("min"))])
  

   Column1 Column2  Column3
1       a     bar        2
2       b     baz       12
6       c     baz        7
7       d     foo        9
  

РЕДАКТИРОВАТЬ, для нескольких столбцов, используя ту же логику, что и выше, вот способ:

 order = ['jim','baz','foo']
d = {i:e for e,i in enumerate(order)}

s = df[['Column2','Column3']].replace(d).apply(pd.to_numeric,errors='coerce').min(1)

out = (s[s.eq(s.groupby(df['Column1']).transform("min"))]
       .replace(dict(enumerate(order))).rename("Col"))

df.loc[out.index,["Column1","Column4"]].join(out)

  Column1  Column4  Col
1       a        2  jim
3       b        4  jim
6       c        7  baz
7       d        9  foo
  

Комментарии:

1. Спасибо за это! Что вы думаете о дополнительной сложности упорядоченных элементов, хранящихся в двух разных столбцах? Может быть, создать и дополнительный столбец с элементом наивысшего приоритета в нем, а затем запустить эту логику в этом новом столбце?

2. Привет, Анки, спасибо, что поделились хорошим решением, не могли бы вы подробнее объяснить, v:k for k,v in dict(enumerate(order)).items() пожалуйста, буду благодарен вам.

3. @RavinderSingh13 мы создаем перечислитель в виде словаря и меняем местами ключи и значения, чтобы мы могли использовать сопоставление с существующей серией, вы также можете напрямую использовать что-то вроде {i:e for e,i in enumerate(order)}

4. @bcalc вы можете попробовать то, что предложил Allolz в своем ответе, я также отредактировал другой способ, используя replace и аналогичную логику

Ответ №2:

Если у вас есть порядок для всех значений в ‘Column2’, вы можете использовать loc после установки индекса, чтобы наложить свой пользовательский порядок, а затем drop_duplicates сохранить только наивысший приоритет.

 order = ['baz', 'bar', 'foo']
df.set_index('Column2').loc[order].drop_duplicates('Column1')

        Column1  Column3
Column2                 
baz           b       12
baz           c        7
bar           a        2
foo           d        9
  

Во втором случае, если вам нужно сделать это в нескольких столбцах, мы сначала melt складываем столбцы 2 и Column3 в один длинный ряд, а остальные — так же, как указано выше:

 order = ['jim', 'baz', 'foo']
(df.melt(id_vars=['Column4', 'Column1'], value_vars=['Column2', 'Column3'])
   .drop(columns='variable')
   .set_index('value')
   .loc[order]
   .drop_duplicates('Column1')
)

       Column4 Column1
value                 
jim          2       a
jim          4       b
baz          7       c
foo          9       d
  

Комментарии:

1. Спасибо за это! Что вы думаете о дополнительной сложности упорядоченных элементов, хранящихся в двух разных столбцах? Может быть, создать и дополнительный столбец с элементом наивысшего приоритета в нем, а затем запустить эту логику в этом новом столбце?

2. @bcalc В этом случае я бы сначала melt затем вы можете применить любое из решений, как и в первом случае.

Ответ №3:

Вы можете попробовать преобразовать Column2 в категориальный:

 df['Column2'] = pd.Categorical(df['Column2'], ordered=True, categories=['baz','bar','foo'])

df.sort_values(['Column1','Column2']).drop_duplicates('Column1')
  

Вывод:

   Column1 Column2  Column3
1       a     bar        2
2       b     baz       12
6       c     baz        7
7       d     foo        9
  

Комментарии:

1.Спасибо за это! Что вы думаете о дополнительной сложности упорядоченных элементов, хранящихся в двух разных столбцах? Может быть, создать и дополнительный столбец с элементом наивысшего приоритета в нем, а затем запустить эту логику в этом новом столбце?