Exogenous Variables for Predictive Maintenance
Statistically prove which exogenous variables improve maintenance predictions.
Introduction
30 Second Summary
Imaginez que votre equipe vous demande si la meteo, l'usure des equipements ou le calendrier influencent reellement les besoins en maintenance. Vous pourriez deviner, ou vous pourriez le prouver statistiquement.
Dans ce projet, vous construirez un notebook d'analyse complet qui teste rigoureusement l'impact de variables exogenes sur la prediction de maintenance, compare 3 modeles de prediction, et valide que les resultats ne sont pas un artefact statistique.
What You'll Build
A la fin de ce projet, vous pourrez lancer votre notebook, executer chaque cellule, et presenter a votre equipe un tableau comparatif prouvant exactement quelles variables externes ameliorent vos predictions de maintenance (et de combien).
By the end of this project, you'll have:
- Un pipeline de tests statistiques (stationnarite, causalite de Granger, heteroscedasticite) qui identifie quelles variables exogenes causent la maintenance et a quel delai temporel elles agissent.
- Un benchmark comparatif de 3 modeles (SARIMAX, Prophet, XGBoost) avec un tableau MAE, RMSE et MAPE montrant le gain mesurable apporte par les variables exogenes.
- Un diagnostic de robustesse complet (analyse des residus, intervalles de confiance, courbes train/test et cross-validation temporelle) prouvant que vos modeles ne sont pas en overfitting.
- Secret Mission: Calculer les SHAP values pour visualiser la contribution marginale de chaque variable exogene sur les predictions XGBoost.
Y a-t-il des prerequis ?
Ce projet suppose une connaissance de base en Python et pandas, ainsi que des notions de series temporelles (tendance, saisonnalite, stationnarite). Vous devez avoir Jupyter Notebook et les librairies listees deja installes sur votre machine.
Before We Start
Before we dive in, take a moment to commit to what you're about to build and why it matters. You're going to prove statistically which exogenous variables (weather, equipment age, calendar effects) actually drive maintenance predictions, and which ones are noise.
Set Up and Prepare the Data
Every statistical test and model you build in this project depends on one thing: clean, properly structured data.
Without excluding data leakage and setting the correct temporal index, your stationarity tests, Granger causality results, and model comparisons would all produce invalid conclusions. In this step, you will import all necessary libraries, remove a leakage column, and prepare your DataFrame with a proper weekly DatetimeIndex.
In this step, get ready to:
- Import all required libraries (statsmodels, Prophet, XGBoost, SHAP, scikit-learn).
- Remove data leakage and configure the temporal index.
- Verify the DataFrame structure and visualize the target series.
Import all libraries
This project uses a wide set of tools for time series analysis, statistical testing, and machine learning. You need them all loaded upfront so you can call any function without interrupting your analysis flow later.
- In your Jupyter Notebook, create a new cell and paste the following imports:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from statsmodels.tsa.stattools import adfuller, kpss, grangercausalitytests, acf, pacf
from statsmodels.stats.diagnostic import het_breuschpagan, het_arch
from statsmodels.tsa.statespace.sarimax import SARIMAX
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.stats.stattools import durbin_watson
from statsmodels.stats.diagnostic import acorr_ljungbox
from scipy import stats
from sklearn.metrics import mean_absolute_error, mean_squared_error
from sklearn.model_selection import TimeSeriesSplit
from statsmodels.stats.outliers_influence import variance_inflation_factor
from prophet import Prophet
import xgboost as xgb
import shap
import warnings
warnings.filterwarnings('ignore')
What does this code do?
- statsmodels provides statistical tests (ADF, KPSS, Granger, Breusch-Pagan, ARCH) and the SARIMAX model.
- Prophet is Facebook's time series forecasting library that supports additional regressors (exogenous variables).
- XGBoost is a gradient boosting library well-suited for small datasets (under 500 observations) thanks to its strong regularization.
- SHAP decomposes each prediction into per-feature contributions based on Shapley values from game theory.
- warnings.filterwarnings('ignore') suppresses convergence and deprecation warnings that would clutter your notebook output.
- Run the cell by pressing Shift+Enter.
The cell should complete without any errors or output.
Seeing an ImportError?
If you see an error on the from prophet import Prophet line, make sure you have the prophet package installed (not the old fbprophet name). Run pip install prophet in a terminal cell.
help me debug my import errors for statsmodels, prophet, or xgboost
Load and clean the data
Your maintenance dataset (df_merged) contains the target variable (Maintenance) alongside several exogenous variables. One column poses a critical problem: Taux de remplacement maintenance/Age du K moy is derived directly from the target itself.
Why remove this column?
Data leakage occurs when information from the target leaks into your features. This column is calculated as target divided by average equipment age. Keeping it would give your models a cheat sheet, making all test results meaningless.
- In a new cell, load your data source and drop the leakage column:
# df_merged is loaded from source (CSV or generated)
# df_merged = pd.read_csv('maintenance_data.csv')
# Remove leakage column
df = df_merged.drop(columns=['Taux de remplacement maintenance/Age du K moy'])
What does this code do?
The first line loads your merged dataset (uncomment and adjust the path to match your data source). The drop() call removes the derived column that would cause leakage, storing the cleaned result in df.
- Run the cell.
No output is expected. The DataFrame is now stored in df without the leakage column.
- In the next cell, set up the temporal index and separate your target from the exogenous variables:
# Set DatetimeIndex
df['WEEK'] = pd.to_datetime(df['WEEK'])
df = df.set_index('WEEK')
df = df.asfreq('W')
# Separate target and exogenous variables
target = df['Maintenance']
exog = df.drop(columns=['Maintenance'])
What does this code do?
- pd.to_datetime() converts the WEEK column from strings to proper datetime objects.
- set_index('WEEK') makes the date column the DataFrame index, which statsmodels and Prophet require for time series operations.
- asfreq('W') explicitly declares weekly frequency so SARIMAX knows the time step between observations.
- target holds the Maintenance series you want to predict. exog holds all remaining columns (weather, equipment, calendar variables).
- Run the cell.
Seeing a KeyError on WEEK?
If you get a KeyError: 'WEEK', check that your original DataFrame uses exactly that column name (case-sensitive). Print df_merged.columns.tolist() to verify.
help me fix a KeyError when setting my DatetimeIndex
Verify the data structure
Before running any statistical test, you need to confirm the DataFrame has the expected shape, no missing values, and the correct index type.
- In a new cell, inspect the DataFrame structure:
print(df.info())
print(df.describe())
print(df.isnull().sum())
- Run the cell.
Before you look at the output, what do you expect to see for the number of rows and columns?
You should see a DatetimeIndex with freq='W-SUN' (weekly frequency), 235 entries, and 8 columns (1 target + 7 exogenous variables). All null counts should be 0.
Why 235 observations and 8 columns?
Your dataset spans approximately 4.5 years of weekly data. You started with 9 columns in df_merged but dropped the leakage column, leaving 8. The WEEK column became the index, so it no longer counts as a data column.
- In a new cell, plot the target time series to visually confirm the data looks correct:
plt.figure(figsize=(14, 4))
plt.plot(target)
plt.title('Serie temporelle - Maintenance')
plt.xlabel('Semaine')
plt.ylabel('Maintenance')
plt.tight_layout()
plt.show()
What should I see?
You should see a line chart showing the Maintenance values over time, with the x-axis spanning your full date range (roughly 4.5 years). Look for visible patterns: seasonality, trends, or sudden spikes. These visual features will guide your choice of model parameters later.
- Run the cell and confirm the plot displays correctly.
Plot not showing?
If the plot does not appear, make sure you are running in a Jupyter environment with inline plotting enabled. Add %matplotlib inline at the top of your notebook if needed.
help me get matplotlib plots to display in my Jupyter Notebook
✔️ Awesome, I've got everything!
Great. Confirm you have saved your notebook with all 7 cells in place.
ⓧ I'd like to double check the full code
Scroll up through your notebook and confirm you have the following cells in order:
- Cell 1: All imports (pandas, numpy, matplotlib, seaborn, statsmodels, scipy, sklearn, prophet, xgboost, shap, warnings).
- Cell 2: Data loading and leakage column removal (df_merged.drop(columns=['Taux de remplacement maintenance/Age du K moy'])).
- Cell 3: DatetimeIndex setup (pd.to_datetime, set_index, asfreq('W')) and target/exog separation.
- Cell 4: df.info(), df.describe(), and df.isnull().sum().
- Cell 5: Target time series plot.
Each cell should run without errors, and you should see the info output plus the maintenance time series chart.
Your data is clean, properly indexed, and ready for analysis. Next up, you will explore correlations and multicollinearity between your exogenous variables to understand their linear relationships before testing for temporal causality.
Exploratory Analysis and Correlation
Your data is clean, properly indexed, and free of leakage. You have a clear target series and 7 exogenous variables ready for analysis.
Before running any predictive model, you need to understand how your variables relate to each other. This step reveals which exogenous variables correlate with maintenance, whether they suffer from multicollinearity, and at what time lag each one exerts its strongest influence.
In this step, get ready to:
- Compute the Pearson correlation matrix and visualize it as an annotated heatmap.
- Calculate the VIF for each exogenous variable to detect multicollinearity.
- Compute the Cross-Correlation Function (CCF) between each exogenous variable and the target across multiple lags.
Compute the Pearson correlation matrix
The Pearson correlation coefficient measures the linear relationship between two variables on a scale from -1 to +1. A value close to +1 or -1 indicates a strong linear relationship, while 0 means no linear association.
- Create a new cell in your notebook and compute the correlation matrix with a heatmap by running this code:
# Cell 8: Pearson correlation matrix
corr_matrix = df.corr()
plt.figure(figsize=(10, 8))
sns.heatmap(corr_matrix, annot=True, fmt='.2f', cmap='coolwarm', center=0)
plt.title('Matrice de correlation de Pearson')
plt.tight_layout()
plt.show()
What does this code do?
This cell computes the Pearson correlation between every pair of columns in your DataFrame, including the target.
- df.corr() produces the full correlation matrix (target + all exogenous variables).
- sns.heatmap(..., annot=True, fmt='.2f') renders each coefficient directly on the cell so you can read exact values.
- cmap='coolwarm', center=0 centers the color scale at zero. Blue means negative correlation, red means positive.
- Run the cell.
- You should see an annotated heatmap with correlation values between -1 and +1 for every variable pair. Look for cells in deep red or deep blue in the Maintenance row to spot strong linear relationships.
Heatmap not showing?
- Make sure you ran all previous cells first. The variable df must exist with your DatetimeIndex.
- If you see a FutureWarning about numeric_only, add numeric_only=True inside the .corr() call. The heatmap still renders correctly either way.
Help me debug my correlation heatmap
Calculate the VIF for multicollinearity detection
Correlation shows pairwise relationships, but it does not reveal when multiple variables together create redundancy. The Variance Inflation Factor (VIF) quantifies how much each variable's variance is inflated by its linear relationship with ALL other exogenous variables simultaneously.
What does VIF tell you?
- VIF = 1 means no multicollinearity at all.
- VIF > 5 indicates problematic multicollinearity. The variable is largely explained by others.
- VIF > 10 is severe. You should consider dropping or combining the variable.
Before you run this next cell, think about what you expect. Based on the heatmap, do you think any exogenous variables are highly correlated with each other?
- Create a new cell and compute the VIF for each exogenous variable by running this code:
# Cell 9: VIF
from statsmodels.stats.outliers_influence import variance_inflation_factor
vif_data = pd.DataFrame()
vif_data['feature'] = exog.columns
vif_data['VIF'] = [variance_inflation_factor(exog.values, i) for i in range(exog.shape[1])]
print(vif_data.sort_values('VIF', ascending=False))
What does this code do?
- variance_inflation_factor(exog.values, i) computes the VIF for the i-th column by regressing it against all other exogenous columns.
- The result is sorted descending so the most problematic variables appear at the top.
- The import is repeated here so Cell 9 can run independently if you restart the notebook at this point.
- Run the cell.
- You should see a printed table with each feature name and its VIF value. Check whether any variable has a VIF above 5.
How to interpret your VIF results
If all VIF values are below 5, your exogenous variables are sufficiently independent from each other. You can use them all in your models without concern.
If some are above 5, note them for later. In Step 4, you will decide whether to combine them, drop them, or use a model (like XGBoost) that handles collinearity naturally.
🙋♀️ Seeing infinity or very large VIF values?
- Infinite VIF means one variable is a perfect linear combination of others. Check if you accidentally kept the leakage column or a constant column.
- Confirm that exog does not include the Maintenance column. Run exog.columns to check.
Help me understand my VIF results
Compute the Cross-Correlation Function (CCF)
The correlation matrix shows simultaneous relationships, but in time series, a variable might influence maintenance with a delay. The Cross-Correlation Function measures correlation at different time lags, revealing WHEN each exogenous variable has its peak influence.
A positive lag means the exogenous variable leads the target. A negative lag means it follows. The lag with the highest absolute correlation is the optimal delay of influence.
- Create a new cell and compute the CCF for each exogenous variable by running this code:
# Cell 10: Cross-Correlation Function (CCF)
fig, axes = plt.subplots(len(exog.columns), 1, figsize=(12, 3 * len(exog.columns)))
for i, col in enumerate(exog.columns):
ccf_values = [target.corr(exog[col].shift(lag)) for lag in range(-8, 9)]
axes[i].bar(range(-8, 9), ccf_values)
axes[i].axhline(0, color='black', linewidth=0.5)
axes[i].set_title(f'CCF: {col} vs Maintenance')
axes[i].set_xlabel('Lag (semaines)')
axes[i].set_ylabel('Correlation')
plt.tight_layout()
plt.show()
What does this code do?
- exog[col].shift(lag) shifts the exogenous variable forward or backward by the specified number of weeks.
- target.corr(exog[col].shift(lag)) calculates the Pearson correlation between the target and the shifted exogenous variable at each lag from -8 to +8 weeks.
- The bar chart for each variable shows correlation strength at each lag. Taller bars indicate stronger time-delayed relationships.
- Run the cell.
- You should see a grid of bar charts, one per exogenous variable. Each chart shows correlation values from lag -8 to lag +8 weeks.
Look at each subplot and identify the lag (positive side) with the tallest bar. That lag is the optimal delay at which that variable most strongly influences maintenance.
Why lags from -8 to +8?
With weekly data, 8 weeks represents about 2 months of potential delay. This is a reasonable maximum for most maintenance-related variables.
In Step 3, the Granger causality test will formally confirm whether the relationships you see here are statistically significant predictors.
🙋♀️ CCF plot not rendering correctly?
- If you see only one subplot or an error about axes indexing, verify that exog has more than one column by running exog.shape.
- If all bars are at 0 for a variable, that variable may be constant. Check with exog[col].nunique() to see how many unique values it has.
Help me fix my CCF subplot grid
✔️ Awesome, I've got everything!
Great. Double check you have saved your notebook with all three new cells (8, 9, and 10) producing their outputs.
ⓧ I'd like to double check the full code
Here are all three cells you added in this step. Confirm each one is present in your notebook in this order:
# Cell 8: Pearson correlation matrix
corr_matrix = df.corr()
plt.figure(figsize=(10, 8))
sns.heatmap(corr_matrix, annot=True, fmt='.2f', cmap='coolwarm', center=0)
plt.title('Matrice de correlation de Pearson')
plt.tight_layout()
plt.show()
# Cell 9: VIF
from statsmodels.stats.outliers_influence import variance_inflation_factor
vif_data = pd.DataFrame()
vif_data['feature'] = exog.columns
vif_data['VIF'] = [variance_inflation_factor(exog.values, i) for i in range(exog.shape[1])]
print(vif_data.sort_values('VIF', ascending=False))
# Cell 10: Cross-Correlation Function (CCF)
fig, axes = plt.subplots(len(exog.columns), 1, figsize=(12, 3 * len(exog.columns)))
for i, col in enumerate(exog.columns):
ccf_values = [target.corr(exog[col].shift(lag)) for lag in range(-8, 9)]
axes[i].bar(range(-8, 9), ccf_values)
axes[i].axhline(0, color='black', linewidth=0.5)
axes[i].set_title(f'CCF: {col} vs Maintenance')
axes[i].set_xlabel('Lag (semaines)')
axes[i].set_ylabel('Correlation')
plt.tight_layout()
plt.show()
You now know which variables correlate with maintenance, which ones are redundant with each other, and at what time delay each one exerts its peak influence. Next up, you will run formal statistical tests to verify stationarity, heteroscedasticity, and Granger causality on these relationships.
Run Statistical Tests
Your correlation analysis revealed which exogenous variables share linear relationships with maintenance, and the CCF identified their optimal time lags. But none of that tells you whether those relationships are statistically valid for modeling.
Without verifying stationarity and heteroscedasticity, the fundamental assumptions of SARIMAX are potentially violated. That means confidence intervals would be incorrect and your model's predictions unreliable. In this step, you will run formal statistical tests to validate these assumptions and identify which variables truly Granger-cause maintenance.
In this step, get ready to:
- Apply ADF and KPSS stationarity tests to the target and all exogenous variables.
- Run Breusch-Pagan and ARCH heteroscedasticity tests to check variance stability.
- Apply Granger causality tests to identify which exogenous variables help predict maintenance.
Apply stationarity tests (ADF + KPSS)
A time series is stationary when its statistical properties (mean, variance) do not change over time. SARIMAX requires stationary inputs. Two complementary tests check this from opposite angles.
Why two tests instead of one?
The ADF test has H0 = non-stationary (you want to reject it). The KPSS test has H0 = stationary (you do not want to reject it).
When both agree, you have a clear answer. When they disagree, the series may be trend-stationary or difference-stationary, requiring further investigation.
- Create a new cell in your notebook and add the stationarity testing function:
# Cell 11: ADF + KPSS stationarity tests
def test_stationarity(series, name):
adf_result = adfuller(series.dropna())
kpss_result = kpss(series.dropna(), regression='c', nlags='auto')
print(f'--- {name} ---')
print(f'ADF: stat={adf_result[0]:.4f}, p={adf_result[1]:.4f} -> {"Stationnaire" if adf_result[1] < 0.05 else "Non-stationnaire"}')
print(f'KPSS: stat={kpss_result[0]:.4f}, p={kpss_result[1]:.4f} -> {"Stationnaire" if kpss_result[1] > 0.05 else "Non-stationnaire"}')
test_stationarity(target, 'Maintenance')
for col in exog.columns:
test_stationarity(exog[col], col)
What does this code do?
- adfuller() runs the Augmented Dickey-Fuller test. A p-value below 0.05 means the series is stationary.
- kpss() runs the KPSS test with the opposite hypothesis. A p-value above 0.05 confirms stationarity.
- The function prints both results with an automatic conclusion for each variable.
- Run the cell. You should see a printed block for each variable showing ADF and KPSS statistics with a conclusion (Stationnaire or Non-stationnaire).
What if ADF and KPSS disagree?
If ADF says stationary but KPSS says non-stationary, the series is likely trend-stationary (remove the trend). If ADF says non-stationary but KPSS says stationary, first-order differencing is the typical fix.
For any non-stationary series, you would apply .diff().dropna() and re-run the tests. The feature engineering step later handles this transformation for the models.
🙋♀️ Seeing a KPSS interpolation warning?
statsmodels may print a warning that the test statistic is outside the range of critical values. This is informational only. The p-value is still usable for your conclusion.
help me understand the KPSS warning in my stationarity test output
Plot ACF/PACF and run heteroscedasticity tests
The ACF and PACF plots of your target series guide the order selection (p and q) for SARIMAX. After that, you will check whether the variance of residuals is constant (homoscedastic) or not.
- Create a new cell and generate the ACF/PACF plots:
# Cell 12: ACF/PACF plots
fig, axes = plt.subplots(1, 2, figsize=(14, 4))
plot_acf(target.dropna(), lags=30, ax=axes[0])
plot_pacf(target.dropna(), lags=30, ax=axes[1])
plt.suptitle('ACF et PACF - Maintenance')
plt.tight_layout()
plt.show()
How to read ACF and PACF?
The ACF shows how the target correlates with its own past values at each lag. Spikes that drop off gradually suggest an AR component. The number of significant PACF spikes tells you the AR order (p).
Significant ACF spikes that cut off abruptly suggest an MA component. The number of those spikes tells you the MA order (q).
- Run the cell. You should see two side-by-side plots showing correlation bars with a shaded confidence band.
Now test for heteroscedasticity. If variance changes over time, your model's confidence intervals will be misleading.
- Create a new cell and run the Breusch-Pagan and ARCH tests:
# Cell 13: Breusch-Pagan and ARCH tests
from statsmodels.regression.linear_model import OLS
from statsmodels.tools import add_constant
X_bp = add_constant(exog.dropna())
y_bp = target.loc[exog.dropna().index]
bp_test = het_breuschpagan(OLS(y_bp, X_bp).fit().resid, X_bp)
print(f'Breusch-Pagan: stat={bp_test[0]:.4f}, p={bp_test[1]:.4f} -> {"Heteroscedastique" if bp_test[1] < 0.05 else "Homoscedastique"}')
arch_test = het_arch(target.dropna())
print(f'ARCH: stat={arch_test[0]:.4f}, p={arch_test[1]:.4f} -> {"Heteroscedastique" if arch_test[1] < 0.05 else "Homoscedastique"}')
What do these two tests check?
- Breusch-Pagan checks if residual variance depends on the exogenous variables (conditional on X). H0 = homoscedastic.
- ARCH checks if residual variance depends on past residuals (conditional on time). H0 = homoscedastic.
- For both tests, p-value below 0.05 means you reject homoscedasticity and the series has unstable variance.
- Run the cell. You should see two printed lines with the test statistic, p-value, and a conclusion for each test.
Getting a singular matrix error?
This happens when exogenous variables contain constant columns or perfect linear combinations. Check that you removed the leakage column in Step 1 and that no column has zero variance.
help me debug a singular matrix error in my Breusch-Pagan test
Run Granger causality tests
The Granger causality test answers a specific question: does the past of variable X help predict the target beyond what the target's own past already provides? This is stronger than simple correlation because it tests predictive power across time.
What does Granger causality actually test?
H0: the exogenous variable does NOT Granger-cause the target. A p-value below 0.05 means the exogenous variable adds predictive information beyond the target's own history.
With maxlag=8, the test checks lags from 1 to 8 weeks. The code extracts the lag with the smallest p-value as the best lag for each variable.
- Create a new cell and run the Granger causality tests on all exogenous variables:
# Cell 14: Granger causality tests
granger_results = []
for col in exog.columns:
data_granger = pd.concat([target, exog[col]], axis=1).dropna()
gc_test = grangercausalitytests(data_granger, maxlag=8, verbose=False)
best_lag = min(gc_test, key=lambda x: gc_test[x][0]['ssr_ftest'][1])
best_pval = gc_test[best_lag][0]['ssr_ftest'][1]
granger_results.append({'Variable': col, 'Best Lag': best_lag, 'p-value': best_pval, 'Granger-cause': best_pval < 0.05})
granger_df = pd.DataFrame(granger_results)
print(granger_df)
What does this code do?
- grangercausalitytests() takes a 2D array where the first column is the dependent variable (target) and the second is the tested causer.
- The loop finds the lag (1 to 8) that gives the smallest p-value for the F-test.
- The final DataFrame shows each variable, its best lag, the p-value, and whether it Granger-causes maintenance.
Before you run this cell, predict: based on your CCF analysis from the previous step, which variables do you expect to Granger-cause maintenance?
- Run the cell. You should see a table with columns Variable, Best Lag, p-value, and Granger-cause (True/False) for each exogenous variable.
Compare with your CCF results
The best lag from Granger should roughly align with the peak you saw in the CCF plots. Variables that showed strong CCF peaks are likely to be Granger-significant here. Any disagreement points to non-linear effects that CCF cannot capture.
🙋♀️ All p-values showing as 0.0000?
This can happen if your data has duplicate timestamps or the series is too short for the requested maxlag. Verify your DatetimeIndex is unique with df.index.is_unique.
help me interpret Granger causality results where all p-values are zero
✔️ Awesome, I've got everything!
Great. Make sure you have saved your notebook with all four new cells (11 through 14) producing output.
ⓧ I'd like to double check the full code
Scroll up through your notebook and confirm these cells are present in order:
- Cell 11: test_stationarity function definition and loop calling it on target and all exogenous columns.
- Cell 12: ACF/PACF subplot with plot_acf and plot_pacf on target.
- Cell 13: Breusch-Pagan test using het_breuschpagan on OLS residuals, plus ARCH test using het_arch on target.
- Cell 14: Granger causality loop with grangercausalitytests producing granger_df summary table.
You now have a complete statistical profile of your data. You know which series are stationary, whether heteroscedasticity is present, and which exogenous variables genuinely help predict maintenance at specific time lags. Next up, you will transform these statistical findings into engineered features and prepare your train/test split for modeling.
Feature Engineering and Temporal Split
Your Granger causality tests and cross-correlation analysis have identified which exogenous variables significantly predict maintenance and at what temporal lag they act.
Raw test results are not directly consumable by models. In this step, you will transform those statistical discoveries into engineered features, set up a rigorous chronological evaluation split, and establish a naive baseline that every model must beat to prove its value.
In this step, get ready to:
- Create lagged and rolling features based on your CCF and Granger results.
- Split the data chronologically into train and test sets.
- Compute a naive baseline to establish the minimum performance threshold.
Create lagged and rolling features from your statistical results
The CCF and Granger causality tests told you the optimal lag for each exogenous variable. Now you need to encode that temporal relationship directly into the DataFrame as new columns.
- Create a new cell in your notebook and add the following feature engineering code:
# optimal_lags dict derived from CCF and Granger results
optimal_lags = {col: int(granger_df.loc[granger_df['Variable'] == col, 'Best Lag'].values[0]) for col in exog.columns}
df_features = df.copy()
for col in exog.columns:
lag = optimal_lags[col]
df_features[f'{col}_lag{lag}'] = df_features[col].shift(lag)
df_features[f'{col}_roll4'] = df_features[col].rolling(4).mean()
df_features = df_features.dropna()
print(f'Shape apres feature engineering: {df_features.shape}')
What does this code do?
The optimal_lags dictionary extracts the best lag for each variable directly from your granger_df results table. This ensures each feature uses the exact delay where its influence on maintenance is strongest.
For each exogenous variable, two new features are created. The shift(lag) call creates a lagged version at the optimal delay, capturing the exact temporal offset identified by the tests. The rolling(4).mean() creates a 4-week moving average to capture local trends beyond a single point.
The dropna() at the end removes the first rows where lags and rolling windows produce NaN values.
- Run the cell.
- You should see the shape printed, confirming that new columns were added and some rows were dropped.
Seeing a KeyError or empty DataFrame?
- Make sure the granger_df DataFrame was created in the previous step and contains a Best Lag column for every variable.
- Verify that exog.columns matches the variable names in your granger_df['Variable'] column exactly.
- help me debug my feature engineering cell
Split the data chronologically into train and test sets
Time series data requires a chronological split. A random split would leak future information into the training set, making performance metrics artificially optimistic.
- Create a new cell and add the temporal split code:
# Temporal train/test split (80/20)
split_idx = int(len(df_features) * 0.8)
train = df_features.iloc[:split_idx]
test = df_features.iloc[split_idx:]
plt.figure(figsize=(14, 4))
plt.plot(train['Maintenance'], label='Train')
plt.plot(test['Maintenance'], label='Test')
plt.axvline(x=train.index[-1], color='red', linestyle='--', label='Coupure train/test')
plt.title('Split temporel Train/Test')
plt.legend()
plt.tight_layout()
plt.show()
Why a chronological split matters
Unlike classification or regression on tabular data, time series cannot be shuffled. The first 80% of observations (chronologically) form the training set, and the last 20% form the test set. This mimics the real-world scenario where you train on historical data and predict future maintenance.
The red dashed line on the plot marks this boundary visually. Everything to its left is known history. Everything to its right is what your models will try to predict.
- Run the cell.
- You should see a time series plot with the train portion in one color, the test portion in another, and a red dashed vertical line marking the split point.
Plot not displaying?
- Confirm that df_features is not empty by checking its shape in a separate cell.
- Make sure matplotlib was imported correctly in your first cell and that %matplotlib inline is active in your notebook.
- help me fix my train/test split visualization
Compute a naive baseline prediction
A naive baseline uses the simplest possible prediction strategy: next week's maintenance equals this week's maintenance. Any model that cannot beat this threshold is adding complexity without value.
- Create a new cell and compute the baseline metrics:
# Naive baseline: predict next week = this week
y_train = train['Maintenance']
y_test = test['Maintenance']
baseline_pred = y_test.shift(1).dropna()
y_test_aligned = y_test.loc[baseline_pred.index]
baseline_mae = mean_absolute_error(y_test_aligned, baseline_pred)
baseline_rmse = np.sqrt(mean_squared_error(y_test_aligned, baseline_pred))
baseline_mape = np.mean(np.abs((y_test_aligned - baseline_pred) / y_test_aligned)) * 100
print(f'Baseline Naive - MAE: {baseline_mae:.4f}, RMSE: {baseline_rmse:.4f}, MAPE: {baseline_mape:.2f}%')
What do these metrics mean?
- MAE (Mean Absolute Error): the average magnitude of prediction errors in the same units as your target.
- RMSE (Root Mean Squared Error): penalizes large errors more heavily than MAE. Useful for detecting if your model occasionally makes big mistakes.
- MAPE (Mean Absolute Percentage Error): expresses error as a percentage of the actual value. Makes it easy to communicate performance to non-technical stakeholders.
Before you run this cell, take a moment to predict: do you think the naive baseline will perform well or poorly on your maintenance data?
- Run the cell.
- You should see the three metrics printed. These numbers are the floor that SARIMAX, Prophet, and XGBoost must beat in the next step.
Seeing a NameError on mean_absolute_error?
- Make sure you imported mean_absolute_error and mean_squared_error from sklearn.metrics in your imports cell at the top of the notebook.
- If the import exists but you restarted the kernel, re-run all cells from the top.
- help me fix my baseline metrics calculation
✔️ Awesome, I've got everything!
Great. Confirm that all three cells (feature engineering, split, baseline) are saved in your notebook.
ⓧ I'd like to double check the full code
Scroll up through this step and confirm that your notebook contains these three cells in order:
- Cell 15: Feature engineering (builds optimal_lags from granger_df, creates lagged and rolling features, drops NaN rows, and prints the shape).
- Cell 16: Temporal split (computes split_idx at 80%, creates train and test DataFrames, plots the split with a red dashed line).
- Cell 17: Naive baseline (extracts y_train and y_test, computes shift-1 baseline, prints MAE, RMSE, and MAPE).
Your features are engineered, your split is rigorous, and you have a performance floor to beat. Next up, you will train SARIMAX, Prophet, and XGBoost to see how much the exogenous variables actually improve predictions over this naive baseline.
Comparative Modeling and Diagnostics
La baseline naive de l'etape precedente a etabli un seuil de performance minimal. L'enjeu maintenant est de determiner si les variables exogenes que vous avez analysees (stationnarite, causalite, lags optimaux) apportent un gain reel et mesurable.
Vous allez entrainer 3 modeles differents (statistique, bayesien, ML) avec et sans variables exogenes, puis verifier que l'amelioration observee n'est pas un artefact d'overfitting. Chaque approche apporte un angle de validation complementaire.
In this step, get ready to:
- Entrainer SARIMAX avec et sans exogenes et Prophet avec regresseurs additionnels.
- Entrainer XGBoost et valider l'absence d'overfitting via cross-validation temporelle.
- Analyser les residus de chaque modele et construire le tableau comparatif final.
Train SARIMAX and Prophet with exogenous variables
SARIMAX est un modele statistique classique qui integre directement les variables exogenes dans son equation. En entrainant d'abord sans exogenes puis avec, vous isolez exactement le gain apporte par vos variables externes.
- Dans une nouvelle cellule de votre notebook, entrainer SARIMAX sans variables exogenes en utilisant les ordres guides par l'ACF/PACF :
# SARIMAX without exogenous variables
sarimax_no_exog = SARIMAX(y_train, order=(1, 0, 1), seasonal_order=(1, 0, 1, 52))
sarimax_no_exog_fit = sarimax_no_exog.fit(disp=False)
pred_no_exog = sarimax_no_exog_fit.forecast(steps=len(y_test))
mae_sarimax_no = mean_absolute_error(y_test, pred_no_exog)
rmse_sarimax_no = np.sqrt(mean_squared_error(y_test, pred_no_exog))
mape_sarimax_no = np.mean(np.abs((y_test.values - pred_no_exog.values) / y_test.values)) * 100
What does this code do?
L'ordre (1, 0, 1) correspond a un AR(1) et MA(1) sans differencing, guides par vos plots ACF/PACF de l'etape 3.
L'ordre saisonnier (1, 0, 1, 52) capture la saisonnalite annuelle (52 semaines). Ce modele n'utilise que l'historique de la target pour predire.
- Executez la cellule. Notez les valeurs MAE, RMSE et MAPE affichees.
Maintenant, ajoutez les variables exogenes pour mesurer leur impact. Dans la cellule suivante, entrainons SARIMAX avec toutes les features et visualisons les intervalles de confiance.
- Creer une nouvelle cellule et entrainer SARIMAX avec variables exogenes puis afficher les predictions avec intervalles de confiance :
# SARIMAX with exogenous variables
exog_cols = [c for c in df_features.columns if c != 'Maintenance']
X_train_exog = train[exog_cols]
X_test_exog = test[exog_cols]
sarimax_exog = SARIMAX(y_train, exog=X_train_exog, order=(1, 0, 1), seasonal_order=(1, 0, 1, 52))
sarimax_exog_fit = sarimax_exog.fit(disp=False)
pred_exog_forecast = sarimax_exog_fit.forecast(steps=len(y_test), exog=X_test_exog)
mae_sarimax_exog = mean_absolute_error(y_test, pred_exog_forecast)
rmse_sarimax_exog = np.sqrt(mean_squared_error(y_test, pred_exog_forecast))
mape_sarimax_exog = np.mean(np.abs((y_test.values - pred_exog_forecast.values) / y_test.values)) * 100
# Confidence intervals
pred_ci = sarimax_exog_fit.get_forecast(steps=len(y_test), exog=X_test_exog).conf_int()
plt.figure(figsize=(14, 4))
plt.plot(y_test.values, label='Reel')
plt.plot(pred_exog_forecast.values, label='SARIMAX avec exog')
plt.fill_between(range(len(y_test)), pred_ci.iloc[:, 0], pred_ci.iloc[:, 1], alpha=0.2, label='IC 95%')
plt.title('SARIMAX avec variables exogenes - Predictions et IC')
plt.legend()
plt.tight_layout()
plt.show()
What does this code do?
- exog_cols selectionne toutes les colonnes sauf la target (features originales + laggees + rolling).
- Le parametre exog=X_train_exog integre les variables exogenes dans l'equation SARIMAX.
- get_forecast().conf_int() calcule les intervalles de confiance a 95% autour des predictions.
- La zone ombree sur le graphique visualise l'incertitude du modele sur chaque prediction.
- Executez la cellule. Vous devriez voir un graphique avec la serie reelle en bleu, les predictions SARIMAX en orange, et la bande de confiance a 95% en zone transparente.
Le modele ne converge pas?
Si vous voyez un warning de convergence, le modele a atteint le nombre max d'iterations. Vous pouvez ajouter maxiter=200 dans l'appel .fit() pour lui donner plus de marge.
help me debug SARIMAX convergence
Passons maintenant a une approche bayesienne. Prophet permet d'ajouter des regresseurs et d'extraire directement leurs coefficients pour mesurer l'impact unitaire de chaque variable.
- Creer une nouvelle cellule et entrainer Prophet avec les regresseurs exogenes :
# Prophet with regressors
df_prophet_train = train[['Maintenance'] + list(exog.columns)].reset_index().rename(columns={'WEEK': 'ds', 'Maintenance': 'y'})
df_prophet_test = test[['Maintenance'] + list(exog.columns)].reset_index().rename(columns={'WEEK': 'ds', 'Maintenance': 'y'})
prophet_model = Prophet()
for col in exog.columns:
prophet_model.add_regressor(col)
prophet_model.fit(df_prophet_train)
future = df_prophet_test[['ds'] + list(exog.columns)]
prophet_forecast = prophet_model.predict(future)
mae_prophet = mean_absolute_error(df_prophet_test['y'], prophet_forecast['yhat'])
rmse_prophet = np.sqrt(mean_squared_error(df_prophet_test['y'], prophet_forecast['yhat']))
mape_prophet = np.mean(np.abs((df_prophet_test['y'].values - prophet_forecast['yhat'].values) / df_prophet_test['y'].values)) * 100
regressor_coeffs = prophet_model.params
print('Prophet regressor coefficients:')
print(prophet_model.params)
What does this code do?
- Prophet exige un DataFrame avec les colonnes ds (date) et y (target).
- add_regressor() ajoute chaque variable exogene comme regresseur additionnel dans le modele.
- Les coefficients imprimes montrent l'impact unitaire de chaque exogene sur la prediction. Un coefficient proche de zero indique un regresseur non significatif.
- Executez la cellule. Prophet affiche les coefficients de chaque regresseur. Notez quelles variables ont les coefficients les plus eleves en valeur absolue.
Prophet affiche des warnings de saisonnalite?
Prophet detecte automatiquement la saisonnalite annuelle sur vos donnees hebdomadaires. Les warnings informationnels sur la saisonnalite sont normaux et n'affectent pas la qualite du modele.
help me with Prophet warnings
Train XGBoost and detect overfitting
XGBoost apporte une troisieme perspective : un modele de gradient boosting qui capture les relations non-lineaires entre features. Le risque avec 235 observations est l'overfitting. Vous allez le detecter explicitement.
- Creer une nouvelle cellule et entrainer XGBoost avec early stopping :
# XGBoost training
feature_cols = [c for c in df_features.columns if c != 'Maintenance']
X_train_xgb = train[feature_cols]
X_test_xgb = test[feature_cols]
xgb_model = xgb.XGBRegressor(n_estimators=200, learning_rate=0.05, max_depth=4, early_stopping_rounds=20, eval_metric='mae')
xgb_model.fit(X_train_xgb, y_train, eval_set=[(X_train_xgb, y_train), (X_test_xgb, y_test)], verbose=False)
pred_xgb = xgb_model.predict(X_test_xgb)
mae_xgb = mean_absolute_error(y_test, pred_xgb)
rmse_xgb = np.sqrt(mean_squared_error(y_test, pred_xgb))
mape_xgb = np.mean(np.abs((y_test.values - pred_xgb) / y_test.values)) * 100
What does this code do?
- early_stopping_rounds=20 arrete l'entrainement si la MAE sur le test set ne s'ameliore pas pendant 20 iterations consecutives.
- eval_set fournit les donnees train et test pour suivre la performance a chaque iteration.
- max_depth=4 limite la profondeur des arbres pour eviter le surapprentissage sur un petit dataset.
- Executez la cellule. Notez les metriques MAE, RMSE, MAPE obtenues.
L'entrainement est termine, mais comment savoir si le modele generalise ou memorise? Comparons les performances train vs test, puis validons avec une cross-validation temporelle.
- Dans la meme cellule (ou une nouvelle), ajouter la detection d'overfitting et la cross-validation temporelle :
# Overfitting detection
pred_xgb_train = xgb_model.predict(X_train_xgb)
mae_xgb_train = mean_absolute_error(y_train, pred_xgb_train)
print(f'XGBoost MAE Train: {mae_xgb_train:.4f}, MAE Test: {mae_xgb:.4f}')
# TimeSeriesSplit cross-validation
tscv = TimeSeriesSplit(n_splits=5)
cv_scores = []
for fold_train_idx, fold_test_idx in tscv.split(df_features):
fold_train = df_features.iloc[fold_train_idx]
fold_test = df_features.iloc[fold_test_idx]
xgb_cv = xgb.XGBRegressor(n_estimators=200, learning_rate=0.05, max_depth=4)
xgb_cv.fit(fold_train[feature_cols], fold_train['Maintenance'])
fold_pred = xgb_cv.predict(fold_test[feature_cols])
cv_scores.append(mean_absolute_error(fold_test['Maintenance'], fold_pred))
print(f'CV MAE scores: {cv_scores}, Mean: {np.mean(cv_scores):.4f}')
What does this code do?
La comparaison MAE Train vs MAE Test revele l'ecart de generalisation. Si le MAE train est largement inferieur au MAE test, le modele surapprend.
TimeSeriesSplit avec 5 folds cree des splits chronologiques progressifs (le train grandit a chaque fold, jamais de fuite temporelle). La stabilite des scores entre folds confirme la robustesse.
- Executez la cellule. Comparez le MAE Train et le MAE Test. Un ratio raisonnable (train pas plus de 2x meilleur que test) indique une generalisation correcte.
Visualisons la courbe de loss et la feature importance pour completer le diagnostic.
- Creer une nouvelle cellule pour afficher la courbe de loss et la feature importance XGBoost :
# Loss curve
results = xgb_model.evals_result()
plt.figure(figsize=(10, 4))
plt.plot(results['validation_0']['mae'], label='Train MAE')
plt.plot(results['validation_1']['mae'], label='Test MAE')
plt.title('XGBoost - Courbe de loss train/validation')
plt.xlabel('Iterations')
plt.ylabel('MAE')
plt.legend()
plt.tight_layout()
plt.show()
# Feature importance
xgb.plot_importance(xgb_model, max_num_features=15)
plt.title('XGBoost Feature Importance')
plt.tight_layout()
plt.show()
How to read these plots?
- Si les courbes Train MAE et Test MAE convergent et restent proches, le modele generalise bien.
- Si elles divergent fortement (train continue de baisser, test stagne), c'est un signe d'overfitting.
- La feature importance montre quelles variables le modele utilise le plus dans ses decisions de split.
- Executez la cellule. Vous devriez voir deux graphiques : la courbe de loss et le bar chart des features les plus importantes.
La courbe de loss montre une divergence forte?
Si le test MAE est beaucoup plus eleve que le train MAE, reduisez max_depth a 3 ou augmentez learning_rate a 0.1 pour regulariser davantage.
help me fix XGBoost overfitting
Analyze residuals and build the final benchmark table
Les metriques globales (MAE, RMSE) ne suffisent pas pour valider un modele de series temporelles. Les residus doivent etre normaux et non-autocorreles pour que les intervalles de confiance soient fiables.
- Creer une nouvelle cellule pour analyser les residus de chaque modele (QQ-plot, Shapiro-Wilk, Ljung-Box) :
# Residuals analysis for each model
for model_name, y_pred in [('SARIMAX sans exog', pred_no_exog.values), ('SARIMAX avec exog', pred_exog_forecast.values), ('Prophet', prophet_forecast['yhat'].values), ('XGBoost', pred_xgb)]:
residuals = y_test.values - y_pred
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
stats.probplot(residuals, dist='norm', plot=axes[0])
axes[0].set_title(f'QQ-Plot residus - {model_name}')
axes[1].plot(residuals)
axes[1].axhline(0, color='red', linestyle='--')
axes[1].set_title(f'Residus - {model_name}')
plt.tight_layout()
plt.show()
shapiro_stat, shapiro_p = stats.shapiro(residuals)
lb_result = acorr_ljungbox(residuals, lags=[10], return_df=True)
print(f'{model_name} - Shapiro-Wilk p={shapiro_p:.4f}, Ljung-Box p={lb_result["lb_pvalue"].values[0]:.4f}')
How to interpret the residual diagnostics?
- QQ-Plot : si les points suivent la diagonale, les residus sont normalement distribues.
- Shapiro-Wilk : p > 0.05 signifie que l'hypothese de normalite n'est pas rejetee.
- Ljung-Box : p > 0.05 signifie qu'il n'y a pas d'autocorrelation significative dans les residus.
- Des residus non-normaux ou autocorreles indiquent que le modele manque une structure dans les donnees.
- Executez la cellule. Vous verrez 4 paires de graphiques (un par modele) et les p-values des tests statistiques.
Il est temps de rassembler tous les resultats dans un tableau comparatif final. Avant de l'executer, selon vous quel modele devrait montrer le plus grand gain par rapport a la baseline?
- Creer une derniere cellule pour construire le tableau de benchmark comparatif :
# Final benchmark table
benchmark = pd.DataFrame({
'Modele': ['Baseline Naive', 'SARIMAX sans exog', 'SARIMAX avec exog', 'Prophet', 'XGBoost'],
'MAE': [baseline_mae, mae_sarimax_no, mae_sarimax_exog, mae_prophet, mae_xgb],
'RMSE': [baseline_rmse, rmse_sarimax_no, rmse_sarimax_exog, rmse_prophet, rmse_xgb],
'MAPE (%)': [baseline_mape, mape_sarimax_no, mape_sarimax_exog, mape_prophet, mape_xgb]
})
benchmark['Gain vs Baseline MAE (%)'] = ((baseline_mae - benchmark['MAE']) / baseline_mae * 100).round(2)
print(benchmark.to_string(index=False))
What does this table tell you?
La colonne Gain vs Baseline MAE (%) quantifie le pourcentage d'amelioration de chaque modele par rapport a la prediction naive.
Comparez SARIMAX sans exog vs SARIMAX avec exog : la difference est directement attribuable aux variables exogenes. C'est la mesure la plus pure de leur impact.
- Executez la cellule. Vous devriez voir un tableau avec 5 modeles, leurs metriques respectives, et le pourcentage de gain par rapport a la baseline.
- Confirmez que les metriques train XGBoost ne sont pas dramatiquement meilleures que les metriques test (pas d'overfitting severe).
Le tableau affiche des valeurs NaN?
Verifiez que toutes les cellules precedentes ont ete executees dans l'ordre (Cell 17 a Cell 22). Les variables comme baseline_mae doivent exister en memoire.
help me debug my benchmark table
✔️ Awesome, I've got everything!
Verifiez que vous avez bien sauvegarde votre notebook avec toutes les cellules executees.
ⓧ I'd like to double check the full code
Confirmez que vous avez les cellules suivantes dans l'ordre dans votre notebook, apres les cellules de l'etape precedente :
- Cell 18 : SARIMAX sans variables exogenes (entrainement + metriques).
- Cell 19 : SARIMAX avec variables exogenes + graphique des intervalles de confiance.
- Cell 20 : Prophet avec regresseurs + affichage des coefficients.
- Cell 21 : XGBoost (entrainement, overfitting detection, cross-validation, courbe de loss, feature importance).
- Cell 22 : Analyse des residus (boucle sur les 4 modeles avec QQ-plot, Shapiro-Wilk, Ljung-Box).
- Cell 23 : Tableau de benchmark comparatif final avec colonne de gain en pourcentage.
Scrollez dans votre notebook et verifiez que chaque cellule est presente et a ete executee sans erreur.
Vous avez un tableau de benchmark complet avec les forces de chaque modele. Mais en production, miser sur un seul modele est risque. Dans l'etape suivante, vous allez combiner les meilleurs modeles via une ponderation calculee sur leur RMSE et exporter une prediction finale prete a l'emploi.
Ensemble Forecasting and Export
Votre tableau de benchmark montre les forces et faiblesses de chaque modele. Se fier a un seul modele expose vos predictions a ses biais specifiques. Une approche ensembliste combine les predictions de plusieurs modeles en ponderant chacun selon sa fiabilite mesuree.
Vous allez calculer des poids inversement proportionnels au RMSE de chaque modele, generer une prediction ensembliste sur un horizon configurable, et exporter les resultats dans un fichier CSV pret pour la production.
In this step, get ready to:
- Selectionner les meilleurs modeles et calculer les poids d'ensemble bases sur l'inverse du RMSE.
- Generer la prediction ensembliste ponderee sur un horizon configurable.
- Sauvegarder les predictions dans un fichier CSV avec les contributions de chaque modele.
Calculer les poids d'ensemble par inverse du RMSE
Le principe est simple : un modele avec un RMSE plus bas est plus fiable, donc il recoit un poids plus eleve. En inversant le RMSE et en normalisant, vous obtenez des poids qui somment a 1 et refletent la performance relative de chaque modele.
- Creer une nouvelle cellule dans votre notebook pour calculer les poids d'ensemble bases sur l'inverse du RMSE :
# Select models and compute inverse-RMSE weights
model_results = {
'SARIMAX_exog': {'rmse': rmse_sarimax_exog, 'predictions': pred_exog_forecast.values},
'Prophet': {'rmse': rmse_prophet, 'predictions': prophet_forecast['yhat'].values},
'XGBoost': {'rmse': rmse_xgb, 'predictions': pred_xgb}
}
# Inverse RMSE weighting
inverse_rmse = {name: 1.0 / res['rmse'] for name, res in model_results.items()}
total_inverse = sum(inverse_rmse.values())
weights = {name: val / total_inverse for name, val in inverse_rmse.items()}
print('Ensemble weights (inverse RMSE):')
for name, w in weights.items():
print(f' {name}: {w:.4f} (RMSE={model_results[name]["rmse"]:.4f})')
What does this code do?
- model_results rassemble les RMSE et les predictions de chaque modele dans un dictionnaire unique.
- L'inverse du RMSE (1 / RMSE) donne un score brut : plus le RMSE est bas, plus le score est eleve.
- La normalisation divise chaque score par la somme totale pour obtenir des poids entre 0 et 1 qui somment exactement a 1.
- Executez la cellule. Vous devriez voir les trois modeles avec leurs poids respectifs. Le modele avec le RMSE le plus bas recoit le poids le plus eleve.
Les poids semblent tous identiques?
Si les trois RMSE sont tres proches, les poids seront quasi-egaux (environ 0.33 chacun). C'est normal et signifie que les modeles ont des performances comparables. Verifiez que vous utilisez bien les variables rmse_sarimax_exog, rmse_prophet et rmse_xgb definies dans l'etape precedente.
help me debug my ensemble weights
Generer la prediction ensembliste et exporter en CSV
Secret mission
SHAP Values pour l'Interpretabilite
La feature importance XGBoost montre quelles variables sont utilisees par le modele, mais pas comment elles influencent chaque prediction. Allez plus loin en calculant les SHAP values pour decomposer la contribution marginale de chaque variable exogene, prediction par prediction.
Clean Up Your Resources
Clean Up Your Resources
Ce projet s'execute entierement en local sur votre machine. Il n'y a aucune ressource cloud ni cout recurrent a gerer.
Decidez si vous souhaitez conserver votre notebook tel quel, le mettre de cote pour y revenir plus tard, ou tout supprimer.
Ressources creees dans ce projet :
- Un notebook Jupyter (notebook.ipynb) contenant toute l'analyse statistique, les modeles et les diagnostics.
Keep everything running
Aucune action necessaire. Choisissez cette option si vous comptez continuer a travailler sur l'analyse ou la presenter a votre equipe.
- Votre notebook reste dans le dossier ou vous l'avez cree.
- Les librairies Python installees (statsmodels, prophet, xgboost, shap) restent disponibles dans votre environnement.
- Vous pouvez rouvrir le notebook a tout moment avec jupyter notebook.
Pause - I'll come back to this later
Arretez le serveur Jupyter pour liberer de la memoire. Vos fichiers restent intacts sur le disque.
- Dans le terminal ou tourne Jupyter, appuyez sur Ctrl+C puis confirmez avec y pour arreter le serveur.
- Le notebook et toutes vos cellules sauvegardees resteront sur votre machine.
- Pour reprendre plus tard, naviguez vers le dossier du projet et relancez jupyter notebook.
Delete - I don't want to use this again
Supprimez tous les fichiers du projet pour repartir de zero.
- Arretez le serveur Jupyter en appuyant sur Ctrl+C dans le terminal.
- Supprimez le dossier du projet. Sur macOS/Linux :
cd ~/Desktop
rm -rf notebook.ipynb
- Sur Windows :
cd ~\Desktop
Remove-Item notebook.ipynb -Force
- Si vous avez cree un environnement virtuel dedie pour ce projet, supprimez-le egalement en supprimant son dossier.
Nice Work!
Nice Work!
Vous venez de construire un notebook d'analyse statistique complet qui prouve quelles variables exogenes impactent significativement la prediction de maintenance. Vos resultats sont reproductibles, valides et prets a etre presentes a votre equipe.
Voici ce que vous avez appris :
- Identifier statistiquement quelles variables exogenes Granger-causent la maintenance, a quel lag temporel elles agissent, et construire un pipeline de tests complet (stationnarite, heteroscedasticite, causalite) avec interpretation rigoureuse.
- Comparer 3 approches de modelisation (SARIMAX, Prophet, XGBoost) et quantifier objectivement le gain apporte par les variables exogenes via un tableau de benchmark (MAE, RMSE, MAPE).
- Valider la robustesse de vos modeles via l'analyse des residus, les intervalles de confiance, et la cross-validation temporelle pour confirmer l'absence d'overfitting.
- Secret Mission : Visualiser la contribution marginale de chaque exogene via les SHAP values et verifier la coherence entre le ranking SHAP, le test de Granger, et les coefficients Prophet.
Ready to quiz yourself?