TIF_E41253310/Model/preprocessing/standarisasi_kolom.py

580 lines
20 KiB
Python

# ============================================================
# MODUL STANDARISASI KOLOM
# ============================================================
# Fungsi: Menyamakan kolom dari berbagai tahun (2019-2025)
# Menjadi satu format standar
# ============================================================
import pandas as pd
import json
from pathlib import Path
from typing import Dict, List, Optional, Tuple
# ============================================================
# KONFIGURASI MAPPING KOLOM
# ============================================================
# Mapping ini menghubungkan nama kolom di setiap tahun Excel
# KE nama kolom STANDAR yang akan kita gunakan di database
#
# Format:
# "tahun": {
# "nama_kolom_standar": "nama_kolom_di_excel_tahun_tersebut"
# }
# ============================================================
COLUMN_MAPPING = {
# ================================================
# TAHUN 2019 - Mapping kolom Excel 2019
# ================================================
# TODO: Ganti "?" dengan nama kolom sebenarnya dari Excel 2019
# ================================================
"2019": {
"nama": "?", # Contoh: "Nama Lengkap"
"perusahaan": "?", # Contoh: "Nama Perusahaan"
"jabatan": "?", # Contoh: "Jabatan"
"nim": "?" # Contoh: "NIM"
},
# ================================================
# TAHUN 2020 - Mapping kolom Excel 2020
# ================================================
# TODO: Ganti "?" dengan nama kolom sebenarnya dari Excel 2020
# ================================================
"2020": {
"nama": "?",
"perusahaan": "?",
"jabatan": "?",
"nim": "?"
},
# ================================================
# TAHUN 2021 - Mapping kolom Excel 2021
# ================================================
# TODO: Ganti "?" dengan nama kolom sebenarnya dari Excel 2021
# ================================================
"2021": {
"nama": "?",
"perusahaan": "?",
"jabatan": "?",
"nim": "?"
},
# ================================================
# TAHUN 2022 - Mapping kolom Excel 2022
# ================================================
# TODO: Ganti "?" dengan nama kolom sebenarnya dari Excel 2022
# ================================================
"2022": {
"nama": "?",
"perusahaan": "?",
"jabatan": "?",
"nim": "?"
},
# ================================================
# TAHUN 2023 - Mapping kolom Excel 2023
# ================================================
# TODO: Ganti "?" dengan nama kolom sebenarnya dari Excel 2023
# ================================================
"2023": {
"nama": "?",
"perusahaan": "?",
"jabatan": "?",
"nim": "?"
},
# ================================================
# TAHUN 2024 - Mapping kolom Excel 2024 (SUDAH DIKETAHUI)
# ================================================
"2024": {
"nama": "4. Nama Lengkap / nmmhsmsmh (Gunakan Huruf Besar)",
"perusahaan": "9. Apa nama perusahaan/kantor tempat Anda bekerja? f5b",
"jabatan": "10. Bila Anda berwiraswasta, apa posisi/jabatan Anda saat ini ? f5c",
"nim": "3. NIM / nimhsmsmh (Gunakan Huruf Besar) "
},
# ================================================
# TAHUN 2025 - Mapping kolom Excel 2025
# ================================================
# TODO: Ganti "?" dengan nama kolom sebenarnya dari Excel 2025
# ================================================
"2025": {
"nama": "?",
"perusahaan": "?",
"jabatan": "?",
"nim": "?"
}
}
# ============================================================
# KOLOM STANDAR (OUTPUT)
# ============================================================
# Ini adalah kolom FINAL yang akan kita gunakan setelah
# semua data dari berbagai tahun di-standardisasi
# ============================================================
STANDAR_KOLOM = ["nama", "perusahaan", "jabatan", "nim"]
# ============================================================
# KELAS: KolomStandarisasi
# ============================================================
# Fungsi: Mengubah kolom Excel yang berbeda-beda dari
# setiap tahun menjadi kolom STANDAR yang konsisten
# ============================================================
class KolomStandarisasi:
"""
Kelas untuk menstandarisasi kolom dari berbagai tahun.
PENJELASAN CARA KERJA:
======================
1. INPUT : DataFrame dengan kolom berbeda-beda per tahun
Contoh: df_2019 punya kolom "Nama Lengkap"
df_2024 punya kolom "4. Nama Lengkap / nmmhsmsmh..."
2. PROSES : Mapping kolom asli → kolom standar
Contoh: "Nama Lengkap" (2019) → "nama" (standar)
"4. Nama Lengkap..." (2024) → "nama" (standar)
3. OUTPUT : DataFrame dengan kolom standar:
["nama", "perusahaan", "jabatan", "nim"]
CONTOH PENGGUNAAN:
==================
>>> standardizer = KolomStandarisasi()
>>> df_standar = standardizer.standarisasi(df, tahun="2024")
>>> print(df_standar.columns.tolist())
['nama', 'perusahaan', 'jabatan', 'nim']
"""
def __init__(self, mapping: Dict = None):
"""
Inisialisasi standardizer.
Args:
mapping: Dictionary mapping kolom. Jika None, pakai default.
PENJELASAN:
===========
Saat objek dibuat, kita simpan mapping kolom yang akan
digunakan untuk konversi. Mapping ini tells kita:
"Kolom X di tahun Y harus menjadi kolom Z di output"
"""
# Gunakan mapping yang diberikan, atau gunakan default
self.mapping = mapping if mapping is not None else COLUMN_MAPPING
# Tracking tahun yang didukung
self.tahun_tersedia = list(self.mapping.keys())
print(f"📋 KolomStandarisasi initialized")
print(f" Tahun yang didukung: {', '.join(self.tahun_tersedia)}")
# ========================================================
# METODE: get_tahun
# ========================================================
def get_tahun(self) -> List[str]:
"""
Ambil daftar tahun yang didukung.
Returns:
List tahun, contoh: ["2019", "2020", "2021", ...]
"""
return self.tahun_tersedia
# ========================================================
# METODE: get_mapping_tahun
# ========================================================
def get_mapping_tahun(self, tahun: str) -> Dict:
"""
Ambil mapping kolom untuk tahun tertentu.
Args:
tahun: Tahun yang ingin diambil mappingnya
Returns:
Dictionary mapping kolom, contoh:
{"nama": "Nama Lengkap", "perusahaan": "..."}
Raises:
ValueError: Jika tahun tidak ditemukan dalam mapping
"""
# Cek apakah tahun ada dalam mapping
if tahun not in self.mapping:
tahun_available = ", ".join(self.tahun_tersedia)
raise ValueError(
f"Tahun '{tahun}' tidak ditemukan dalam mapping. "
f"Tahun tersedia: {tahun_available}"
)
return self.mapping[tahun]
# ========================================================
# METODE: deteksi_tahun
# ========================================================
def deteksi_tahun(self, df: pd.DataFrame, nama_file: str = None) -> Optional[str]:
"""
Deteksi tahun dari DataFrame atau nama file.
CARA KERJA:
===========
1. Pertama, coba deteksi dari nama file (filename)
- Jika filename mengandung "2024", berarti tahun 2024
2. Jika gagal, cek dari kolom DataFrame
- Bandingkan kolom df dengan mapping setiap tahun
- Tahun dengan kecocokan kolom PALING BANYAK = tahun yang terdeteksi
Args:
df: DataFrame yang akan dideteksi tahun-nya
nama_file: Nama file Excel (opsional)
Returns:
Tahun dalam format string, contoh: "2024"
Returns None jika tidak dapat mendeteksi
"""
# ================================================
# CARA 1: Deteksi dari nama file
# ================================================
if nama_file:
for tahun in self.tahun_tersedia:
# Cek apakah tahun ada di nama file
# Contoh: "Tracer_Study_2024.xlsx" → mengandung "2024"
if tahun in nama_file:
print(f"🔍 Tahun terdeteksi dari filename: {tahun}")
return tahun
# ================================================
# CARA 2: Deteksi dari kolom DataFrame
# ================================================
# Inisialisasi tracker untuk setiap tahun
# Menyimpan jumlah kolom yang cocok
tahun_match = {}
for tahun, kolom_mapping in self.mapping.items():
# Hitung berapa kolom dari df yang cocok dengan mapping tahun ini
cocok = 0
for kolom_target, kolom_asli in kolom_mapping.items():
if kolom_asli in df.columns:
cocok += 1
# Simpan hasil
tahun_match[tahun] = cocok
# Cari tahun dengan kecocokan PALING BANYAK
if tahun_match:
# Pilih tahun dengan match tertinggi
tahun_terbaik = max(tahun_match, key=tahun_match.get)
jumlah_match = tahun_match[tahun_terbaik]
# Jika ada kecocokan (minimal 1 kolom)
if jumlah_match >= 1:
print(f"🔍 Tahun terdeteksi dari kolom: {tahun_terbaik} ({jumlah_match} kolom cocok)")
return tahun_terbaik
# ================================================
# GAGAL DETEKSI
# ================================================
print(f"⚠️ Tidak dapat mendeteksi tahun secara otomatis")
print(f" Kolom DataFrame: {df.columns.tolist()[:5]}...") # Tampilkan 5 kolom pertama
return None
# ========================================================
# METODE: standarisasi
# ========================================================
def standarisasi(self, df: pd.DataFrame, tahun: str = None,
nama_file: str = None) -> pd.DataFrame:
"""
Standarisasi kolom DataFrame ke format standa
CARA KERJA:
===========
1. Deteksi tahun jika tidak disebutkan
2. Ambil mapping untuk tahun tersebut
3. Buat DataFrame baru dengan kolom standar
4. Copy data dari kolom asli ke kolom standar
Args:
df: DataFrame asli dari file Excel
tahun: Tahun data (otomatis detect jika None)
nama_file: Nama file (untuk deteksi otomatis)
Returns:
DataFrame dengan kolom standar
Contoh:
=======
>>> df = pd.read_excel("data_2024.xlsx")
>>> std = KolomStandarisasi()
>>> df_standar = std.standarisasi(df, tahun="2024")
>>> print(df_standar.columns.tolist())
['nama', 'perusahaan', 'jabatan', 'nim']
"""
# ================================================
# LANGKAH 1: Deteksi tahun
# ================================================
if tahun is None:
tahun = self.deteksi_tahun(df, nama_file)
if tahun is None:
raise ValueError(
"Tidak dapat memproses data. "
"Tentukan tahun secara manual atau pastikan nama file mengandung tahun."
)
# ================================================
# LANGKAH 2: Ambil mapping
# ================================================
mapping = self.get_mapping_tahun(tahun)
print(f"📊 Melakukan standarisasi untuk tahun {tahun}")
# ================================================
# LANGKAH 3: Buat DataFrame kosong dengan kolom standar
# ================================================
df_standar = pd.DataFrame()
# ================================================
# LANGKAH 4: Copy data ke kolom standar
# ================================================
kolom_diproses = 0
kolom_gagal = []
for kolom_standar, kolom_asli in mapping.items():
# Cek apakah kolom ada di DataFrame asli
if kolom_asli in df.columns:
# Copy data
df_standar[kolom_standar] = df[kolom_asli]
kolom_diproses += 1
else:
# Kolom tidak ditemukan - tandai sebagai None
df_standar[kolom_standar] = None
kolom_gagal.append(kolom_asli)
# ================================================
# LANGKAH 5: Tampilkan ringkasan
# ================================================
print(f" ✅ Kolom berhasil diproses: {kolom_diproses}/{len(mapping)}")
if kolom_gagal:
print(f" ⚠️ Kolom tidak ditemukan: {kolom_gagal}")
return df_standar
# ========================================================
# METODE: standarisasi_batch
# ========================================================
def standarisasi_batch(self, data_list: List[Tuple[pd.DataFrame, str]]) -> List[pd.DataFrame]:
"""
Standarisasi multiple DataFrame sekaligus.
CARA KERJA:
===========
Proses beberapa DataFrame dari tahun berbeda dalam satu panggil
Args:
data_list: List of tuple (DataFrame, tahun)
Contoh: [(df_2019, "2019"), (df_2024, "2024")]
Returns:
List DataFrame yang sudah distandarisasi
Contoh:
=======
>>> data = [
... (df_2019, "2019"),
... (df_2024, "2024")
... ]
>>> std = KolomStandarisasi()
>>> results = std.standarisasi_batch(data)
"""
hasil = []
for df, tahun in data_list:
try:
df_standar = self.standarisasi(df, tahun=tahun)
hasil.append(df_standar)
except Exception as e:
print(f"❌ Error memproses {tahun}: {e}")
# Skip tahun yang error, lanjut ke berikutnya
return hasil
# ============================================================
# FUNGSI: gabung_data
# ============================================================
def gabung_data(dataframes: List[pd.DataFrame],
tahun_list: List[str] = None) -> pd.DataFrame:
"""
Gabungkan beberapa DataFrame menjadi satu.
CARA KERJA:
===========
1. Terima list DataFrame dari berbagai tahun
2. Tambahkan kolom 'tahun' untuk setiap DataFrame
3. Gabungkan semua menjadi satu DataFrame
Args:
dataframes: List DataFrame yang akan digabungkan
tahun_list: List tahun untuk setiap DataFrame (opsional)
Returns:
DataFrame gabungan dengan kolom tambahan 'tahun'
"""
# Validasi input
if tahun_list and len(dataframes) != len(tahun_list):
raise ValueError(
f"Jumlah DataFrame ({len(dataframes)}) "
f"tidak sama dengan jumlah tahun ({len(tahun_list)})"
)
# ================================================
# LANGKAH 1: Tambahkan kolom tahun
# ================================================
df_dengan_tahun = []
for i, df in enumerate(dataframes):
df_copy = df.copy()
# Tentukan tahun
tahun = tahun_list[i] if tahun_list else f"tahun_{i+1}"
# Tambahkan kolom tahun
df_copy['tahun'] = tahun
df_dengan_tahun.append(df_copy)
# ================================================
# LANGKAH 2: Gabungkan semua DataFrame
# ================================================
df_gabungan = pd.concat(df_dengan_tahun, ignore_index=True)
print(f"✅ Data berhasil digabungkan: {len(df_gabungan)} records")
return df_gabungan
# ============================================================
# FUNGSI: bersihkan_data
# ============================================================
def bersihkan_data(df: pd.DataFrame) -> pd.DataFrame:
"""
Bersihkan data dari nilai kosong dan duplikat.
CARA KERJA:
===========
1. Hapus baris yang semua kolomnya kosong
2. Hapus baris duplikat
3. Isi nilai kosong dengan string kosong
Args:
df: DataFrame yang akan dibersihkan
Returns:
DataFrame yang sudah bersih
"""
print(f"🧹 Membersihkan data...")
# Simpan jumlah awal
jumlah_awal = len(df)
# ================================================
# LANGKAH 1: Hapus baris kosong
# ================================================
# dropna(how='all') = hapus baris yang SEMUA kolomnya kosong
df = df.dropna(how='all')
# ================================================
# LANGKAH 2: Hapus duplikat
# ================================================
# drop_duplicates() = hapus baris yang identik
df = df.drop_duplicates()
# ================================================
# LANGKAH 3: Isi nilai kosong
# ================================================
# fillna('') = ganti NaN/None dengan string kosong
df = df.fillna('')
# ================================================
# LANGKAH 4: Hitung statistik
# ================================================
dihapus = jumlah_awal - len(df)
if dihapus > 0:
print(f" ✅ Data dihapus: {dihapus} baris (kosong/duplikat)")
else:
print(f" ✅ Data bersih, tidak ada yang dihapus")
return df
# ============================================================
# CONTOH PENGGUNAAN
# ============================================================
if __name__ == "__main__":
# Contoh 1: Inisialisasi
print("=" * 60)
print("CONTOH 1: Inisialisasi Standardizer")
print("=" * 60)
std = KolomStandarisasi()
# Contoh 2: Lihat tahun yang didukung
print("\n" + "=" * 60)
print("CONTOH 2: Tahun yang Didukung")
print("=" * 60)
tahun_list = std.get_tahun()
print(f"Tahun: {', '.join(tahun_list)}")
# Contoh 3: Lihat mapping untuk tahun tertentu
print("\n" + "=" * 60)
print("CONTOH 3: Mapping Tahun 2024")
print("=" * 60)
mapping_2024 = std.get_mapping_tahun("2024")
for standar, asli in mapping_2024.items():
print(f" {standar:12} ← {asli}")
# Contoh 4: Standarisasi data dummy
print("\n" + "=" * 60)
print("CONTOH 4: Standarisasi Data Dummy")
print("=" * 60)
# Buat data dummy yang mirip Excel 2024
data_dummy = pd.DataFrame({
"4. Nama Lengkap / nmmhsmsmh (Gunakan Huruf Besar)": [
"Muhammad Ali", "Budi Santoso", "Citra Dewi"
],
"9. Apa nama perusahaan/kantor tempat Anda bekerja? f5b": [
"Google Indonesia", "Tokopedia", "Gojek"
],
"10. Bila Anda berwiraswasta, apa posisi/jabatan Anda saat ini ? f5c": [
"Software Engineer", "Data Analyst", "Product Manager"
],
"3. NIM / nimhsmsmh (Gunakan Huruf Besar) ": [
"E41220001", "E41220002", "E41220003"
]
})
print("Data sebelum standarisasi:")
print(data_dummy.head())
# Standarisasi
data_standar = std.standarisasi(data_dummy, tahun="2024")
print("\nData setelah standarisasi:")
print(data_standar.head())
# Contoh 5: Gabungkan dan bersihkan
print("\n" + "=" * 60)
print("CONTOH 5: Gabungkan dan Bersihkan")
print("=" * 60)
# Gabungkan data dummy dengan dirinya sendiri (untuk simulasi)
data_gabungan = gabung_data([data_standar, data_standar], ["2024", "2024"])
print(f"Jumlah setelah gabung: {len(data_gabungan)} records")
# Bersihkan
data_bersih = bersihkan_data(data_gabungan)
print(f"Jumlah setelah bersih: {len(data_bersih)} records")