Ordinamento personalizzato in dataframe panda


93

Ho dataframe panda python, in cui una colonna contiene il nome del mese.

Come posso eseguire un ordinamento personalizzato utilizzando un dizionario, ad esempio:

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

1
Una colonna contiene il nome del mese significa che esiste una colonna che contiene i nomi dei mesi (come la mia risposta) o molte colonne con i nomi delle colonne come nomi dei mesi (come quelli di eumiro)?
Andy Hayden

1
La risposta accettata è obsoleta ed è anche tecnicamente errata, in quanto pd.Categoricalnon interpreta le categorie come ordinate per impostazione predefinita. Vedi questa risposta .
cs95

Risposte:


149

Pandas 0.15 ha introdotto la serie categoriale , che consente un modo molto più chiaro per farlo:

Per prima cosa rendi categorica la colonna del mese e specifica l'ordine da utilizzare.

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

Ora, quando si ordina la colonna del mese, verrà ordinata rispetto a quella 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 un valore non è nell'elenco verrà convertito in NaN.


Una vecchia risposta per chi è interessato ...

Potresti creare una serie intermedia e set_indexsu questo:

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

Come commentato, nei nuovi panda, Series ha un replacemetodo per farlo in modo più elegante:

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

La leggera differenza è che questo non aumenterà se c'è un valore al di fuori del dizionario (rimarrà lo stesso).


s = df['m'].replace({'March':0, 'April':1, 'Dec':3})funziona anche per la riga 2 - solo per il bene di chiunque impari i panda come me
kdauria

@kdauria buon posto! (è passato un po 'di tempo da quando ho scritto questo!) Sostituisci sicuramente l'opzione migliore, un'altra è quella di usare .apply({'March':0, 'April':1, 'Dec':3}.get):) In 0.15 avremo Serie / colonne categoriali, quindi il modo migliore sarà usarle e poi l'ordinamento funzionerà.
Andy Hayden

@ AndyHayden mi sono preso la libertà di sostituire la seconda riga con il metodo "sostituisci". Spero che sia ok.
Faheem Mitha

@ AndyHayden modifica rifiutata, ma continuo a pensare che sia un cambiamento ragionevole.
Faheem Mitha

7
Assicurati di usarlo df.sort_values("m")nei nuovi panda (invece di df.sort("m")), altrimenti otterrai un AttributeError: 'DataFrame' object has no attribute 'sort';)
brainstorming

22

panda> = 1.1

Presto potrai usare sort_valuescon keyargomento:

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

L' keyargomento prende come input una serie e restituisce una serie. Questa serie è classificata internamente e gli indici ordinati vengono utilizzati per riordinare il DataFrame di input. Se sono presenti più colonne su cui ordinare, la funzione chiave verrà applicata a ciascuna di esse a turno. Vedere Ordinamento con i tasti .


panda <= 1.0.X

Un metodo semplice è usare l'output Series.mape Series.argsortindicizzare in dfusing DataFrame.iloc(poiché argsort produce posizioni intere ordinate); visto che hai un dizionario; questo diventa facile.

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

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

Se è necessario ordinare in ordine decrescente , invertire la mappatura.

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

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

Nota che questo funziona solo su elementi numerici. Altrimenti, dovrai risolvere questo problema utilizzando sort_valuese accedendo all'indice:

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

Sono disponibili più opzioni con astype(ora è deprecato), o pd.Categorical, ma è necessario specificarlo ordered=Trueaffinché funzioni correttamente .

# 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)

Ora, una semplice sort_valueschiamata farà il trucco:

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

Anche l'ordinamento categoriale sarà rispettato quando groupbyordina l'output.


2
Lo hai già enfatizzato, ma vorrei ribadirlo nel caso in cui qualcun altro lo sfogli e lo perde: Pandas Categorical imposta ordered=Noneper impostazione predefinita. Se non impostato, l'ordine sarà sbagliato o si interromperà su V23. La funzione Max in particolare fornisce un TypeError (Categorical non è ordinato per l'operazione max).
Dave Liu

17

Aggiornare

usa la risposta selezionata ! è più recente di questo post e non è solo il modo ufficiale per mantenere i dati ordinati nei panda, è migliore sotto ogni aspetto, comprese le caratteristiche / prestazioni, ecc. Non usare il mio metodo hacky che descrivo di seguito.

Sto solo scrivendo questo aggiornamento perché le persone continuano a votare la mia risposta, ma è decisamente peggio di quella accettata :)

Post originale

Un po 'in ritardo per il gioco, ma ecco un modo per creare una funzione che ordina gli oggetti panda Series, DataFrame e multiindex DataFrame utilizzando funzioni arbitrarie.

Uso il df.iloc[index]metodo, che fa riferimento a una riga in un Series / DataFrame in base alla posizione (rispetto a df.loc, che fa riferimento in base al valore). Usando questo, dobbiamo solo avere una funzione che restituisca una serie di argomenti posizionali:

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

È possibile utilizzarlo per creare funzioni di ordinamento personalizzate. Funziona sul dataframe utilizzato nella risposta di 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

Funziona anche su DataFrame multiindex e oggetti 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

Per me questo sembra pulito, ma utilizza pesantemente le operazioni python piuttosto che fare affidamento su operazioni panda ottimizzate. Non ho eseguito alcun test di stress, ma immagino che questo potrebbe rallentare su DataFrame molto grandi. Non sono sicuro di come le prestazioni si confrontino con l'aggiunta, l'ordinamento e l'eliminazione di una colonna. Eventuali suggerimenti per velocizzare il codice sarebbero apprezzati!


Funzionerebbe per l'ordinamento di più colonne / indici?
ConanG

sì, ma la risposta selezionata è un modo molto migliore per farlo. Se si dispone di più indici, è sufficiente disporli in base all'ordinamento che si preferisce, quindi utilizzare df.sort_index()per ordinare tutti i livelli di indice.
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))

restituisce un DataFrame con colonne March, April, Dec


Questo ordina le colonne effettive, invece di ordinare le righe in base al predicato personalizzato nella colonna?
cs95
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.