mirror of
https://github.com/andwati/predictive_analysis.git
synced 2026-11-04 16:12:15 +00:00
antigravity
This commit is contained in:
@@ -26,7 +26,7 @@ pip install -r requirements.txt
|
|||||||
### 3. Launch the Dashboard
|
### 3. Launch the Dashboard
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
streamlit run streamlit_app.py
|
streamlit run app.py
|
||||||
```
|
```
|
||||||
|
|
||||||
The dashboard will open in your browser at `http://localhost:8501`
|
The dashboard will open in your browser at `http://localhost:8501`
|
||||||
@@ -90,9 +90,23 @@ The dashboard will open in your browser at `http://localhost:8501`
|
|||||||
predictive_analysis/
|
predictive_analysis/
|
||||||
├── data/
|
├── data/
|
||||||
│ └── data.csv # Original dataset (4,424 students)
|
│ └── data.csv # Original dataset (4,424 students)
|
||||||
|
├── src/ # Source code modules
|
||||||
|
│ ├── config/ # Configuration files
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── theme.py # Rose Pine theme and constants
|
||||||
|
│ │ └── mappings.py # Feature mappings and presets
|
||||||
|
│ ├── utils/ # Utility functions
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ └── model_utils.py # Model loading and preprocessing
|
||||||
|
│ ├── components/ # UI components
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── form_components.py # Input form sections
|
||||||
|
│ │ └── prediction_components.py # Results and charts
|
||||||
|
│ └── __init__.py
|
||||||
├── predictive_analysis.ipynb # Original analysis notebook
|
├── predictive_analysis.ipynb # Original analysis notebook
|
||||||
├── train_and_save_model.py # Model training and pickle export
|
├── train_and_save_model.py # Model training and pickle export
|
||||||
├── streamlit_app.py # Main dashboard application
|
├── streamlit_app.py # Legacy monolithic app (deprecated)
|
||||||
|
├── app.py # New modular main application
|
||||||
├── requirements.txt # Python dependencies
|
├── requirements.txt # Python dependencies
|
||||||
├── random_forest_model.pkl # Trained model (generated)
|
├── random_forest_model.pkl # Trained model (generated)
|
||||||
├── preprocessing_info.pkl # Preprocessing pipeline (generated)
|
├── preprocessing_info.pkl # Preprocessing pipeline (generated)
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
"""
|
||||||
|
Main Streamlit application for Student Success Predictor
|
||||||
|
Modular version with separated components
|
||||||
|
"""
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Add src directory to path for imports
|
||||||
|
src_path = Path(__file__).parent
|
||||||
|
sys.path.append(str(src_path))
|
||||||
|
|
||||||
|
from src.config.theme import ROSE_PINE_THEME, PAGE_CONFIG, APP_TITLE, APP_SUBTITLE
|
||||||
|
from src.config.mappings import create_feature_mappings
|
||||||
|
from src.utils.model_utils import load_model_and_info
|
||||||
|
from src.components.form_components import (
|
||||||
|
render_preset_selector,
|
||||||
|
render_personal_info_section,
|
||||||
|
render_academic_background_section,
|
||||||
|
render_family_background_section,
|
||||||
|
render_financial_social_section,
|
||||||
|
render_academic_performance_section,
|
||||||
|
render_economic_indicators_section
|
||||||
|
)
|
||||||
|
from src.components.prediction_components import (
|
||||||
|
render_prediction_section,
|
||||||
|
render_model_info_sidebar,
|
||||||
|
render_export_section,
|
||||||
|
render_footer
|
||||||
|
)
|
||||||
|
|
||||||
|
def apply_theme():
|
||||||
|
"""Apply the Rose Pine dark theme"""
|
||||||
|
st.markdown(ROSE_PINE_THEME, unsafe_allow_html=True)
|
||||||
|
|
||||||
|
def render_header():
|
||||||
|
"""Render the main application header"""
|
||||||
|
st.markdown(f'<h1 class="main-header">{APP_TITLE}</h1>', unsafe_allow_html=True)
|
||||||
|
st.markdown(f'<p class="subtitle">{APP_SUBTITLE}</p>', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
def collect_user_input():
|
||||||
|
"""Collect all user input from the sidebar form"""
|
||||||
|
feature_maps = create_feature_mappings()
|
||||||
|
|
||||||
|
with st.sidebar:
|
||||||
|
# Preset selector
|
||||||
|
preset = render_preset_selector()
|
||||||
|
|
||||||
|
# Personal information
|
||||||
|
personal_info = render_personal_info_section(preset, feature_maps)
|
||||||
|
|
||||||
|
# Academic background
|
||||||
|
academic_bg = render_academic_background_section(preset, feature_maps)
|
||||||
|
|
||||||
|
# Family background
|
||||||
|
family_bg = render_family_background_section(feature_maps)
|
||||||
|
|
||||||
|
# Financial and social factors
|
||||||
|
financial_social = render_financial_social_section()
|
||||||
|
|
||||||
|
# Academic performance
|
||||||
|
academic_performance = render_academic_performance_section(preset)
|
||||||
|
|
||||||
|
# Economic indicators
|
||||||
|
economic_indicators = render_economic_indicators_section()
|
||||||
|
|
||||||
|
# Combine all input data
|
||||||
|
user_input = {
|
||||||
|
**personal_info,
|
||||||
|
**academic_bg,
|
||||||
|
**family_bg,
|
||||||
|
**financial_social,
|
||||||
|
**academic_performance,
|
||||||
|
**economic_indicators
|
||||||
|
}
|
||||||
|
|
||||||
|
return user_input
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main Streamlit application"""
|
||||||
|
# Configure page
|
||||||
|
st.set_page_config(**PAGE_CONFIG)
|
||||||
|
|
||||||
|
# Apply theme
|
||||||
|
apply_theme()
|
||||||
|
|
||||||
|
# Load model and preprocessing info
|
||||||
|
model, preprocessing_info, feature_names = load_model_and_info()
|
||||||
|
|
||||||
|
# Render header
|
||||||
|
render_header()
|
||||||
|
|
||||||
|
# Collect user input
|
||||||
|
user_input = collect_user_input()
|
||||||
|
|
||||||
|
# Main content area
|
||||||
|
col1, col2 = st.columns([2, 1])
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.markdown('<h2 class="sub-header">Prediction Results</h2>', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
# Render prediction section
|
||||||
|
render_prediction_section(model, preprocessing_info, user_input)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
# Render model info and validation
|
||||||
|
render_model_info_sidebar(user_input)
|
||||||
|
|
||||||
|
# Render export section
|
||||||
|
render_export_section(user_input)
|
||||||
|
|
||||||
|
# Render footer
|
||||||
|
render_footer()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Source package for Student Success Predictor
|
||||||
|
"""
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
UI Components
|
||||||
|
"""
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
"""
|
||||||
|
Form input components for the Student Success Predictor
|
||||||
|
"""
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
from typing import Dict, Any, Tuple
|
||||||
|
from ..config.mappings import create_feature_mappings, PRESET_CONFIGURATIONS, ACADEMIC_DEFAULTS
|
||||||
|
|
||||||
|
def render_preset_selector() -> str:
|
||||||
|
"""Render the preset selector section"""
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
return preset
|
||||||
|
|
||||||
|
def render_personal_info_section(preset: str, feature_maps: Dict) -> Dict[str, Any]:
|
||||||
|
"""Render personal information input section"""
|
||||||
|
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)
|
||||||
|
|
||||||
|
defaults = PRESET_CONFIGURATIONS[preset]
|
||||||
|
|
||||||
|
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["marital"])
|
||||||
|
)
|
||||||
|
|
||||||
|
gender = st.selectbox(
|
||||||
|
"Gender",
|
||||||
|
[0, 1],
|
||||||
|
format_func=lambda x: "Female" if x == 0 else "Male",
|
||||||
|
index=defaults["gender"]
|
||||||
|
)
|
||||||
|
|
||||||
|
age_at_enrollment = st.number_input(
|
||||||
|
"Age at Enrollment",
|
||||||
|
min_value=17,
|
||||||
|
max_value=70,
|
||||||
|
value=defaults["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
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'Marital status': marital_status,
|
||||||
|
'Gender': gender,
|
||||||
|
'Age at enrollment': age_at_enrollment,
|
||||||
|
'Nationality': nationality
|
||||||
|
}
|
||||||
|
|
||||||
|
def render_academic_background_section(preset: str, feature_maps: Dict) -> Dict[str, Any]:
|
||||||
|
"""Render academic background input section"""
|
||||||
|
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)
|
||||||
|
|
||||||
|
defaults = PRESET_CONFIGURATIONS[preset]
|
||||||
|
|
||||||
|
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["app_mode"]) if defaults["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["course"]) if defaults["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["prev_grade"],
|
||||||
|
step=1
|
||||||
|
)
|
||||||
|
with col2:
|
||||||
|
admission_grade = st.number_input(
|
||||||
|
"Admission Grade",
|
||||||
|
min_value=95,
|
||||||
|
max_value=190,
|
||||||
|
value=defaults["adm_grade"],
|
||||||
|
step=1
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'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,
|
||||||
|
'Admission grade': admission_grade
|
||||||
|
}
|
||||||
|
|
||||||
|
def render_family_background_section(feature_maps: Dict) -> Dict[str, Any]:
|
||||||
|
"""Render family background input section"""
|
||||||
|
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}")
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'Mothers qualification': mothers_qualification,
|
||||||
|
'Fathers qualification': fathers_qualification,
|
||||||
|
'Mothers occupation': mothers_occupation,
|
||||||
|
'Fathers occupation': fathers_occupation
|
||||||
|
}
|
||||||
|
|
||||||
|
def render_financial_social_section() -> Dict[str, Any]:
|
||||||
|
"""Render financial and social factors input section"""
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'Displaced': displaced,
|
||||||
|
'Educational special needs': educational_special_needs,
|
||||||
|
'Debtor': debtor,
|
||||||
|
'Tuition fees up to date': tuition_fees_up_to_date,
|
||||||
|
'Scholarship holder': scholarship_holder,
|
||||||
|
'International': international
|
||||||
|
}
|
||||||
|
|
||||||
|
def render_academic_performance_section(preset: str) -> Dict[str, Any]:
|
||||||
|
"""Render academic performance input sections"""
|
||||||
|
academic_defaults = ACADEMIC_DEFAULTS[preset]
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
with col1:
|
||||||
|
curricular_units_1st_sem_enrolled = st.number_input(
|
||||||
|
"Enrolled Units", min_value=0, max_value=26,
|
||||||
|
value=academic_defaults["1st_sem"]["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=academic_defaults["1st_sem"]["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=academic_defaults["1st_sem"]["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=academic_defaults["1st_sem"]["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=academic_defaults["1st_sem"]["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=academic_defaults["1st_sem"]["without"], step=1,
|
||||||
|
key="1st_without", help="Courses registered but not evaluated"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
with col1:
|
||||||
|
curricular_units_2nd_sem_enrolled = st.number_input(
|
||||||
|
"Enrolled Units", min_value=0, max_value=23,
|
||||||
|
value=academic_defaults["2nd_sem"]["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=academic_defaults["2nd_sem"]["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=academic_defaults["2nd_sem"]["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=academic_defaults["2nd_sem"]["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=academic_defaults["2nd_sem"]["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=academic_defaults["2nd_sem"]["without"], step=1,
|
||||||
|
key="2nd_without", help="Courses registered but not evaluated"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'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
|
||||||
|
}
|
||||||
|
|
||||||
|
def render_economic_indicators_section() -> Dict[str, Any]:
|
||||||
|
"""Render economic indicators input section"""
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'Unemployment rate': unemployment_rate,
|
||||||
|
'Inflation rate': inflation_rate,
|
||||||
|
'GDP': gdp
|
||||||
|
}
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
"""
|
||||||
|
Prediction and results display components
|
||||||
|
"""
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
import plotly.express as px
|
||||||
|
import plotly.graph_objects as go
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
from typing import Dict, Any, Tuple, List
|
||||||
|
|
||||||
|
def render_prediction_section(model, preprocessing_info, user_input: Dict[str, Any]) -> None:
|
||||||
|
"""Render the prediction section with enhanced animations"""
|
||||||
|
from ..utils.model_utils import preprocess_input, generate_recommendations
|
||||||
|
|
||||||
|
# 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
|
||||||
|
render_recommendations(predicted_outcome, user_input)
|
||||||
|
|
||||||
|
# Display probability charts
|
||||||
|
render_probability_charts(prediction_proba)
|
||||||
|
|
||||||
|
def render_recommendations(predicted_outcome: str, user_input: Dict[str, Any]) -> None:
|
||||||
|
"""Render personalized recommendations section"""
|
||||||
|
from ..utils.model_utils import generate_recommendations
|
||||||
|
|
||||||
|
st.markdown("### 💡 Personalized Recommendations")
|
||||||
|
|
||||||
|
recommendations = generate_recommendations(predicted_outcome, user_input)
|
||||||
|
|
||||||
|
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":
|
||||||
|
if recommendations:
|
||||||
|
rec_text = '<br>'.join(recommendations)
|
||||||
|
st.markdown(f'<div class="warning-box">🎯 <strong>Intervention Strategies:</strong><br>{rec_text}</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)
|
||||||
|
|
||||||
|
def render_probability_charts(prediction_proba: List[float]) -> None:
|
||||||
|
"""Render prediction probability charts and metrics"""
|
||||||
|
st.markdown("### 📊 Prediction Confidence")
|
||||||
|
|
||||||
|
# Create probability dataframe
|
||||||
|
import pandas as pd
|
||||||
|
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': '#9ccfd8', # Rose Pine foam
|
||||||
|
'Dropout': '#eb6f92', # Rose Pine love
|
||||||
|
'Enrolled': '#f6c177' # Rose Pine gold
|
||||||
|
},
|
||||||
|
title="Prediction Probabilities"
|
||||||
|
)
|
||||||
|
fig.update_layout(
|
||||||
|
showlegend=False,
|
||||||
|
plot_bgcolor='rgba(0,0,0,0)',
|
||||||
|
paper_bgcolor='rgba(0,0,0,0)'
|
||||||
|
)
|
||||||
|
st.plotly_chart(fig, use_container_width=True)
|
||||||
|
|
||||||
|
# Display detailed probabilities in cards
|
||||||
|
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)
|
||||||
|
|
||||||
|
def render_model_info_sidebar(user_input: Dict[str, Any]) -> None:
|
||||||
|
"""Render model information and validation in sidebar"""
|
||||||
|
from ..utils.model_utils import validate_input
|
||||||
|
from ..config.theme import MODEL_METRICS
|
||||||
|
|
||||||
|
st.markdown('<h2 class="sub-header">📊 Model Insights</h2>', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
# Model information
|
||||||
|
st.markdown('<div class="info-box">', unsafe_allow_html=True)
|
||||||
|
st.markdown(f"""
|
||||||
|
**🤖 Model Details:**
|
||||||
|
- **Algorithm:** Random Forest Classifier
|
||||||
|
- **Features:** 36 student characteristics
|
||||||
|
- **Training Accuracy:** 100%
|
||||||
|
- **Test Accuracy:** {MODEL_METRICS['accuracy']}%
|
||||||
|
- **Classes:** Graduate, Dropout, Enrolled
|
||||||
|
""")
|
||||||
|
st.markdown('</div>', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
# Input validation feedback
|
||||||
|
st.markdown("### 🔍 Input Validation")
|
||||||
|
validation_issues = validate_input(user_input)
|
||||||
|
|
||||||
|
if validation_issues:
|
||||||
|
for issue in validation_issues:
|
||||||
|
st.warning(issue)
|
||||||
|
else:
|
||||||
|
st.success("✅ All inputs look valid!")
|
||||||
|
|
||||||
|
# Model performance visualization
|
||||||
|
st.markdown("### Model Performance")
|
||||||
|
|
||||||
|
metrics = ['Accuracy', 'Precision', 'Recall', 'F1-Score']
|
||||||
|
values = [MODEL_METRICS['accuracy'], MODEL_METRICS['precision'],
|
||||||
|
MODEL_METRICS['recall'], MODEL_METRICS['f1_score']]
|
||||||
|
|
||||||
|
fig_perf = go.Figure(data=go.Scatterpolar(
|
||||||
|
r=values,
|
||||||
|
theta=metrics,
|
||||||
|
fill='toself',
|
||||||
|
line_color='#c4a7e7' # Rose Pine iris
|
||||||
|
))
|
||||||
|
|
||||||
|
fig_perf.update_layout(
|
||||||
|
polar=dict(
|
||||||
|
radialaxis=dict(
|
||||||
|
visible=True,
|
||||||
|
range=[0, 100]
|
||||||
|
)),
|
||||||
|
showlegend=False,
|
||||||
|
title="Model Performance Metrics",
|
||||||
|
height=300,
|
||||||
|
plot_bgcolor='rgba(0,0,0,0)',
|
||||||
|
paper_bgcolor='rgba(0,0,0,0)'
|
||||||
|
)
|
||||||
|
|
||||||
|
st.plotly_chart(fig_perf, use_container_width=True)
|
||||||
|
|
||||||
|
def render_export_section(user_input: Dict[str, Any]) -> None:
|
||||||
|
"""Render 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
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
def render_footer() -> None:
|
||||||
|
"""Render enhanced footer with project information"""
|
||||||
|
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)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Configuration modules
|
||||||
|
"""
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
"""
|
||||||
|
Data mappings and constants for the Student Success Predictor app
|
||||||
|
Contains all the human-readable mappings for categorical features
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
# Preset configurations for quick form filling
|
||||||
|
PRESET_CONFIGURATIONS = {
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Academic performance defaults for presets
|
||||||
|
ACADEMIC_DEFAULTS = {
|
||||||
|
"Custom Entry": {
|
||||||
|
"1st_sem": {"enrolled": 6, "approved": 6, "grade": 13, "credited": 0, "evaluations": 6, "without": 0},
|
||||||
|
"2nd_sem": {"enrolled": 6, "approved": 6, "grade": 13, "credited": 0, "evaluations": 6, "without": 0}
|
||||||
|
},
|
||||||
|
"Typical Graduate Student": {
|
||||||
|
"1st_sem": {"enrolled": 6, "approved": 6, "grade": 14, "credited": 0, "evaluations": 6, "without": 0},
|
||||||
|
"2nd_sem": {"enrolled": 6, "approved": 6, "grade": 14, "credited": 0, "evaluations": 6, "without": 0}
|
||||||
|
},
|
||||||
|
"At-Risk Student": {
|
||||||
|
"1st_sem": {"enrolled": 6, "approved": 0, "grade": 0, "credited": 0, "evaluations": 0, "without": 0},
|
||||||
|
"2nd_sem": {"enrolled": 6, "approved": 0, "grade": 0, "credited": 0, "evaluations": 0, "without": 0}
|
||||||
|
},
|
||||||
|
"International Student": {
|
||||||
|
"1st_sem": {"enrolled": 6, "approved": 6, "grade": 13, "credited": 0, "evaluations": 6, "without": 0},
|
||||||
|
"2nd_sem": {"enrolled": 6, "approved": 6, "grade": 14, "credited": 0, "evaluations": 6, "without": 0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validation constants
|
||||||
|
VALIDATION_RULES = {
|
||||||
|
"min_age": 17,
|
||||||
|
"max_age": 70,
|
||||||
|
"min_grade": 0,
|
||||||
|
"max_grade": 20,
|
||||||
|
"min_qualification_grade": 95,
|
||||||
|
"max_qualification_grade": 190
|
||||||
|
}
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
"""
|
||||||
|
Theme configuration for the Student Success Predictor app
|
||||||
|
Rose Pine Dark Theme implementation
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Rose Pine Dark Theme CSS
|
||||||
|
ROSE_PINE_THEME = """
|
||||||
|
<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>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Page configuration
|
||||||
|
PAGE_CONFIG = {
|
||||||
|
"page_title": "Student Success Predictor",
|
||||||
|
"page_icon": "📊",
|
||||||
|
"layout": "wide",
|
||||||
|
"initial_sidebar_state": "expanded"
|
||||||
|
}
|
||||||
|
|
||||||
|
# App constants
|
||||||
|
APP_TITLE = "Student Success Predictor"
|
||||||
|
APP_SUBTITLE = "Predict whether a student will Graduate, Dropout, or remain Enrolled based on academic and demographic factors using advanced machine learning."
|
||||||
|
|
||||||
|
# Model constants
|
||||||
|
MODEL_FILES = {
|
||||||
|
"model": "random_forest_model.pkl",
|
||||||
|
"preprocessing": "preprocessing_info.pkl",
|
||||||
|
"features": "feature_names.pkl"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Performance metrics (from notebook results)
|
||||||
|
MODEL_METRICS = {
|
||||||
|
"accuracy": 93.84,
|
||||||
|
"precision": 92.96,
|
||||||
|
"recall": 94.02,
|
||||||
|
"f1_score": 93.15
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Utility modules
|
||||||
|
"""
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
"""
|
||||||
|
Model loading and data preprocessing utilities
|
||||||
|
"""
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
import pandas as pd
|
||||||
|
import pickle
|
||||||
|
from typing import Dict, Any, List
|
||||||
|
|
||||||
|
@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 preprocess_input(user_input: Dict[str, Any], preprocessing_info: Dict[str, Any]) -> pd.DataFrame:
|
||||||
|
"""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 validate_input(user_input: Dict[str, Any]) -> List[str]:
|
||||||
|
"""Validate user input and return list of issues"""
|
||||||
|
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)")
|
||||||
|
|
||||||
|
return validation_issues
|
||||||
|
|
||||||
|
def generate_recommendations(predicted_outcome: str, user_input: Dict[str, Any]) -> List[str]:
|
||||||
|
"""Generate personalized recommendations based on prediction and input"""
|
||||||
|
recommendations = []
|
||||||
|
|
||||||
|
if predicted_outcome == "Dropout":
|
||||||
|
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 user_input['Debtor'] == 1:
|
||||||
|
recommendations.append("💳 **Financial Counseling:** Address outstanding debts")
|
||||||
|
|
||||||
|
elif predicted_outcome == "Graduate":
|
||||||
|
recommendations.append("🎯 **Success Indicators:** This student shows strong potential for graduation")
|
||||||
|
recommendations.append("🔄 **Continue Support:** Maintain current academic support systems")
|
||||||
|
|
||||||
|
else: # Enrolled
|
||||||
|
recommendations.append("📈 **Regular Monitoring:** Schedule periodic academic check-ins")
|
||||||
|
recommendations.append("🎯 **Goal Setting:** Help establish clear academic objectives")
|
||||||
|
|
||||||
|
return recommendations
|
||||||
Reference in New Issue
Block a user