deploy to streamlit

This commit is contained in:
2025-09-28 00:19:29 +03:00
parent 4a690d5f72
commit fab863c3f6
+427 -49
View File
@@ -10,6 +10,7 @@ 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(
@@ -19,46 +20,279 @@ st.set_page_config(
initial_sidebar_state="expanded"
)
# Custom CSS for beautiful styling
# 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: 3rem;
color: #1f77b4;
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: 2rem;
font-weight: bold;
}
.sub-header {
font-size: 1.5rem;
color: #2c3e50;
margin-bottom: 1rem;
font-weight: 700;
letter-spacing: -1px;
}
.prediction-box {
padding: 2rem;
border-radius: 10px;
.subtitle {
text-align: center;
font-size: 1.5rem;
font-weight: bold;
margin: 1rem 0;
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, #28a745, #20c997);
color: white;
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, #dc3545, #fd7e14);
color: white;
background: linear-gradient(135deg, var(--rp-love), #c74665);
color: var(--rp-text);
border-color: var(--rp-love);
}
.enrolled-box {
background: linear-gradient(135deg, #ffc107, #fd7e14);
color: white;
background: linear-gradient(135deg, var(--rp-gold), #e6a85c);
color: var(--rp-base);
border-color: var(--rp-gold);
}
/* Metric Cards */
.metric-card {
background: #f8f9fa;
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;
border-left: 4px solid #1f77b4;
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)
@@ -252,7 +486,10 @@ def main():
# Header
st.markdown('<h1 class="main-header">Student Success Predictor</h1>', unsafe_allow_html=True)
st.markdown("**Predict whether a student will Graduate, Dropout, or remain Enrolled based on academic and demographic factors.**")
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)
@@ -260,10 +497,12 @@ def main():
# Create input form
with st.sidebar:
# Quick presets
st.markdown("### Quick Start")
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 = {
@@ -274,10 +513,16 @@ def main():
}
if preset != "Custom Entry":
st.info(f"Loading {preset} preset values. You can modify any field below.")
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("### 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",
@@ -291,7 +536,9 @@ def main():
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("### 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",
@@ -324,7 +571,9 @@ def main():
admission_grade = st.number_input("Admission Grade", min_value=95, max_value=190, value=defaults[preset]["adm_grade"], step=1)
# Family Background
st.markdown("### 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",
@@ -351,7 +600,9 @@ def main():
)
# Financial and Social Factors
st.markdown("### Financial & Social")
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:
@@ -364,8 +615,9 @@ def main():
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("### 1st Semester Performance")
st.caption("Enter the student's academic performance for their first 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 = {
@@ -386,8 +638,9 @@ def main():
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("### 2nd Semester Performance")
st.caption("Enter the student's academic performance for their second 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 = {
@@ -408,8 +661,9 @@ def main():
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("### Economic Indicators")
st.caption("These are typically set by the institution based on current economic conditions")
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:
@@ -465,9 +719,32 @@ def main():
with col1:
st.markdown('<h2 class="sub-header">Prediction Results</h2>', unsafe_allow_html=True)
# Make prediction
if st.button("Predict Student Outcome", type="primary"):
with st.spinner("Analyzing student data..."):
# 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)
@@ -479,16 +756,56 @@ def main():
target_reverse_mapping = preprocessing_info['target_reverse_mapping']
predicted_outcome = target_reverse_mapping[prediction]
# Display prediction with styling
# 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">Predicted Outcome: GRADUATE</div>', unsafe_allow_html=True)
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">Predicted Outcome: DROPOUT</div>', unsafe_allow_html=True)
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">Predicted Outcome: ENROLLED</div>', unsafe_allow_html=True)
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")
st.markdown("### 📊 Prediction Confidence")
prob_df = pd.DataFrame({
'Outcome': ['Dropout', 'Enrolled', 'Graduate'],
@@ -539,16 +856,40 @@ def main():
""", unsafe_allow_html=True)
with col2:
st.markdown('<h2 class="sub-header">About the Model</h2>', unsafe_allow_html=True)
st.markdown('<h2 class="sub-header">📊 Model Insights</h2>', unsafe_allow_html=True)
st.info("""
**Model Details:**
# 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("""
@@ -594,9 +935,46 @@ def main():
st.plotly_chart(fig_perf, use_container_width=True)
# Footer
# 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("*Developed using Streamlit and scikit-learn*")
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()