Saya telah melihat banyak jawaban yang diposting untuk pertanyaan tentang Stack Overflow yang melibatkan penggunaan metode Pandas apply
. Saya juga melihat pengguna berkomentar di bawah mereka mengatakan bahwa " apply
lambat, dan harus dihindari".
Saya telah membaca banyak artikel tentang topik kinerja yang menjelaskan apply
lambat. Saya juga telah melihat penafian di dokumen tentang bagaimana apply
fungsi kemudahan untuk meneruskan UDF (sepertinya tidak dapat menemukannya sekarang). Jadi, konsensus umum adalah yang apply
harus dihindari jika memungkinkan. Namun, hal ini menimbulkan pertanyaan berikut:
- Jika
apply
sangat buruk, lalu mengapa di API? - Bagaimana dan kapan saya harus membuat kode saya
apply
bebas? - Adakah situasi di mana
apply
yang baik (lebih baik dari solusi lain yang mungkin)?
python
pandas
performance
apply
cs95
sumber
sumber
returns.add(1).apply(np.log)
vs.np.log(returns.add(1)
adalah kasus di manaapply
umumnya akan sedikit lebih cepat, yang merupakan kotak hijau kanan bawah dalam diagram jpp di bawah ini.Jawaban:
apply
, Fungsi Kenyamanan yang Tidak Pernah Anda ButuhkanKami mulai dengan menjawab pertanyaan-pertanyaan di OP, satu per satu.
DataFrame.apply
danSeries.apply
adalah fungsi kenyamanan yang ditentukan masing-masing pada objek DataFrame dan Series.apply
menerima setiap fungsi yang ditentukan pengguna yang menerapkan transformasi / agregasi pada DataFrame.apply
secara efektif adalah peluru perak yang melakukan apa pun yang tidak dapat dilakukan oleh fungsi panda yang ada.Beberapa hal yang
apply
dapat dilakukan:axis=1
) atau kolom-bijaksana (axis=0
) pada DataFrameagg
atautransform
dalam kasus ini)result_type
argumen)....Diantara yang lain. Untuk informasi lebih lanjut, lihat Aplikasi Fungsi Baris atau Kolom dalam dokumentasi.
Jadi, dengan semua fitur ini, mengapa
apply
buruk? Hal ini karenaapply
ini lambat . Panda tidak membuat asumsi tentang sifat fungsi Anda, dan karenanya menerapkan fungsi Anda secara berulang ke setiap baris / kolom seperlunya. Selain itu, menangani semua situasi di atas berartiapply
menimbulkan beberapa overhead besar pada setiap iterasi. Lebih lanjut,apply
mengkonsumsi lebih banyak memori, yang merupakan tantangan untuk aplikasi yang dibatasi memori.Ada sangat sedikit situasi
apply
yang sesuai untuk digunakan (lebih lanjut tentang itu di bawah). Jika Anda tidak yakin apakah Anda harus menggunakanapply
, Anda mungkin sebaiknya tidak menggunakannya.Mari kita bahas pertanyaan selanjutnya.
Untuk mengubah kalimatnya, berikut adalah beberapa situasi umum di mana Anda ingin menghilangkan panggilan ke
apply
.Data Numerik
Jika Anda bekerja dengan data numerik, kemungkinan sudah ada fungsi cython yang di-vectorisasi yang melakukan apa yang Anda coba lakukan (jika tidak, silakan ajukan pertanyaan di Stack Overflow atau buka permintaan fitur di GitHub).
Bandingkan kinerja
apply
untuk operasi penjumlahan sederhana.Dari segi kinerja, tidak ada perbandingan, setara dengan cythonized jauh lebih cepat. Tidak perlu grafik, karena perbedaannya jelas bahkan untuk data mainan.
Bahkan jika Anda mengaktifkan melewatkan larik mentah dengan
raw
argumen, itu masih dua kali lebih lambat.Contoh lain:
Secara umum, carilah alternatif vektor jika memungkinkan.
String / Regex
Pandas menyediakan fungsi string "vektor" di sebagian besar situasi, tetapi ada kasus yang jarang terjadi di mana fungsi tersebut tidak ... "berlaku", bisa dikatakan.
Masalah umum adalah memeriksa apakah nilai dalam kolom ada di kolom lain dari baris yang sama.
Ini harus mengembalikan baris kedua dan ketiga, karena "donald" dan "minnie" ada di kolom "Judul" masing-masing.
Menggunakan apply, ini akan dilakukan dengan menggunakan
Namun, solusi yang lebih baik ada dengan menggunakan pemahaman daftar.
Hal yang perlu diperhatikan di sini adalah bahwa rutinitas berulang terjadi lebih cepat daripada
apply
, karena overhead yang lebih rendah. Jika Anda perlu menangani NaN dan dtypes yang tidak valid, Anda dapat membangunnya menggunakan fungsi kustom yang kemudian dapat Anda panggil dengan argumen di dalam pemahaman daftar.Untuk informasi lebih lanjut tentang kapan pemahaman daftar harus dianggap sebagai pilihan yang baik, lihat artikel saya: Untuk loop dengan pandas - Kapan saya harus peduli? .
Kesalahan Umum: Kolom Daftar yang Meledak
Orang-orang tergoda untuk menggunakan
apply(pd.Series)
. Ini mengerikan dalam hal performa.Pilihan yang lebih baik adalah dengan mendengarkan kolom dan meneruskannya ke pd.DataFrame.
Akhirnya,
Terapkan adalah fungsi kenyamanan, jadi ada yang situasi di mana overhead cukup diabaikan untuk memaafkan. Itu benar-benar tergantung pada berapa kali fungsi tersebut dipanggil.
Fungsi yang Vectorized untuk Seri, tapi bukan DataFrames
Bagaimana jika Anda ingin menerapkan operasi string pada beberapa kolom? Bagaimana jika Anda ingin mengonversi beberapa kolom menjadi datetime? Fungsi ini dibuat vektor untuk Seri saja, sehingga harus diterapkan di setiap kolom yang ingin Anda konversi / operasikan.
Ini adalah kasus yang dapat diterima untuk
apply
:Perhatikan bahwa ini juga masuk akal
stack
, atau hanya menggunakan loop eksplisit. Semua opsi ini sedikit lebih cepat daripada menggunakanapply
, tetapi perbedaannya cukup kecil untuk dimaafkan.Anda dapat membuat kasus serupa untuk operasi lain seperti operasi string, atau konversi ke kategori.
v / s
Dan seterusnya...
Mengonversi Seri menjadi
str
:astype
versusapply
Ini sepertinya merupakan keistimewaan API. Menggunakan
apply
untuk mengonversi bilangan bulat dalam Seri menjadi string sebanding (dan terkadang lebih cepat) daripada menggunakanastype
.Grafik diplot menggunakan
perfplot
perpustakaan.Dengan pelampung, saya melihat
astype
secara konsisten secepat, atau sedikit lebih cepat dariapply
. Jadi ini ada hubungannya dengan fakta bahwa data dalam pengujian adalah tipe integer.GroupBy
operasi dengan transformasi berantaiGroupBy.apply
belum dibahas hingga saat ini, tetapiGroupBy.apply
juga merupakan fungsi kemudahan berulang untuk menangani apa pun yang tidak dimilikiGroupBy
fungsi yang ada .Satu persyaratan umum adalah melakukan GroupBy dan kemudian dua operasi utama seperti "cumsum tertinggal":
Anda memerlukan dua panggilan grup melalui telepon di sini:
Dengan menggunakan
apply
, Anda dapat mempersingkat ini menjadi satu panggilan.Sangat sulit untuk mengukur kinerja karena bergantung pada data. Tetapi secara umum,
apply
merupakan solusi yang dapat diterima jika tujuannya adalah untuk mengurangigroupby
panggilan (karenagroupby
juga cukup mahal).Peringatan Lainnya
Selain dari peringatan yang disebutkan di atas, perlu juga disebutkan bahwa
apply
beroperasi pada baris pertama (atau kolom) dua kali. Ini dilakukan untuk menentukan apakah fungsi tersebut memiliki efek samping. Jika tidak,apply
mungkin dapat menggunakan jalur cepat untuk mengevaluasi hasil, jika tidak, akan kembali ke implementasi yang lambat.Perilaku ini juga terlihat
GroupBy.apply
pada pandas versi <0,25 (telah diperbaiki untuk 0,25, lihat di sini untuk informasi selengkapnya .)sumber
%timeit for c in df.columns: df[c] = pd.to_datetime(df[c], errors='coerce')
pasti setelah iterasi pertama itu akan jauh lebih cepat karena Anda beralihdatetime
ke ...datetime
?to_datetime
string secepat pada ...datetime
objek" .. benarkah? Saya menyertakan pembuatan kerangka data (biaya tetap) dalam pengaturan waktuapply
vsfor
loop dan perbedaannya jauh lebih kecil.Tidak semua
apply
samaBagan di bawah ini menyarankan kapan harus mempertimbangkan
apply
1 . Hijau berarti mungkin efisien; merah menghindari.Beberapa di antaranya intuitif:
pd.Series.apply
adalah loop baris-bijaksana tingkat Python, dittopd.DataFrame.apply
-baris-bijaksana (axis=1
). Penyalahgunaan ini banyak dan luas. Posting lainnya membahasnya secara lebih mendalam. Solusi populer adalah menggunakan metode vektor, pemahaman daftar (mengasumsikan data bersih), atau alat yang efisien sepertipd.DataFrame
konstruktor (misalnya untuk menghindariapply(pd.Series)
).Jika Anda menggunakan
pd.DataFrame.apply
bijak-baris, menentukanraw=True
(jika mungkin) sering kali bermanfaat. Pada tahap ini,numba
biasanya merupakan pilihan yang lebih baik.GroupBy.apply
: umumnya disukaigroupby
Operasi berulang yang harus dihindariapply
akan merusak kinerja.GroupBy.apply
biasanya baik-baik saja di sini, asalkan metode yang Anda gunakan dalam fungsi kustom Anda sendiri vektorisasi. Terkadang tidak ada metode Pandas asli untuk agregasi berkelompok yang ingin Anda terapkan. Dalam kasus ini, untuk sejumlah kecil grupapply
dengan fungsi kustom mungkin masih menawarkan kinerja yang wajar.pd.DataFrame.apply
kolom-bijaksana: tas campuranpd.DataFrame.apply
column-Wise (axis=0
) adalah kasus yang menarik. Untuk sejumlah kecil baris versus sejumlah besar kolom, biayanya hampir selalu mahal. Untuk jumlah baris yang besar relatif terhadap kolom, kasus yang lebih umum, terkadang Anda mungkin melihat peningkatan kinerja yang signifikan menggunakanapply
:1 Ada pengecualian, tetapi biasanya marginal atau tidak umum. Beberapa contoh:
df['col'].apply(str)
mungkin sedikit mengunggulidf['col'].astype(str)
.df.apply(pd.to_datetime)
bekerja pada string tidak diskalakan dengan baik dengan baris versusfor
loop biasa .sumber
apply
secara signifikan lebih cepat daripada solusi saya denganany
. Ada pemikiran tentang ini?any
sekitar 100 kali lebih cepat dariapply
. Itu melakukan tes pertama saya dengan 2000 baris x 1000 cols dan di siniapply
dua kali lebih cepatany
Untuk
axis=1
(yaitu fungsi baris-bijaksana) maka Anda bisa menggunakan fungsi berikut sebagai penggantiapply
. Saya bertanya-tanya mengapa ini bukanpandas
perilakunya. (Belum teruji dengan indeks gabungan, tetapi tampaknya jauh lebih cepat daripadaapply
)sumber
zip(df, row[1:])
cukup di sini; Sungguh, pada tahap ini, pertimbangkannumba
apakah func adalah perhitungan numerik. Lihat jawaban ini untuk penjelasannya.numba
lebih cepat,faster_df_apply
dimaksudkan untuk orang yang hanya menginginkan sesuatu yang setara, tetapi lebih cepat daripada,DataFrame.apply
(yang anehnya lambat).Apakah pernah ada situasi
apply
yang baik? Ya kadang kadang.Tugas: memecahkan kode string Unicode.
Pembaruan
Saya sama sekali tidak menganjurkan penggunaan
apply
, hanya berpikir karenaNumPy
tidak dapat menangani situasi di atas, itu bisa menjadi kandidat yang baikpandas apply
. Tapi saya lupa pemahaman daftar biasa berkat pengingat oleh @jpp.sumber
[unidecode.unidecode(x) for x in s]
ataulist(map(unidecode.unidecode, s))
?apply
, hanya berpikir ini bisa menjadi bagus kasus penggunaan.