Kursus
Banyak orang menggunakan .apply() untuk “menghindari loop” dan berharap ini cepat serta mudah. Dalam praktiknya, .apply(axis=1) per baris tetaplah loop di level Python. Ini bisa lambat pada data besar, dan terkadang mengembalikan bentuk yang tidak Anda harapkan. Solusinya sederhana: gunakan operasi vektorisasi pandas/NumPy untuk tugas umum, dan gunakan .apply() hanya untuk logika yang benar-benar membutuhkan beberapa kolom.
Panduan ini menunjukkan cara kerja .apply() saat ini, menyoroti jebakan umum, dan menyediakan pola siap pakai yang lebih cepat dan lebih jelas.
Apa Itu DataFrame.apply() dan Series.apply()?
DataFrame.apply(func, axis=0) secara bawaan memanggil func pada tiap kolom. Dengan axis=1, ia memanggil func pada tiap baris. Series.apply(func) memanggil func pada tiap elemen (atau seluruh Series, tergantung tanda tangan fungsi pada versi terbaru).
- Per baris vs. per kolom:
axis=1berarti “per baris.”axis=0(bawaan) berarti “per kolom.”
- Aturan bentuk keluaran (pandas ≥0,23): Jika fungsi Anda mengembalikan skalar per baris/kolom, Anda mendapatkan sebuah
Series. Jika mengembalikanSeriesataudict, pandas akan mengembangkan kunci-kunci tersebut menjadi sebuahDataFrame. Jika mengembalikan list/array, Anda akan mendapatkan Series berisi list kecuali Anda menetapkanresult_type='expand'.
- Error yang lebih ketat (pandas ≥2.0): Panjang mirip-list yang tidak cocok kini memunculkan error alih-alih diam-diam menghasilkan hasil yang tidak konsisten.
Pilihan Utama yang Disarankan: Operasi Tervektorisasi
Sebagian besar tugas yang melibatkan penggabungan atau transformasi kolom lebih cepat dan lebih jelas dengan ekspresi tervektorisasi atau metode bawaan. Contoh berikut menghitung selisih run sebuah tim bisbol tanpa .apply().
import pandas as pd
team_stats = pd.DataFrame({
"Team": ["ARI", "ATL", "BAL"],
"League": ["NL", "NL", "AL"],
"Year": [2012, 2012, 2012],
"RunsScored": [734, 700, 712],
"RunsAllowed": [688, 600, 705],
"Wins": [81, 94, 93],
"Games": [162, 162, 162],
"Playoffs": [0, 1, 1],
})
# Vectorized: fast and idiomatic
team_stats["RunDiff"] = team_stats["RunsScored"] - team_stats["RunsAllowed"]
print(team_stats[["Team", "RunsScored", "RunsAllowed", "RunDiff"]])
.apply() Per Baris Saat Anda Benar-Benar Membutuhkannya
Gunakan .apply(axis=1) ketika logika Anda benar-benar melibatkan beberapa kolom dan tidak mudah divektorisasi (misalnya, aturan kondisional yang bergantung pada beberapa field).
hitung nilai turunan dari beberapa kolom
Pola ini menghitung nilai per baris menggunakan beberapa masukan. Pendekatan tervektorisasi di atas tetap lebih disukai, namun ini menunjukkan penggunaan per baris yang benar dan opsi yang memengaruhi kecepatan serta keluaran.
def compute_run_diff(row):
# Treat the row as read-only; return a scalar
return row["RunsScored"] - row["RunsAllowed"]
# Row-wise apply (Python-level loop; can be slow on large data)
team_stats["RunDiff_apply"] = team_stats.apply(compute_run_diff, axis=1)
Jika fungsi bersifat numerik dan hanya membutuhkan array mentah, memberikan argumen raw=True melewati sebagian overhead pandas dengan menyediakan array NumPy ke fungsi Anda.
import numpy as np
def compute_run_diff_raw(values):
# values is a NumPy array when raw=True
# Order matches the column order we select
rs, ra = values
return rs - ra
team_stats["RunDiff_raw"] = team_stats[["RunsScored", "RunsAllowed"]].apply(
compute_run_diff_raw, axis=1, raw=True
)
verifikasi masukan fungsi sekali
Saat saya memasang fungsi baris baru, saya mencetak satu baris sekali untuk memastikan apa yang diteruskan.
def debug_row(row):
# Print the first row only
if row.name == team_stats.index[0]:
print("Example row:", row.to_dict())
return 0
_ = team_stats.apply(debug_row, axis=1)
Studi Kasus: Penjumlahan, Total Musim, dan Flag Teks
Diberikan statistik Rays per tahun, utamakan metode bawaan dan alat se-elemen dibanding .apply() bila memungkinkan.
rays_by_year = pd.DataFrame(
{
"Year": [2012, 2011, 2010, 2009, 2008],
"RunsScored": [697, 707, 802, 803, 774],
"RunsAllowed": [577, 614, 649, 754, 671],
"Wins": [90, 91, 96, 84, 97],
"Playoffs": [0, 1, 1, 0, 1],
}
).set_index("Year")
dapatkan jumlah kolom secara efisien
Gunakan DataFrame.sum() alih-alih .apply(sum). Lebih cepat dan lebih jelas.
# Preferred
totals = rays_by_year.sum(axis=0)
print(totals)
# If you must use apply (not recommended here)
totals_apply = rays_by_year.apply(sum, axis=0)
hitung total run yang dicetak dalam satu musim
Gunakan aritmetika tervektorisasi antar kolom; tidak perlu .apply().
rays_by_year["TotalRuns"] = rays_by_year["RunsScored"] + rays_by_year["RunsAllowed"]
print(rays_by_year[["RunsScored", "RunsAllowed", "TotalRuns"]].head())
ubah flag 0/1 menjadi teks
Utamakan Series.map() atau replace() untuk transformasi se-elemen pada satu kolom.
# Using map with a dict
rays_by_year["PlayoffsText"] = rays_by_year["Playoffs"].map({0: "No", 1: "Yes"})
# Equivalent with replace
rays_by_year["PlayoffsText2"] = rays_by_year["Playoffs"].replace({0: "No", 1: "Yes"})
Kendalikan Bentuk Keluaran dari .apply()
Saat fungsi Anda mengembalikan lebih dari satu nilai per baris, tetapkan bentuknya secara eksplisit. Ini menghindari kejutan dan kerusakan pada versi mendatang.
kembalikan list dan perluas menjadi kolom
Gunakan result_type='expand' untuk membagi list/array menjadi beberapa kolom.
def wins_losses(row):
# Derive wins and losses as two outputs
wins = row["Wins"]
losses = row["Games"] - row["Wins"] if "Games" in row else np.nan
return [wins, losses]
# Example using team_stats, which has "Wins" and "Games"
expanded = team_stats.apply(wins_losses, axis=1, result_type="expand")
expanded.columns = ["WinsOut", "LossesOut"]
team_stats = pd.concat([team_stats, expanded], axis=1)
kembalikan series atau dict untuk menamai kolom secara otomatis
Mengembalikan Series atau dict dari tiap baris menghasilkan DataFrame dengan label kolom yang sesuai.
def summary_row(row):
return pd.Series(
{
"IsWinningSeason": row["Wins"] >= 90,
"RunRatio": (row["RunsScored"] / row["RunsAllowed"]),
}
)
summary = team_stats.apply(summary_row, axis=1)
team_stats = pd.concat([team_stats, summary], axis=1)
Tips Kinerja yang Penting
Penerapan .apply(axis=1) per baris memang praktis namun lambat pada frame besar karena memanggil fungsi Python Anda sekali per baris. Pola berikut menghindari hambatan tersebut.
- Utamakan operasi pandas/NumPy tervektorisasi dan metode bawaan seperti
sum,mean,clip,where,astype, dan metode string/aksesornya.
- Untuk operasi numerik per baris/kolom yang menerima array, berikan
raw=Trueagar mendapatkan array NumPy dan mengurangi overhead pandas.
- Untuk pekerjaan se-elemen pada satu kolom, gunakan
Series.map()atau metode tervektorisasi alih-alihDataFrame.apply(axis=1).
- Hindari memodifikasi baris/kolom yang diberikan di dalam fungsi Anda; kembalikan nilai baru sebagai gantinya.
Catatan Versi yang Perlu Anda Ketahui (pandas 2.x)
Rilis pandas terbaru memperketat perilaku terkait .apply() sehingga hasil lebih dapat diprediksi.
- Keluaran mirip-list: Pada 2.0+, panjang list yang tidak konsisten memunculkan error. Gunakan panjang yang konsisten dan tetapkan
result_type='expand'saat Anda menginginkan beberapa kolom.
- Perluasan keluaran: Mengembalikan
Seriesataudictdiperluas menjadi DataFrame dengan kunci-kunci tersebut sebagai kolom (stabil sejak 0,23).
Series.apply()berubah: Argumenconvert_dtypesudah deprecated. Jika Anda membutuhkan tipe campuran, ubah dulu keobject(misalnya,s.astype("object").apply(fn)). Tanda tangan baru memungkinkan mengontrol apakahfnmenerima skalar atau Series dalam lebih banyak konteks (misalnya,by_row).
.apply() vs. .map() vs. .applymap() vs. .agg()
Pilih API yang sesuai dengan bentuk transformasi Anda.
Series.map(func_or_dict): Transformasi se-elemen pada satu kolom. Terbaik untuk lookup atau fungsi sederhana.
DataFrame.apply(func, axis=1): Logika per baris yang membutuhkan beberapa kolom.
DataFrame.apply(func, axis=0): Logika per kolom (masukan berupa Series yang merepresentasikan kolom).
DataFrame.applymap(func): Se-elemen pada setiap sel. Gunakan seperlunya; metode tervektorisasi lebih cepat.
.agg()/.transform(): Agregasi dan transformasi per grup; utamakan ini dalam pipeline groupby.
Kesalahan Umum dan Perbaikan Cepat
Inilah isu yang paling sering menyebabkan hasil keliru atau perlambatan, beserta cara memperbaikinya.
- Lupa axis=1 untuk logika per baris: Jika fungsi Anda tiba-tiba menerima kolom alih-alih baris, tambahkan
axis=1.
- Series tak terduga berisi list: Saat mengembalikan list/array dari tiap baris, tetapkan
result_type='expand'untuk memecahnya menjadi kolom.
- Kode per baris yang lambat: Ganti dengan ekspresi tervektorisasi atau metode bawaan; jika tidak memungkinkan, pertimbangkan
raw=Trueuntuk mengurangi overhead.
- Memodifikasi baris masukan: Perlakukan masukan sebagai hanya-baca, dan kembalikan nilai baru.
- Perubahan dtype: Jika fungsi Anda kadang mengembalikan non-integer, kolom integer dapat naik kasta menjadi float atau object. Lakukan cast setelahnya dengan
astypejika diperlukan.
Kesimpulan
.apply() adalah alat yang fleksibel, namun bukan jalan pintas kinerja. Gunakan operasi tervektorisasi untuk aritmetika, agregasi, dan transformasi se-elemen pada satu kolom. Gunakan DataFrame.apply(axis=1) hanya ketika logika Anda benar-benar membutuhkan beberapa kolom per baris. Saat Anda menggunakannya, kendalikan bentuk keluaran dengan result_type, pertimbangkan raw=True untuk fungsi numerik, dan perhatikan dtypes. Pola ini menghasilkan hasil yang dapat diprediksi pada pandas modern dan lebih skalabel seiring pertumbuhan data Anda.
