Mental Health Prediction App
An ensemble ML pipeline predicting mental health risk from behavioral and workplace survey data — 85%+ accuracy with a real-time Streamlit interface for clinicians and researchers.
Overview
What it does
Predicts whether an individual is at risk for a mental health condition using behavioral and workplace survey responses, with real-time inference via a Streamlit dashboard.
Problem solved
Mental health screening is slow, expensive, and inaccessible. This turns a structured survey into an instant, private risk assessment — no clinician required for initial triage.
Built for
Healthcare researchers, HR teams running workplace wellness programs, and CS students exploring applied ML on real-world health datasets.
The project uses the Open Sourcing Mental Illness (OSMI) dataset, which contains ~1,400 survey responses from tech industry workers. After extensive preprocessing — handling missing values, encoding ordinal features, and applying SMOTE to correct a 70/30 class imbalance — four classifiers were trained and compared: Logistic Regression, Random Forest, SVM, and XGBoost.
XGBoost was the top performer across all metrics. The final pipeline is serialized with joblib and served through a Streamlit UI that collects the same survey inputs, runs inference in real time, and visualizes feature importance so users can understand why the model flagged a risk.
Technical Architecture
Data flow
Key components
- ▸Preprocessing pipeline — label encoding, one-hot encoding for categoricals, StandardScaler for numeric features
- ▸SMOTE oversampling — synthetic minority oversampling to fix the 70/30 class split before training
- ▸Model ensemble — XGBoost, Random Forest, SVM, Logistic Regression compared with k-fold CV
- ▸Streamlit app — form UI, real-time inference, feature importance chart, downloadable report
Model selection
Four algorithms were evaluated using 5-fold stratified cross-validation. XGBoost outperformed all others on F1 and AUC-ROC. Chi-squared tests and mutual information scoring reduced features from 27 to the 16 most predictive columns.
Key Features
85%+ prediction accuracy
XGBoost with hyperparameter tuning via GridSearchCV achieves 85%+ accuracy and 0.91 AUC-ROC on held-out test data.
Real-time inference
Streamlit form collects inputs and returns a risk probability score in under 100ms — no server required.
Explainable predictions
Feature importance bar chart shows which survey answers drove the prediction — critical for clinical trust.
Model comparison dashboard
Side-by-side accuracy, F1, precision, and recall metrics for all four tested classifiers.
Challenges & Solutions
Class imbalance in the dataset
Challenge: 70% of respondents had no mental health concern, causing the model to default to predicting the majority class.
Solution: Applied SMOTE (Synthetic Minority Oversampling Technique) inside each CV fold to prevent data leakage while generating synthetic minority samples. Improved recall on the positive class from 61% to 81%.
High-cardinality categorical features
Challenge: The "Country" and "Work_interfere" columns had many categories, and naive one-hot encoding inflated the feature space.
Solution: Used chi-squared testing and mutual information to identify and drop low-signal categories. Grouped rare countries into an "Other" bin, reducing dimensionality by 40%.
Streamlit state management
Challenge: The model pipeline (scaler + encoder + classifier) needed to be loaded once, not re-instantiated on every rerun.
Solution: Used @st.cache_resource to load the joblib pipeline once and keep it in memory across sessions.
Results & Impact
85%+
Prediction accuracy
0.91
AUC-ROC score
0.83
F1 score
4
Models compared
XGBoost consistently outperformed the other three classifiers. The model generalizes well on the held-out test set, with no significant gap between training and test metrics — indicating minimal overfitting despite the SMOTE augmentation.
What I'd do differently
- →Optimize for recall, not accuracy. Accuracy is dominated by the majority class. For mental health screening, a false negative — missing someone who needs help — is far more costly than a false positive. I'd set the classification threshold to 0.3 and report per-class recall prominently.
- →Surface SHAP explanations in the UI. The current app shows feature importance globally. SHAP would show per-prediction explanations — which specific survey answers drove this person's score — making the output clinically actionable rather than just a number.
- →Add confidence intervals, not just a point estimate. A model this size on 1,400 samples has real variance. Showing "65–80% risk" rather than "74% risk" is more honest about what the data actually supports.
Code Highlight
Applying SMOTE inside each CV fold prevents data leakage while correcting class imbalance.
from xgboost import XGBClassifier
from sklearn.model_selection import StratifiedKFold, cross_val_score
from imblearn.over_sampling import SMOTE
from imblearn.pipeline import Pipeline as ImbPipeline
# SMOTE inside the pipeline ensures no leakage across CV folds
pipeline = ImbPipeline([
('smote', SMOTE(random_state=42)),
('scaler', StandardScaler()),
('model', XGBClassifier(
n_estimators=200,
max_depth=6,
learning_rate=0.05,
scale_pos_weight=2.3, # extra weight on positive class
use_label_encoder=False,
eval_metric='auc'
))
])
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(pipeline, X, y, cv=cv, scoring='roc_auc')
print(f"AUC-ROC: {scores.mean():.3f} ± {scores.std():.3f}")