Spaces:
Sleeping
Sleeping
| """ | |
| Monthly Budget Forecasting with Real Dataset | |
| --------------------------------------------- | |
| End-to-end pipeline: | |
| 1. Load real dataset from CSV (monthly_forecast_dataset_large.csv) | |
| 2. Clean and preprocess data | |
| 3. Engineer temporal features | |
| 4. Train multiple regression models | |
| 5. Compare performance and select best | |
| 6. Evaluate on test set | |
| 7. Forecast next 3 months | |
| """ | |
| import numpy as np | |
| import pandas as pd | |
| import matplotlib.pyplot as plt | |
| from datetime import datetime, timedelta | |
| from pathlib import Path | |
| import joblib | |
| from sklearn.model_selection import train_test_split | |
| from sklearn.preprocessing import StandardScaler | |
| from sklearn.linear_model import Ridge, Lasso, LinearRegression | |
| from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score | |
| import warnings | |
| warnings.filterwarnings('ignore') | |
| # ============================================================================ | |
| # PART 1: LOAD REAL DATASET FROM CSV | |
| # ============================================================================ | |
| def load_real_budget_data(csv_path): | |
| """ | |
| Load real budget dataset from CSV. | |
| Args: | |
| csv_path: Path to the CSV file | |
| Returns: | |
| pandas DataFrame with budget data | |
| """ | |
| print("="*70) | |
| print("LOADING REAL DATASET FROM CSV") | |
| print("="*70) | |
| df = pd.read_csv(csv_path) | |
| # Restrict to month and monthly_budget_pkr for single-family series | |
| keep_cols = ['month', 'monthly_budget_pkr'] | |
| df = df[keep_cols] | |
| print(f"\nβ Loaded {len(df)} rows from {csv_path}") | |
| print(f"\nDataset shape: {df.shape}") | |
| print(f"\nColumns: {list(df.columns)}") | |
| print(f"\nFirst 10 rows:") | |
| print(df.head(10)) | |
| print(f"\nStatistics:") | |
| print(df.describe().round(2)) | |
| return df | |
| # ============================================================================ | |
| # PART 2: DATA CLEANING & PREPROCESSING | |
| # ============================================================================ | |
| def clean_and_preprocess(df): | |
| """ | |
| Clean data and engineer features for the real dataset. | |
| Args: | |
| df: Input DataFrame | |
| Returns: | |
| Cleaned DataFrame with engineered features | |
| """ | |
| print("\n" + "="*70) | |
| print("DATA CLEANING & PREPROCESSING") | |
| print("="*70) | |
| df = df.copy() | |
| # Keep only month and target for single-family series | |
| df = df[['month', 'monthly_budget_pkr']] | |
| # Handle missing values | |
| missing = df.isnull().sum() | |
| if missing.any(): | |
| print(f"\nβ Missing values detected:\n{missing[missing > 0]}") | |
| df = df.dropna() | |
| # Temporal features | |
| df['month_date'] = pd.to_datetime(df['month']) | |
| df['time_idx'] = (df['month_date'] - df['month_date'].min()).dt.days // 30 | |
| df['month_num'] = df['month_date'].dt.month | |
| df['month_sin'] = np.sin(2 * np.pi * df['month_num'] / 12) | |
| df['month_cos'] = np.cos(2 * np.pi * df['month_num'] / 12) | |
| print(f"\nβ Data cleaned successfully") | |
| print(f" Final shape: {df.shape}") | |
| print(f" Missing values: {df.isnull().sum().sum()}") | |
| print(f" Columns: {list(df.columns)}") | |
| return df | |
| # ============================================================================ | |
| # PART 3: MODEL TRAINING & COMPARISON | |
| # ============================================================================ | |
| def train_and_compare_models(X_train, y_train, X_test, y_test): | |
| """ | |
| Train multiple models and compare performance. | |
| Args: | |
| X_train, y_train: Training data | |
| X_test, y_test: Test data | |
| Returns: | |
| Dictionary with model results | |
| """ | |
| print("\n" + "="*70) | |
| print("TRAINING & COMPARING MULTIPLE MODELS") | |
| print("="*70) | |
| scaler = StandardScaler() | |
| X_train_scaled = scaler.fit_transform(X_train) | |
| X_test_scaled = scaler.transform(X_test) | |
| models = { | |
| 'Linear Regression': LinearRegression(), | |
| 'Ridge Regression': Ridge(alpha=1.0, random_state=42), | |
| 'Lasso Regression': Lasso(alpha=0.1, random_state=42), | |
| } | |
| results = {} | |
| for model_name, model in models.items(): | |
| print(f"\n--- Training {model_name} ---") | |
| # Train | |
| model.fit(X_train_scaled, y_train) | |
| # Predict | |
| y_pred = model.predict(X_test_scaled) | |
| # Evaluate | |
| mae = mean_absolute_error(y_test, y_pred) | |
| rmse = np.sqrt(mean_squared_error(y_test, y_pred)) | |
| r2 = r2_score(y_test, y_pred) | |
| mape = np.mean(np.abs((y_test - y_pred) / (y_test + 1e-6))) * 100 | |
| results[model_name] = { | |
| 'model': model, | |
| 'scaler': scaler, | |
| 'mae': mae, | |
| 'rmse': rmse, | |
| 'r2': r2, | |
| 'mape': mape, | |
| 'predictions': y_pred | |
| } | |
| print(f" MAE: {mae:>10.2f}") | |
| print(f" RMSE: {rmse:>10.2f}") | |
| print(f" RΒ²: {r2:>10.4f}") | |
| print(f" MAPE: {mape:>10.2f}%") | |
| # Find best model | |
| best_model_name = max(results, key=lambda x: results[x]['r2']) | |
| print(f"\n" + "="*70) | |
| print(f"π BEST MODEL: {best_model_name}") | |
| print(f" RΒ² Score: {results[best_model_name]['r2']:.4f}") | |
| print("="*70) | |
| return results, best_model_name, scaler | |
| # ============================================================================ | |
| # PART 4: EVALUATION & VISUALIZATION | |
| # ============================================================================ | |
| def evaluate_and_visualize(results, best_model_name, X_test, y_test, output_dir='output'): | |
| """ | |
| Detailed evaluation and visualization of best model. | |
| """ | |
| print("\n" + "="*70) | |
| print("DETAILED EVALUATION OF BEST MODEL") | |
| print("="*70) | |
| output_path = Path(output_dir) | |
| output_path.mkdir(exist_ok=True) | |
| best_result = results[best_model_name] | |
| y_pred = best_result['predictions'] | |
| # Performance metrics table | |
| print("\nCOMPARATIVE MODEL PERFORMANCE:") | |
| print("-" * 70) | |
| print(f"{'Model':<20} {'MAE':>12} {'RMSE':>12} {'RΒ²':>12} {'MAPE':>10}") | |
| print("-" * 70) | |
| for model_name in sorted(results.keys()): | |
| metrics = results[model_name] | |
| print(f"{model_name:<20} {metrics['mae']:>12.2f} {metrics['rmse']:>12.2f} " | |
| f"{metrics['r2']:>12.4f} {metrics['mape']:>10.2f}%") | |
| print("-" * 70) | |
| # Visualization | |
| fig, axes = plt.subplots(2, 2, figsize=(14, 10)) | |
| # 1. Actual vs Predicted | |
| ax = axes[0, 0] | |
| ax.scatter(y_test, y_pred, alpha=0.6, s=50) | |
| ax.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--', lw=2) | |
| ax.set_xlabel('Actual Budget (PKR)', fontsize=11) | |
| ax.set_ylabel('Predicted Budget (PKR)', fontsize=11) | |
| ax.set_title(f'{best_model_name} - Actual vs Predicted', fontsize=12, fontweight='bold') | |
| ax.grid(True, alpha=0.3) | |
| # 2. Residuals | |
| ax = axes[0, 1] | |
| residuals = y_test - y_pred | |
| ax.scatter(y_pred, residuals, alpha=0.6, s=50) | |
| ax.axhline(y=0, color='r', linestyle='--', lw=2) | |
| ax.set_xlabel('Predicted Budget (PKR)', fontsize=11) | |
| ax.set_ylabel('Residuals (PKR)', fontsize=11) | |
| ax.set_title('Residual Plot', fontsize=12, fontweight='bold') | |
| ax.grid(True, alpha=0.3) | |
| # 3. Distribution of Residuals | |
| ax = axes[1, 0] | |
| ax.hist(residuals, bins=20, edgecolor='black', alpha=0.7) | |
| ax.set_xlabel('Residuals (PKR)', fontsize=11) | |
| ax.set_ylabel('Frequency', fontsize=11) | |
| ax.set_title('Distribution of Residuals', fontsize=12, fontweight='bold') | |
| ax.axvline(x=0, color='r', linestyle='--', lw=2) | |
| ax.grid(True, alpha=0.3, axis='y') | |
| # 4. Model Comparison | |
| ax = axes[1, 1] | |
| model_names = list(results.keys()) | |
| r2_scores = [results[m]['r2'] for m in model_names] | |
| colors = ['green' if m == best_model_name else 'lightblue' for m in model_names] | |
| ax.barh(model_names, r2_scores, color=colors, edgecolor='black') | |
| ax.set_xlabel('RΒ² Score', fontsize=11) | |
| ax.set_title('Model Comparison (RΒ² Score)', fontsize=12, fontweight='bold') | |
| ax.set_xlim([min(0, min(r2_scores) - 0.1), 1]) | |
| for i, v in enumerate(r2_scores): | |
| ax.text(v + 0.01, i, f'{v:.4f}', va='center', fontsize=10) | |
| plt.tight_layout() | |
| plot_path = output_path / f'{best_model_name.replace(" ", "_")}_evaluation.png' | |
| plt.savefig(plot_path, dpi=150, bbox_inches='tight') | |
| print(f"\nβ Evaluation plot saved to {plot_path}") | |
| plt.close() | |
| # Save metrics to CSV | |
| metrics_df = pd.DataFrame(results).T[['mae', 'rmse', 'r2', 'mape']] | |
| metrics_path = output_path / 'model_comparison.csv' | |
| metrics_df.to_csv(metrics_path) | |
| print(f"β Metrics comparison saved to {metrics_path}") | |
| # ============================================================================ | |
| # PART 5: FUTURE PREDICTIONS | |
| # ============================================================================ | |
| def predict_future_budgets(df, best_result, feature_columns, n_future_months=3): | |
| """ | |
| Predict budget for next n months. | |
| Args: | |
| df: Full preprocessed dataframe | |
| best_result: Best model result dictionary | |
| feature_columns: List of feature column names | |
| n_future_months: Number of months to forecast | |
| Returns: | |
| DataFrame with future predictions | |
| """ | |
| print("\n" + "="*70) | |
| print(f"FORECASTING NEXT {n_future_months} MONTHS") | |
| print("="*70) | |
| model = best_result['model'] | |
| scaler = best_result['scaler'] | |
| last_month = df['month_date'].max() | |
| future_dates = [last_month + timedelta(days=30 * (i + 1)) for i in range(n_future_months)] | |
| future_months = [d.strftime('%Y-%m') for d in future_dates] | |
| base_row = df.iloc[-1].copy() | |
| future_predictions = [] | |
| for future_date, future_month in zip(future_dates, future_months): | |
| future_row = base_row.copy() | |
| future_row['month'] = future_month | |
| future_row['month_date'] = future_date | |
| future_row['time_idx'] = (future_date - df['month_date'].min()).days // 30 | |
| future_row['month_num'] = future_date.month | |
| future_row['month_sin'] = np.sin(2 * np.pi * future_row['month_num'] / 12) | |
| future_row['month_cos'] = np.cos(2 * np.pi * future_row['month_num'] / 12) | |
| X_future = future_row[feature_columns].values.reshape(1, -1) | |
| X_future_scaled = scaler.transform(X_future) | |
| predicted_budget = model.predict(X_future_scaled)[0] | |
| future_predictions.append({ | |
| 'month': future_month, | |
| 'predicted_monthly_budget_pkr': predicted_budget, | |
| }) | |
| future_df = pd.DataFrame(future_predictions) | |
| print("\nFuture Budget Forecasts:") | |
| print("-" * 70) | |
| print(future_df.to_string(index=False)) | |
| print("-" * 70) | |
| return future_df | |
| # ============================================================================ | |
| # PART 6: MODEL PERSISTENCE & RELOAD PREDICTION | |
| # ============================================================================ | |
| def save_best_model(best_result, best_model_name, feature_columns, output_dir): | |
| """Persist best model, scaler, and feature list to disk.""" | |
| output_path = Path(output_dir) | |
| output_path.mkdir(exist_ok=True) | |
| model_bundle = { | |
| 'model': best_result['model'], | |
| 'scaler': best_result['scaler'], | |
| 'feature_columns': feature_columns, | |
| 'model_name': best_model_name, | |
| } | |
| model_path = output_path / f"best_model_{best_model_name.replace(' ', '_').lower()}.joblib" | |
| joblib.dump(model_bundle, model_path) | |
| print(f"\nβ Saved best model bundle to {model_path}") | |
| return model_path | |
| def load_model(model_path): | |
| """Load persisted model bundle from disk.""" | |
| model_bundle = joblib.load(model_path) | |
| print(f"β Loaded model bundle from {model_path}") | |
| return model_bundle | |
| def predict_with_loaded_model(df, model_bundle, n_future_months=3): | |
| """Forecast using a reloaded model bundle.""" | |
| best_result = { | |
| 'model': model_bundle['model'], | |
| 'scaler': model_bundle['scaler'], | |
| } | |
| feature_columns = model_bundle['feature_columns'] | |
| return predict_future_budgets(df, best_result, feature_columns, n_future_months) | |
| # ============================================================================ | |
| # PART 7: MAIN EXECUTION | |
| # ============================================================================ | |
| def main(): | |
| """Main execution pipeline""" | |
| print("\n") | |
| print("β" * 70) | |
| print("β" + " " * 68 + "β") | |
| print("β" + " BUDGET FORECASTING WITH REAL DATASET - PIPELINE".center(68) + "β") | |
| print("β" + " " * 68 + "β") | |
| print("β" * 70) | |
| csv_path = 'd:/FoodData/Monthly forcast/monthly_budget_single_family_24m.csv' | |
| output_dir = 'output' | |
| Path(output_dir).mkdir(exist_ok=True) | |
| # Step 1: Load real dataset | |
| df = load_real_budget_data(csv_path) | |
| # Step 2: Clean and preprocess | |
| df_processed = clean_and_preprocess(df) | |
| # Step 3: Prepare features | |
| feature_cols = ['time_idx', 'month_num', 'month_sin', 'month_cos'] | |
| X = df_processed[feature_cols] | |
| y = df_processed['monthly_budget_pkr'] | |
| # Step 4: Train-test split (chronological) | |
| X_train, X_test, y_train, y_test = train_test_split( | |
| X, y, test_size=0.2, random_state=42, shuffle=False | |
| ) | |
| print(f"\n" + "="*70) | |
| print("DATA SPLIT") | |
| print("="*70) | |
| print(f"Training set: {len(X_train)} samples") | |
| print(f"Test set: {len(X_test)} samples") | |
| print(f"Features: {X_train.shape[1]}") | |
| print(f"\nFeature columns used ({len(feature_cols)}):") | |
| for i, col in enumerate(feature_cols, 1): | |
| print(f" {i:2d}. {col}") | |
| # Step 5: Train and compare models | |
| results, best_model_name, scaler = train_and_compare_models( | |
| X_train, y_train, X_test, y_test | |
| ) | |
| # Step 6: Evaluate and visualize | |
| evaluate_and_visualize(results, best_model_name, X_test, y_test, output_dir) | |
| # Step 7: Make future predictions (next month by default) | |
| best_result = results[best_model_name] | |
| future_df = predict_future_budgets( | |
| df_processed, best_result, feature_cols, n_future_months=1 | |
| ) | |
| # Save future predictions | |
| future_path = Path(output_dir) / 'future_predictions_real_data.csv' | |
| future_df.to_csv(future_path, index=False) | |
| print(f"\nβ Future prediction saved to {future_path}") | |
| # Save and reload the best model, then predict again to validate persistence | |
| model_path = save_best_model(best_result, best_model_name, feature_cols, output_dir) | |
| loaded_bundle = load_model(model_path) | |
| future_df_loaded = predict_with_loaded_model( | |
| df_processed, loaded_bundle, n_future_months=1 | |
| ) | |
| future_reloaded_path = Path(output_dir) / 'future_predictions_real_data_reloaded.csv' | |
| future_df_loaded.to_csv(future_reloaded_path, index=False) | |
| print(f"β Future prediction (reloaded model) saved to {future_reloaded_path}") | |
| # Save best model info | |
| model_info_path = Path(output_dir) / 'best_model_info.txt' | |
| with open(model_info_path, 'w') as f: | |
| f.write(f"BEST MODEL: {best_model_name}\n") | |
| f.write(f"RΒ² Score: {results[best_model_name]['r2']:.4f}\n") | |
| f.write(f"MAE: {results[best_model_name]['mae']:.2f}\n") | |
| f.write(f"RMSE: {results[best_model_name]['rmse']:.2f}\n") | |
| f.write(f"MAPE: {results[best_model_name]['mape']:.2f}%\n") | |
| f.write(f"\nFeatures used: {len(feature_cols)}\n") | |
| for col in feature_cols: | |
| f.write(f" - {col}\n") | |
| print(f"β Model info saved to {model_info_path}") | |
| print("\n" + "β" * 70) | |
| print("β" + " β PIPELINE EXECUTION COMPLETED SUCCESSFULLY".center(68) + "β") | |
| print("β" * 70 + "\n") | |
| if __name__ == '__main__': | |
| main() | |