Upload files to "/"
This commit is contained in:
commit
f75fcfc6a6
101
klasifikasi_mhs.csv
Normal file
101
klasifikasi_mhs.csv
Normal file
@ -0,0 +1,101 @@
|
||||
Tempat Tinggal,Pekerjaan Orang Tua,Penghasilan Orang Tua,Jumlah Tanggungan Orang Tua,Kendaraan,Kelayakan Keringanan UKT
|
||||
0,PNS,10000000,3,1,0
|
||||
0,TNI/POLRI,8000000,2,2,1
|
||||
1,Petani,4000000,4,0,0
|
||||
1,Nelayan,3000000,5,1,0
|
||||
0,Buruh,2000000,2,1,1
|
||||
1,Ibu Rumah Tangga,5000000,3,0,0
|
||||
0,PNS,9000000,2,2,1
|
||||
1,Wiraswasta,6000000,1,1,1
|
||||
1,Guru,7000000,2,1,1
|
||||
0,Ibu Rumah Tangga,3000000,4,1,0
|
||||
0,TNI/POLRI,8000000,2,2,1
|
||||
1,Petani,4000000,3,0,0
|
||||
1,Buruh,2000000,2,1,0
|
||||
0,Guru,7000000,2,1,1
|
||||
0,Ibu Rumah Tangga,3000000,4,1,1
|
||||
1,PNS,10000000,2,2,1
|
||||
1,Wiraswasta,6000000,1,1,1
|
||||
0,Petani,4000000,4,0,0
|
||||
0,Buruh,2000000,2,1,1
|
||||
1,TNI/POLRI,8000000,2,2,1
|
||||
1,Nelayan,3000000,5,1,0
|
||||
0,Guru,7000000,2,1,1
|
||||
1,Ibu Rumah Tangga,5000000,3,0,1
|
||||
0,PNS,9000000,2,2,1
|
||||
1,Wiraswasta,6000000,1,1,0
|
||||
0,Ibu Rumah Tangga,3000000,4,1,0
|
||||
0,TNI/POLRI,8000000,2,2,1
|
||||
1,Petani,4000000,3,0,0
|
||||
1,Buruh,2000000,2,1,1
|
||||
0,Guru,7000000,2,1,1
|
||||
0,Ibu Rumah Tangga,3000000,4,1,0
|
||||
1,PNS,10000000,2,2,1
|
||||
1,Wiraswasta,6000000,1,1,1
|
||||
0,Petani,4000000,4,0,0
|
||||
0,Buruh,2000000,2,1,1
|
||||
1,TNI/POLRI,8000000,2,2,1
|
||||
1,Nelayan,3000000,5,1,0
|
||||
0,Guru,7000000,2,1,1
|
||||
1,Ibu Rumah Tangga,5000000,3,0,1
|
||||
0,PNS,9000000,2,2,1
|
||||
1,Wiraswasta,6000000,1,1,1
|
||||
0,Ibu Rumah Tangga,3000000,4,1,0
|
||||
0,TNI/POLRI,8000000,2,2,1
|
||||
1,Petani,4000000,3,0,0
|
||||
1,Buruh,2000000,2,1,1
|
||||
0,Guru,7000000,2,1,1
|
||||
0,Ibu Rumah Tangga,3000000,4,1,0
|
||||
1,PNS,10000000,2,2,1
|
||||
1,Wiraswasta,6000000,1,1,1
|
||||
0,Petani,4000000,4,0,0
|
||||
0,Buruh,2000000,2,1,1
|
||||
1,TNI/POLRI,8000000,2,2,1
|
||||
1,Nelayan,3000000,5,1,0
|
||||
0,Guru,7000000,2,1,1
|
||||
1,Ibu Rumah Tangga,5000000,3,0,1
|
||||
0,PNS,9000000,2,2,1
|
||||
1,Wiraswasta,6000000,1,1,1
|
||||
0,Ibu Rumah Tangga,3000000,4,1,0
|
||||
0,TNI/POLRI,8000000,2,2,1
|
||||
1,Petani,4000000,3,0,0
|
||||
1,Buruh,2000000,2,1,1
|
||||
0,Guru,7000000,2,1,1
|
||||
0,Ibu Rumah Tangga,3000000,4,1,0
|
||||
1,PNS,10000000,2,2,1
|
||||
1,Wiraswasta,6000000,1,1,1
|
||||
0,Petani,4000000,4,0,0
|
||||
0,Buruh,2000000,2,1,1
|
||||
1,TNI/POLRI,8000000,2,2,1
|
||||
1,Nelayan,3000000,5,1,0
|
||||
0,Guru,7000000,2,1,1
|
||||
1,Ibu Rumah Tangga,5000000,3,0,1
|
||||
0,PNS,9000000,2,2,1
|
||||
1,Wiraswasta,6000000,1,1,1
|
||||
0,Ibu Rumah Tangga,3000000,4,1,0
|
||||
0,TNI/POLRI,8000000,2,2,1
|
||||
1,Petani,4000000,3,0,0
|
||||
1,Buruh,2000000,2,1,1
|
||||
0,Guru,7000000,2,1,1
|
||||
0,Ibu Rumah Tangga,3000000,4,1,0
|
||||
1,PNS,10000000,2,2,1
|
||||
1,Wiraswasta,6000000,1,1,1
|
||||
0,Petani,4000000,4,0,0
|
||||
0,Buruh,2000000,2,1,1
|
||||
1,TNI/POLRI,8000000,2,2,1
|
||||
1,Nelayan,3000000,5,1,0
|
||||
0,Guru,7000000,2,1,1
|
||||
1,Ibu Rumah Tangga,5000000,3,0,1
|
||||
0,PNS,9000000,2,2,1
|
||||
1,Wiraswasta,6000000,1,1,1
|
||||
0,Ibu Rumah Tangga,3000000,4,1,0
|
||||
0,TNI/POLRI,8000000,2,2,1
|
||||
1,Petani,4000000,3,0,0
|
||||
1,Buruh,2000000,2,1,1
|
||||
1,Guru,7000000,2,1,1
|
||||
0,Ibu Rumah Tangga,3000000,4,1,0
|
||||
0,PNS,9000000,2,2,1 1
|
||||
1,Wiraswasta,6000000,1,1,1
|
||||
1,TNI/POLRI,8000000,2,2,1
|
||||
0,Nelayan,3000000,5,1,0
|
||||
0,Petani,700000,3,1,0
|
||||
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
streamlit
|
||||
pandas
|
||||
scikit-learn
|
||||
3
run_streamlit.bat
Normal file
3
run_streamlit.bat
Normal file
@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
cd /d "%~dp0"
|
||||
python -m streamlit run streamlit_app.py
|
||||
426
streamlit_app.py
Normal file
426
streamlit_app.py
Normal file
@ -0,0 +1,426 @@
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
import streamlit as st
|
||||
from sklearn.compose import ColumnTransformer
|
||||
from sklearn.metrics import accuracy_score, f1_score
|
||||
from sklearn.model_selection import train_test_split
|
||||
from sklearn.naive_bayes import GaussianNB
|
||||
from sklearn.pipeline import Pipeline
|
||||
from sklearn.preprocessing import OneHotEncoder
|
||||
from sklearn.tree import DecisionTreeClassifier
|
||||
|
||||
|
||||
APP_TITLE = "Klasifikasi Kelayakan Keringanan UKT"
|
||||
DATA_PATH = Path(__file__).with_name("klasifikasi_mhs.csv")
|
||||
HISTORY_PATH = Path(__file__).with_name("riwayat_prediksi_ukt.csv")
|
||||
|
||||
TARGET = "Kelayakan Keringanan UKT"
|
||||
FEATURES = [
|
||||
"Tempat Tinggal",
|
||||
"Pekerjaan Orang Tua",
|
||||
"Penghasilan Orang Tua",
|
||||
"Jumlah Tanggungan Orang Tua",
|
||||
"Kendaraan",
|
||||
]
|
||||
|
||||
LABEL_TEXT = {
|
||||
0: "Layak menerima keringanan UKT",
|
||||
1: "Tidak layak menerima keringanan UKT",
|
||||
}
|
||||
|
||||
TEMPAT_TINGGAL_OPTIONS = {
|
||||
"Rumah sendiri": 0,
|
||||
"Bukan rumah sendiri": 1,
|
||||
}
|
||||
|
||||
MODEL_OPTIONS = {
|
||||
"Decision Tree": "decision_tree",
|
||||
"Naive Bayes": "naive_bayes",
|
||||
}
|
||||
|
||||
|
||||
st.set_page_config(
|
||||
page_title=APP_TITLE,
|
||||
layout="wide",
|
||||
)
|
||||
|
||||
|
||||
def normalize_target(series: pd.Series) -> pd.Series:
|
||||
return (
|
||||
series.astype(str)
|
||||
.str.strip()
|
||||
.replace({"1 1": "1"})
|
||||
.astype(int)
|
||||
)
|
||||
|
||||
|
||||
def load_data() -> pd.DataFrame:
|
||||
if not DATA_PATH.exists():
|
||||
st.error(f"File data tidak ditemukan: {DATA_PATH}")
|
||||
st.stop()
|
||||
|
||||
data = pd.read_csv(DATA_PATH)
|
||||
data[TARGET] = normalize_target(data[TARGET])
|
||||
return data
|
||||
|
||||
|
||||
def save_training_row(row: dict) -> None:
|
||||
data = load_data()
|
||||
updated = pd.concat([data, pd.DataFrame([row])], ignore_index=True)
|
||||
updated.to_csv(DATA_PATH, index=False)
|
||||
|
||||
|
||||
def save_prediction_history(row: dict) -> None:
|
||||
row["Waktu Prediksi"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
history = pd.DataFrame([row])
|
||||
if HISTORY_PATH.exists():
|
||||
old_history = pd.read_csv(HISTORY_PATH)
|
||||
history = pd.concat([old_history, history], ignore_index=True)
|
||||
history.to_csv(HISTORY_PATH, index=False)
|
||||
|
||||
|
||||
def make_one_hot_encoder() -> OneHotEncoder:
|
||||
try:
|
||||
return OneHotEncoder(handle_unknown="ignore", sparse_output=False)
|
||||
except TypeError:
|
||||
return OneHotEncoder(handle_unknown="ignore", sparse=False)
|
||||
|
||||
|
||||
def make_preprocessor() -> ColumnTransformer:
|
||||
return ColumnTransformer(
|
||||
transformers=[
|
||||
("pekerjaan", make_one_hot_encoder(), ["Pekerjaan Orang Tua"]),
|
||||
(
|
||||
"angka",
|
||||
"passthrough",
|
||||
[
|
||||
"Tempat Tinggal",
|
||||
"Penghasilan Orang Tua",
|
||||
"Jumlah Tanggungan Orang Tua",
|
||||
"Kendaraan",
|
||||
],
|
||||
),
|
||||
],
|
||||
remainder="drop",
|
||||
)
|
||||
|
||||
|
||||
def build_models() -> dict:
|
||||
return {
|
||||
"decision_tree": Pipeline(
|
||||
steps=[
|
||||
("preprocess", make_preprocessor()),
|
||||
(
|
||||
"model",
|
||||
DecisionTreeClassifier(
|
||||
max_depth=4,
|
||||
random_state=42,
|
||||
class_weight="balanced",
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
"naive_bayes": Pipeline(
|
||||
steps=[
|
||||
("preprocess", make_preprocessor()),
|
||||
("model", GaussianNB()),
|
||||
]
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def train_models(data: pd.DataFrame) -> tuple[dict, pd.DataFrame]:
|
||||
x = data[FEATURES]
|
||||
y = data[TARGET]
|
||||
|
||||
models = build_models()
|
||||
metrics = []
|
||||
|
||||
can_split = y.nunique() == 2 and y.value_counts().min() >= 2
|
||||
if can_split:
|
||||
x_train, x_test, y_train, y_test = train_test_split(
|
||||
x,
|
||||
y,
|
||||
test_size=0.2,
|
||||
random_state=42,
|
||||
stratify=y,
|
||||
)
|
||||
else:
|
||||
x_train, x_test, y_train, y_test = x, x, y, y
|
||||
|
||||
for display_name, model_key in MODEL_OPTIONS.items():
|
||||
model = models[model_key]
|
||||
model.fit(x_train, y_train)
|
||||
prediction = model.predict(x_test)
|
||||
metrics.append(
|
||||
{
|
||||
"Model": display_name,
|
||||
"Accuracy": accuracy_score(y_test, prediction),
|
||||
"F1-Score": f1_score(y_test, prediction, average="weighted", zero_division=0),
|
||||
}
|
||||
)
|
||||
|
||||
return models, pd.DataFrame(metrics).round(4)
|
||||
|
||||
|
||||
def make_input_dataframe(
|
||||
tempat_tinggal: int,
|
||||
pekerjaan: str,
|
||||
penghasilan: int,
|
||||
tanggungan: int,
|
||||
kendaraan: int,
|
||||
) -> pd.DataFrame:
|
||||
return pd.DataFrame(
|
||||
[
|
||||
{
|
||||
"Tempat Tinggal": tempat_tinggal,
|
||||
"Pekerjaan Orang Tua": pekerjaan,
|
||||
"Penghasilan Orang Tua": penghasilan,
|
||||
"Jumlah Tanggungan Orang Tua": tanggungan,
|
||||
"Kendaraan": kendaraan,
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def predict_with_models(models: dict, input_df: pd.DataFrame) -> pd.DataFrame:
|
||||
results = []
|
||||
for display_name, model_key in MODEL_OPTIONS.items():
|
||||
model = models[model_key]
|
||||
prediction = int(model.predict(input_df)[0])
|
||||
|
||||
probability_text = "-"
|
||||
if hasattr(model, "predict_proba"):
|
||||
probabilities = model.predict_proba(input_df)[0]
|
||||
classes = list(model.classes_)
|
||||
probability = probabilities[classes.index(prediction)]
|
||||
probability_text = f"{probability:.2%}"
|
||||
|
||||
results.append(
|
||||
{
|
||||
"Model": display_name,
|
||||
"Prediksi": LABEL_TEXT[prediction],
|
||||
"Kode Prediksi": prediction,
|
||||
"Keyakinan Model": probability_text,
|
||||
}
|
||||
)
|
||||
return pd.DataFrame(results)
|
||||
|
||||
|
||||
def render_prediction_result(results: pd.DataFrame, selected_model_name: str) -> int:
|
||||
selected = results[results["Model"] == selected_model_name].iloc[0]
|
||||
prediction_code = int(selected["Kode Prediksi"])
|
||||
prediction_text = selected["Prediksi"]
|
||||
|
||||
if prediction_code == 0:
|
||||
st.success(f"Hasil prediksi {selected_model_name}: {prediction_text}")
|
||||
else:
|
||||
st.warning(f"Hasil prediksi {selected_model_name}: {prediction_text}")
|
||||
|
||||
st.dataframe(
|
||||
results[["Model", "Prediksi", "Keyakinan Model"]],
|
||||
use_container_width=True,
|
||||
hide_index=True,
|
||||
)
|
||||
return prediction_code
|
||||
|
||||
|
||||
def render_form(prefix: str, data: pd.DataFrame) -> pd.DataFrame:
|
||||
pekerjaan_list = sorted(data["Pekerjaan Orang Tua"].dropna().astype(str).unique())
|
||||
pekerjaan_options = pekerjaan_list + ["Lainnya"]
|
||||
|
||||
col_left, col_right = st.columns(2)
|
||||
with col_left:
|
||||
tempat_label = st.selectbox(
|
||||
"Tempat tinggal",
|
||||
options=list(TEMPAT_TINGGAL_OPTIONS.keys()),
|
||||
key=f"{prefix}_tempat",
|
||||
)
|
||||
pekerjaan_choice = st.selectbox(
|
||||
"Pekerjaan orang tua",
|
||||
options=pekerjaan_options,
|
||||
key=f"{prefix}_pekerjaan_choice",
|
||||
)
|
||||
if pekerjaan_choice == "Lainnya":
|
||||
pekerjaan = st.text_input(
|
||||
"Tulis pekerjaan orang tua",
|
||||
value="",
|
||||
key=f"{prefix}_pekerjaan_baru",
|
||||
).strip()
|
||||
else:
|
||||
pekerjaan = pekerjaan_choice
|
||||
|
||||
with col_right:
|
||||
penghasilan = st.number_input(
|
||||
"Penghasilan orang tua per bulan",
|
||||
min_value=0,
|
||||
value=3_000_000,
|
||||
step=100_000,
|
||||
key=f"{prefix}_penghasilan",
|
||||
)
|
||||
tanggungan = st.number_input(
|
||||
"Jumlah tanggungan orang tua",
|
||||
min_value=0,
|
||||
value=3,
|
||||
step=1,
|
||||
key=f"{prefix}_tanggungan",
|
||||
)
|
||||
kendaraan = st.number_input(
|
||||
"Jumlah kendaraan",
|
||||
min_value=0,
|
||||
value=1,
|
||||
step=1,
|
||||
key=f"{prefix}_kendaraan",
|
||||
)
|
||||
|
||||
if not pekerjaan:
|
||||
st.warning("Isi pekerjaan orang tua terlebih dahulu.")
|
||||
return pd.DataFrame()
|
||||
|
||||
return make_input_dataframe(
|
||||
tempat_tinggal=TEMPAT_TINGGAL_OPTIONS[tempat_label],
|
||||
pekerjaan=pekerjaan,
|
||||
penghasilan=int(penghasilan),
|
||||
tanggungan=int(tanggungan),
|
||||
kendaraan=int(kendaraan),
|
||||
)
|
||||
|
||||
|
||||
data = load_data()
|
||||
models, metrics_df = train_models(data)
|
||||
|
||||
st.title(APP_TITLE)
|
||||
st.caption("Deployment interaktif menggunakan Decision Tree dan Naive Bayes")
|
||||
|
||||
model_name = st.sidebar.radio("Model utama", list(MODEL_OPTIONS.keys()))
|
||||
st.sidebar.divider()
|
||||
st.sidebar.write("Arti label")
|
||||
st.sidebar.write("0 = Layak")
|
||||
st.sidebar.write("1 = Tidak Layak")
|
||||
|
||||
tab_ringkasan, tab_pilih, tab_input, tab_data = st.tabs(
|
||||
[
|
||||
"Ringkasan",
|
||||
"Pilih Data Lama",
|
||||
"Input Data Baru",
|
||||
"Data & Riwayat",
|
||||
]
|
||||
)
|
||||
|
||||
with tab_ringkasan:
|
||||
total_data = len(data)
|
||||
layak_count = int((data[TARGET] == 0).sum())
|
||||
tidak_layak_count = int((data[TARGET] == 1).sum())
|
||||
|
||||
col_a, col_b, col_c = st.columns(3)
|
||||
col_a.metric("Total data latih", total_data)
|
||||
col_b.metric("Layak", layak_count)
|
||||
col_c.metric("Tidak layak", tidak_layak_count)
|
||||
|
||||
st.subheader("Performa model")
|
||||
st.dataframe(metrics_df, use_container_width=True, hide_index=True)
|
||||
|
||||
st.subheader("Distribusi label")
|
||||
label_counts = (
|
||||
data[TARGET]
|
||||
.map(LABEL_TEXT)
|
||||
.value_counts()
|
||||
.rename_axis("Label")
|
||||
.reset_index(name="Jumlah")
|
||||
)
|
||||
st.bar_chart(label_counts, x="Label", y="Jumlah")
|
||||
|
||||
with tab_pilih:
|
||||
st.subheader("Cek prediksi dari data yang sudah ada")
|
||||
|
||||
pilihan = [
|
||||
(
|
||||
idx,
|
||||
f"Data {idx + 1} | {row['Pekerjaan Orang Tua']} | "
|
||||
f"Rp{int(row['Penghasilan Orang Tua']):,} | "
|
||||
f"Tanggungan {int(row['Jumlah Tanggungan Orang Tua'])}",
|
||||
)
|
||||
for idx, row in data.iterrows()
|
||||
]
|
||||
selected_index = st.selectbox(
|
||||
"Pilih data mahasiswa",
|
||||
options=[idx for idx, _ in pilihan],
|
||||
format_func=lambda value: dict(pilihan)[value],
|
||||
)
|
||||
|
||||
selected_row = data.loc[[selected_index], FEATURES]
|
||||
st.dataframe(selected_row, use_container_width=True, hide_index=True)
|
||||
|
||||
actual_label = int(data.loc[selected_index, TARGET])
|
||||
st.info(f"Label asli pada dataset: {LABEL_TEXT[actual_label]}")
|
||||
|
||||
selected_results = predict_with_models(models, selected_row)
|
||||
render_prediction_result(selected_results, model_name)
|
||||
|
||||
with tab_input:
|
||||
st.subheader("Prediksi data mahasiswa baru")
|
||||
input_df = render_form("predict", data)
|
||||
|
||||
if not input_df.empty:
|
||||
st.dataframe(input_df, use_container_width=True, hide_index=True)
|
||||
input_results = predict_with_models(models, input_df)
|
||||
predicted_label = render_prediction_result(input_results, model_name)
|
||||
|
||||
with st.form("save_prediction_form"):
|
||||
st.write("Simpan data ini")
|
||||
save_mode = st.radio(
|
||||
"Jenis penyimpanan",
|
||||
[
|
||||
"Simpan sebagai riwayat prediksi saja",
|
||||
"Tambahkan ke dataset latih dengan label aktual",
|
||||
],
|
||||
)
|
||||
|
||||
actual_label_text = st.selectbox(
|
||||
"Label aktual",
|
||||
["Layak menerima keringanan UKT", "Tidak layak menerima keringanan UKT"],
|
||||
disabled=save_mode == "Simpan sebagai riwayat prediksi saja",
|
||||
)
|
||||
submitted = st.form_submit_button("Simpan")
|
||||
|
||||
if submitted:
|
||||
row = input_df.iloc[0].to_dict()
|
||||
row["Prediksi Model Utama"] = predicted_label
|
||||
row["Prediksi Teks"] = LABEL_TEXT[predicted_label]
|
||||
|
||||
if save_mode == "Tambahkan ke dataset latih dengan label aktual":
|
||||
actual_label = 0 if actual_label_text.startswith("Layak") else 1
|
||||
training_row = input_df.iloc[0].to_dict()
|
||||
training_row[TARGET] = actual_label
|
||||
save_training_row(training_row)
|
||||
st.success("Data baru sudah ditambahkan ke dataset latih. Aplikasi akan memuat ulang model.")
|
||||
st.rerun()
|
||||
else:
|
||||
save_prediction_history(row)
|
||||
st.success("Riwayat prediksi sudah disimpan.")
|
||||
|
||||
with tab_data:
|
||||
st.subheader("Dataset saat ini")
|
||||
st.dataframe(data, use_container_width=True, hide_index=True)
|
||||
|
||||
st.download_button(
|
||||
"Download dataset CSV",
|
||||
data=data.to_csv(index=False).encode("utf-8"),
|
||||
file_name="klasifikasi_mhs_terbaru.csv",
|
||||
mime="text/csv",
|
||||
)
|
||||
|
||||
st.subheader("Riwayat prediksi")
|
||||
if HISTORY_PATH.exists():
|
||||
history_df = pd.read_csv(HISTORY_PATH)
|
||||
st.dataframe(history_df, use_container_width=True, hide_index=True)
|
||||
st.download_button(
|
||||
"Download riwayat prediksi",
|
||||
data=history_df.to_csv(index=False).encode("utf-8"),
|
||||
file_name="riwayat_prediksi_ukt.csv",
|
||||
mime="text/csv",
|
||||
)
|
||||
else:
|
||||
st.info("Belum ada riwayat prediksi yang disimpan.")
|
||||
Loading…
x
Reference in New Issue
Block a user