Files
2025-09-28 00:19:29 +03:00

980 lines
41 KiB
Python

"""
Student Success Prediction Dashboard
A beautiful Streamlit app for predicting student academic outcomes using the trained Random Forest model.
"""
import streamlit as st
import pandas as pd
import numpy as np
import pickle
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import time
# Configure Streamlit page
st.set_page_config(
page_title="Student Success Predictor",
page_icon="📊",
layout="wide",
initial_sidebar_state="expanded"
)
# Rose Pine Dark Theme CSS
st.markdown("""
<style>
/* Import Google Fonts */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
/* Rose Pine Color Palette */
:root {
--rp-base: #191724;
--rp-surface: #1f1d2e;
--rp-overlay: #26233a;
--rp-muted: #6e6a86;
--rp-subtle: #908caa;
--rp-text: #e0def4;
--rp-love: #eb6f92;
--rp-gold: #f6c177;
--rp-rose: #ebbcba;
--rp-pine: #31748f;
--rp-foam: #9ccfd8;
--rp-iris: #c4a7e7;
}
/* Global Styles */
.stApp {
font-family: 'Inter', sans-serif;
background-color: var(--rp-base);
color: var(--rp-text);
}
/* Override Streamlit's default colors */
.main .block-container {
background-color: var(--rp-base);
color: var(--rp-text);
}
/* Sidebar styling */
.css-1d391kg {
background-color: var(--rp-surface);
}
.css-1lcbmhc {
background-color: var(--rp-surface);
}
/* Header Styles */
.main-header {
font-size: 3.5rem;
background: linear-gradient(135deg, var(--rp-iris), var(--rp-foam));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-align: center;
margin-bottom: 1rem;
font-weight: 700;
letter-spacing: -1px;
}
.subtitle {
text-align: center;
font-size: 1.2rem;
color: var(--rp-subtle);
margin-bottom: 3rem;
font-weight: 400;
}
.sub-header {
font-size: 1.4rem;
color: var(--rp-text);
margin-bottom: 1.5rem;
font-weight: 600;
border-bottom: 2px solid var(--rp-overlay);
padding-bottom: 0.5rem;
}
/* Prediction Result Boxes */
.prediction-box {
padding: 2.5rem 2rem;
border-radius: 16px;
text-align: center;
font-size: 1.8rem;
font-weight: 700;
margin: 2rem 0;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
transform: translateY(0);
transition: transform 0.3s ease, box-shadow 0.3s ease;
border: 2px solid;
}
.prediction-box:hover {
transform: translateY(-5px);
box-shadow: 0 20px 40px rgba(0,0,0,0.4);
}
.graduate-box {
background: linear-gradient(135deg, var(--rp-foam), var(--rp-pine));
color: var(--rp-base);
border-color: var(--rp-foam);
}
.dropout-box {
background: linear-gradient(135deg, var(--rp-love), #c74665);
color: var(--rp-text);
border-color: var(--rp-love);
}
.enrolled-box {
background: linear-gradient(135deg, var(--rp-gold), #e6a85c);
color: var(--rp-base);
border-color: var(--rp-gold);
}
/* Metric Cards */
.metric-card {
background: linear-gradient(135deg, var(--rp-surface), var(--rp-overlay));
padding: 1.5rem;
border-radius: 12px;
border-left: 4px solid var(--rp-iris);
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
transition: transform 0.2s ease;
}
.metric-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0,0,0,0.3);
}
.metric-card h4 {
color: var(--rp-subtle);
font-weight: 600;
margin-bottom: 0.5rem;
}
.metric-card h2 {
color: var(--rp-iris);
font-weight: 700;
}
/* Sidebar Styling */
.sidebar-section {
background: var(--rp-overlay);
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
border-left: 3px solid var(--rp-iris);
}
/* Info boxes */
.info-box {
background: linear-gradient(135deg, var(--rp-overlay), var(--rp-surface));
padding: 1.5rem;
border-radius: 12px;
border-left: 4px solid var(--rp-foam);
margin: 1rem 0;
color: var(--rp-text);
}
.warning-box {
background: linear-gradient(135deg, var(--rp-overlay), var(--rp-surface));
padding: 1.5rem;
border-radius: 12px;
border-left: 4px solid var(--rp-gold);
margin: 1rem 0;
color: var(--rp-text);
}
.success-box {
background: linear-gradient(135deg, var(--rp-overlay), var(--rp-surface));
padding: 1.5rem;
border-radius: 12px;
border-left: 4px solid var(--rp-pine);
margin: 1rem 0;
color: var(--rp-text);
}
/* Button Styling */
.stButton > button {
background: linear-gradient(135deg, var(--rp-iris), var(--rp-love));
color: var(--rp-text);
border: none;
border-radius: 25px;
font-weight: 600;
font-size: 1.1rem;
padding: 0.75rem 2rem;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.stButton > button:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(196, 167, 231, 0.4);
}
/* Streamlit widget overrides */
.stSelectbox > div > div {
background-color: var(--rp-surface);
color: var(--rp-text);
border-color: var(--rp-overlay);
}
.stNumberInput > div > div > input {
background-color: var(--rp-surface);
color: var(--rp-text);
border-color: var(--rp-overlay);
}
.stRadio > div {
background-color: var(--rp-surface);
border-radius: 8px;
padding: 0.5rem;
}
/* Progress indicator */
.progress-container {
background: var(--rp-overlay);
border-radius: 10px;
height: 8px;
margin: 1rem 0;
}
.progress-bar {
background: linear-gradient(90deg, var(--rp-iris), var(--rp-foam));
height: 100%;
border-radius: 10px;
transition: width 0.3s ease;
}
/* Section dividers */
.section-divider {
height: 2px;
background: linear-gradient(90deg, transparent, var(--rp-iris), transparent);
margin: 2rem 0;
}
/* Text color overrides */
.markdown-text-container {
color: var(--rp-text);
}
h1, h2, h3, h4, h5, h6 {
color: var(--rp-text);
}
.stMarkdown {
color: var(--rp-text);
}
/* Animations */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeInUp 0.6s ease forwards;
}
/* Mobile responsiveness */
@media (max-width: 768px) {
.main-header {
font-size: 2.5rem !important;
}
.prediction-box {
font-size: 1.4rem !important;
padding: 1.5rem 1rem !important;
}
.metric-card {
padding: 1rem !important;
}
}
</style>
""", unsafe_allow_html=True)
@st.cache_data
def load_model_and_info():
"""Load the trained model and preprocessing information"""
try:
with open('random_forest_model.pkl', 'rb') as f:
model = pickle.load(f)
with open('preprocessing_info.pkl', 'rb') as f:
preprocessing_info = pickle.load(f)
with open('feature_names.pkl', 'rb') as f:
feature_names = pickle.load(f)
return model, preprocessing_info, feature_names
except FileNotFoundError:
st.error("❌ Model files not found! Please run 'train_and_save_model.py' first.")
st.stop()
def create_feature_mappings():
"""Create human-readable mappings for categorical features"""
# Marital status mapping
marital_status_map = {
1: "Single",
2: "Married",
3: "Widowed",
4: "Divorced",
5: "Civil Union",
6: "Legally Separated"
}
# Application mode mapping (simplified for demo)
application_mode_map = {
1: "1st Phase - General Contingent",
2: "Ordinance No. 612/93",
5: "1st Phase - Special Contingent (Azores)",
7: "Holders of Other Higher Courses",
10: "Ordinance No. 854-B/99",
15: "International Student (Bachelor)",
16: "1st Phase - Special Contingent (Madeira)",
17: "2nd Phase - General Contingent",
18: "3rd Phase - General Contingent",
26: "Ordinance No. 533-A/99, item b2",
27: "Ordinance No. 533-A/99, item b3",
39: "Over 23 years old",
42: "Transfer",
43: "Change of course",
44: "Technological specialization diploma holders",
51: "Change of institution/course",
53: "Short cycle diploma holders",
57: "Change of institution/course (International)"
}
# Course mapping (simplified - showing most common ones)
course_map = {
33: "Biofuel Production Technologies",
171: "Animation and Multimedia Design",
8014: "Social Service (evening attendance)",
9003: "Agronomy",
9070: "Communication Design",
9085: "Veterinary Nursing",
9119: "Informatics Engineering",
9130: "Equinculture",
9147: "Management",
9238: "Social Service",
9254: "Tourism",
9500: "Nursing",
9556: "Oral Hygiene",
9670: "Advertising and Marketing Management",
9773: "Journalism and Communication",
9853: "Basic Education",
9991: "Management (evening attendance)"
}
# Previous qualification mapping
qualification_map = {
1: "Secondary education",
2: "Higher education - bachelor's degree",
3: "Higher education - degree",
4: "Higher education - master's",
5: "Higher education - doctorate",
6: "Frequency of higher education",
9: "12th year of schooling - not completed",
10: "11th year of schooling - not completed",
12: "Other - 11th year of schooling",
14: "10th year of schooling",
15: "10th year of schooling - not completed",
19: "Basic education 3rd cycle (9th/10th/11th year) or equiv.",
38: "Basic education 2nd cycle (6th/7th/8th year) or equiv.",
39: "Technological specialization course",
40: "Higher education - degree (1st cycle)",
42: "Professional course",
43: "Higher education - master (2nd cycle)"
}
# Occupation mapping (simplified)
occupation_map = {
0: "Student",
1: "Representatives of the Legislative Power and Executive Bodies",
2: "Specialists in Intellectual and Scientific Activities",
3: "Intermediate Level Technicians and Professions",
4: "Administrative staff",
5: "Personal Services, Security and Safety Workers",
6: "Farmers and Skilled Workers in Agriculture",
7: "Skilled Workers in Industry, Construction and Craftsmen",
8: "Installation and Machine Operators and Assembly Workers",
9: "Unskilled Workers",
10: "Armed Forces Professions",
90: "Other Situation",
99: "Unspecified",
122: "Health professionals",
123: "Teachers",
125: "Specialists in information and communication technologies",
131: "Intermediate level science and engineering technicians",
132: "Technicians and associate professionals, health",
134: "Intermediate level technicians from legal, social, sports",
141: "Office workers, secretaries in general and data processing",
143: "Data, accounting, statistical, financial services",
144: "Other administrative support staff",
151: "Personal service workers",
152: "Sellers",
153: "Personal care workers and the like",
171: "Skilled construction workers and the like, except electricians",
173: "Skilled workers in printing, precision instrument manufacturing",
175: "Workers in food processing, woodworking, clothing",
191: "Cleaning workers",
192: "Unskilled workers in agriculture, animal production",
193: "Unskilled workers in extractive industry, construction",
194: "Meal preparation assistants"
}
return {
'marital_status': marital_status_map,
'application_mode': application_mode_map,
'course': course_map,
'qualification': qualification_map,
'occupation': occupation_map
}
def preprocess_input(user_input, preprocessing_info):
"""Preprocess user input to match the training data format"""
# Create a DataFrame with the user input
input_df = pd.DataFrame([user_input])
# Get categorical columns that need one-hot encoding
categorical_columns = [
'Marital status', 'Application mode', 'Course', 'Daytime/evening attendance',
'Previous qualification', 'Nationality', 'Mothers qualification',
'Fathers qualification', 'Mothers occupation', 'Fathers occupation',
'Gender', 'Displaced', 'Educational special needs', 'Debtor',
'Tuition fees up to date', 'Scholarship holder', 'International',
'Application order'
]
# Perform one-hot encoding for categorical columns
for column in categorical_columns:
if column in input_df.columns:
# Get dummies with prefix
dummies = pd.get_dummies(input_df[column], prefix=column)
# Concatenate with original data
input_df = pd.concat([input_df, dummies], axis=1)
# Drop original column
input_df.drop(columns=[column], inplace=True)
# Ensure all training features are present
feature_names = preprocessing_info['feature_names']
# Add missing columns with 0 values
for feature in feature_names:
if feature not in input_df.columns:
input_df[feature] = 0
# Select only the features used in training and in the same order
input_df = input_df[feature_names]
return input_df
def main():
"""Main Streamlit application"""
# Load model and preprocessing info
model, preprocessing_info, feature_names = load_model_and_info()
# Create feature mappings for user-friendly display
feature_maps = create_feature_mappings()
# Header
st.markdown('<h1 class="main-header">Student Success Predictor</h1>', unsafe_allow_html=True)
st.markdown('<p class="subtitle">Predict whether a student will Graduate, Dropout, or remain Enrolled based on academic and demographic factors using advanced machine learning.</p>', unsafe_allow_html=True)
# Progress indicator (shows completion percentage)
progress_placeholder = st.empty()
# Sidebar for input
st.sidebar.markdown('<h2 class="sub-header">Student Information</h2>', unsafe_allow_html=True)
# Create input form
with st.sidebar:
# Quick presets
st.markdown('<div class="sidebar-section">', unsafe_allow_html=True)
st.markdown("### 🚀 Quick Start")
preset = st.selectbox("Load Sample Data",
["Custom Entry", "Typical Graduate Student", "At-Risk Student", "International Student"],
help="Choose a preset to quickly fill common scenarios")
st.markdown('</div>', unsafe_allow_html=True)
# Set default values based on preset
defaults = {
"Custom Entry": {"age": 20, "gender": 0, "marital": 1, "app_mode": 1, "course": 9254, "prev_grade": 120, "adm_grade": 120},
"Typical Graduate Student": {"age": 19, "gender": 1, "marital": 1, "app_mode": 15, "course": 9254, "prev_grade": 160, "adm_grade": 142},
"At-Risk Student": {"age": 22, "gender": 1, "marital": 1, "app_mode": 17, "course": 171, "prev_grade": 100, "adm_grade": 110},
"International Student": {"age": 21, "gender": 0, "marital": 1, "app_mode": 1, "course": 9238, "prev_grade": 135, "adm_grade": 130}
}
if preset != "Custom Entry":
st.markdown(f'<div class="success-box">✅ Loading <strong>{preset}</strong> preset values. You can modify any field below.</div>', unsafe_allow_html=True)
# Form progress tracking
form_sections = ["Personal Information", "Academic Background", "Family Background", "Financial & Social", "1st Semester Performance", "2nd Semester Performance", "Economic Indicators"]
current_section = 0
# Personal Information
st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
st.markdown("### 👤 Personal Information")
st.markdown('<div class="info-box">Basic demographic information about the student</div>', unsafe_allow_html=True)
marital_status = st.selectbox(
"Marital Status",
options=list(feature_maps['marital_status'].keys()),
format_func=lambda x: feature_maps['marital_status'][x],
index=list(feature_maps['marital_status'].keys()).index(defaults[preset]["marital"])
)
gender = st.selectbox("Gender", [0, 1], format_func=lambda x: "Female" if x == 0 else "Male", index=defaults[preset]["gender"])
age_at_enrollment = st.number_input("Age at Enrollment", min_value=17, max_value=70, value=defaults[preset]["age"], step=1)
nationality = st.selectbox("Nationality", [1, 2, 6, 11, 13, 14, 17, 21, 22, 24, 25, 26, 32, 41, 62, 100, 101, 103, 105, 108, 109], index=0)
# Academic Background
st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
st.markdown("### 🎓 Academic Background")
st.markdown('<div class="info-box">Information about the student\'s application and academic history</div>', unsafe_allow_html=True)
application_mode = st.selectbox(
"Application Mode",
options=list(feature_maps['application_mode'].keys()),
format_func=lambda x: feature_maps['application_mode'].get(x, f"Mode {x}"),
index=list(feature_maps['application_mode'].keys()).index(defaults[preset]["app_mode"]) if defaults[preset]["app_mode"] in feature_maps['application_mode'] else 0
)
application_order = st.selectbox("Application Order (1st choice, 2nd choice, etc.)", [1, 2, 3, 4, 5, 6, 7, 8, 9], index=0)
course = st.selectbox(
"Course",
options=list(feature_maps['course'].keys()),
format_func=lambda x: feature_maps['course'].get(x, f"Course {x}"),
index=list(feature_maps['course'].keys()).index(defaults[preset]["course"]) if defaults[preset]["course"] in feature_maps['course'] else 0
)
daytime_evening = st.selectbox("Attendance", [0, 1], format_func=lambda x: "Evening" if x == 0 else "Daytime")
previous_qualification = st.selectbox(
"Previous Qualification",
options=list(feature_maps['qualification'].keys()),
format_func=lambda x: feature_maps['qualification'].get(x, f"Qualification {x}")
)
col1, col2 = st.columns(2)
with col1:
previous_qualification_grade = st.number_input("Previous Qualification Grade", min_value=95, max_value=190, value=defaults[preset]["prev_grade"], step=1)
with col2:
admission_grade = st.number_input("Admission Grade", min_value=95, max_value=190, value=defaults[preset]["adm_grade"], step=1)
# Family Background
st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
st.markdown("### 👨‍👩‍👧‍👦 Family Background")
st.markdown('<div class="info-box">Parent education and occupation information</div>', unsafe_allow_html=True)
mothers_qualification = st.selectbox(
"Mother's Qualification",
options=list(feature_maps['qualification'].keys()),
format_func=lambda x: feature_maps['qualification'].get(x, f"Qualification {x}")
)
fathers_qualification = st.selectbox(
"Father's Qualification",
options=list(feature_maps['qualification'].keys()),
format_func=lambda x: feature_maps['qualification'].get(x, f"Qualification {x}")
)
mothers_occupation = st.selectbox(
"Mother's Occupation",
options=list(feature_maps['occupation'].keys()),
format_func=lambda x: feature_maps['occupation'].get(x, f"Occupation {x}")
)
fathers_occupation = st.selectbox(
"Father's Occupation",
options=list(feature_maps['occupation'].keys()),
format_func=lambda x: feature_maps['occupation'].get(x, f"Occupation {x}")
)
# Financial and Social Factors
st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
st.markdown("### 💰 Financial & Social")
st.markdown('<div class="info-box">Student financial status and special circumstances</div>', unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
displaced = st.radio("Displaced Student", [0, 1], format_func=lambda x: "No" if x == 0 else "Yes", horizontal=True)
educational_special_needs = st.radio("Educational Special Needs", [0, 1], format_func=lambda x: "No" if x == 0 else "Yes", horizontal=True)
debtor = st.radio("Has Outstanding Debts", [0, 1], format_func=lambda x: "No" if x == 0 else "Yes", horizontal=True)
with col2:
tuition_fees_up_to_date = st.radio("Tuition Fees Up to Date", [0, 1], format_func=lambda x: "No" if x == 0 else "Yes", horizontal=True)
scholarship_holder = st.radio("Scholarship Holder", [0, 1], format_func=lambda x: "No" if x == 0 else "Yes", horizontal=True)
international = st.radio("International Student", [0, 1], format_func=lambda x: "No" if x == 0 else "Yes", horizontal=True)
# Academic Performance - 1st Semester
st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
st.markdown("### 📚 1st Semester Performance")
st.markdown('<div class="warning-box">⚠️ <strong>Important:</strong> Academic performance is a key predictor of student success</div>', unsafe_allow_html=True)
# Set default academic values based on preset
first_sem_defaults = {
"Custom Entry": {"enrolled": 6, "approved": 6, "grade": 13, "credited": 0, "evaluations": 6, "without": 0},
"Typical Graduate Student": {"enrolled": 6, "approved": 6, "grade": 14, "credited": 0, "evaluations": 6, "without": 0},
"At-Risk Student": {"enrolled": 6, "approved": 0, "grade": 0, "credited": 0, "evaluations": 0, "without": 0},
"International Student": {"enrolled": 6, "approved": 6, "grade": 13, "credited": 0, "evaluations": 6, "without": 0}
}
col1, col2 = st.columns(2)
with col1:
curricular_units_1st_sem_enrolled = st.number_input("Enrolled Units", min_value=0, max_value=26, value=first_sem_defaults[preset]["enrolled"], step=1, key="1st_enrolled", help="Number of courses registered for")
curricular_units_1st_sem_approved = st.number_input("Approved Units", min_value=0, max_value=26, value=first_sem_defaults[preset]["approved"], step=1, key="1st_approved", help="Number of courses passed")
curricular_units_1st_sem_credited = st.number_input("Credited Units", min_value=0, max_value=26, value=first_sem_defaults[preset]["credited"], step=1, key="1st_credited", help="Credits from previous education")
with col2:
curricular_units_1st_sem_evaluations = st.number_input("Evaluations", min_value=0, max_value=45, value=first_sem_defaults[preset]["evaluations"], step=1, key="1st_evaluations", help="Number of exams taken")
curricular_units_1st_sem_grade = st.number_input("Average Grade (0-20)", min_value=0, max_value=20, value=first_sem_defaults[preset]["grade"], step=1, key="1st_grade", help="Average grade on 0-20 scale")
curricular_units_1st_sem_without_evaluations = st.number_input("Without Evaluations", min_value=0, max_value=14, value=first_sem_defaults[preset]["without"], step=1, key="1st_without", help="Courses registered but not evaluated")
# Academic Performance - 2nd Semester
st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
st.markdown("### 📖 2nd Semester Performance")
st.markdown('<div class="info-box">Second semester data helps improve prediction accuracy</div>', unsafe_allow_html=True)
# Set default academic values for 2nd semester based on preset
second_sem_defaults = {
"Custom Entry": {"enrolled": 6, "approved": 6, "grade": 13, "credited": 0, "evaluations": 6, "without": 0},
"Typical Graduate Student": {"enrolled": 6, "approved": 6, "grade": 14, "credited": 0, "evaluations": 6, "without": 0},
"At-Risk Student": {"enrolled": 6, "approved": 0, "grade": 0, "credited": 0, "evaluations": 0, "without": 0},
"International Student": {"enrolled": 6, "approved": 6, "grade": 14, "credited": 0, "evaluations": 6, "without": 0}
}
col1, col2 = st.columns(2)
with col1:
curricular_units_2nd_sem_enrolled = st.number_input("Enrolled Units", min_value=0, max_value=23, value=second_sem_defaults[preset]["enrolled"], step=1, key="2nd_enrolled", help="Number of courses registered for")
curricular_units_2nd_sem_approved = st.number_input("Approved Units", min_value=0, max_value=20, value=second_sem_defaults[preset]["approved"], step=1, key="2nd_approved", help="Number of courses passed")
curricular_units_2nd_sem_credited = st.number_input("Credited Units", min_value=0, max_value=20, value=second_sem_defaults[preset]["credited"], step=1, key="2nd_credited", help="Credits from previous education")
with col2:
curricular_units_2nd_sem_evaluations = st.number_input("Evaluations", min_value=0, max_value=33, value=second_sem_defaults[preset]["evaluations"], step=1, key="2nd_evaluations", help="Number of exams taken")
curricular_units_2nd_sem_grade = st.number_input("Average Grade (0-20)", min_value=0, max_value=20, value=second_sem_defaults[preset]["grade"], step=1, key="2nd_grade", help="Average grade on 0-20 scale")
curricular_units_2nd_sem_without_evaluations = st.number_input("Without Evaluations", min_value=0, max_value=12, value=second_sem_defaults[preset]["without"], step=1, key="2nd_without", help="Courses registered but not evaluated")
# Economic Indicators
st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
st.markdown("### 📊 Economic Indicators")
st.markdown('<div class="info-box">📈 Economic context data - typically set by institution based on current conditions</div>', unsafe_allow_html=True)
col1, col2, col3 = st.columns(3)
with col1:
unemployment_rate = st.number_input("Unemployment Rate (%)", min_value=7.0, max_value=16.2, value=10.8, step=0.1, format="%.1f")
with col2:
inflation_rate = st.number_input("Inflation Rate (%)", min_value=-0.8, max_value=3.7, value=1.4, step=0.1, format="%.1f")
with col3:
gdp = st.number_input("GDP Growth", min_value=-4.06, max_value=3.51, value=1.74, step=0.1, format="%.2f")
# Create user input dictionary
user_input = {
'Marital status': marital_status,
'Application mode': application_mode,
'Application order': application_order,
'Course': course,
'Daytime/evening attendance': daytime_evening,
'Previous qualification': previous_qualification,
'Previous qualification (grade)': previous_qualification_grade,
'Nationality': nationality,
'Mothers qualification': mothers_qualification,
'Fathers qualification': fathers_qualification,
'Mothers occupation': mothers_occupation,
'Fathers occupation': fathers_occupation,
'Admission grade': admission_grade,
'Displaced': displaced,
'Educational special needs': educational_special_needs,
'Debtor': debtor,
'Tuition fees up to date': tuition_fees_up_to_date,
'Gender': gender,
'Scholarship holder': scholarship_holder,
'Age at enrollment': age_at_enrollment,
'International': international,
'Curricular units 1st sem (credited)': curricular_units_1st_sem_credited,
'Curricular units 1st sem (enrolled)': curricular_units_1st_sem_enrolled,
'Curricular units 1st sem (evaluations)': curricular_units_1st_sem_evaluations,
'Curricular units 1st sem (approved)': curricular_units_1st_sem_approved,
'Curricular units 1st sem (grade)': curricular_units_1st_sem_grade,
'Curricular units 1st sem (without evaluations)': curricular_units_1st_sem_without_evaluations,
'Curricular units 2nd sem (credited)': curricular_units_2nd_sem_credited,
'Curricular units 2nd sem (enrolled)': curricular_units_2nd_sem_enrolled,
'Curricular units 2nd sem (evaluations)': curricular_units_2nd_sem_evaluations,
'Curricular units 2nd sem (approved)': curricular_units_2nd_sem_approved,
'Curricular units 2nd sem (grade)': curricular_units_2nd_sem_grade,
'Curricular units 2nd sem (without evaluations)': curricular_units_2nd_sem_without_evaluations,
'Unemployment rate': unemployment_rate,
'Inflation rate': inflation_rate,
'GDP': gdp
}
# Main content area
col1, col2 = st.columns([2, 1])
with col1:
st.markdown('<h2 class="sub-header">Prediction Results</h2>', unsafe_allow_html=True)
# Prediction button with enhanced styling
predict_button = st.button("🔬 Analyze Student Data", type="primary", use_container_width=True)
if predict_button:
# Enhanced loading animation
progress_bar = st.progress(0)
status_text = st.empty()
# Simulate processing steps
steps = [
("Loading student data...", 20),
("Preprocessing features...", 40),
("Running machine learning model...", 70),
("Calculating prediction confidence...", 90),
("Finalizing results...", 100)
]
for step, progress in steps:
status_text.text(step)
progress_bar.progress(progress)
time.sleep(0.3)
# Clear progress indicators
progress_bar.empty()
status_text.empty()
# Preprocess input
processed_input = preprocess_input(user_input, preprocessing_info)
# Make prediction
prediction = model.predict(processed_input)[0]
prediction_proba = model.predict_proba(processed_input)[0]
# Get prediction label
target_reverse_mapping = preprocessing_info['target_reverse_mapping']
predicted_outcome = target_reverse_mapping[prediction]
# Store in session state for export
st.session_state['last_prediction'] = {
'outcome': predicted_outcome,
'probabilities': prediction_proba,
'timestamp': time.time()
}
# Enhanced prediction display with confidence indicators
confidence_level = max(prediction_proba)
confidence_text = "High" if confidence_level > 0.7 else "Medium" if confidence_level > 0.5 else "Low"
# Display prediction with enhanced styling and confidence
if predicted_outcome == "Graduate":
st.markdown(f'''<div class="prediction-box graduate-box fade-in">
🎓 GRADUATE
<br><small style="font-size: 1rem; opacity: 0.9;">Confidence: {confidence_level:.1%} ({confidence_text})</small>
</div>''', unsafe_allow_html=True)
st.balloons()
elif predicted_outcome == "Dropout":
st.markdown(f'''<div class="prediction-box dropout-box fade-in">
⚠️ AT RISK OF DROPOUT
<br><small style="font-size: 1rem; opacity: 0.9;">Confidence: {confidence_level:.1%} ({confidence_text})</small>
</div>''', unsafe_allow_html=True)
else:
st.markdown(f'''<div class="prediction-box enrolled-box fade-in">
📚 LIKELY TO STAY ENROLLED
<br><small style="font-size: 1rem; opacity: 0.9;">Confidence: {confidence_level:.1%} ({confidence_text})</small>
</div>''', unsafe_allow_html=True)
# Add personalized recommendations based on prediction
st.markdown("### 💡 Personalized Recommendations")
if predicted_outcome == "Graduate":
st.markdown('<div class="success-box">🎯 <strong>Success Indicators:</strong> This student shows strong potential for graduation. Continue supporting their academic journey.</div>', unsafe_allow_html=True)
elif predicted_outcome == "Dropout":
recommendations = []
if user_input['Curricular units 1st sem (approved)'] == 0:
recommendations.append("📚 **Academic Support:** Provide tutoring for struggling courses")
if user_input['Scholarship holder'] == 0:
recommendations.append("💰 **Financial Aid:** Consider scholarship opportunities")
if user_input['Age at enrollment'] > 25:
recommendations.append("🕐 **Flexible Scheduling:** Offer evening or part-time options")
if recommendations:
st.markdown('<div class="warning-box">🎯 <strong>Intervention Strategies:</strong><br>' + '<br>'.join(recommendations) + '</div>', unsafe_allow_html=True)
else:
st.markdown('<div class="info-box">📈 <strong>Monitoring Needed:</strong> This student may benefit from regular check-ins and academic support.</div>', unsafe_allow_html=True)
# Display probabilities
st.markdown("### 📊 Prediction Confidence")
prob_df = pd.DataFrame({
'Outcome': ['Dropout', 'Enrolled', 'Graduate'],
'Probability': prediction_proba
})
# Create probability chart
fig = px.bar(
prob_df,
x='Outcome',
y='Probability',
color='Outcome',
color_discrete_map={
'Graduate': '#28a745',
'Dropout': '#dc3545',
'Enrolled': '#ffc107'
},
title="Prediction Probabilities"
)
fig.update_layout(showlegend=False)
st.plotly_chart(fig, use_container_width=True)
# Display detailed probabilities
col_a, col_b, col_c = st.columns(3)
with col_a:
st.markdown(f"""
<div class="metric-card">
<h4>Graduate</h4>
<h2>{prediction_proba[2]:.1%}</h2>
</div>
""", unsafe_allow_html=True)
with col_b:
st.markdown(f"""
<div class="metric-card">
<h4>Dropout</h4>
<h2>{prediction_proba[0]:.1%}</h2>
</div>
""", unsafe_allow_html=True)
with col_c:
st.markdown(f"""
<div class="metric-card">
<h4>Enrolled</h4>
<h2>{prediction_proba[1]:.1%}</h2>
</div>
""", unsafe_allow_html=True)
with col2:
st.markdown('<h2 class="sub-header">📊 Model Insights</h2>', unsafe_allow_html=True)
# Dynamic model information based on current inputs
st.markdown('<div class="info-box">', unsafe_allow_html=True)
st.markdown("""
**🤖 Model Details:**
- **Algorithm:** Random Forest Classifier
- **Features:** 36 student characteristics
- **Training Accuracy:** 100%
- **Test Accuracy:** 93.84%
- **Classes:** Graduate, Dropout, Enrolled
""")
st.markdown('</div>', unsafe_allow_html=True)
# Add input validation feedback
st.markdown("### 🔍 Input Validation")
validation_container = st.container()
# Basic validation checks
validation_issues = []
# Check for unrealistic combinations
if user_input['Age at enrollment'] < 17:
validation_issues.append("⚠️ Age seems too low for enrollment")
if user_input['Curricular units 1st sem (approved)'] > user_input['Curricular units 1st sem (enrolled)']:
validation_issues.append("⚠️ Approved units exceed enrolled units (1st sem)")
if user_input['Curricular units 2nd sem (approved)'] > user_input['Curricular units 2nd sem (enrolled)']:
validation_issues.append("⚠️ Approved units exceed enrolled units (2nd sem)")
if validation_issues:
for issue in validation_issues:
st.warning(issue)
else:
st.success("✅ All inputs look valid!")
st.markdown("### Key Factors")
st.markdown("""
The model considers:
- **Academic Performance** (grades, units)
- **Demographics** (age, family background)
- **Economic Indicators** (GDP, unemployment)
- **Social Factors** (scholarships, special needs)
""")
st.markdown("### Use Cases")
st.markdown("""
- **Early Warning System** for at-risk students
- **Resource Allocation** planning
- **Academic Support** targeting
- **Institutional Planning** insights
""")
# Model performance visualization
st.markdown("### Model Performance")
# Create a simple performance chart
metrics = ['Accuracy', 'Precision', 'Recall', 'F1-Score']
values = [93.84, 92.96, 94.02, 93.15] # From the notebook results
fig_perf = go.Figure(data=go.Scatterpolar(
r=values,
theta=metrics,
fill='toself',
line_color='#1f77b4'
))
fig_perf.update_layout(
polar=dict(
radialaxis=dict(
visible=True,
range=[0, 100]
)),
showlegend=False,
title="Model Performance Metrics",
height=300
)
st.plotly_chart(fig_perf, use_container_width=True)
# Add export functionality
st.markdown("### 📥 Export Results")
if st.session_state.get('last_prediction'):
export_data = {
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
'prediction': st.session_state['last_prediction']['outcome'],
'confidence': f"{max(st.session_state['last_prediction']['probabilities']):.1%}",
'student_data': user_input
}
# Convert to JSON for download
import json
json_str = json.dumps(export_data, indent=2, ensure_ascii=False)
st.download_button(
label="📁 Download Prediction Report",
data=json_str,
file_name=f"student_prediction_{time.strftime('%Y%m%d_%H%M%S')}.json",
mime="application/json"
)
# Enhanced Footer with additional info
st.markdown('<div class="section-divider"></div>', unsafe_allow_html=True)
col1, col2, col3 = st.columns(3)
with col1:
st.markdown("**📊 Data Source**")
st.caption("UCI ML Repository - Student Academic Success")
with col2:
st.markdown("**🔬 Model Info**")
st.caption("Random Forest with 93.84% accuracy")
with col3:
st.markdown("**🛠️ Built With**")
st.caption("Streamlit • scikit-learn • Plotly")
st.markdown("---")
st.markdown('<p style="text-align: center; color: #6c757d; font-size: 0.9rem;">🎓 Empowering educational institutions with data-driven insights for student success</p>', unsafe_allow_html=True)
if __name__ == "__main__":
main()