TIF_E41253310/Backend/Model/preprocessing/dashboard.py

708 lines
23 KiB
Python

"""
=============================================================
DASHBOARD VISUALIZATION
=============================================================
Generate visualisasi untuk hasil clustering alumni:
- Distribusi Kategori C0-C3
- Distribusi per Prodi
- Distribusi per Tahun
- Cluster Analysis
- Pie Charts dan Bar Charts
Output: PNG charts + HTML Dashboard
=============================================================
"""
import pandas as pd
import numpy as np
import json
import os
from datetime import datetime
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import seaborn as sns
# Set style
plt.style.use('seaborn-v0_8-whitegrid')
sns.set_palette("husl")
# Path configuration
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(BASE_DIR, '..', 'data', 'processed')
RESULT_DIR = os.path.join(BASE_DIR, '..', 'data', 'result')
OUTPUT_DIR = os.path.join(BASE_DIR, '..', 'data', 'processed', 'dashboard')
# Create output directory
os.makedirs(OUTPUT_DIR, exist_ok=True)
# Colors
COLORS = {
'C0': '#2ecc71', # Green - Sesuai Profil
'C1': '#3498db', # Blue - Bidang Diketahui
'C2': '#f39c12', # Orange - Bidang Tidak Jelas
'C3': '#e74c3c', # Red - Belum Bekerja
'TIF': '#9b59b6', # Purple
'MIF': '#1abc9c', # Teal
'TKK': '#e67e22' # Orange
}
def load_data():
"""Load semua data yang diperlukan."""
print(">> Loading data...")
# Interpretasi results (from result folder)
df = pd.read_csv(os.path.join(RESULT_DIR, 'interpretasi_results.csv'), encoding='utf-8-sig')
# Processed dataset
processed_df = pd.read_csv(os.path.join(DATA_DIR, 'processed_dataset.csv'), encoding='utf-8-sig')
# Cluster statistics
with open(os.path.join(DATA_DIR, 'cluster_statistics.json'), 'r') as f:
cluster_stats = json.load(f)
print(f" Total data: {len(df)}")
return df, processed_df, cluster_stats
def create_pie_chart_kategori(df, output_path):
"""Pie chart distribusi kategori C0-C3."""
print(">> Creating Pie Chart - Kategori C0-C3...")
kategori_counts = df['kategori'].value_counts()
labels = kategori_counts.index.tolist()
sizes = kategori_counts.values
colors = [COLORS.get(k, '#95a5a6') for k in labels]
fig, ax = plt.subplots(figsize=(10, 8))
wedges, texts, autotexts = ax.pie(
sizes,
labels=labels,
colors=colors,
autopct='%1.1f%%',
startangle=90,
explode=[0.02] * len(labels)
)
# Style
for autotext in autotexts:
autotext.set_fontsize(12)
autotext.set_fontweight('bold')
ax.set_title('Distribusi Kategori Alumni (C0-C3)\n', fontsize=16, fontweight='bold')
# Add legend
legend_labels = [
f'C0: Sesuai Profil ({kategori_counts.get("C0", 0)})',
f'C1: Bidang Diketahui ({kategori_counts.get("C1", 0)})',
f'C2: Bidang Tidak Jelas ({kategori_counts.get("C2", 0)})',
f'C3: Belum Bekerja ({kategori_counts.get("C3", 0)})'
]
ax.legend(wedges, legend_labels, title="Kategori", loc="center left", bbox_to_anchor=(1, 0, 0.5, 1))
plt.tight_layout()
plt.savefig(output_path, dpi=150, bbox_inches='tight', facecolor='white')
plt.close()
print(f" Saved: {output_path}")
def create_bar_chart_prodi(df, output_path):
"""Bar chart distribusi C0-C3 per prodi."""
print(">> Creating Bar Chart - Distribusi per Prodi...")
# Map prodi codes
prodi_map = {'55301': 'TIF', '57401': 'MIF', '56401': 'TKK'}
df['prodi'] = df['kode_prodi'].astype(str).str.replace('.0', '', regex=False).str.strip()
df['prodi'] = df['prodi'].map(prodi_map).fillna('Lainnya')
# Group by prodi and kategori
grouped = df.groupby(['prodi', 'kategori']).size().unstack(fill_value=0)
fig, ax = plt.subplots(figsize=(12, 7))
categories = ['C0', 'C1', 'C2', 'C3']
colors = [COLORS[c] for c in categories]
x = np.arange(len(grouped.index))
width = 0.2
for i, cat in enumerate(categories):
if cat in grouped.columns:
values = grouped[cat].values
bars = ax.bar(x + i * width, values, width, label=cat, color=colors[i])
# Add value labels
for bar in bars:
height = bar.get_height()
if height > 0:
ax.annotate(f'{int(height)}',
xy=(bar.get_x() + bar.get_width() / 2, height),
xytext=(0, 3),
textcoords="offset points",
ha='center', va='bottom', fontsize=9)
ax.set_xlabel('Program Studi', fontsize=12)
ax.set_ylabel('Jumlah Alumni', fontsize=12)
ax.set_title('Distribusi Kategori C0-C3 per Program Studi\n', fontsize=16, fontweight='bold')
ax.set_xticks(x + width * 1.5)
ax.set_xticklabels(grouped.index)
ax.legend(title='Kategori')
plt.tight_layout()
plt.savefig(output_path, dpi=150, bbox_inches='tight', facecolor='white')
plt.close()
print(f" Saved: {output_path}")
def create_stacked_bar_tahun(df, output_path):
"""Stacked bar chart distribusi per tahun."""
print(">> Creating Stacked Bar - Distribusi per Tahun...")
# Get tahun from processed dataset
processed_df = pd.read_csv(os.path.join(DATA_DIR, 'processed_dataset.csv'), encoding='utf-8-sig')
df = df.merge(processed_df[['nim', 'tahun_lulus']], on='nim', how='left')
# Group by tahun and kategori
grouped = df.groupby(['tahun_lulus', 'kategori']).size().unstack(fill_value=0)
fig, ax = plt.subplots(figsize=(12, 7))
categories = ['C0', 'C1', 'C2', 'C3']
colors = [COLORS[c] for c in categories]
bottom = np.zeros(len(grouped.index))
for i, cat in enumerate(categories):
if cat in grouped.columns:
values = grouped[cat].values
ax.bar(grouped.index.astype(str), values, bottom=bottom, label=cat, color=colors[i])
bottom += values
ax.set_xlabel('Tahun Lulus', fontsize=12)
ax.set_ylabel('Jumlah Alumni', fontsize=12)
ax.set_title('Distribusi Kategori C0-C3 per Tahun Lulus\n', fontsize=16, fontweight='bold')
ax.legend(title='Kategori', loc='upper right')
plt.tight_layout()
plt.savefig(output_path, dpi=150, bbox_inches='tight', facecolor='white')
plt.close()
print(f" Saved: {output_path}")
def create_donut_chart_cluster(df, output_path):
"""Donut chart distribusi cluster."""
print(">> Creating Donut Chart - Distribusi Cluster...")
cluster_counts = df['cluster'].value_counts().sort_index()
fig, ax = plt.subplots(figsize=(10, 8))
colors = ['#3498db', '#2ecc71', '#f39c12', '#9b59b6']
labels = [f'Cluster {i}' for i in cluster_counts.index]
wedges, texts, autotexts = ax.pie(
cluster_counts.values,
labels=labels,
colors=colors,
autopct='%1.1f%%',
startangle=90,
pctdistance=0.75,
explode=[0.02] * len(labels)
)
# Create donut hole
centre_circle = plt.Circle((0, 0), 0.50, fc='white')
ax.add_patch(centre_circle)
# Style
for autotext in autotexts:
autotext.set_fontsize(11)
autotext.set_fontweight('bold')
ax.set_title('Distribusi Cluster K-Means (K=4)\n', fontsize=16, fontweight='bold')
# Add legend
legend_labels = [f'Cluster {i}: {cluster_counts.get(i, 0)}' for i in range(4)]
ax.legend(wedges, legend_labels, title="Cluster", loc="center left", bbox_to_anchor=(1, 0, 0.5, 1))
plt.tight_layout()
plt.savefig(output_path, dpi=150, bbox_inches='tight', facecolor='white')
plt.close()
print(f" Saved: {output_path}")
def create_summary_cards(stats, output_path):
"""Generate summary cards as image."""
print(">> Creating Summary Cards...")
fig, axes = plt.subplots(2, 4, figsize=(16, 8))
fig.patch.set_facecolor('white')
# Total Alumni
ax = axes[0, 0]
ax.text(0.5, 0.7, '535', fontsize=36, ha='center', fontweight='bold', color='#2c3e50')
ax.text(0.5, 0.3, 'Total Alumni', fontsize=12, ha='center', color='#7f8c8d')
ax.axis('off')
# C0
ax = axes[0, 1]
ax.text(0.5, 0.7, '18', fontsize=36, ha='center', fontweight='bold', color=COLORS['C0'])
ax.text(0.5, 0.3, 'C0 - Sesuai Profil', fontsize=10, ha='center', color='#7f8c8d')
ax.axis('off')
# C1
ax = axes[0, 2]
ax.text(0.5, 0.7, '85', fontsize=36, ha='center', fontweight='bold', color=COLORS['C1'])
ax.text(0.5, 0.3, 'C1 - Bidang Diketahui', fontsize=10, ha='center', color='#7f8c8d')
ax.axis('off')
# C2
ax = axes[0, 3]
ax.text(0.5, 0.7, '416', fontsize=36, ha='center', fontweight='bold', color=COLORS['C2'])
ax.text(0.5, 0.3, 'C2 - Bidang Tidak Jelas', fontsize=10, ha='center', color='#7f8c8d')
ax.axis('off')
# C3
ax = axes[1, 0]
ax.text(0.5, 0.7, '16', fontsize=36, ha='center', fontweight='bold', color=COLORS['C3'])
ax.text(0.5, 0.3, 'C3 - Belum Bekerja', fontsize=10, ha='center', color='#7f8c8d')
ax.axis('off')
# TIF
ax = axes[1, 1]
ax.text(0.5, 0.7, '225', fontsize=36, ha='center', fontweight='bold', color=COLORS['TIF'])
ax.text(0.5, 0.3, 'TIF - Teknik Informatika', fontsize=10, ha='center', color='#7f8c8d')
ax.axis('off')
# MIF
ax = axes[1, 2]
ax.text(0.5, 0.7, '165', fontsize=36, ha='center', fontweight='bold', color=COLORS['MIF'])
ax.text(0.5, 0.3, 'MIF - Manajemen Informatika', fontsize=10, ha='center', color='#7f8c8d')
ax.axis('off')
# TKK
ax = axes[1, 3]
ax.text(0.5, 0.7, '145', fontsize=36, ha='center', fontweight='bold', color=COLORS['TKK'])
ax.text(0.5, 0.3, 'TKK - Teknik Komputer', fontsize=10, ha='center', color='#7f8c8d')
ax.axis('off')
plt.suptitle('Dashboard Summary - Sistem Clustering Alumni', fontsize=18, fontweight='bold', y=1.02)
plt.tight_layout()
plt.savefig(output_path, dpi=150, bbox_inches='tight', facecolor='white')
plt.close()
print(f" Saved: {output_path}")
def create_html_dashboard(df, output_path):
"""Generate interactive HTML dashboard."""
print(">> Creating HTML Dashboard...")
# Calculate stats
kategori_counts = df['kategori'].value_counts()
total = len(df)
# Prodi distribution
prodi_map = {'55301': 'TIF', '57401': 'MIF', '56401': 'TKK'}
df['prodi'] = df['kode_prodi'].astype(str).str.replace('.0', '', regex=False).str.strip()
df['prodi'] = df['prodi'].map(prodi_map).fillna('Lainnya')
prodi_counts = df['prodi'].value_counts()
html_content = f"""
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard Clustering Alumni - Politeknik Negeri Jember</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
* {{
margin: 0;
padding: 0;
box-sizing: border-box;
}}
body {{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}}
.container {{
max-width: 1400px;
margin: 0 auto;
}}
.header {{
background: white;
border-radius: 15px;
padding: 30px;
margin-bottom: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}}
.header h1 {{
color: #2c3e50;
font-size: 28px;
margin-bottom: 10px;
}}
.header p {{
color: #7f8c8d;
font-size: 14px;
}}
.cards {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 20px;
}}
.card {{
background: white;
border-radius: 15px;
padding: 25px;
text-align: center;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
transition: transform 0.3s;
}}
.card:hover {{
transform: translateY(-5px);
}}
.card-value {{
font-size: 42px;
font-weight: bold;
margin-bottom: 5px;
}}
.card-label {{
font-size: 14px;
color: #7f8c8d;
}}
.card-c0 {{ color: #2ecc71; }}
.card-c1 {{ color: #3498db; }}
.card-c2 {{ color: #f39c12; }}
.card-c3 {{ color: #e74c3c; }}
.card-tif {{ color: #9b59b6; }}
.card-mif {{ color: #1abc9c; }}
.card-tkk {{ color: #e67e22; }}
.grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
}}
.chart-container {{
background: white;
border-radius: 15px;
padding: 25px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}}
.chart-title {{
font-size: 18px;
font-weight: bold;
color: #2c3e50;
margin-bottom: 20px;
text-align: center;
}}
.chart-wrapper {{
position: relative;
height: 300px;
}}
.definitions {{
background: white;
border-radius: 15px;
padding: 25px;
margin-top: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}}
.definitions h2 {{
color: #2c3e50;
margin-bottom: 15px;
}}
.definition-item {{
display: flex;
align-items: center;
margin-bottom: 12px;
padding: 10px;
border-radius: 8px;
background: #f8f9fa;
}}
.definition-badge {{
padding: 8px 15px;
border-radius: 20px;
font-weight: bold;
color: white;
margin-right: 15px;
min-width: 50px;
text-align: center;
}}
.badge-c0 {{ background: #2ecc71; }}
.badge-c1 {{ background: #3498db; }}
.badge-c2 {{ background: #f39c12; }}
.badge-c3 {{ background: #e74c3c; }}
.footer {{
text-align: center;
margin-top: 30px;
color: white;
font-size: 12px;
}}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Dashboard Clustering Alumni</h1>
<p>Sistem Pengelompokan Pekerjaan Alumni dengan Kompetensi Lulusan - Jurusan Teknologi Informasi, Politeknik Negeri Jember</p>
</div>
<div class="cards">
<div class="card">
<div class="card-value card-tif">{prodi_counts.get('TIF', 0)}</div>
<div class="card-label">Teknik Informatika (TIF)</div>
</div>
<div class="card">
<div class="card-value card-mif">{prodi_counts.get('MIF', 0)}</div>
<div class="card-label">Manajemen Informatika (MIF)</div>
</div>
<div class="card">
<div class="card-value card-tkk">{prodi_counts.get('TKK', 0)}</div>
<div class="card-label">Teknik Komputer (TKK)</div>
</div>
<div class="card">
<div class="card-value" style="color:#2c3e50">{total}</div>
<div class="card-label">Total Alumni</div>
</div>
</div>
<div class="cards">
<div class="card">
<div class="card-value card-c0">{kategori_counts.get('C0', 0)}</div>
<div class="card-label">Sesuai Profil (C0)</div>
</div>
<div class="card">
<div class="card-value card-c1">{kategori_counts.get('C1', 0)}</div>
<div class="card-label">Bidang Diketahui (C1)</div>
</div>
<div class="card">
<div class="card-value card-c2">{kategori_counts.get('C2', 0)}</div>
<div class="card-label">Bidang Tidak Jelas (C2)</div>
</div>
<div class="card">
<div class="card-value card-c3">{kategori_counts.get('C3', 0)}</div>
<div class="card-label">Belum Bekerja (C3)</div>
</div>
</div>
<div class="grid">
<div class="chart-container">
<div class="chart-title">Distribusi Kategori Alumni (C0-C3)</div>
<div class="chart-wrapper">
<canvas id="kategoriChart"></canvas>
</div>
</div>
<div class="chart-container">
<div class="chart-title">Distribusi per Program Studi</div>
<div class="chart-wrapper">
<canvas id="prodiChart"></canvas>
</div>
</div>
</div>
<div class="grid" style="margin-top:20px">
<div class="chart-container">
<div class="chart-title">Distribusi Cluster K-Means (K=4)</div>
<div class="chart-wrapper">
<canvas id="clusterChart"></canvas>
</div>
</div>
<div class="chart-container">
<div class="chart-title">Match Rate per Program Studi</div>
<div class="chart-wrapper">
<canvas id="matchChart"></canvas>
</div>
</div>
</div>
<div class="definitions">
<h2>Definisi Kategori</h2>
<div class="definition-item">
<span class="definition-badge badge-c0">C0</span>
<span><strong>Sesuai Profil Lulusan:</strong> Alumni bekerja dan pekerjaan match dengan kamus kata kunci profil lulusannya.</span>
</div>
<div class="definition-item">
<span class="definition-badge badge-c1">C1</span>
<span><strong>Bidang Diketahui:</strong> Alumni bekerja, bidang pekerjaan jelas, tapi tidak match kamus prodi.</span>
</div>
<div class="definition-item">
<span class="definition-badge badge-c2">C2</span>
<span><strong>Bidang Tidak Teridentifikasi:</strong> Alumni bekerja tapi informasi pekerjaan kurang jelas atau terlalu umum.</span>
</div>
<div class="definition-item">
<span class="definition-badge badge-c3">C3</span>
<span><strong>Belum/Tidak Bekerja:</strong> Alumni belum bekerja atau tidak bekerja saat tracer study.</span>
</div>
</div>
<div class="footer">
<p>Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
<p>Sistem Clustering Alumni - Politeknik Negeri Jember</p>
</div>
</div>
<script>
// Kategori Chart
new Chart(document.getElementById('kategoriChart'), {{
type: 'doughnut',
data: {{
labels: ['C0 - Sesuai Profil', 'C1 - Bidang Diketahui', 'C2 - Bidang Tidak Jelas', 'C3 - Belum Bekerja'],
datasets: [{{
data: [
{kategori_counts.get('C0', 0)},
{kategori_counts.get('C1', 0)},
{kategori_counts.get('C2', 0)},
{kategori_counts.get('C3', 0)}
],
backgroundColor: ['#2ecc71', '#3498db', '#f39c12', '#e74c3c']
}}]
}},
options: {{
responsive: true,
maintainAspectRatio: false,
plugins: {{
legend: {{ position: 'bottom' }}
}}
}}
}});
// Prodi Chart
new Chart(document.getElementById('prodiChart'), {{
type: 'bar',
data: {{
labels: ['TIF', 'MIF', 'TKK'],
datasets: [{{
label: 'Jumlah Alumni',
data: [
{prodi_counts.get('TIF', 0)},
{prodi_counts.get('MIF', 0)},
{prodi_counts.get('TKK', 0)}
],
backgroundColor: ['#9b59b6', '#1abc9c', '#e67e22']
}}]
}},
options: {{
responsive: true,
maintainAspectRatio: false,
plugins: {{
legend: {{ display: false }}
}}
}}
}});
// Cluster Chart
new Chart(document.getElementById('clusterChart'), {{
type: 'pie',
data: {{
labels: ['Cluster 0', 'Cluster 1', 'Cluster 2', 'Cluster 3'],
datasets: [{{
data: [326, 27, 66, 100],
backgroundColor: ['#3498db', '#2ecc71', '#f39c12', '#9b59b6']
}}]
}},
options: {{
responsive: true,
maintainAspectRatio: false
}}
}});
// Match Rate Chart
new Chart(document.getElementById('matchChart'), {{
type: 'bar',
data: {{
labels: ['TIF', 'MIF', 'TKK'],
datasets: [{{
label: 'Match Rate (%)',
data: [2.22, 1.82, 6.90],
backgroundColor: ['#9b59b6', '#1abc9c', '#e67e22']
}}]
}},
options: {{
responsive: true,
maintainAspectRatio: false,
scales: {{
y: {{
beginAtZero: true,
max: 10
}}
}},
plugins: {{
legend: {{ display: false }}
}}
}}
}});
</script>
</body>
</html>
"""
with open(output_path, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f" Saved: {output_path}")
def main():
"""Main function."""
print("=" * 60)
print("GENERATING DASHBOARD VISUALIZATIONS")
print("=" * 60)
# Load data
df, processed_df, cluster_stats = load_data()
# Create visualizations
create_summary_cards(cluster_stats, os.path.join(OUTPUT_DIR, 'summary_cards.png'))
create_pie_chart_kategori(df, os.path.join(OUTPUT_DIR, 'kategori_pie.png'))
create_bar_chart_prodi(df, os.path.join(OUTPUT_DIR, 'prodi_bar.png'))
create_donut_chart_cluster(df, os.path.join(OUTPUT_DIR, 'cluster_donut.png'))
create_stacked_bar_tahun(df, os.path.join(OUTPUT_DIR, 'tahun_stacked.png'))
create_html_dashboard(df, os.path.join(OUTPUT_DIR, 'dashboard.html'))
print("\n" + "=" * 60)
print("[SUCCESS] DASHBOARD VISUALIZATIONS COMPLETE!")
print("=" * 60)
print(f"\nOutput directory: {OUTPUT_DIR}")
print("Files generated:")
print(" - summary_cards.png")
print(" - kategori_pie.png")
print(" - prodi_bar.png")
print(" - cluster_donut.png")
print(" - tahun_stacked.png")
print(" - dashboard.html (Interactive)")
if __name__ == "__main__":
main()