How to standardize features with scikit-learn

Numeric model features often arrive in different units, such as ages, income amounts, and activity counts. StandardScaler in scikit-learn puts each numeric column on a zero-mean, unit-variance scale so scale-sensitive estimators do not let the largest-unit feature dominate model fitting.

StandardScaler learns the mean and standard deviation during fit() and stores them in mean_ and scale_. Fit the scaler on training rows only, then reuse transform() for validation, test, and future rows so evaluation data does not change the training statistics.

Standardization is a preprocessing choice for numeric columns, not a replacement for handling outliers or categorical values. Extreme values affect the mean and standard deviation, while mixed tabular data usually belongs in a ColumnTransformer or Pipeline so every preprocessing step is fitted with the estimator consistently.

Steps to standardize features with scikit-learn:

  1. Create standardize_features.py with training rows, one held-out row, a scaler, and a small classifier.
    standardize_features.py
    import numpy as np
    import sklearn
    from sklearn.linear_model import LogisticRegression
    from sklearn.preprocessing import StandardScaler
     
     
    feature_names = ["age_years", "annual_income", "monthly_visits"]
     
    X_train = np.array(
        [
            [22, 38000, 4],
            [25, 42000, 5],
            [47, 88000, 7],
            [52, 92000, 8],
            [31, 58000, 6],
            [45, 76000, 7],
            [28, 54000, 5],
            [39, 69000, 6],
        ],
        dtype=float,
    )
    y_train = np.array([0, 0, 1, 1, 0, 1, 0, 1])
     
    X_holdout = np.array([[34, 62000, 6]], dtype=float)
     
    scaler = StandardScaler()
    X_train_scaled = scaler.fit_transform(X_train)
    X_holdout_scaled = scaler.transform(X_holdout)
     
    model = LogisticRegression(max_iter=1000, random_state=0)
    model.fit(X_train_scaled, y_train)
    prediction = model.predict(X_holdout_scaled)
     
     
    def display_zero(value):
        return 0.0 if abs(value) < 5e-12 else value
     
     
    print(f"scikit-learn {sklearn.__version__}")
    print("Scaler learned on training rows only:")
    print(f"- rows_seen: {int(scaler.n_samples_seen_)}")
    print(
        "- mean_: "
        + ", ".join(
            f"{name}={value:.2f}" for name, value in zip(feature_names, scaler.mean_)
        )
    )
    print(
        "- scale_: "
        + ", ".join(
            f"{name}={value:.2f}" for name, value in zip(feature_names, scaler.scale_)
        )
    )
    print()
    print("Scaled training columns:")
    for name, mean, std in zip(
        feature_names, X_train_scaled.mean(axis=0), X_train_scaled.std(axis=0)
    ):
        print(f"- {name}: mean={display_zero(mean):.6f}, std={std:.6f}")
    print()
    print("Scaled holdout row:")
    for name, value in zip(feature_names, X_holdout_scaled[0]):
        print(f"- {name}: {value:.3f}")
    print()
    print(f"Holdout prediction from scaled features: {prediction.tolist()}")

    The scaler uses fit_transform() only on X_train. The held-out row uses transform() so its values are scaled with the training means and standard deviations.

  2. Run the standardization script.
    $ python standardize_features.py
    scikit-learn 1.9.0
    Scaler learned on training rows only:
    - rows_seen: 8
    - mean_: age_years=36.12, annual_income=64625.00, monthly_visits=6.00
    - scale_: age_years=10.45, annual_income=18781.22, monthly_visits=1.22
    
    Scaled training columns:
    - age_years: mean=0.000000, std=1.000000
    - annual_income: mean=0.000000, std=1.000000
    - monthly_visits: mean=0.000000, std=1.000000
    
    Scaled holdout row:
    - age_years: -0.203
    - annual_income: -0.140
    - monthly_visits: 0.000
    
    Holdout prediction from scaled features: [0]
  3. Confirm the fitted scaler learned statistics from the eight training rows.
    Scaler learned on training rows only:
    - rows_seen: 8
    - mean_: age_years=36.12, annual_income=64625.00, monthly_visits=6.00
    - scale_: age_years=10.45, annual_income=18781.22, monthly_visits=1.22
  4. Check that each scaled training column is centered and scaled.
    Scaled training columns:
    - age_years: mean=0.000000, std=1.000000
    - annual_income: mean=0.000000, std=1.000000
    - monthly_visits: mean=0.000000, std=1.000000

    StandardScaler uses the fitted training statistics for each feature, so the transformed training columns print zero means and unit standard deviations.

  5. Verify the held-out row was transformed without refitting the scaler.
    Scaled holdout row:
    - age_years: -0.203
    - annual_income: -0.140
    - monthly_visits: 0.000
    
    Holdout prediction from scaled features: [0]

    For cross-validation or production prediction, place StandardScaler inside a Pipeline with the estimator so each split fits preprocessing only on its own training fold.

  6. Remove the scratch file when the smoke check is complete.
    $ rm standardize_features.py