580 lines
20 KiB
Python
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")
|