Подтвердить что ты не робот

Именование возвращенных столбцов в агрегированной функции Pandas?

У меня возникают проблемы с функциональностью Pandas groupby. Я прочитал документацию, но я не могу понять, как применять агрегированные функции к нескольким столбцам и иметь собственные имена для этих столбцов.

Это очень близко, но возвращенная структура данных имеет вложенные заголовки столбцов:

data.groupby("Country").agg(
        {"column1": {"foo": sum()}, "column2": {"mean": np.mean, "std": np.std}})

(т.е. хочу взять среднее значение и std столбца2, но возвращать эти столбцы как "mean" и "std" )

Что мне не хватает?

4b9b3361

Ответ 1

Это приведет к удалению внешнего уровня из индекса иерархического столбца:

df = data.groupby(...).agg(...)
df.columns = df.columns.droplevel(0)

Если вы хотите сохранить внешний уровень, вы можете использовать функцию ravel() в многоуровневом столбце для создания новых меток:

df.columns = ["_".join(x) for x in df.columns.ravel()]

Например:

import pandas as pd
import pandas.rpy.common as com
import numpy as np

data = com.load_data('Loblolly')
print(data.head())
#     height  age Seed
# 1     4.51    3  301
# 15   10.89    5  301
# 29   28.72   10  301
# 43   41.74   15  301
# 57   52.70   20  301

df = data.groupby('Seed').agg(
    {'age':['sum'],
     'height':['mean', 'std']})
print(df.head())
#       age     height           
#       sum        std       mean
# Seed                           
# 301    78  22.638417  33.246667
# 303    78  23.499706  34.106667
# 305    78  23.927090  35.115000
# 307    78  22.222266  31.328333
# 309    78  23.132574  33.781667

df.columns = df.columns.droplevel(0)
print(df.head())

дает

      sum        std       mean
Seed                           
301    78  22.638417  33.246667
303    78  23.499706  34.106667
305    78  23.927090  35.115000
307    78  22.222266  31.328333
309    78  23.132574  33.781667

В качестве альтернативы, чтобы сохранить первый уровень индекса:

df = data.groupby('Seed').agg(
    {'age':['sum'],
     'height':['mean', 'std']})
df.columns = ["_".join(x) for x in df.columns.ravel()]

дает

      age_sum   height_std  height_mean
Seed                           
301        78    22.638417    33.246667
303        78    23.499706    34.106667
305        78    23.927090    35.115000
307        78    22.222266    31.328333
309        78    23.132574    33.781667

Ответ 2

Для панд> = 0,25

Функциональность для именования возвращаемых агрегатных столбцов была повторно введена в основную ветку и предназначена для панд 0.25. Новый синтаксис: .agg(new_col_name=('col_name', 'agg_func'). Подробный пример из PR, связанного выше:

In [2]: df = pd.DataFrame({'kind': ['cat', 'dog', 'cat', 'dog'],
   ...:                    'height': [9.1, 6.0, 9.5, 34.0],
   ...:                    'weight': [7.9, 7.5, 9.9, 198.0]})
   ...:

In [3]: df
Out[3]:
  kind  height  weight
0  cat     9.1     7.9
1  dog     6.0     7.5
2  cat     9.5     9.9
3  dog    34.0   198.0

In [4]: df.groupby('kind').agg(min_height=('height', 'min'), 
                               max_weight=('weight', 'max'))
Out[4]:
      min_height  max_weight
kind
cat          9.1         9.9
dog          6.0       198.0

Также будет возможно использовать несколько лямбда-выражений с этим синтаксисом и двухэтапным синтаксисом переименования, который я предложил ранее (ниже) в соответствии с этим PR. Опять же, копирование из примера в пиаре:

In [2]: df = pd.DataFrame({"A": ['a', 'a'], 'B': [1, 2], 'C': [3, 4]})

In [3]: df.groupby("A").agg({'B': [lambda x: 0, lambda x: 1]})
Out[3]:
         B
  <lambda> <lambda 1>
A
a        0          1

а затем .rename() или за один раз:

In [4]: df.groupby("A").agg(b=('B', lambda x: 0), c=('B', lambda x: 1))
Out[4]:
   b  c
A
a  0  0

Для панд <0,25

В настоящее время принятый ответ от unutbu описывает отличный способ сделать это в версиях для панд <= 0.20. Тем не менее, что касается панд 0,20, использование этого метода вызывает предупреждение, указывающее, что синтаксис не будет доступен в будущих версиях панд.

Серии:

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

DataFrames:

FutureWarning: использование dict с переименованием устарело и будет удалено в будущей версии

Согласно журналу изменений панд 0,20, рекомендуемый способ переименования столбцов при агрегировании следующий.

# Create a sample data frame
df = pd.DataFrame({'A': [1, 1, 1, 2, 2],
                   'B': range(5),
                   'C': range(5)})

# ==== SINGLE COLUMN (SERIES) ====
# Syntax soon to be deprecated
df.groupby('A').B.agg({'foo': 'count'})
# Recommended replacement syntax
df.groupby('A').B.agg(['count']).rename(columns={'count': 'foo'})

# ==== MULTI COLUMN ====
# Syntax soon to be deprecated
df.groupby('A').agg({'B': {'foo': 'sum'}, 'C': {'bar': 'min'}})
# Recommended replacement syntax
df.groupby('A').agg({'B': 'sum', 'C': 'min'}).rename(columns={'B': 'foo', 'C': 'bar'})
# As the recommended syntax is more verbose, parentheses can
# be used to introduce line breaks and increase readability
(df.groupby('A')
    .agg({'B': 'sum', 'C': 'min'})
    .rename(columns={'B': 'foo', 'C': 'bar'})
)

Пожалуйста, смотрите список изменений 0,20 для получения дополнительной информации.

Обновление 2017-01-03 в ответ на комментарий @JunkMechanic.

С синтаксисом словаря старого стиля можно было передавать несколько lambda функций в .agg, поскольку они будут переименованы с помощью ключа в переданном словаре:

>>> df.groupby('A').agg({'B': {'min': lambda x: x.min(), 'max': lambda x: x.max()}})

    B    
  max min
A        
1   2   0
2   4   3

Несколько функций также могут быть переданы в один столбец в виде списка:

>>> df.groupby('A').agg({'B': [np.min, np.max]})

     B     
  amin amax
A          
1    0    2
2    3    4

Однако это не работает с лямбда-функциями, так как они являются анонимными и все возвращают <lambda>, что вызывает конфликт имен:

>>> df.groupby('A').agg({'B': [lambda x: x.min(), lambda x: x.max]})
SpecificationError: Function names must be unique, found multiple named <lambda>

Чтобы избежать SpecificationError, именованные функции могут быть определены априори вместо использования lambda. Подходящие имена функций также избегают вызова .rename во фрейме данных впоследствии. Эти функции могут быть переданы с тем же синтаксисом списка, что и выше:

>>> def my_min(x):
>>>     return x.min()

>>> def my_max(x):
>>>     return x.max()

>>> df.groupby('A').agg({'B': [my_min, my_max]})

       B       
  my_min my_max
A              
1      0      2
2      3      4

Ответ 3

Если вы хотите иметь поведение, подобное JMP, создавая заголовки столбцов, которые сохраняют всю информацию из мультииндекса, который вы можете использовать:

newidx = []
for (n1,n2) in df.columns.ravel():
    newidx.append("%s-%s" % (n1,n2))
df.columns=newidx

Он изменит ваш фреймворк с помощью:

    I                       V
    mean        std         first
V
4200.0  25.499536   31.557133   4200.0
4300.0  25.605662   31.678046   4300.0
4400.0  26.679005   32.919996   4400.0
4500.0  26.786458   32.811633   4500.0

к

    I-mean      I-std       V-first
V
4200.0  25.499536   31.557133   4200.0
4300.0  25.605662   31.678046   4300.0
4400.0  26.679005   32.919996   4400.0
4500.0  26.786458   32.811633   4500.0

Ответ 4

Я согласен с ОП, что кажется более естественным и непротиворечивым называть и определять выходные столбцы в одном и том же месте (например, как это делается с помощью summarize по тидиверсу в R), но сейчас обходной путь в пандах - это создание новых столбцов. с желаемыми именами через assign перед агрегацией:

data.assign(
    f=data['column1'],
    mean=data['column2'],
    std=data['column2']
).groupby('Country').agg(dict(f=sum, mean=np.mean, std=np.std)).reset_index()

(Использование reset_index превращает 'Country', 'f', 'mean' и 'std' в обычные столбцы с отдельным целочисленным индексом.)

Ответ 5

С вдохновением @Joel Ostblom

Для тех, у кого уже есть работоспособный словарь для просто агрегации, вы можете использовать/модифицировать следующий код для агрегации более новой версии, разделяя агрегацию и переименование. Обратите внимание на вложенный словарь, если имеется более одного элемента.

def agg_translate_agg_rename(input_agg_dict):
    agg_dict = {}
    rename_dict = {}
    for k, v in input_agg_dict.items():
        if len(v) == 1:
            agg_dict[k] = list(v.values())[0]
            rename_dict[k] = list(v.keys())[0]
        else:
            updated_index = 1
            for nested_dict_k, nested_dict_v in v.items():
                modified_key = k + "_" + str(updated_index)
                agg_dict[modified_key] = nested_dict_v
                rename_dict[modified_key] = nested_dict_k
                updated_index += 1
    return agg_dict, rename_dict

one_dict = {"column1": {"foo": 'sum'}, "column2": {"mean": 'mean', "std": 'std'}}
agg, rename = agg_translator_aa(one_dict)

Получаем

agg = {'column1': 'sum', 'column2_1': 'mean', 'column2_2': 'std'}
rename = {'column1': 'foo', 'column2_1': 'mean', 'column2_2': 'std'}

Пожалуйста, дайте мне знать, если есть более разумный способ сделать это. Спасибо.

Ответ 6

например, этот тип данных, существует два уровня имени столбца:

 shop_id  item_id   date_block_num item_cnt_day       
                                  target              
0   0       30          1            31               

мы можем использовать этот код:

df.columns = [col[0] if col[-1]=='' else col[-1] for col in df.columns.values]

результат:

 shop_id  item_id   date_block_num target              
0   0       30          1            31