import numpy as np
from sklearn.base import clone
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_absolute_error, mean_squared_error
from sklearn.model_selection import KFold, cross_val_score, train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler

rng = np.random.default_rng(7)
X = rng.uniform(-3, 3, size=(500, 2))
y = (2 * X[:, 0] ** 2 + 0.7 * X[:, 1]
     + 1.2 * X[:, 0] * X[:, 1] + rng.normal(0, 0.5, 500))
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)
cv = KFold(n_splits=5, shuffle=True, random_state=42)
folds = list(cv.split(X_train))
models = {
    "raw": Pipeline([
        ("scale", StandardScaler()),
        ("regressor", Ridge(alpha=1.0)),
    ]),
    "degree_2": Pipeline([
        ("features", PolynomialFeatures(degree=2, include_bias=False)),
        ("scale", StandardScaler()),
        ("regressor", Ridge(alpha=1.0)),
    ]),
}
validation_mae = {}
for name, estimator in models.items():
    # sklearn scorers are larger-is-better, so MAE is negated.
    errors = -cross_val_score(estimator, X_train, y_train,
                              cv=folds, scoring="neg_mean_absolute_error")
    validation_mae[name] = errors.mean()
    print(name, "validation MAE:", round(errors.mean(), 3),
          "fold std:", round(errors.std(), 3))

selected_name = min(validation_mae, key=validation_mae.get)
selected = clone(models[selected_name]).fit(X_train, y_train)
prediction = selected.predict(X_test)
assert np.isfinite(prediction).all()
print("Selected:", selected_name)
print("Test MAE:", mean_absolute_error(y_test, prediction))
print("Test RMSE:", np.sqrt(mean_squared_error(y_test, prediction)))
print("Features:", selected[:-1].get_feature_names_out(["x1", "x2"]))

hours = np.array([23.0, 0.0])
cyclic = np.column_stack([
    np.sin(2 * np.pi * hours / 24),
    np.cos(2 * np.pi * hours / 24),
])
print("23:00-to-00:00 cyclic distance:", np.linalg.norm(cyclic[0] - cyclic[1]))
