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