scikit-learn
scikit-learn is the standard Python library for classical machine learning: classification, regression, clustering, and dimensionality reduction on tabular, in-memory data. Every algorithm, from logistic regression to random forests to k-means, shares the same fit/predict/transform API, so swapping models is a one-line change. Around the estimators sit the pieces that matter in practice: preprocessing transformers, Pipelines that keep preprocessing and models together without leaking test data, cross-validation, and hyperparameter search. Built on NumPy and SciPy under a BSD-3 license, started as a Google Summer of Code project in 2007, and maintained by a large community with institutional backing.
For classical ML on in-memory tabular data, this is the library, and its Pipeline/cross-validation design quietly teaches correct methodology. Know its edges: no serious deep learning, no GPU story, and datasets beyond RAM push you to other tools.
Use it if
- You are doing classical ML on tabular data that fits in memory; for that job its breadth and API consistency are unmatched
- You want leak-free workflows: Pipeline plus ColumnTransformer plus cross_val_score encode the correct methodology so preprocessing is fit only on training folds
- You are learning ML; the user guide teaches the statistics behind each method, not just the function signatures, and is one of the best free ML texts anywhere
- You need a solid baseline before reaching for anything fancy; a Pipeline with LogisticRegression or RandomForest is the honest benchmark every deep model should beat
- Your problem is deep learning (images, audio, text embeddings, transformers); that is torch territory, scikit-learn deliberately does not do neural networks beyond a basic MLP
- Your data does not fit in RAM; the API is built around in-memory arrays, and out-of-core support is limited to the minority of estimators with partial_fit
- You need GPU training; scikit-learn is CPU-oriented, and for large gradient-boosted models xgboost or lightgbm will train faster and often score better on tabular data
- You need statistical inference (p-values, confidence intervals, model diagnostics); statsmodels exists precisely because scikit-learn optimizes for prediction, not inference
- You must serve models across environments long-term; pickled models are not guaranteed to load across scikit-learn versions, so version pinning becomes a production constraint
Setup reality
pip install scikit-learn works painlessly on major platforms because binary wheels ship for everything; it pulls numpy, scipy, joblib, threadpoolctl, and narwhals. Note the interpreter floor: release 1.9.0 requires Python 3.11+. The real-world annoyances: mixing conda and pip in one environment invites BLAS and threading conflicts (threadpoolctl exists for a reason); building from source needs C/C++ toolchains, Cython, and meson and is slow, so avoid platforms without wheels; plotting utilities need an optional matplotlib install; and model persistence via pickle ties you to the exact library version, which the project's own docs warn about, so record versions next to every saved model.
Patterns
Split data, train, and score a first modeltrain-evaluate
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
clf = RandomForestClassifier(n_estimators=300, random_state=42)
clf.fit(X_train, y_train)
print(accuracy_score(y_test, clf.predict(X_test)))stratify=y keeps class proportions equal across the split; forgetting it skews evaluation on imbalanced data.
Chain preprocessing and a model in a Pipelinepipeline
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
model = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
model.fit(X_train, y_train)
model.predict(X_test)The scaler is fit on training data only and reused at predict time, which is exactly the data leak you cause by scaling before splitting.
Preprocess numeric and categorical columns differentlymixed-columns
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LogisticRegression
pre = ColumnTransformer([
('num', StandardScaler(), ['age', 'income']),
('cat', OneHotEncoder(handle_unknown='ignore'), ['city', 'plan']),
])
model = make_pipeline(pre, LogisticRegression(max_iter=1000))
model.fit(X_train, y_train)handle_unknown='ignore' stops predict from crashing when a category unseen in training shows up in production data.
Get an honest score with cross-validationcross-validation
from sklearn.model_selection import cross_val_score
scores = cross_val_score(model, X, y, cv=5, scoring='f1_macro')
print(scores.mean(), scores.std())Cross-validate the whole pipeline, not just the estimator, or preprocessing leaks information across folds.
Tune hyperparameters with GridSearchCVhyperparameter-search
from sklearn.model_selection import GridSearchCV
param_grid = {
'randomforestclassifier__n_estimators': [100, 300],
'randomforestclassifier__max_depth': [None, 10, 30],
}
search = GridSearchCV(model, param_grid, cv=5, n_jobs=-1)
search.fit(X_train, y_train)
print(search.best_params_, search.best_score_)
best = search.best_estimator_Pipeline parameter names use the step name plus double underscore; RandomizedSearchCV covers big grids at a fraction of the cost.
See per-class performance, not just accuracyclassification-metrics
from sklearn.metrics import classification_report, confusion_matrix
y_pred = model.predict(X_test)
print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred))On imbalanced data accuracy flatters bad models; read the per-class recall rows before believing any headline number.
Persist a trained model with joblibsave-model
import joblib
joblib.dump(model, 'model-v1.joblib')
# later, in the serving process
model = joblib.load('model-v1.joblib')
model.predict(X_new)Loads are only guaranteed with the same scikit-learn version that saved the file; pin the version and record it alongside the artifact.
Get probabilities and set your own thresholdpredict-probabilities
proba = model.predict_proba(X_test)[:, 1] # P(class 1)
custom_threshold = 0.3 # favor recall over precision
y_pred = (proba >= custom_threshold).astype(int)predict() hard-codes a 0.5 threshold; cost-sensitive problems almost always want a different operating point.
Rank features with permutation importancefeature-importance
from sklearn.inspection import permutation_importance
result = permutation_importance(model, X_test, y_test, n_repeats=10, random_state=42)
for idx in result.importances_mean.argsort()[::-1][:10]:
print(X_test.columns[idx], round(result.importances_mean[idx], 4))Prefer this over tree feature_importances_, which inflates high-cardinality features; run it on held-out data, not training data.
Cluster unlabeled data with k-meansclustering
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
X_scaled = StandardScaler().fit_transform(X)
km = KMeans(n_clusters=4, n_init='auto', random_state=42)
labels = km.fit_predict(X_scaled)k-means uses Euclidean distance, so unscaled features let the widest-ranged column dominate the clustering.
Reduce dimensions with PCAdimensionality-reduction
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
pca = make_pipeline(StandardScaler(), PCA(n_components=0.95))
X_reduced = pca.fit_transform(X_train)
print(pca.named_steps['pca'].n_components_)n_components=0.95 keeps enough components for 95 percent of variance; transform test data with the fitted pipeline, never refit.
Impute missing values inside the pipelinehandle-missing-values
from sklearn.impute import SimpleImputer
from sklearn.pipeline import make_pipeline
from sklearn.ensemble import HistGradientBoostingClassifier
model = make_pipeline(
SimpleImputer(strategy='median'),
HistGradientBoostingClassifier(random_state=42)
)
model.fit(X_train, y_train)Most estimators raise on NaN, so impute in-pipeline; HistGradientBoosting models are the exception and handle NaN natively.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| xgboost | PyPI | Gradient-boosted trees on medium-to-large tabular data where leaderboard accuracy matters |
| lightgbm | PyPI | Boosted trees with faster training and lower memory on large datasets |
| statsmodels | PyPI | You need p-values, confidence intervals, and classical statistical inference, not just predictions |
| torch | PyPI | Deep learning on images, text, or audio, or anything that needs GPUs and custom architectures |