Niestandardowe sortowanie w ramce danych pandy

89

Mam Dataframe Pandy Pythona, w której kolumna zawiera nazwę miesiąca.

Jak mogę wykonać niestandardowe sortowanie za pomocą słownika, na przykład:

custom_dict = {'March':0, 'April':1, 'Dec':3}  
Kathirmani Sukumar
źródło
1
Czy kolumny zawierają nazwę miesiąca, to znaczy, że istnieje kolumna zawierająca nazwy miesięcy (jak moja odpowiedź), czy też wiele kolumn z nazwami kolumn jako nazwami miesięcy (jak eumiro)?
Andy Hayden
1
Przyjęta odpowiedź jest nieaktualna, a także technicznie niepoprawna, ponieważ pd.Categoricaldomyślnie nie interpretuje kategorii w kolejności. Zobacz tę odpowiedź .
cs95

Odpowiedzi:

141

Pandy 0.15 wprowadziły serię kategorialną , która pozwala na znacznie jaśniejszy sposób:

Najpierw ustaw kolumnę miesiąca jako kategoryczną i określ kolejność, która ma być używana.

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

Teraz, kiedy posortujesz kolumnę miesiąca, zostanie ona posortowana według tej listy:

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

Uwaga: jeśli wartość nie znajduje się na liście, zostanie przekonwertowana na NaN.


Starsza odpowiedź dla zainteresowanych ...

Możesz stworzyć serię pośrednią i set_indexna tym:

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

Jak skomentowano, w nowszych pandach Series ma replacemetodę, aby zrobić to bardziej elegancko:

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

Niewielka różnica polega na tym, że nie wzrośnie, jeśli wartość znajduje się poza słownikiem (po prostu pozostanie taka sama).

Andy Hayden
źródło
s = df['m'].replace({'March':0, 'April':1, 'Dec':3})działa również dla linii 2 - tylko dla dobra każdego uczącego się pandy jak ja
kdauria
@kdauria dobre miejsce! (minęło trochę czasu, odkąd to napisałem!) zastąp zdecydowanie najlepszą opcją, inną jest użycie .apply({'March':0, 'April':1, 'Dec':3}.get):) W 0.15 będziemy mieli Serie / kolumny kategorialne, więc najlepszym sposobem będzie użycie tego i wtedy sortowanie po prostu zadziała.
Andy Hayden
@AndyHayden Pozwoliłem sobie zastąpić drugą linię metodą „zamień”. Mam nadzieję, że wszystko w porządku.
Faheem Mitha
Edycja @AndyHayden odrzucona, ale nadal uważam, że jest to rozsądna zmiana.
Faheem Mitha
7
Tylko upewnij się, że używasz df.sort_values("m")w nowszych pandach (zamiast df.sort("m")), w przeciwnym razie otrzymasz AttributeError: 'DataFrame' object has no attribute 'sort';)
burza mózgów
17

pandy> = 1.1

Wkrótce będziecie mogli korzystać sort_valuesz keyargumentu:

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

keyArgumentem bierze jako wejście serii i wraca serii. Ta seria jest wewnętrznie posortowana za pomocą argumentów, a posortowane indeksy są używane do zmiany kolejności wejściowej ramki DataFrame. Jeśli istnieje wiele kolumn do sortowania, funkcja klucza zostanie zastosowana do każdej z nich po kolei. Zobacz Sortowanie za pomocą kluczy .


pandy <= 1.0.X

Jedną z prostych metod jest użycie wyjścia Series.mapi Series.argsortindeksowanie do dfusing DataFrame.iloc(ponieważ argsort tworzy posortowane pozycje liczb całkowitych); ponieważ masz słownik; staje się to łatwe.

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

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

Jeśli chcesz posortować w porządku malejącym , odwróć mapowanie.

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

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

Zwróć uwagę, że działa to tylko w przypadku elementów numerycznych. W przeciwnym razie będziesz musiał obejść ten problem, używając sort_valuesi uzyskując dostęp do indeksu:

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

Więcej opcji jest dostępnych z astype(jest to obecnie przestarzałe) lub pd.Categorical, ale musisz określić ordered=True, aby działało poprawnie .

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

Teraz wystarczy prosty sort_valuestelefon:

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

Porządkowanie kategorialne będzie również uwzględniane podczas groupbysortowania wyników.

cs95
źródło
2
Podkreśliłeś już to, ale chciałbym powtórzyć na wypadek, gdyby ktoś inny go przejrzał i przegapił: ordered=NoneDomyślnie ustawia kategorię Pandy . Jeśli nie zostanie ustawione, kolejność będzie błędna lub zepsuje się na V23. W szczególności funkcja Max daje błąd TypeError (kategoria nie jest uporządkowana dla operacji max).
Dave Liu
16

Trochę późno w grze, ale oto sposób na utworzenie funkcji, która sortuje pandy Series, DataFrame i obiekty DataFrame z wieloma indeksami przy użyciu dowolnych funkcji.

Korzystam z df.iloc[index]metody, która odwołuje się do wiersza w Series / DataFrame według pozycji (w porównaniu z df.locodwołaniami według wartości). Używając tego, musimy po prostu mieć funkcję, która zwraca serię argumentów pozycyjnych:

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

Możesz użyć tego do tworzenia niestandardowych funkcji sortowania. Działa to na ramce danych użytej w odpowiedzi Andy'ego Haydena:

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

Działa to również w przypadku obiektów DataFrames i Series z wieloma indeksami:

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

Wydaje mi się, że jest to czyste, ale intensywnie wykorzystuje operacje w Pythonie, zamiast polegać na zoptymalizowanych operacjach pand. Nie wykonałem żadnych testów warunków skrajnych, ale wyobrażam sobie, że może to działać wolno na bardzo dużych ramkach DataFrame. Nie wiem, jak wypada porównanie wydajności z dodawaniem, sortowaniem, a następnie usuwaniem kolumny. Wszelkie wskazówki dotyczące przyspieszenia kodu będą mile widziane!

Michael Delgado
źródło
Czy to zadziała w przypadku sortowania wielu kolumn / indeksów?
ConanG
tak, ale wybrana odpowiedź jest o wiele lepszym sposobem na zrobienie tego. Jeśli masz wiele indeksów, po prostu ułóż je zgodnie z preferowanym porządkiem sortowania, a następnie użyj df.sort_index()do posortowania wszystkich poziomów indeksów.
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))

zwraca DataFrame z kolumnami March, April, Dec

eumiro
źródło
Powoduje to sortowanie rzeczywistych kolumn zamiast sortowania wierszy na podstawie niestandardowego predykatu w kolumnie?
cs95