340 lines
12 KiB
Python
340 lines
12 KiB
Python
import pandas as pd
|
|
import numpy as np
|
|
from sklearn.linear_model import LinearRegression
|
|
from sklearn.ensemble import RandomForestRegressor
|
|
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
|
|
from sklearn.preprocessing import LabelEncoder
|
|
import joblib
|
|
import warnings
|
|
import os
|
|
warnings.filterwarnings('ignore')
|
|
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
# ============================================================================
|
|
# LOAD AND PREPARE DATA
|
|
# ============================================================================
|
|
print("=" * 80)
|
|
print("MODEL TESTING - LINEAR REGRESSION vs RANDOM FOREST")
|
|
print("=" * 80)
|
|
|
|
file_path = os.path.join(script_dir, 'dataset_prediksi_permintaan_bahan_2021_2025_update_tanggal.xlsx')
|
|
|
|
try:
|
|
df = pd.read_excel(file_path)
|
|
print(f"\n[OK] Data loaded: {df.shape[0]} baris, {df.shape[1]} kolom")
|
|
except Exception as e:
|
|
print(f"[ERROR] Gagal load data: {e}")
|
|
exit(1)
|
|
|
|
# Display data info
|
|
print(f"\nColumn Names:")
|
|
print(df.columns.tolist())
|
|
print(f"\nFirst 5 rows:")
|
|
print(df.head())
|
|
print(f"\nData Types:")
|
|
print(df.dtypes)
|
|
print(f"\nMissing Values:")
|
|
print(df.isnull().sum())
|
|
|
|
# ============================================================================
|
|
# FEATURE ENGINEERING
|
|
# ============================================================================
|
|
print("\n" + "=" * 80)
|
|
print("FEATURE ENGINEERING")
|
|
print("=" * 80)
|
|
|
|
# Identify target column (column with quantity/jumlah)
|
|
target_col = None
|
|
for col in df.columns:
|
|
if 'jumlah' in col.lower() or 'qty' in col.lower() or 'quantity' in col.lower():
|
|
target_col = col
|
|
break
|
|
|
|
if target_col is None:
|
|
print("[ERROR] Kolom target tidak ditemukan. Pilihan kolom:")
|
|
print(df.columns.tolist())
|
|
exit(1)
|
|
|
|
print(f"\nTarget column identified: {target_col}")
|
|
|
|
# Find date column
|
|
date_col = None
|
|
for col in df.columns:
|
|
if 'tanggal' in col.lower() or 'date' in col.lower():
|
|
date_col = col
|
|
break
|
|
|
|
if date_col:
|
|
print(f"Date column identified: {date_col}")
|
|
df[date_col] = pd.to_datetime(df[date_col])
|
|
df['tahun'] = df[date_col].dt.year
|
|
df['bulan'] = df[date_col].dt.month
|
|
df['hari'] = df[date_col].dt.day
|
|
df['hari_minggu'] = df[date_col].dt.dayofweek
|
|
print("[OK] Tanggal dipecah menjadi: tahun, bulan, hari, hari_minggu")
|
|
|
|
# Identify and encode categorical columns
|
|
categorical_cols = df.select_dtypes(include=['object']).columns.tolist()
|
|
if date_col and date_col in categorical_cols:
|
|
categorical_cols.remove(date_col)
|
|
|
|
encoders = {}
|
|
for col in categorical_cols:
|
|
le = LabelEncoder()
|
|
df[f'{col}_encoded'] = le.fit_transform(df[col].astype(str))
|
|
encoders[col] = le
|
|
print(f"[OK] {col} di-encode")
|
|
|
|
# Select features
|
|
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
|
|
leakage_cols = [
|
|
target_col,
|
|
'total_harga_update',
|
|
]
|
|
feature_cols = [col for col in numeric_cols if col not in leakage_cols]
|
|
|
|
removed_leakage_cols = [
|
|
col for col in leakage_cols
|
|
if col in numeric_cols and col != target_col
|
|
]
|
|
if removed_leakage_cols:
|
|
print(f"\n[OK] Fitur data leakage dihapus: {removed_leakage_cols}")
|
|
print("[INFO] total_harga_update tidak dipakai karena nilainya bergantung pada jumlah permintaan.")
|
|
|
|
print(f"\nFeatures yang digunakan: {len(feature_cols)}")
|
|
print(f"Features: {feature_cols}")
|
|
|
|
X = df[feature_cols].copy()
|
|
y = df[target_col].copy()
|
|
|
|
print(f"\nX shape: {X.shape}")
|
|
print(f"y shape: {y.shape}")
|
|
|
|
# Remove NaN values
|
|
mask = ~(X.isnull().any(axis=1) | y.isnull())
|
|
X = X[mask]
|
|
y = y[mask]
|
|
if date_col:
|
|
date_series = df.loc[mask, date_col]
|
|
|
|
print(f"After removing NaN: X={X.shape}, y={y.shape}")
|
|
|
|
# Split data by date order so testing represents future data.
|
|
if date_col:
|
|
sorted_index = date_series.sort_values().index
|
|
X = X.loc[sorted_index]
|
|
y = y.loc[sorted_index]
|
|
date_series = date_series.loc[sorted_index]
|
|
print("[OK] Data diurutkan berdasarkan tanggal untuk testing prediksi masa depan")
|
|
|
|
split_index = int(len(X) * 0.8)
|
|
X_train = X.iloc[:split_index]
|
|
X_test = X.iloc[split_index:]
|
|
y_train = y.iloc[:split_index]
|
|
y_test = y.iloc[split_index:]
|
|
|
|
print(f"\nData dibagi:")
|
|
print(f" - Training: {len(X_train)} samples ({len(X_train)/len(X)*100:.1f}%)")
|
|
print(f" - Testing: {len(X_test)} samples ({len(X_test)/len(X)*100:.1f}%)")
|
|
if date_col:
|
|
print(f" - Periode training: {date_series.iloc[:split_index].min().date()} s/d {date_series.iloc[:split_index].max().date()}")
|
|
print(f" - Periode testing: {date_series.iloc[split_index:].min().date()} s/d {date_series.iloc[split_index:].max().date()}")
|
|
|
|
# ============================================================================
|
|
# MODEL 1: LINEAR REGRESSION
|
|
# ============================================================================
|
|
print("\n" + "=" * 80)
|
|
print("MODEL 1: LINEAR REGRESSION")
|
|
print("=" * 80)
|
|
|
|
lr_model = LinearRegression()
|
|
lr_model.fit(X_train, y_train)
|
|
|
|
# Predictions
|
|
y_train_pred_lr = lr_model.predict(X_train)
|
|
y_test_pred_lr = lr_model.predict(X_test)
|
|
|
|
# Metrics - Training
|
|
train_mse_lr = mean_squared_error(y_train, y_train_pred_lr)
|
|
train_rmse_lr = np.sqrt(train_mse_lr)
|
|
train_mae_lr = mean_absolute_error(y_train, y_train_pred_lr)
|
|
train_r2_lr = r2_score(y_train, y_train_pred_lr)
|
|
|
|
# Metrics - Testing
|
|
test_mse_lr = mean_squared_error(y_test, y_test_pred_lr)
|
|
test_rmse_lr = np.sqrt(test_mse_lr)
|
|
test_mae_lr = mean_absolute_error(y_test, y_test_pred_lr)
|
|
test_r2_lr = r2_score(y_test, y_test_pred_lr)
|
|
|
|
print(f"\nTraining Metrics:")
|
|
print(f" R² Score: {train_r2_lr:.4f}")
|
|
print(f" Mean Absolute Error: {train_mae_lr:.2f}")
|
|
print(f" Root Mean Squared Error: {train_rmse_lr:.2f}")
|
|
|
|
print(f"\nTesting Metrics:")
|
|
print(f" R² Score: {test_r2_lr:.4f}")
|
|
print(f" Mean Absolute Error: {test_mae_lr:.2f}")
|
|
print(f" Root Mean Squared Error: {test_rmse_lr:.2f}")
|
|
|
|
# ============================================================================
|
|
# MODEL 2: RANDOM FOREST
|
|
# ============================================================================
|
|
print("\n" + "=" * 80)
|
|
print("MODEL 2: RANDOM FOREST REGRESSOR")
|
|
print("=" * 80)
|
|
|
|
rf_model = RandomForestRegressor(n_estimators=100, random_state=42, n_jobs=-1)
|
|
|
|
rf_model.fit(X_train, y_train)
|
|
|
|
# Predictions
|
|
y_train_pred_rf = rf_model.predict(X_train)
|
|
y_test_pred_rf = rf_model.predict(X_test)
|
|
|
|
# Metrics - Training
|
|
train_mse_rf = mean_squared_error(y_train, y_train_pred_rf)
|
|
train_rmse_rf = np.sqrt(train_mse_rf)
|
|
train_mae_rf = mean_absolute_error(y_train, y_train_pred_rf)
|
|
train_r2_rf = r2_score(y_train, y_train_pred_rf)
|
|
|
|
# Metrics - Testing
|
|
test_mse_rf = mean_squared_error(y_test, y_test_pred_rf)
|
|
test_rmse_rf = np.sqrt(test_mse_rf)
|
|
test_mae_rf = mean_absolute_error(y_test, y_test_pred_rf)
|
|
test_r2_rf = r2_score(y_test, y_test_pred_rf)
|
|
|
|
print(f"\nTraining Metrics:")
|
|
print(f" R² Score: {train_r2_rf:.4f}")
|
|
print(f" Mean Absolute Error: {train_mae_rf:.2f}")
|
|
print(f" Root Mean Squared Error: {train_rmse_rf:.2f}")
|
|
|
|
print(f"\nTesting Metrics:")
|
|
print(f" R² Score: {test_r2_rf:.4f}")
|
|
print(f" Mean Absolute Error: {test_mae_rf:.2f}")
|
|
print(f" Root Mean Squared Error: {test_rmse_rf:.2f}")
|
|
|
|
# ============================================================================
|
|
# COMPARISON & RECOMMENDATION
|
|
# ============================================================================
|
|
print("\n" + "=" * 80)
|
|
print("PERBANDINGAN KEDUA MODEL")
|
|
print("=" * 80)
|
|
|
|
comparison_data = {
|
|
'Metrics': ['R² Score', 'MAE', 'RMSE'],
|
|
'Linear Regression': [f'{test_r2_lr:.4f}', f'{test_mae_lr:.2f}', f'{test_rmse_lr:.2f}'],
|
|
'Random Forest': [f'{test_r2_rf:.4f}', f'{test_mae_rf:.2f}', f'{test_rmse_rf:.2f}']
|
|
}
|
|
comparison_df = pd.DataFrame(comparison_data)
|
|
|
|
print("\nHasil Testing (Test Set):")
|
|
print(comparison_df.to_string(index=False))
|
|
|
|
print("\n" + "-" * 80)
|
|
print("REKOMENDASI:")
|
|
print("-" * 80)
|
|
|
|
if test_r2_rf > test_r2_lr:
|
|
best_model = "Random Forest"
|
|
best_r2 = test_r2_rf
|
|
worst_r2 = test_r2_lr
|
|
else:
|
|
best_model = "Linear Regression"
|
|
best_r2 = test_r2_lr
|
|
worst_r2 = test_r2_rf
|
|
|
|
app_model = "Random Forest"
|
|
app_r2 = test_r2_rf
|
|
app_mae = test_mae_rf
|
|
app_rmse = test_rmse_rf
|
|
|
|
print(f"\n[BEST] MODEL TERBAIK: {best_model}")
|
|
print(f" - Akurasi {best_model} (R²) = {best_r2:.4f}")
|
|
if test_r2_rf > test_r2_lr:
|
|
print(f" - Akurasi Linear Regression (R²) = {test_r2_lr:.4f}")
|
|
print(f" - Perbedaan: {(test_r2_rf - test_r2_lr):.4f}")
|
|
else:
|
|
print(f" - Akurasi Random Forest (R²) = {test_r2_rf:.4f}")
|
|
print(f" - Perbedaan: {(test_r2_lr - test_r2_rf):.4f}")
|
|
|
|
print("\n[INFO] Model yang disimpan untuk aplikasi: Random Forest")
|
|
print("[INFO] Linear Regression hanya dipakai sebagai pembanding evaluasi.")
|
|
|
|
# ============================================================================
|
|
# SAVE RESULTS
|
|
# ============================================================================
|
|
results_file = open(os.path.join(script_dir, 'model_testing_results.txt'), 'w', encoding='utf-8')
|
|
results_file.write("=" * 80 + "\n")
|
|
results_file.write("HASIL TESTING MODEL - PREDIKSI PERMINTAAN STOK BAHAN KUE\n")
|
|
results_file.write("=" * 80 + "\n\n")
|
|
|
|
results_file.write(f"Total Data: {df.shape[0]} baris\n")
|
|
results_file.write(f"Training Set: {len(X_train)} ({len(X_train)/len(X)*100:.1f}%)\n")
|
|
results_file.write(f"Testing Set: {len(X_test)} ({len(X_test)/len(X)*100:.1f}%)\n")
|
|
results_file.write(f"Features: {len(feature_cols)}\n")
|
|
results_file.write(f"Fitur yang dihapus karena data leakage: {removed_leakage_cols}\n")
|
|
results_file.write(f"Target: {target_col}\n")
|
|
if date_col:
|
|
results_file.write("Metode split: chronological split berdasarkan tanggal transaksi\n")
|
|
results_file.write(f"Periode Training: {date_series.iloc[:split_index].min().date()} s/d {date_series.iloc[:split_index].max().date()}\n")
|
|
results_file.write(f"Periode Testing: {date_series.iloc[split_index:].min().date()} s/d {date_series.iloc[split_index:].max().date()}\n")
|
|
results_file.write("\n")
|
|
|
|
results_file.write("LINEAR REGRESSION - Test Metrics:\n")
|
|
results_file.write(f" R² Score: {test_r2_lr:.4f}\n")
|
|
results_file.write(f" MAE: {test_mae_lr:.2f}\n")
|
|
results_file.write(f" RMSE: {test_rmse_lr:.2f}\n\n")
|
|
|
|
results_file.write("RANDOM FOREST - Test Metrics:\n")
|
|
results_file.write(f" R² Score: {test_r2_rf:.4f}\n")
|
|
results_file.write(f" MAE: {test_mae_rf:.2f}\n")
|
|
results_file.write(f" RMSE: {test_rmse_rf:.2f}\n\n")
|
|
|
|
results_file.close()
|
|
|
|
print(f"\n[OK] Hasil disimpan ke: {os.path.join(script_dir, 'model_testing_results.txt')}")
|
|
|
|
# ============================================================================
|
|
# SAVE BEST MODEL
|
|
# ============================================================================
|
|
print("\n" + "=" * 80)
|
|
print("MENYIMPAN MODEL TERBAIK")
|
|
print("=" * 80)
|
|
|
|
joblib.dump(rf_model, os.path.join(script_dir, 'model_prediksi.pkl'))
|
|
print("\n[OK] Model Random Forest disimpan: model_prediksi.pkl")
|
|
|
|
# Save encoders
|
|
joblib.dump(encoders, os.path.join(script_dir, 'encoders.pkl'))
|
|
print("[OK] Encoders disimpan: encoders.pkl")
|
|
|
|
# Save feature columns
|
|
joblib.dump(feature_cols, os.path.join(script_dir, 'feature_columns.pkl'))
|
|
print("[OK] Feature columns disimpan: feature_columns.pkl")
|
|
|
|
# Save metadata
|
|
metadata = {
|
|
'model_type': app_model,
|
|
'r2_score': app_r2,
|
|
'mae': app_mae,
|
|
'rmse': app_rmse,
|
|
'feature_columns': feature_cols,
|
|
'removed_leakage_columns': removed_leakage_cols,
|
|
'split_method': 'chronological',
|
|
'comparison_best_model': best_model,
|
|
'target_column': target_col,
|
|
'total_samples': len(df)
|
|
}
|
|
|
|
joblib.dump(metadata, os.path.join(script_dir, 'model_metadata.pkl'))
|
|
print("[OK] Metadata disimpan: model_metadata.pkl")
|
|
|
|
print("\nFile yang telah disimpan:")
|
|
print(" 1. model_prediksi.pkl - Model Terbaik")
|
|
print(" 2. encoders.pkl - Label Encoders")
|
|
print(" 3. feature_columns.pkl - Daftar Fitur")
|
|
print(" 4. model_metadata.pkl - Metadata Model")
|
|
|
|
print("\n" + "=" * 80)
|