Ferli28 commited on
Commit
704fb57
Β·
verified Β·
1 Parent(s): 0efc32c

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +72 -117
src/streamlit_app.py CHANGED
@@ -9,99 +9,72 @@ import os
9
 
10
  # --- KONFIGURASI HALAMAN ---
11
  st.set_page_config(
12
- page_title="EWS Prediksi Dropout Mahasiswa",
13
  page_icon="πŸŽ“",
14
  layout="wide"
15
  )
16
 
17
- # --- JUDUL & DESKRIPSI ---
18
- st.title("πŸŽ“ Early Warning System: Student Dropout Prediction")
19
- st.markdown("""
20
- Aplikasi ini menggunakan **Machine Learning (CatBoost)** untuk mendeteksi dini mahasiswa yang berisiko *dropout*.
21
- Model ini dilatih menggunakan data historis akademik dan demografis dengan akurasi tinggi.
22
- """)
23
-
24
  # --- FUNGSI LOAD MODEL ---
25
  @st.cache_resource
26
  def load_resources():
27
- # 1. Cari tahu di folder mana script ini (streamlit_app.py) berada
28
  current_dir = os.path.dirname(os.path.abspath(__file__))
29
-
30
- # 2. Gabungkan path folder tersebut dengan nama file model
31
  model_path = os.path.join(current_dir, "catboost_dropout_model.cbm")
32
  scaler_path = os.path.join(current_dir, "scaler.pkl")
33
 
34
- # --- DEBUGGING (OPSIONAL: Hapus jika sudah jalan) ---
35
- # Ini akan memberi tahu kita file apa saja yang dilihat oleh sistem
36
  if not os.path.exists(model_path):
37
- st.error(f"❌ Error: File model tidak ditemukan di jalur: {model_path}")
38
- st.warning(f"πŸ“‚ Isi folder '{current_dir}': {os.listdir(current_dir)}")
39
  st.stop()
40
- # ----------------------------------------------------
41
 
42
- # Load Model dengan jalur absolut yang sudah pasti benar
43
  model = CatBoostClassifier()
44
  model.load_model(model_path)
45
-
46
- # Load Scaler
47
  scaler = joblib.load(scaler_path)
48
  return model, scaler
49
 
50
  try:
51
  model, scaler = load_resources()
52
  except Exception as e:
53
- st.error(f"Gagal memuat model. Pastikan file 'catboost_dropout_model.cbm' dan 'scaler.pkl' ada di folder yang sama. Error: {e}")
54
  st.stop()
55
 
56
- # --- SIDEBAR: INPUT DATA ---
 
 
 
 
57
  st.sidebar.header("πŸ“ Input Data Mahasiswa")
58
 
59
  def user_input_features():
60
- # Kelompok Data Demografis & Administratif
61
  st.sidebar.subheader("1. Data Administratif")
62
- # Mapping untuk input user agar lebih mudah dibaca
63
- jalur_masuk_map = {0: 'SNMPTN', 1: 'SBMPTN', 2: 'Mandiri', 3: 'Lainnya'} # Sesuaikan dengan encoding asli Anda jika perlu
64
- provinsi_map = {0: 'Jawa Timur', 1: 'Jawa Tengah', 2: 'Jawa Barat', 3: 'Luar Jawa'} # Contoh mapping
65
-
66
- # Input menggunakan selectbox/number_input
67
- # Note: Di model Anda, fitur ini masuk sebagai angka (Label Encoded).
68
- # Di sini user memilih angka/kategori yang sesuai.
69
  jalur_masuk = st.sidebar.selectbox("Jalur Masuk (Kode)", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
70
  provinsi = st.sidebar.number_input("Kode Provinsi", min_value=0, max_value=50, value=35)
71
  kurikulum = st.sidebar.selectbox("Kurikulum", options=[0, 1, 2, 3])
72
 
73
- st.sidebar.subheader("2. Riwayat Akademik (Semester 1-4)")
74
-
75
- # IPS per Semester
76
  ips1 = st.sidebar.slider("IPS Semester 1", 0.0, 4.0, 3.5)
77
  ips2 = st.sidebar.slider("IPS Semester 2", 0.0, 4.0, 3.4)
78
  ips3 = st.sidebar.slider("IPS Semester 3", 0.0, 4.0, 3.2)
79
  ips4 = st.sidebar.slider("IPS Semester 4", 0.0, 4.0, 3.0)
80
 
81
- # Rata-rata Nilai Angka
82
- nilai1 = st.sidebar.number_input("Rata-rata Nilai Sem 1", 0.0, 100.0, 80.0)
83
- nilai2 = st.sidebar.number_input("Rata-rata Nilai Sem 2", 0.0, 100.0, 78.0)
84
- nilai3 = st.sidebar.number_input("Rata-rata Nilai Sem 3", 0.0, 100.0, 75.0)
85
- nilai4 = st.sidebar.number_input("Rata-rata Nilai Sem 4", 0.0, 100.0, 70.0)
86
 
87
- # Status Mahasiswa (Aktif/Cuti/dll) - Asumsi 1=Aktif
88
- status1 = st.sidebar.selectbox("Status Sem 1", [0, 1], index=1)
89
- status2 = st.sidebar.selectbox("Status Sem 2", [0, 1], index=1)
90
- status3 = st.sidebar.selectbox("Status Sem 3", [0, 1], index=1)
91
- status4 = st.sidebar.selectbox("Status Sem 4", [0, 1], index=1)
92
 
93
  st.sidebar.subheader("3. Beban Studi")
94
- jumlah_mk = st.sidebar.number_input("Total MK Diambil", min_value=10, max_value=100, value=40)
95
- banyak_mk_ulang = st.sidebar.number_input("Total MK Mengulang", min_value=0, max_value=20, value=2)
96
 
97
- # --- FEATURE ENGINEERING OTOMATIS ---
98
- # Menghitung fitur turunan sesuai 'data preparation.ipynb'
99
  delta_ips_1_4 = ips4 - ips1
100
  delta_ips_3_4 = ips4 - ips3
101
  rata_rata_total = (nilai1 + nilai2 + nilai3 + nilai4) / 4
102
  rasio_mengulang = banyak_mk_ulang / (jumlah_mk + 1e-5)
103
 
104
- # Menyusun data ke dalam Dictionary
105
  data = {
106
  'provinsi': provinsi,
107
  'jalur masuk': jalur_masuk,
@@ -125,84 +98,66 @@ def user_input_features():
125
  'rata_rata_total': rata_rata_total,
126
  'rasio_mengulang': rasio_mengulang
127
  }
128
-
129
- # Pastikan urutan kolom sesuai dengan training data
130
- features = pd.DataFrame(data, index=[0])
131
- return features
132
 
133
  input_df = user_input_features()
134
 
135
- # --- TAMPILAN UTAMA ---
136
  col1, col2 = st.columns([1, 2])
137
 
138
  with col1:
139
- st.subheader("Data Input Mahasiswa")
140
- st.write("Fitur Utama (dari input & kalkulasi):")
141
- st.dataframe(input_df.T, height=600)
142
 
143
  with col2:
144
- st.subheader("πŸ” Hasil Prediksi")
145
-
146
- # Tombol Prediksi
147
- if st.button("Analisis Risiko Dropout"):
148
- # Scaling Data
149
- try:
150
- input_scaled = scaler.transform(input_df)
151
-
152
- # Prediksi
153
- prediction = model.predict(input_scaled)
154
- probability = model.predict_proba(input_scaled)
155
-
156
- # Output Prediksi
157
- prob_dropout = probability[0][1] * 100
158
-
159
- if prediction[0] == 1:
160
- st.error(f"⚠️ **PERINGATAN: Berisiko Tinggi DROPOUT**")
161
- st.write(f"Probabilitas Dropout: **{prob_dropout:.2f}%**")
162
- st.info("Saran: Mahasiswa ini memerlukan intervensi akademik atau konseling segera.")
163
- else:
164
- st.success(f"βœ… **Prediksi: AMAN (Pass)**")
165
- st.write(f"Probabilitas Dropout: **{prob_dropout:.2f}%**")
166
- st.write("Mahasiswa diprediksi dapat melanjutkan studi dengan baik.")
167
-
168
- # --- SHAP EXPLAINABILITY ---
169
- st.markdown("---")
170
- st.subheader("πŸ“Š Mengapa model memprediksi demikian?")
171
- st.write("Grafik di bawah menunjukkan faktor-faktor yang paling mendorong prediksi ke arah Dropout (Merah) atau Aman (Biru).")
172
-
173
- # SHAP Calculation
174
- explainer = shap.TreeExplainer(model)
175
- shap_values = explainer.shap_values(input_scaled)
176
-
177
- # Force Plot (Visualisasi untuk 1 data spesifik)
178
- # Karena Streamlit agak tricky dengan JS SHAP, kita pakai Matplotlib static plot
179
- fig, ax = plt.subplots(figsize=(10, 5))
180
- shap.force_plot(
181
- explainer.expected_value,
182
- shap_values[0],
183
- input_df,
184
- matplotlib=True,
185
- show=False,
186
- text_rotation=0
187
- )
188
- st.pyplot(fig, clear_figure=True)
189
-
190
- # Waterfall Plot (Alternatif yang lebih detail)
191
- st.write("**Detail Kontribusi Fitur:**")
192
- fig2, ax2 = plt.subplots()
193
- shap.waterfall_plot(
194
- shap.Explanation(values=shap_values[0],
195
- base_values=explainer.expected_value,
196
- data=input_df.iloc[0],
197
- feature_names=input_df.columns),
198
- show=False
199
- )
200
- st.pyplot(fig2)
201
-
202
- except Exception as e:
203
- st.error(f"Terjadi kesalahan saat pemrosesan: {e}")
204
- st.warning("Pastikan urutan fitur pada scaler.pkl sama persis dengan input di aplikasi ini.")
205
 
206
  # --- FOOTER ---
207
  st.markdown("---")
208
- st.caption("Dibuat untuk keperluan Publikasi IEEE - ITS Surabaya")
 
9
 
10
  # --- KONFIGURASI HALAMAN ---
11
  st.set_page_config(
12
+ page_title="EWS Prediksi Dropout",
13
  page_icon="πŸŽ“",
14
  layout="wide"
15
  )
16
 
 
 
 
 
 
 
 
17
  # --- FUNGSI LOAD MODEL ---
18
  @st.cache_resource
19
  def load_resources():
 
20
  current_dir = os.path.dirname(os.path.abspath(__file__))
 
 
21
  model_path = os.path.join(current_dir, "catboost_dropout_model.cbm")
22
  scaler_path = os.path.join(current_dir, "scaler.pkl")
23
 
 
 
24
  if not os.path.exists(model_path):
25
+ st.error(f"❌ File model tidak ditemukan di: {model_path}")
 
26
  st.stop()
 
27
 
 
28
  model = CatBoostClassifier()
29
  model.load_model(model_path)
 
 
30
  scaler = joblib.load(scaler_path)
31
  return model, scaler
32
 
33
  try:
34
  model, scaler = load_resources()
35
  except Exception as e:
36
+ st.error(f"Error loading resources: {e}")
37
  st.stop()
38
 
39
+ # --- JUDUL ---
40
+ st.title("πŸŽ“ Early Warning System: Student Dropout Prediction")
41
+ st.markdown("Aplikasi ini menggunakan **Machine Learning (CatBoost)** untuk mendeteksi dini mahasiswa yang berisiko *dropout*.")
42
+
43
+ # --- SIDEBAR INPUT ---
44
  st.sidebar.header("πŸ“ Input Data Mahasiswa")
45
 
46
  def user_input_features():
 
47
  st.sidebar.subheader("1. Data Administratif")
 
 
 
 
 
 
 
48
  jalur_masuk = st.sidebar.selectbox("Jalur Masuk (Kode)", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
49
  provinsi = st.sidebar.number_input("Kode Provinsi", min_value=0, max_value=50, value=35)
50
  kurikulum = st.sidebar.selectbox("Kurikulum", options=[0, 1, 2, 3])
51
 
52
+ st.sidebar.subheader("2. Riwayat Akademik")
 
 
53
  ips1 = st.sidebar.slider("IPS Semester 1", 0.0, 4.0, 3.5)
54
  ips2 = st.sidebar.slider("IPS Semester 2", 0.0, 4.0, 3.4)
55
  ips3 = st.sidebar.slider("IPS Semester 3", 0.0, 4.0, 3.2)
56
  ips4 = st.sidebar.slider("IPS Semester 4", 0.0, 4.0, 3.0)
57
 
58
+ nilai1 = st.sidebar.number_input("Nilai Angka Sem 1", 0.0, 100.0, 80.0)
59
+ nilai2 = st.sidebar.number_input("Nilai Angka Sem 2", 0.0, 100.0, 78.0)
60
+ nilai3 = st.sidebar.number_input("Nilai Angka Sem 3", 0.0, 100.0, 75.0)
61
+ nilai4 = st.sidebar.number_input("Nilai Angka Sem 4", 0.0, 100.0, 70.0)
 
62
 
63
+ status1 = st.sidebar.selectbox("Status Sem 1 (1=Aktif)", [0, 1], index=1)
64
+ status2 = st.sidebar.selectbox("Status Sem 2 (1=Aktif)", [0, 1], index=1)
65
+ status3 = st.sidebar.selectbox("Status Sem 3 (1=Aktif)", [0, 1], index=1)
66
+ status4 = st.sidebar.selectbox("Status Sem 4 (1=Aktif)", [0, 1], index=1)
 
67
 
68
  st.sidebar.subheader("3. Beban Studi")
69
+ jumlah_mk = st.sidebar.number_input("Total MK Diambil", min_value=10, max_value=150, value=40)
70
+ banyak_mk_ulang = st.sidebar.number_input("Total MK Mengulang", min_value=0, max_value=50, value=0)
71
 
72
+ # Feature Engineering
 
73
  delta_ips_1_4 = ips4 - ips1
74
  delta_ips_3_4 = ips4 - ips3
75
  rata_rata_total = (nilai1 + nilai2 + nilai3 + nilai4) / 4
76
  rasio_mengulang = banyak_mk_ulang / (jumlah_mk + 1e-5)
77
 
 
78
  data = {
79
  'provinsi': provinsi,
80
  'jalur masuk': jalur_masuk,
 
98
  'rata_rata_total': rata_rata_total,
99
  'rasio_mengulang': rasio_mengulang
100
  }
101
+ return pd.DataFrame(data, index=[0])
 
 
 
102
 
103
  input_df = user_input_features()
104
 
105
+ # --- MAIN LAYOUT ---
106
  col1, col2 = st.columns([1, 2])
107
 
108
  with col1:
109
+ st.info("Pastikan data di sidebar sudah benar sebelum menekan tombol analisis.")
110
+ st.write("**Preview Data Input:**")
111
+ st.dataframe(input_df.T, height=400)
112
 
113
  with col2:
114
+ if st.button("πŸš€ Analisis Risiko Dropout", type="primary"):
115
+ with st.spinner('Sedang menganalisis...'):
116
+ try:
117
+ # 1. Transform Data
118
+ input_scaled = scaler.transform(input_df)
119
+
120
+ # 2. Prediksi
121
+ prediction = model.predict(input_scaled)
122
+ proba = model.predict_proba(input_scaled)[0][1]
123
+
124
+ # 3. Tampilkan Hasil
125
+ st.subheader("Hasil Analisis")
126
+
127
+ if prediction[0] == 1:
128
+ st.error(f"⚠️ **STATUS: BERISIKO DROPOUT**")
129
+ st.metric("Probabilitas Dropout", f"{proba*100:.1f}%")
130
+ st.warning("Mahasiswa ini memerlukan perhatian akademik khusus.")
131
+ else:
132
+ st.success(f"βœ… **STATUS: AMAN (Pass)**")
133
+ st.metric("Probabilitas Dropout", f"{proba*100:.1f}%")
134
+ st.info("Performa akademik mahasiswa terpantau stabil.")
135
+
136
+ # 4. Visualisasi SHAP (Waterfall Plot)
137
+ st.markdown("---")
138
+ st.subheader("πŸ“Š Faktor Penentu Keputusan")
139
+ st.caption("Grafik ini menunjukkan fitur apa yang paling mendorong (+) atau mengurangi (-) risiko dropout.")
140
+
141
+ explainer = shap.TreeExplainer(model)
142
+ shap_values = explainer.shap_values(input_scaled)
143
+
144
+ # Menggunakan Waterfall Plot (Lebih stabil & informatif)
145
+ fig, ax = plt.subplots(figsize=(8, 6))
146
+ shap.waterfall_plot(
147
+ shap.Explanation(
148
+ values=shap_values[0],
149
+ base_values=explainer.expected_value,
150
+ data=input_df.iloc[0],
151
+ feature_names=input_df.columns
152
+ ),
153
+ max_display=10,
154
+ show=False
155
+ )
156
+ st.pyplot(fig) # Menggambar figure waterfall
157
+
158
+ except Exception as e:
159
+ st.error(f"Terjadi kesalahan: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
  # --- FOOTER ---
162
  st.markdown("---")
163
+ st.markdown("Β© 2025 EWS System | Institut Teknologi Sepuluh Nopember")