Classificação personalizada no dataframe do pandas

93

Tenho dataframe python pandas, em que uma coluna contém o nome do mês.

Como posso fazer uma classificação personalizada usando um dicionário, por exemplo:

custom_dict = {'March':0, 'April':1, 'Dec':3}  
Kathirmani Sukumar
fonte
1
Uma coluna contém o nome do mês significa que há uma coluna que contém os nomes dos meses (como minha resposta), ou muitas colunas com nomes de colunas como nomes de meses (como eumiro)?
Andy Hayden
1
A resposta aceita está desatualizada e também é tecnicamente incorreta, pois pd.Categoricalnão interpreta as categorias como ordenadas por padrão. Veja esta resposta .
cs95 de

Respostas:

149

O Pandas 0.15 introduziu a série categórica , que permite uma maneira muito mais clara de fazer isso:

Primeiro, torne a coluna do mês categórica e especifique a ordem a ser usada.

In [21]: df['m'] = pd.Categorical(df['m'], ["March", "April", "Dec"])

In [22]: df  # looks the same!
Out[22]:
   a  b      m
0  1  2  March
1  5  6    Dec
2  3  4  April

Agora, quando você classificar a coluna do mês, ela classificará em relação a essa lista:

In [23]: df.sort_values("m")
Out[23]:
   a  b      m
0  1  2  March
2  3  4  April
1  5  6    Dec

Nota: se um valor não estiver na lista, ele será convertido para NaN.


Uma resposta mais antiga para os interessados ​​...

Você poderia criar uma série intermediária, e set_indexsobre isso:

df = pd.DataFrame([[1, 2, 'March'],[5, 6, 'Dec'],[3, 4, 'April']], columns=['a','b','m'])
s = df['m'].apply(lambda x: {'March':0, 'April':1, 'Dec':3}[x])
s.sort_values()

In [4]: df.set_index(s.index).sort()
Out[4]: 
   a  b      m
0  1  2  March
1  3  4  April
2  5  6    Dec

Conforme comentado, em pandas mais recentes, Series tem um replacemétodo para fazer isso de forma mais elegante:

s = df['m'].replace({'March':0, 'April':1, 'Dec':3})

A pequena diferença é que ele não aumentará se houver um valor fora do dicionário (ele apenas permanecerá o mesmo).

Andy Hayden
fonte
s = df['m'].replace({'March':0, 'April':1, 'Dec':3})funciona para a linha 2 também - apenas para o bem de qualquer pessoa que esteja aprendendo pandas como eu
kdauria
@kdauria good spot! (já faz um tempo que eu escrevi isso!) substitua definitivamente a melhor opção, outra é usar .apply({'March':0, 'April':1, 'Dec':3}.get):) No 0.15 teremos séries / colunas categóricas, então a melhor maneira será usar isso e então a classificação funcionará.
Andy Hayden,
@AndyHayden Tomei a liberdade de substituir a segunda linha pelo método 'replace'. Espero que esteja tudo bem.
Faheem Mitha 01 de
@AndyHayden edição rejeitada, mas ainda acho que é uma mudança razoável.
Faheem Mitha 01 de
7
Apenas certifique-se de usar df.sort_values("m")em pandas mais novos (em vez de df.sort("m")), caso contrário, você terá um AttributeError: 'DataFrame' object has no attribute 'sort';)
brainstorm de
22

pandas> = 1,1

Em breve você poderá usar sort_valuescom o keyargumento:

pd.__version__
# '1.1.0.dev0+2004.g8d10bfb6f'

custom_dict = {'March': 0, 'April': 1, 'Dec': 3} 
df

   a  b      m
0  1  2  March
1  5  6    Dec
2  3  4  April

df.sort_values(by=['m'], key=lambda x: x.map(custom_dict))

   a  b      m
0  1  2  March
2  3  4  April
1  5  6    Dec

O keyargumento recebe como entrada uma série e retorna uma série. Esta série é classificada internamente e os índices classificados são usados ​​para reordenar o DataFrame de entrada. Se houver várias colunas para classificar, a função principal será aplicada a cada uma delas. Consulte Classificando com chaves .


pandas <= 1.0.X

Um método simples é usar a saída Series.mape Series.argsortindexar em dfusing DataFrame.iloc(uma vez que argsort produz posições inteiras classificadas); já que você tem um dicionário; isso se torna fácil.

df.iloc[df['m'].map(custom_dict).argsort()]

   a  b      m
0  1  2  March
2  3  4  April
1  5  6    Dec

Se você precisar classificar em ordem decrescente , inverta o mapeamento.

df.iloc[(-df['m'].map(custom_dict)).argsort()]

   a  b      m
1  5  6    Dec
2  3  4  April
0  1  2  March

Observe que isso só funciona com itens numéricos. Caso contrário, você precisará contornar isso usando sort_valuese acessando o índice:

df.loc[df['m'].map(custom_dict).sort_values(ascending=False).index]

   a  b      m
1  5  6    Dec
2  3  4  April
0  1  2  March

Mais opções estão disponíveis com astype(está obsoleto agora), ou pd.Categorical, mas você precisa especificar ordered=Truepara que funcione corretamente .

# Older version,
# df['m'].astype('category', 
#                categories=sorted(custom_dict, key=custom_dict.get), 
#                ordered=True)
df['m'] = pd.Categorical(df['m'], 
                         categories=sorted(custom_dict, key=custom_dict.get), 
                         ordered=True)

Agora, uma simples sort_valueschamada resolverá o problema:

df.sort_values('m')
 
   a  b      m
0  1  2  March
2  3  4  April
1  5  6    Dec

A ordem categórica também será respeitada ao groupbyclassificar a saída.

cs95
fonte
2
Você já enfatizou isso, mas eu gostaria de reiterar no caso de outra pessoa passar despercebida e perder: Conjuntos categóricos do Pandas ordered=Nonepor padrão. Se não for definido, a ordem estará errada ou falhará no V23. A função Max em particular fornece um TypeError (Categórico não é solicitado para a operação max).
Dave Liu
17

Atualizar

use a resposta selecionada ! é mais recente do que este post e não é apenas a forma oficial de manter dados ordenados nos pandas, é melhor em todos os aspectos, incluindo recursos / desempenho, etc. Não use meu método hacky que descrevo abaixo.

Só estou escrevendo esta atualização porque as pessoas continuam votando em minha resposta, mas é definitivamente pior do que a aceita :)

Postagem original

Um pouco tarde para o jogo, mas aqui está uma maneira de criar uma função que classifica objetos da série pandas, DataFrame e DataFrame multiindex usando funções arbitrárias.

Eu uso o df.iloc[index]método, que faz referência a uma linha em um Series / DataFrame por posição (em comparação com df.loc, que faz referência por valor). Usando isso, só precisamos ter uma função que retorne uma série de argumentos posicionais:

def sort_pd(key=None,reverse=False,cmp=None):
    def sorter(series):
        series_list = list(series)
        return [series_list.index(i) 
           for i in sorted(series_list,key=key,reverse=reverse,cmp=cmp)]
    return sorter

Você pode usar isso para criar funções de classificação personalizadas. Isso funciona no dataframe usado na resposta de Andy Hayden:

df = pd.DataFrame([
    [1, 2, 'March'],
    [5, 6, 'Dec'],
    [3, 4, 'April']], 
  columns=['a','b','m'])

custom_dict = {'March':0, 'April':1, 'Dec':3}
sort_by_custom_dict = sort_pd(key=custom_dict.get)

In [6]: df.iloc[sort_by_custom_dict(df['m'])]
Out[6]:
   a  b  m
0  1  2  March
2  3  4  April
1  5  6  Dec

Isso também funciona em DataFrames multi-índice e objetos Series:

months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']

df = pd.DataFrame([
    ['New York','Mar',12714],
    ['New York','Apr',89238],
    ['Atlanta','Jan',8161],
    ['Atlanta','Sep',5885],
  ],columns=['location','month','sales']).set_index(['location','month'])

sort_by_month = sort_pd(key=months.index)

In [10]: df.iloc[sort_by_month(df.index.get_level_values('month'))]
Out[10]:
                 sales
location  month  
Atlanta   Jan    8161
New York  Mar    12714
          Apr    89238
Atlanta   Sep    5885

sort_by_last_digit = sort_pd(key=lambda x: x%10)

In [12]: pd.Series(list(df['sales'])).iloc[sort_by_last_digit(df['sales'])]
Out[12]:
2    8161
0   12714
3    5885
1   89238

Para mim, isso parece limpo, mas usa fortemente as operações do python em vez de depender de operações otimizadas do pandas. Não fiz nenhum teste de estresse, mas imagino que isso poderia ficar lento em DataFrames muito grandes. Não tenho certeza de como o desempenho se compara à adição, classificação e exclusão de uma coluna. Quaisquer dicas sobre como acelerar o código serão apreciadas!

Michael Delgado
fonte
Isso funcionaria para classificar várias colunas / índices?
ConanG
sim, mas a resposta selecionada é uma maneira muito melhor de fazer isso. Se você tiver vários índices, organize-os de acordo com a ordem de classificação de sua preferência e use df.sort_index()para classificar todos os níveis de índice.
Michael Delgado
9
import pandas as pd
custom_dict = {'March':0,'April':1,'Dec':3}

df = pd.DataFrame(...) # with columns April, March, Dec (probably alphabetically)

df = pd.DataFrame(df, columns=sorted(custom_dict, key=custom_dict.get))

retorna um DataFrame com colunas março, abril, dezembro

eumiro
fonte
Isso classifica as colunas reais, em vez de classificar as linhas com base no predicado personalizado na coluna?
cs95 de