pandas: Manipulasi Data Skala Profesional
pandas adalah library Python yang paling banyak dipakai data analyst di seluruh dunia. Kalau Excel adalah kalkulator meja, pandas adalah superkomputer yang bisa memproses jutaan baris dalam detik. Abbas akan belajar dari fondasi hingga data cleaning lanjutan.
Apa yang akan Abbas kuasai bulan ini?
- Membuat dan membaca DataFrame dari CSV, Excel, dan dictionary Python
- Memilih data dengan iloc (posisi numerik) vs loc (label nama) tanpa bingung
- Filter baris dengan boolean indexing dan query() yang kompleks
- Transformasi kolom dengan apply(), lambda, dan string methods (.str.)
- Parse dan manipulasi data tanggal/waktu dengan datetime dan resample()
- Aggregasi data dengan groupby() dan agg() seperti pivot table otomatis
- Menggabungkan tabel dengan merge() (ekuivalen JOIN di SQL) dan concat()
- Mendeteksi dan menangani missing values, outlier IQR, dan duplikat data
Minggu 1: DataFrame Fundamentals
Series vs DataFrame: Dua Senjata Utama
import pandas as pd
# Dari dictionary
df = pd.DataFrame({
'nama': ['Abbas', 'Budi', 'Cici'],
'nilai': [88, 75, 92]
})
# Dari file
df = pd.read_csv('nilai.csv')
df = pd.read_excel('data.xlsx')iloc vs loc: Dua Cara Memilih Data
# iloc: posisi [baris, kolom]
df.iloc[0] # baris pertama
df.iloc[1:4, :2] # baris 1-3, 2 kolom pertama
# loc: nama label
df.loc[0, 'nama'] # baris 0, kolom 'nama'
df.loc[:, ['nama', 'nilai']] # semua baris, 2 kolomRitual Eksplorasi: head(), info(), describe()
df.head(5) # 5 baris pertama
df.info() # tipe data, non-null count, memori
df.describe() # statistik kolom numerik
df.shape # (baris, kolom)
df.dtypes # tipe setiap kolom
df.isnull().sum() # jumlah NaN per kolomBoolean Filtering: WHERE ala SQL
# Filter tunggal
df[df['nilai'] > 80]
# Ganda: & = AND, | = OR, ~ = NOT
df[(df['nilai'] > 80) & (df['kelas'] == '11A')]
# isin() dan str.contains()
df[df['kelas'].isin(['11A', '11B'])]
df[df['nama'].str.contains('Ab', na=False)]
# query() lebih mudah dibaca
df.query('nilai > 80 and kelas == "11A"')Latihan: Eksplorasi Dataset Titanic
pd.read_csv('https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv')Jawab 5 pertanyaan ini dengan pandas:
- Berapa total penumpang? Berapa selamat vs tidak?
- Persen penumpang perempuan yang selamat vs laki-laki?
- Penumpang termuda dan tertua?
- Rata-rata harga tiket per kelas (1st/2nd/3rd)?
- Kolom mana yang punya missing values dan berapa banyak?
Minggu 2: Transformasi Data
Tambah dan Ubah Kolom
# Kolom baru dari kolom lain
df['total'] = df['uts'] * 0.4 + df['uas'] * 0.6
# Kolom kategori dengan lambda
df['grade'] = df['nilai'].apply(
lambda x: 'A' if x >= 85 else 'B' if x >= 70 else 'C'
)
# Hapus kolom dan rename
df = df.drop(columns=['kol_tidak_perlu'])
df = df.rename(columns={'old': 'new'})apply() per Baris: Akses Banyak Kolom Sekaligus
def kategori_siswa(row):
if row['nilai'] >= 85 and row['hadir'] >= 90:
return 'Berprestasi'
elif row['nilai'] < 60:
return 'Perlu perhatian'
return 'Normal'
df['kategori'] = df.apply(kategori_siswa, axis=1)String Operations: .str.
df['nama'].str.upper()
df['nama'].str.strip()
df['nama'].str.contains('Abbas', na=False)
df['nama'].str.replace(',', '')
df['nama'].str.split(' ').str[0] # ambil kata pertamaDatetime: Tanggal Bukan Sekadar String
# Parse string jadi datetime
df['tgl'] = pd.to_datetime(df['tgl'])
# Ekstrak komponen
df['tahun'] = df['tgl'].dt.year
df['bulan'] = df['tgl'].dt.month
df['hari'] = df['tgl'].dt.day_name()
# Resample per bulan
df.set_index('tgl').resample('M')['revenue'].sum()Latihan: Bersihkan Dataset Penjualan
Dataset penjualan fiktif dengan 5 masalah sengaja dibuat:
- Kolom tanggal masih string "2025-01-15" (parse ke datetime)
- Nama produk campur huruf besar-kecil (standardisasi ke Title Case)
- Harga pakai titik: "1.500" (ubah ke integer 1500)
- Tambah kolom: bulan, tahun, total = qty * harga
- Tambah kategori harga: "Murah", "Sedang", "Mahal"
Minggu 3: groupby dan merge
groupby() + agg(): Pivot Table Otomatis
# Sederhana
df.groupby('kelas')['nilai'].mean()
# Banyak aggregasi
df.groupby('kelas').agg({
'nilai': ['mean', 'max', 'count'],
'hadir': 'mean'
}).reset_index()
# Group by dua kolom
df.groupby(['kelas', 'semester'])['nilai'].mean()merge(): JOIN Tabel Persis seperti SQL
# INNER JOIN
result = pd.merge(df_siswa, df_nilai,
on='id_siswa', how='inner')
# LEFT JOIN
result = pd.merge(df_siswa, df_nilai,
on='id_siswa', how='left')
# Key berbeda nama
result = pd.merge(df_a, df_b,
left_on='id', right_on='student_id')concat(): Gabungkan Vertikal
# Gabungkan baris dari banyak file
df_semua = pd.concat(
[df_jan, df_feb, df_mar],
ignore_index=True
)pivot_table() dan crosstab()
pivot = df.pivot_table(
values='nilai',
index='kelas',
columns='mapel',
aggfunc='mean',
fill_value=0
)
ct = pd.crosstab(
df['kelas'], df['grade'], margins=True
)Latihan: Analisis E-Commerce
Buat dataset e-commerce 50 baris (tanggal, produk, kategori, qty, harga, kota). Jawab:
- Total revenue per kategori
- Produk terlaris per bulan
- Kota dengan rata-rata order value tertinggi
- Pivot: revenue per kategori x bulan
Minggu 4: Data Cleaning Lanjutan
Missing Values: 3 Strategi Penanganan
# Deteksi
df.isnull().sum()
df.isnull().mean() * 100 # persen
# Drop
df.dropna(subset=['nilai']) # hapus jika 'nilai' NaN
# Fill
df['nilai'].fillna(df['nilai'].median(), inplace=True)
df['kota'].fillna('Tidak Diketahui', inplace=True)Deteksi Outlier: Metode IQR
Q1 = df['nilai'].quantile(0.25)
Q3 = df['nilai'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
outliers = df[(df['nilai'] < lower) | (df['nilai'] > upper)]
print(f"{len(outliers)} outlier ditemukan")Duplikat dan Anomali Kategorik
# Duplikat
df.duplicated().sum()
df = df.drop_duplicates(subset=['id_siswa'], keep='last')
# Anomali teks: 'jakarta', 'Jakarta', 'JAKARTA'
df['kota'].value_counts()
df['kota'] = df['kota'].str.title().str.strip()Binning: Ubah Numerik jadi Kategori
# pd.cut: batas manual
df['grade'] = pd.cut(df['nilai'],
bins=[0, 59, 74, 84, 100],
labels=['D', 'C', 'B', 'A'])
# pd.qcut: batas berdasarkan quantile
df['kuartil'] = pd.qcut(df['nilai'], q=4,
labels=['Q1', 'Q2', 'Q3', 'Q4'])End-to-End Cleaning Dataset Publik Indonesia
Pipeline yang harus Abbas selesaikan:
- Load data, cek shape, dtypes, missing values
- Parse kolom tanggal jika ada
- Standardisasi nama kolom (lowercase, underscore)
- Handle missing values dengan strategi yang tepat
- Hapus duplikat jika ada
- Deteksi dan catat outlier (jangan langsung hapus)
- Export ke CSV bersih: data_clean.csv
- Laporan singkat: apa yang ditemukan dan bagaimana ditangani
Buku Pendukung Bulan 5
Tools yang Digunakan
pip install pandas numpy openpyxl pyarrow jupyterTips Praktis dan Jebakan Pemula Pandas
Kalau Abbas memfilter DataFrame lalu mengubah nilainya, pandas kadang tidak menyimpan perubahan itu karena yang dimodifikasi adalah salinan sementara, bukan DataFrame asli. Ini tidak error, tapi hasilnya tidak sesuai harapan.
# SALAH: mungkin tidak tersimpan
df_baru = df[df['nilai'] > 70]
df_baru['grade'] = 'Lulus' # SettingWithCopyWarning!
# BENAR: tambahkan .copy() setelah filter
df_baru = df[df['nilai'] > 70].copy()
df_baru['grade'] = 'Lulus' # amanAturan Abbas: setiap kali memfilter DataFrame untuk diolah lebih lanjut, selalu tambahkan .copy() di akhir.
Banyak tutorial mengajarkan inplace=True, tapi ini membuat kode susah dibaca dan tidak bisa di-chain. Lebih baik assign kembali ke variabel yang sama.
# HINDARI: inplace=True
df.dropna(inplace=True)
df.reset_index(drop=True, inplace=True)
# LEBIH BAIK: assign kembali
df = df.dropna().reset_index(drop=True)
# Kenapa inplace berbahaya:
# df.sort_values('nilai', inplace=True).head() -> TypeError!
# karena inplace=True mengembalikan None, bukan DataFrameKalau kolom terbaca sebagai object padahal isinya angka, operasi matematika akan error. Sebaliknya, kalau kolom angka terbaca sebagai string, .str. tidak akan bekerja. Cek dulu sebelum jalan.
# Cek semua tipe sekaligus
print(df.dtypes)
# Konversi paksa ke numerik (salah jadi NaN, bukan error)
df['harga'] = pd.to_numeric(df['harga'], errors='coerce')
# Konversi ke string untuk operasi .str.
df['kode'] = df['kode'].astype('str')
# Diagnosa: kenapa harga tidak bisa dijumlah?
df['harga'].unique()[:5] # cari nilai aneh seperti 'N/A', '-', ''Ini mengejutkan banyak pemula. Kalau kolom integer (bilangan bulat) punya satu NaN saja, pandas otomatis mengubah seluruh kolom jadi float. Angka 5 menjadi 5.0.
import numpy as np
df = pd.DataFrame({'nilai': [85, 90, np.nan, 75]})
print(df['nilai'].dtype) # float64, bukan int!
# Solusi pandas 1.0+: gunakan nullable integer
df['nilai'] = df['nilai'].astype('Int64') # kapital I
print(df['nilai'].dtype) # Int64, dengan NaN tetap ada
# Atau isi NaN dulu, baru convert ke int
df['nilai'] = df['nilai'].fillna(0).astype('int')Hasil groupby menjadikan kolom grup sebagai index (baris label), bukan kolom biasa. Ini membuat operasi selanjutnya jadi aneh. Selalu tambahkan reset_index() agar hasilnya rapi.
# Tanpa reset_index: 'kelas' jadi index, susah dipakai
hasil = df.groupby('kelas')['nilai'].mean()
print(hasil.index) # Index(['11A', '11B', '11C'])
# Dengan reset_index: 'kelas' jadi kolom normal
hasil = df.groupby('kelas')['nilai'].mean().reset_index()
print(hasil.columns) # ['kelas', 'nilai'] - mudah dipakai!
# Rename sekalian supaya rapi
hasil = hasil.rename(columns={'nilai': 'rata_nilai'})Kalau kolom yang dipakai sebagai kunci JOIN punya nilai duplikat di kedua tabel, hasilnya akan lebih banyak dari yang diharapkan. Abbas harus selalu cek shape sebelum dan sesudah merge.
# Cek sebelum merge
print("df_a:", df_a.shape)
print("df_b:", df_b.shape)
result = pd.merge(df_a, df_b, on='id', how='left')
# Cek sesudah merge
print("result:", result.shape)
# Kalau result > df_a, berarti ada duplikat di df_b!
# Diagnosa: cek duplikat di kolom kunci
print(df_b['id'].duplicated().sum(), "duplikat di df_b")Sebelum groupby atau analisis kategorik, Abbas harus tahu nilai unik apa saja yang ada dan seberapa sering muncul. value_counts() adalah alat diagnostik terkuat untuk ini.
# Lihat distribusi + persentase
df['kota'].value_counts()
df['kota'].value_counts(normalize=True) * 100 # persen
# Tangkap typo: 'jakarta', 'Jakarta', 'JAKARTA', 'Jakartaa'
df['kota'].value_counts().tail(10) # nilai langka biasanya typo
# Cek nilai dengan frekuensi sangat rendah (kemungkinan error)
rare = df['kota'].value_counts()
rare[rare < 3] # nilai yang muncul kurang dari 3xPandas menebak tipe data secara otomatis saat read_csv. Tebakan ini kadang salah, misalnya kode produk "007" dibaca sebagai angka 7, atau kolom campuran teks dan angka dibaca object. Lebih aman tentukan dtype dari awal.
# Tentukan dtype saat membaca
df = pd.read_csv('penjualan.csv', dtype={
'kode_produk': 'str', # jangan jadi angka
'qty': 'int',
'harga': 'float'
}, parse_dates=['tanggal']) # parse datetime sekaligus
# Untuk file besar: baca sample dulu
df_sample = pd.read_csv('big_file.csv', nrows=100)
print(df_sample.dtypes) # lihat tipe yang dideteksiKebiasaan ini juga membuat kode lebih cepat karena pandas tidak perlu menebak tipe dari setiap kolom.
Latihan Tambahan: Level Lebih Menantang
url = 'https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv'
df = pd.read_csv(url)
df.head()Buat Kolom Kategori Usia
Buat kolom baru age_group berdasarkan kolom Age. Gunakan pd.cut() dengan batas: 0-12 = "Anak", 13-17 = "Remaja", 18-59 = "Dewasa", 60+ = "Lansia". Hati-hati dengan NaN di kolom Age.
# Petunjuk: isi NaN dulu sebelum pd.cut
df['Age'] = df['Age'].fillna(df['Age'].median())
df['age_group'] = pd.cut(
df['Age'],
bins=[0, 12, 17, 59, 100],
labels=['Anak', 'Remaja', 'Dewasa', 'Lansia']
)Survival Rate per Kombinasi Gender + Kelas
Buat pivot table yang menunjukkan rata-rata survival rate untuk setiap kombinasi Sex dan Pclass. Hasilnya harus berbentuk tabel 2x3. Temukan pola: siapa yang punya survival rate tertinggi dan terendah?
pivot = df.pivot_table(
values='Survived',
index='Sex',
columns='Pclass',
aggfunc='mean'
).round(2)
print(pivot)Keluarga Besar vs Keluarga Kecil: Siapa Lebih Selamat?
Kolom SibSp (saudara/pasangan) dan Parch (orang tua/anak) menunjukkan ukuran keluarga. Buat kolom family_size = SibSp + Parch + 1 (diri sendiri). Lalu buat kolom is_alone = 1 jika family_size == 1. Bandingkan survival rate antara yang sendirian vs yang punya keluarga.
df['family_size'] = df['SibSp'] + df['Parch'] + 1
df['is_alone'] = (df['family_size'] == 1).astype('int')
# Bandingkan survival rate
df.groupby('is_alone')['Survived'].mean().round(2)Buat 3 Tabel Sendiri
import pandas as pd
import numpy as np
np.random.seed(42)
n = 60
df_orders = pd.DataFrame({
'order_id': range(1001, 1001 + n),
'customer_id': np.random.randint(1, 11, n),
'product_id': np.random.randint(1, 6, n),
'qty': np.random.randint(1, 10, n),
'tanggal': pd.date_range('2025-01-01', periods=n, freq='3D')
})
df_products = pd.DataFrame({
'product_id': [1, 2, 3, 4, 5],
'nama_produk': ['Buku', 'Pensil', 'Tas', 'Penggaris', 'Penghapus'],
'kategori': ['Tulis', 'Tulis', 'Aksesoris', 'Tulis', 'Tulis'],
'harga': [25000, 3000, 75000, 5000, 2000]
})
df_customers = pd.DataFrame({
'customer_id': range(1, 11),
'nama': ['Abbas', 'Budi', 'Cici', 'Dani', 'Eka',
'Fani', 'Gita', 'Hadi', 'Ika', 'Joko'],
'kota': ['Jakarta', 'Bandung', 'Surabaya', 'Jakarta', 'Medan',
'Bandung', 'Jakarta', 'Yogyakarta', 'Surabaya', 'Medan']
})Join Ketiga Tabel Menjadi Satu
Gabungkan df_orders, df_products, dan df_customers sehingga setiap baris order memiliki: nama pelanggan, kota, nama produk, kategori, harga, qty, dan total (qty * harga). Cek shape hasil: harus 60 baris.
# Join bertahap: orders + products dulu
df = pd.merge(df_orders, df_products, on='product_id', how='left')
# Lalu gabung dengan customers
df = pd.merge(df, df_customers, on='customer_id', how='left')
# Buat kolom total
df['total'] = df['qty'] * df['harga']
print(df.shape) # harus (60, ...)
df.head()Analisis Lengkap dari Data Gabungan
Dari DataFrame gabungan, jawab pertanyaan ini menggunakan groupby dan pivot:
- Siapa pelanggan dengan total belanja terbesar? (top 3)
- Produk apa yang menghasilkan revenue terbesar?
- Kota mana yang rata-rata order value-nya paling tinggi?
- Buat pivot: total revenue per kota vs kategori produk
- Bulan mana yang penjualan paling tinggi?
# Ekstrak bulan dari kolom tanggal dulu
df['bulan'] = df['tanggal'].dt.to_period('M')
# Contoh: top 3 pelanggan
top3 = df.groupby('nama')['total'].sum().sort_values(ascending=False).head(3)
print(top3)laporan_bulanan(df, bulan) yang menerima nama bulan dan mengembalikan: total revenue, jumlah order, produk terlaris, dan pelanggan terbanyak belanja di bulan tersebut.Latihan ini mensimulasikan analisis yang sangat umum di dunia kerja: Abbas mendapat data penjualan harian selama 1 tahun dan diminta membuat laporan tren per minggu dan per bulan.
# Buat data penjualan harian 1 tahun
import pandas as pd
import numpy as np
np.random.seed(7)
dates = pd.date_range('2025-01-01', '2025-12-31', freq='D')
df_ts = pd.DataFrame({
'tanggal': dates,
'penjualan': np.random.randint(50, 300, len(dates)) +
(np.sin(np.arange(len(dates)) * 2 * np.pi / 30) * 50).astype('int')
})Resample dan Rolling Average
- Set kolom tanggal sebagai index dengan
set_index('tanggal') - Resample ke mingguan: total penjualan per minggu
- Resample ke bulanan: total penjualan per bulan
- Hitung rolling average 7 hari untuk menghaluskan fluktuasi harian
- Temukan: bulan dengan penjualan tertinggi dan terendah
df_ts = df_ts.set_index('tanggal')
# Resample mingguan dan bulanan
mingguan = df_ts.resample('W')['penjualan'].sum()
bulanan = df_ts.resample('ME')['penjualan'].sum()
# Rolling average 7 hari
df_ts['rolling_7d'] = df_ts['penjualan'].rolling(window=7).mean()
# Bulan terbaik
print("Bulan terbaik:", bulanan.idxmax())
print("Bulan terlemah:", bulanan.idxmin())'ME' (Month End) bukan 'M' yang sudah deprecated untuk resample. Kalau muncul FutureWarning, ini penyebabnya.Abbas mendapat file CSV dari rekan yang mengisi data manual selama 6 bulan. File ini punya semua jenis masalah yang sering ditemui di dunia nyata. Tugas Abbas: buat DataFrame akhir yang bersih dan bisa langsung dianalisis.
# Dataset kotor dengan 8 masalah berbeda
data_kotor = {
'tanggal': ['2025-01-05', '05/02/2025', '2025-03-10',
'2025-01-05', '2025-04-20', 'tidak tahu'],
'produk': ['buku tulis', 'BUKU TULIS', 'Buku Tulis',
'buku tulis', 'Pensil ', 'pensil'],
'harga': ['25.000', '25000', '25,000',
'25.000', '3000', 'N/A'],
'qty': [2, 5, 3, 2, 10, None],
'kota': ['jakarta', 'JAKARTA', 'Jakartaa',
'jakarta', 'bandung', 'Bandung']
}
df_kotor = pd.DataFrame(data_kotor)# Step 1: Identifikasi semua masalah
print(df_kotor.dtypes)
print(df_kotor.isnull().sum())
print(df_kotor.duplicated().sum())
# Step 2: Parse tanggal (toleransi format campur)
df_kotor['tanggal'] = pd.to_datetime(df_kotor['tanggal'],
dayfirst=False, errors='coerce')
# Step 3: Bersihkan harga (hapus titik dan koma, convert)
df_kotor['harga'] = (df_kotor['harga']
.str.replace('.', '', regex=False)
.str.replace(',', '', regex=False)
)
df_kotor['harga'] = pd.to_numeric(df_kotor['harga'], errors='coerce')
# Step 4: Standardisasi teks
df_kotor['produk'] = df_kotor['produk'].str.strip().str.title()
df_kotor['kota'] = df_kotor['kota'].str.strip().str.title()
# Step 5: Hapus baris dengan data kritis yang hilang
df_bersih = df_kotor.dropna(subset=['tanggal', 'harga'])
# Step 6: Hapus duplikat (baris 0 dan 3 identik)
df_bersih = df_bersih.drop_duplicates().reset_index(drop=True)
# Step 7: Isi qty yang kosong dengan median
df_bersih['qty'] = df_bersih['qty'].fillna(df_bersih['qty'].median())
# Step 8: Buat kolom total dan export
df_bersih['total'] = df_bersih['qty'] * df_bersih['harga']
df_bersih.to_csv('data_bersih.csv', index=False)
print("Data bersih:", df_bersih.shape)
df_bersihStudi Kasus: Analisis Nilai Sekolah dari Nol ke Insight
Ini adalah latihan terpadu yang menggabungkan semua yang Abbas pelajari bulan ini. Dataset: 200 siswa, 5 mata pelajaran, selama 2 semester.
Generate Dataset Realistis
import pandas as pd
import numpy as np
np.random.seed(42)
n = 200
df = pd.DataFrame({
"id_siswa": range(1, n+1),
"nama": [f"Siswa {i}" for i in range(1, n+1)],
"kelas": np.random.choice(["10A","10B","11A","11B"], n),
"matematika": np.random.normal(72, 15, n).clip(0,100).round(1),
"bahasa": np.random.normal(78, 12, n).clip(0,100).round(1),
"ipa": np.random.normal(70, 18, n).clip(0,100).round(1),
"hadir_pct": np.random.uniform(60, 100, n).round(1),
})
# Tambah beberapa missing values (realistis)
df.loc[df.sample(10).index, "matematika"] = np.nan
df.to_csv("nilai_siswa.csv", index=False)
print(df.shape)EDA Awal: Kenali Data Sebelum Apapun
df = pd.read_csv("nilai_siswa.csv")
print("Shape:", df.shape)
print("
Missing values:")
print(df.isnull().sum())
print("
Statistik dasar:")
print(df.describe().round(2))
print("
Distribusi kelas:")
print(df["kelas"].value_counts())Cleaning: Handle Missing Values
# Isi NaN matematika dengan median per kelas
df["matematika"] = df.groupby("kelas")["matematika"].transform(
lambda x: x.fillna(x.median())
)
print("Missing setelah cleaning:", df.isnull().sum().sum())Transformasi: Buat Kolom Baru yang Bermakna
# Rata-rata nilai semua mapel
df["nilai_rata"] = df[["matematika","bahasa","ipa"]].mean(axis=1).round(1)
# Kategori kehadiran
df["kategori_hadir"] = pd.cut(df["hadir_pct"],
bins=[0,75,90,100],
labels=["Sering Absen","Cukup Hadir","Rajin Hadir"])
# Status kelulusan
df["lulus"] = df["nilai_rata"] >= 70Analisis, Pivot, dan Export Laporan
# Rata-rata per kelas
laporan_kelas = df.groupby("kelas").agg({
"nilai_rata": ["mean","min","max"],
"lulus": "mean", # persen lulus
"id_siswa": "count"
}).round(2)
print(laporan_kelas)
# Korelasi kehadiran vs nilai
print("
Korelasi hadir-nilai:",
df["hadir_pct"].corr(df["nilai_rata"]).round(3))
# Export
df.to_csv("nilai_bersih.csv", index=False)
laporan_kelas.to_excel("laporan_kelas.xlsx")
print("Laporan tersimpan!")10 Jebakan Pemula pandas yang Sering Bikin Frustasi
Terjadi saat Abbas menulis
df[df['a'] > 0]['b'] = 1 (chained assignment). Fix: gunakan df.loc[df['a'] > 0, 'b'] = 1groupby().mean() menghasilkan Series dengan index aneh. Selalu tambah .reset_index() di akhir.Kalau key tidak unik di salah satu tabel, merge akan menghasilkan lebih banyak baris dari yang diharapkan. Selalu cek
df.duplicated('key_col').sum() sebelum merge.Kalau bisa, gunakan operasi vektor langsung:
df['a'] + df['b'] jauh lebih cepat dari df.apply(lambda r: r['a'] + r['b'], axis=1)np.nan == np.nan menghasilkan False! Selalu gunakan pd.isna() atau df.isnull() untuk cek missing values.