Penyortiran kustom dalam bingkai data panda

93

Saya memiliki dataframe python pandas, di mana kolom berisi nama bulan.

Bagaimana cara melakukan pengurutan kustom menggunakan kamus, misalnya:

custom_dict = {'March':0, 'April':1, 'Dec':3}  
Kathirmani Sukumar
sumber
1
Apakah kolom berisi nama bulan berarti ada kolom yang berisi nama bulan (sebagai jawaban saya), atau banyak kolom dengan nama kolom sebagai nama bulan (sebagai eumiro's)?
Andy Hayden
1
Jawaban yang diterima sudah usang, dan juga secara teknis salah, karena pd.Categoricaltidak menafsirkan kategori seperti yang diurutkan secara default. Lihat jawaban ini .
cs95

Jawaban:

149

Pandas 0.15 memperkenalkan Categorical Series , yang memungkinkan cara yang lebih jelas untuk melakukan ini:

Pertama buat kolom bulan menjadi kategorikal dan tentukan urutan yang akan digunakan.

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

Sekarang, ketika Anda mengurutkan kolom bulan itu akan mengurutkan sehubungan dengan daftar itu:

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

Catatan: jika suatu nilai tidak ada dalam daftar, itu akan dikonversi ke NaN.


Jawaban yang lebih tua bagi mereka yang tertarik ...

Anda bisa membuat seri perantara, dan set_indexdi atasnya:

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

Seperti yang dikomentari, di panda yang lebih baru, Series memiliki replacemetode untuk melakukannya dengan lebih elegan:

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

Perbedaan kecilnya adalah bahwa ini tidak akan meningkat jika ada nilai di luar kamus (itu hanya akan tetap sama).

Andy Hayden
sumber
s = df['m'].replace({'March':0, 'April':1, 'Dec':3})berfungsi untuk baris 2 juga - hanya untuk siapa saja yang mempelajari panda seperti saya
kdauria
@kdauria tempat yang bagus! (sudah lama sejak saya menulis ini!) ganti pilihan terbaik yang pasti, yang lain adalah menggunakan .apply({'March':0, 'April':1, 'Dec':3}.get):) Dalam 0.15 kita akan memiliki Seri / kolom Kategorikal, jadi cara terbaik adalah menggunakan itu dan kemudian mengurutkan hanya akan bekerja.
Andy Hayden
@AndyHayden Saya telah mengambil kebebasan untuk mengganti baris kedua dengan metode 'ganti'. Saya harap itu baik-baik saja.
Faheem Mitha
Edit @AndyHayden ditolak, tetapi menurut saya ini adalah perubahan yang wajar.
Faheem Mitha
7
Pastikan Anda menggunakan df.sort_values("m")panda yang lebih baru (bukan df.sort("m")), jika tidak Anda akan mendapatkan AttributeError: 'DataFrame' object has no attribute 'sort';)
brainstorming
21

panda> = 1.1

Anda akan segera dapat menggunakan sort_valuesdengan keyargumen:

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

The keyArgumen mengambil sebagai masukan Series dan kembali Seri. Seri ini diurutkan secara internal dan indeks yang diurutkan digunakan untuk menyusun ulang input DataFrame. Jika ada beberapa kolom untuk diurutkan, fungsi kunci akan diterapkan ke setiap kolom secara bergantian. Lihat Menyortir dengan kunci .


panda <= 1.0.X

Satu metode sederhana adalah menggunakan output Series.mapdan Series.argsortmengindeks ke dalam dfpenggunaan DataFrame.iloc(karena argsort menghasilkan posisi integer yang diurutkan); karena Anda memiliki kamus; ini menjadi mudah.

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

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

Jika Anda perlu mengurutkan dalam urutan menurun , balikkan pemetaan.

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

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

Perhatikan bahwa ini hanya berfungsi pada item numerik. Jika tidak, Anda perlu mengatasinya dengan menggunakan sort_values, dan mengakses indeks:

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

Lebih banyak opsi tersedia dengan astype(ini tidak digunakan lagi sekarang), atau pd.Categorical, tetapi Anda perlu menentukannya ordered=Trueagar berfungsi dengan benar .

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

Sekarang, sort_valuespanggilan sederhana akan berhasil:

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

Urutan kategoris juga akan diberlakukan saat groupbymengurutkan output.

cs95
sumber
2
Anda sudah menekankannya, tetapi saya ingin mengulangi jika ada orang lain yang membaca sekilas dan melewatkannya: Pandas Categorical disetel ordered=Nonesecara default. Jika tidak disetel, urutannya akan salah, atau rusak di V23. Fungsi Max khususnya memberikan TypeError (Categorical tidak dipesan untuk operasi max).
Dave Liu
17

Memperbarui

gunakan jawaban yang dipilih ! Ini lebih baru dari posting ini dan bukan hanya cara resmi untuk memelihara data yang dipesan di panda, itu lebih baik dalam segala hal, termasuk fitur / kinerja, dll. Jangan gunakan metode hacky yang saya jelaskan di bawah ini.

Saya hanya menulis pembaruan ini karena orang-orang terus memberi suara positif pada jawaban saya, tetapi ini jelas lebih buruk daripada yang diterima :)

Posting asli

Agak terlambat untuk permainan, tapi berikut adalah cara untuk membuat fungsi yang mengurutkan pandas Series, DataFrame, dan multiindex objek DataFrame menggunakan fungsi arbitrer.

Saya menggunakan df.iloc[index]metode ini, yang mereferensikan baris dalam Seri / DataFrame berdasarkan posisi (dibandingkan df.loc, yang mereferensikan berdasarkan nilai). Menggunakan ini, kita hanya perlu memiliki sebuah fungsi yang mengembalikan serangkaian argumen posisi:

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

Anda dapat menggunakan ini untuk membuat fungsi pengurutan kustom. Ini bekerja pada kerangka data yang digunakan dalam jawaban 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

Ini juga berfungsi pada objek DataFrames dan Series multiindex:

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

Bagi saya ini terasa bersih, tetapi menggunakan operasi python daripada mengandalkan operasi panda yang dioptimalkan. Saya belum melakukan pengujian stres tetapi saya membayangkan ini bisa menjadi lambat pada DataFrames yang sangat besar. Tidak yakin bagaimana kinerja dibandingkan dengan menambahkan, menyortir, lalu menghapus kolom. Setiap tip untuk mempercepat kode akan sangat kami hargai!

Michael Delgado
sumber
Apakah ini akan berfungsi untuk menyortir banyak kolom / indeks?
ConanG
ya, tetapi jawaban yang dipilih adalah cara yang jauh lebih baik untuk melakukan ini. Jika Anda memiliki beberapa indeks, cukup susun menurut urutan yang Anda inginkan, lalu gunakan df.sort_index()untuk mengurutkan semua tingkat indeks.
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))

mengembalikan DataFrame dengan kolom Maret, April, Desember

eumiro
sumber
Ini mengurutkan kolom sebenarnya, daripada mengurutkan baris berdasarkan predikat kustom di kolom?
cs95