# Source: content/notes/libraries/scikit-learn.md
# Independent CPU example; use the curriculum environment.
# See /notes/ml/#example-environment or /notes/deep-learning/#example-environment.

import numpy as np
from sklearn.base import BaseEstimator, TransformerMixin, OneToOneFeatureMixin, clone
from sklearn.utils.validation import check_is_fitted
try:
    from sklearn.utils.validation import validate_data  # public API, sklearn >= 1.6
except ImportError:
    def validate_data(estimator, X, **kwargs):
        return estimator._validate_data(X, **kwargs)  # pinned older sklearn compatibility

class ClipOutliers(OneToOneFeatureMixin, TransformerMixin, BaseEstimator):
    """Finite, numeric, two-dimensional inputs; compatibility path for sklearn 1.3+."""
    def __init__(self, lower=0.01, upper=0.99):
        self.lower, self.upper = lower, upper       # store, do not validate here

    def fit(self, X, y=None):
        if not 0 <= self.lower <= self.upper <= 1:
            raise ValueError("quantiles must satisfy 0 <= lower <= upper <= 1")
        X = validate_data(self, X, reset=True, dtype=float)
        self.lo_ = np.quantile(X, self.lower, axis=0)
        self.hi_ = np.quantile(X, self.upper, axis=0)
        return self

    def transform(self, X):
        check_is_fitted(self, ["lo_", "hi_"])
        X = validate_data(self, X, reset=False, dtype=float)
        return np.clip(X, self.lo_, self.hi_)

X = np.arange(12., dtype=float).reshape(6, 2)
t = ClipOutliers(0.2, 0.8).fit(X)
assert t.transform(X).shape == X.shape
assert len(t.get_feature_names_out()) == 2
assert not hasattr(clone(t), "lo_")
try:
    t.transform(np.ones((2, 3)))
except ValueError:
    pass
else:
    raise AssertionError("changed feature width accepted")
print("transformer validation and clone checks passed")
