I am trying to implement a simple modelling pipeline with rolling c.v., making use of the TimeSeries split. The code is provided below with a working example dataset. (please don't pay too much attention to the dataset construction, it is a way to get a working exemple for the pipeline, not a way to replicate a good regression problem).
import pandas as pd, numpy as np
import seaborn as sns, matplotlib.pyplot as plt
from sklearn.datasets import make_regression
from sklearn.dummy import DummyRegressor
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import TimeSeriesSplit
X_test, y_test = [], []
start_year = 2010
end_year = 2020
#create data
for year in np.arange(start_year, end_year+1):
X_year, y_year = make_regression(n_samples=5, n_features=2, noise=1, random_state=year)
X_year = pd.DataFrame(X_year).rename(columns={0:'X1', 1:'X2'})
X_year['year'] = year
y_year = pd.Series(y_year)
X_test.append(X_year)
y_test.append(y_year)
X_test, y_test = pd.concat(X_test), pd.concat(y_test)
# modelling
X = X_test
y = y_test
years = np.unique(X_test['year'])
# modelisation
model = DummyRegressor(strategy="mean")
metric = mean_squared_error
cv_scheme = TimeSeriesSplit(n_splits=len(years)-1)
years_folds = []
res = []
for i, (train_year, test_year) in enumerate(cv_scheme.split(years)):
print(f"Fold {i}:")
print(f" Train: index={years[train_year]}")
print(f" Test: index={years[test_year]}")
years_folds.append((years[train_year], years[test_year]))
train_filter = X['year'].isin(years[train_year])
test_filter = X['year'].isin(years[train_year])
X_train, y_train = X.loc[train_filter.values], y[train_filter.values]
X_test, y_test = X.loc[test_filter.values], y[test_filter.values]
model.fit(X_train, y_train)
preds = model.predict(X_test)
score = metric(preds, y_test)
print(f' {score=:.3}')
res.append((years[test_year][0], score))
folds_res = pd.DataFrame(res,columns=['test_year', metric.__name__])
folds_res.plot.scatter(x='test_year', y=metric.__name__, title=f'{metric.__name__} over test_year');
The code produces the expected output, that is it perform a cross validation with the scheme illustrated below.
However, I am wondering if this can be simplified, notably:
- The cv split is done on year then the data is filtered based on years splits, isn't there a more natural way to use the spliter?
- Isn't there a better pipeline approach to handle everything without a loop ? (just a function taking model, metric and cv scheme and outputting the results per fold / the plot)
- Can this be extended to parameter tuning?