Benutzerdefinierte Sortierung im Pandas-Datenrahmen

89

Ich habe Python Pandas Datenrahmen, in dem eine Spalte Monatsnamen enthält.

Wie kann ich eine benutzerdefinierte Sortierung mithilfe eines Wörterbuchs durchführen, zum Beispiel:

custom_dict = {'March':0, 'April':1, 'Dec':3}  
Kathirmani Sukumar
quelle
1
Bedeutet eine Spalte einen Monatsnamen, dass es eine Spalte gibt, die Monatsnamen enthält (als meine Antwort), oder viele Spalten mit Spaltennamen als Monatsnamen (als Eumiros)?
Andy Hayden
1
Die akzeptierte Antwort ist veraltet und auch technisch falsch, da pd.Categoricaldie Kategorien nicht wie standardmäßig sortiert interpretiert werden. Siehe diese Antwort .
CS95

Antworten:

141

Mit Pandas 0.15 wurde die kategoriale Serie eingeführt , mit der dies viel klarer möglich ist:

Machen Sie zuerst die Monatsspalte zu einer Kategorie und geben Sie die zu verwendende Reihenfolge an.

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

Wenn Sie nun die Monatsspalte sortieren, wird sie in Bezug auf diese Liste sortiert:

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

Hinweis: Wenn ein Wert nicht in der Liste enthalten ist, wird er in NaN konvertiert.


Eine ältere Antwort für Interessierte ...

Sie könnten eine Zwischenserie erstellen, und dazu set_index:

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

Wie bereits erwähnt, hat Series bei neueren Pandas eine replaceMethode, um dies eleganter zu tun:

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

Der kleine Unterschied besteht darin, dass dies nicht erhöht wird, wenn es einen Wert außerhalb des Wörterbuchs gibt (er bleibt einfach gleich).

Andy Hayden
quelle
s = df['m'].replace({'March':0, 'April':1, 'Dec':3})funktioniert auch für Zeile 2 - nur für alle, die Pandas wie mich lernen
kdauria
@ Kdauria guter Ort! (Es ist schon eine Weile her, seit ich das geschrieben habe!) Ersetze definitiv die beste Option, eine andere ist die Verwendung .apply({'March':0, 'April':1, 'Dec':3}.get):) In 0.15 werden wir kategoriale Reihen / Spalten haben, also wird der beste Weg sein, das zu verwenden und dann zu sortieren wird einfach funktionieren.
Andy Hayden
@AndyHayden Ich habe mir erlaubt, die zweite Zeile durch die 'replace'-Methode zu ersetzen. Ich hoffe das ist ok
Faheem Mitha
@AndyHayden bearbeiten abgelehnt, aber ich denke immer noch, dass es eine vernünftige Änderung ist.
Faheem Mitha
7
Stellen Sie einfach sicher, dass Sie df.sort_values("m")in neueren Pandas (anstelle von df.sort("m")) verwenden, sonst erhalten Sie ein AttributeError: 'DataFrame' object has no attribute 'sort';)
Brainstorming
17

Pandas> = 1.1

Sie werden bald in der Lage sein, sort_valuesmit keyArgumenten zu verwenden:

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

Das keyArgument nimmt eine Serie als Eingabe und gibt eine Serie zurück. Diese Reihe ist intern sortiert und die sortierten Indizes werden verwendet, um den eingegebenen DataFrame neu zu ordnen. Wenn mehrere Spalten sortiert werden müssen, wird die Schlüsselfunktion nacheinander auf jede Spalte angewendet. Siehe Sortieren mit Schlüsseln .


Pandas <= 1.0.X.

Eine einfache Methode besteht darin, die Ausgabe zu verwenden Series.mapund Series.argsortin dfusing zu indizieren DataFrame.iloc(da argsort sortierte ganzzahlige Positionen erzeugt). da du ein Wörterbuch hast; das wird einfach.

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

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

Wenn Sie in absteigender Reihenfolge sortieren müssen , kehren Sie die Zuordnung um.

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

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

Beachten Sie, dass dies nur bei numerischen Elementen funktioniert. Andernfalls müssen Sie dies umgehen sort_valuesund auf den Index zugreifen:

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

Weitere Optionen sind verfügbar mit astype(dies ist jetzt veraltet) oder pd.Categorical, aber Sie müssen angeben, ordered=Truedamit es ordnungsgemäß funktioniert .

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

Jetzt reicht ein einfacher sort_valuesAnruf aus:

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

Die kategoriale Reihenfolge wird auch beim groupbySortieren der Ausgabe berücksichtigt.

cs95
quelle
2
Sie haben es bereits betont, aber ich möchte es wiederholen, falls jemand anderes es überfliegt und verpasst: Pandas Kategoriesätze ordered=Nonestandardmäßig. Wenn nicht eingestellt, ist die Reihenfolge falsch oder bricht auf V23. Insbesondere die Max-Funktion gibt einen TypeError aus (Categorical ist für Operation max nicht geordnet).
Dave Liu
16

Ein bisschen spät im Spiel, aber hier ist eine Möglichkeit, eine Funktion zu erstellen, die Pandas Series-, DataFrame- und Multiindex-DataFrame-Objekte mit beliebigen Funktionen sortiert.

Ich benutze die df.iloc[index]Methode, die eine Zeile in einem Series / DataFrame nach Position referenziert (im Vergleich zu der df.loc, die nach Wert referenziert). Damit benötigen wir nur eine Funktion, die eine Reihe von Positionsargumenten zurückgibt:

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

Hiermit können Sie benutzerdefinierte Sortierfunktionen erstellen. Dies funktioniert mit dem Datenrahmen, der in Andy Haydens Antwort verwendet wird:

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

Dies funktioniert auch bei Multiindex-DataFrames- und Serienobjekten:

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

Für mich fühlt sich das sauber an, aber es verwendet Python-Operationen stark, anstatt sich auf optimierte Pandas-Operationen zu verlassen. Ich habe keine Stresstests durchgeführt, aber ich würde mir vorstellen, dass dies bei sehr großen DataFrames langsam werden könnte. Nicht sicher, wie die Leistung im Vergleich zum Hinzufügen, Sortieren und Löschen einer Spalte ist. Tipps zur Beschleunigung des Codes sind willkommen!

Michael Delgado
quelle
Würde dies zum Sortieren mehrerer Spalten / Indizes funktionieren?
ConanG
Ja, aber die ausgewählte Antwort ist ein weitaus besserer Weg, dies zu tun. Wenn Sie mehrere Indizes haben, ordnen Sie diese einfach in der von Ihnen bevorzugten Sortierreihenfolge an und sortieren Sie dann df.sort_index()alle Indexebenen.
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))

Gibt einen DataFrame mit den Spalten März, April, Dezember zurück

Eumiro
quelle
Dadurch werden die tatsächlichen Spalten sortiert, anstatt die Zeilen nach dem benutzerdefinierten Prädikat für die Spalte zu sortieren.
cs95