• Follow Us On :
Scikit-Learn Tutorial

Top Scikit-Learn Tutorial with Examples: A Complete Guide for Beginners (2026)

Scikit-learn is the library most people’s first real machine learning model gets built with, and for good reason. It wraps decades of statistical learning research into a consistent, predictable API: nearly every algorithm in the library is used the same way, whether you’re fitting a linear regression or a random forest. Once you understand that pattern, the rest of the library opens up quickly.

This tutorial walks through scikit-learn from installation to a full working model, with real code examples at every step: data loading, preprocessing, training, evaluation, cross-validation, pipelines, and hyperparameter tuning. It also covers what’s changed in the 1.8 and 1.9 releases specifically, since scikit-learn has added genuine GPU support in the last year that older tutorials won’t mention. No prior machine learning experience required, though basic Python and a little NumPy familiarity will make this much easier to follow.

What Is Scikit-Learn?

Scikit-learn is an open-source Python library for machine learning, built on top of NumPy, SciPy, and matplotlib. It covers the classical machine learning toolkit: classification, regression, clustering, dimensionality reduction, and model selection, along with the preprocessing and evaluation tools needed to actually use those algorithms on real data.

It’s worth being clear about what scikit-learn is not. It isn’t a deep learning framework: for neural networks at scale, you’d reach for TensorFlow or PyTorch instead. Scikit-learn’s strength is the other 80% of machine learning work, structured, tabular data, classical algorithms, and the preprocessing and evaluation pipeline surrounding them, which is exactly the kind of work most real-world data science and analytics roles spend the majority of their time on.

In practice, scikit-learn shows up behind a huge range of real applications: credit scoring and fraud detection models at banks, customer churn prediction for subscription businesses, demand forecasting for retail and logistics, spam filtering, and as a preprocessing and baseline-modeling step even in projects that eventually move to deep learning for their final production model. It’s also the library most data science courses and interviews assume you already know, which makes it one of the highest-leverage libraries to actually get comfortable with rather than just recognize by name.

Installing Scikit-Learn

Scikit-learn installs through pip like most Python packages:

bash
pip install scikit-learn

If you’re working with the broader data science stack, which you almost always will be, install the common companions alongside it:

bash
pip install scikit-learn numpy pandas matplotlib

Verify the installation and check your version, since the examples in this guide assume a reasonably current release:

python
import sklearn
print(sklearn.__version__)

As of mid-2026, the current stable release is 1.9.0, following 1.8’s introduction of native Array API support, covered in more detail later in this guide.

Scikit-Learn’s Core Design: The Estimator API

Nearly everything in scikit-learn follows the same basic pattern, and understanding this pattern is more valuable than memorizing any specific algorithm’s parameters.

Every model, called an “estimator” in scikit-learn’s terminology, implements the same core methods: fit() trains the model on your data, predict() generates predictions on new data, and for many models, transform() applies a learned transformation (like scaling or dimensionality reduction) to new data. This consistency means switching from a logistic regression to a random forest to a support vector machine involves changing one line of code, not relearning an entire new interface.

python
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier

# Both follow the exact same pattern
model_a = LogisticRegression()
model_a.fit(X_train, y_train)
predictions_a = model_a.predict(X_test)

model_b = RandomForestClassifier()
model_b.fit(X_train, y_train)
predictions_b = model_b.predict(X_test)

Loading and Splitting Data

Scikit-learn ships with several small, well-known datasets built in, useful for learning and testing before you move to real project data.

python
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

# Load the classic Iris flower dataset
data = load_iris()
X, y = data.data, data.target

# Split into training and test sets
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

print(f"Training samples: {X_train.shape[0]}")
print(f"Test samples: {X_test.shape[0]}")

Splitting data before doing anything else is one of the most important habits to build early. Evaluating a model on data it was trained on tells you almost nothing about how it will perform on data it hasn’t seen, which is the entire point of building a model in the first place. random_state=42 makes the split reproducible; using any fixed number here ensures you get the same split every time you run the code.

Data Preprocessing: Scaling Features

Many algorithms, particularly ones based on distance calculations (like k-nearest neighbors) or gradient-based optimization (like logistic regression), perform better when features are on a similar scale. StandardScaler transforms features to have a mean of 0 and a standard deviation of 1.

python
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()

# Fit the scaler on training data only, then transform both sets
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

Notice the pattern here: fit_transform() on the training data, but only transform() on the test data. Fitting the scaler separately on the test set would leak information from the test set into your preprocessing, quietly inflating how good your model looks during evaluation. This is one of the most common beginner mistakes in the entire library, covered in more detail later in this guide.

Handling Missing Data and Categorical Variables

Real-world data is rarely as clean as the Iris dataset. Two preprocessing steps come up in almost every practical project: filling in missing values and converting categorical text data into a numeric format models can actually use.

python
import numpy as np
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder

# Handling missing numeric values
imputer = SimpleImputer(strategy='mean')
X_train_filled = imputer.fit_transform(X_train)
X_test_filled = imputer.transform(X_test)

# Encoding categorical text data, e.g. a "color" column with values like "red", "blue", "green"
categories = np.array([['red'], ['blue'], ['green'], ['blue']])
encoder = OneHotEncoder(sparse_output=False)
encoded = encoder.fit_transform(categories)
print(encoded)

SimpleImputer fills missing values using a specified strategy, mean, median, most frequent value, or a constant, applied consistently across your dataset rather than requiring you to write custom fill logic by hand. OneHotEncoder converts categorical text values into separate binary columns, one per category, since most scikit-learn models expect purely numeric input and can’t work with raw text labels directly. Both follow the same fit_transform() on training data, transform() on test data pattern covered above, for exactly the same data-leakage reasons.

Your First Classification Model

Here’s a complete, working classification example using logistic regression on the Iris dataset:

python
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report

# Train the model
model = LogisticRegression(max_iter=200)
model.fit(X_train_scaled, y_train)

# Generate predictions
y_pred = model.predict(X_test_scaled)

# Evaluate
print(f"Accuracy: {accuracy_score(y_test, y_pred):.2f}")
print(classification_report(y_test, y_pred))

That’s a genuinely complete machine learning workflow: load data, split it, scale it, train a model, and evaluate it. Every more advanced example in this guide builds on this same basic shape.

Your First Regression Model

Classification predicts categories; regression predicts continuous numbers. Here’s a simple example using a built-in dataset:

python
from sklearn.datasets import fetch_california_housing
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

housing = fetch_california_housing()
X, y = housing.data, housing.target

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

reg_model = LinearRegression()
reg_model.fit(X_train, y_train)
predictions = reg_model.predict(X_test)

print(f"R-squared: {r2_score(y_test, predictions):.2f}")
print(f"Mean Squared Error: {mean_squared_error(y_test, predictions):.2f}")

R-squared tells you what proportion of the variance in the target variable your model explains, with 1.0 being a perfect fit. Mean squared error gives you a sense of the average prediction error in the original units, squared, which penalizes large errors more heavily than small ones.

Model Evaluation Metrics: Choosing the Right One

Accuracy is the most intuitive classification metric, but it’s often the wrong one to optimize for, particularly on imbalanced datasets.

python
from sklearn.metrics import confusion_matrix, precision_score, recall_score, f1_score

y_pred = model.predict(X_test_scaled)

print("Confusion Matrix:")
print(confusion_matrix(y_test, y_pred))
print(f"Precision: {precision_score(y_test, y_pred, average='weighted'):.2f}")
print(f"Recall: {recall_score(y_test, y_pred, average='weighted'):.2f}")
print(f"F1 Score: {f1_score(y_test, y_pred, average='weighted'):.2f}")

If you’re building a fraud detection model where fraud cases are rare, a model that predicts “not fraud” every single time might score 99% accuracy while being completely useless. Precision, recall, and F1 score give you a much clearer picture of how a model actually performs on the class you care about, which is exactly why relying on accuracy alone is a common and costly mistake in real projects.

Cross-Validation: Getting a More Reliable Performance Estimate

A single train-test split gives you one performance number, but that number can vary depending on which specific rows happened to land in the test set. Cross-validation addresses this by splitting the data multiple ways and averaging the results.

python
from sklearn.model_selection import cross_val_score

scores = cross_val_score(model, X_train_scaled, y_train, cv=5)

print(f"Cross-validation scores: {scores}")
print(f"Average accuracy: {scores.mean():.2f} (+/- {scores.std():.2f})")

cv=5 here means 5-fold cross-validation: the training data gets split into 5 parts, the model trains on 4 and validates on the 5th, and this repeats 5 times with a different part held out each time. The result is a more reliable estimate of how the model will perform on genuinely new data than a single train-test split alone.

Building Pipelines: Combining Preprocessing and Modeling

Manually calling scaler and model separately works fine for a simple example, but real projects benefit from bundling every step into a single Pipeline object, which prevents data leakage mistakes and makes your workflow much easier to reuse.

python
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('classifier', LogisticRegression(max_iter=200))
])

pipeline.fit(X_train, y_train)
predictions = pipeline.predict(X_test)

print(f"Pipeline accuracy: {accuracy_score(y_test, predictions):.2f}")

The pipeline handles the fit/transform distinction automatically and correctly for you: calling pipeline.fit() fits the scaler on training data and fits the model on the scaled result, while pipeline.predict() correctly applies the already-fitted scaler’s transform (not a new fit) before predicting. This is genuinely one of the highest-value habits to build early, since it removes an entire category of subtle, hard-to-spot bugs.

Hyperparameter Tuning with GridSearchCV

Most models have hyperparameters, settings you choose before training rather than values the model learns, that meaningfully affect performance. GridSearchCV automates the process of testing combinations of these settings.

python
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier

param_grid = {
    'n_estimators': [50, 100, 200],
    'max_depth': [None, 10, 20]
}

grid_search = GridSearchCV(
    RandomForestClassifier(random_state=42),
    param_grid,
    cv=5,
    scoring='accuracy'
)

grid_search.fit(X_train, y_train)

print(f"Best parameters: {grid_search.best_params_}")
print(f"Best cross-validation score: {grid_search.best_score_:.2f}")

This tests every combination of the specified parameters (3 values for n_estimators times 3 values for max_depth, so 9 combinations total), using 5-fold cross-validation for each, and reports which combination performed best. For larger parameter grids, RandomizedSearchCV samples a random subset of combinations instead of testing all of them, trading a small amount of thoroughness for a significant speed improvement.

Understanding Feature Importance

Once a model is trained, it’s often just as valuable to understand which features actually drove its predictions as it is to know the prediction itself, particularly when you need to explain a model’s behavior to a non-technical stakeholder.

python
import pandas as pd

best_model = grid_search.best_estimator_
feature_names = data.feature_names

importances = pd.Series(
    best_model.feature_importances_, index=feature_names
).sort_values(ascending=False)

print(importances)

Tree-based models like random forests expose a feature_importances_ attribute directly after fitting, ranking each input feature by how much it contributed to the model’s decisions on average across all its trees. This is genuinely useful beyond pure curiosity: it can reveal that a feature you assumed was important barely matters, or that a feature you overlooked is doing most of the work, both of which are worth investigating before trusting a model in production.

A Quick Clustering Example

Not every problem has labeled data to predict against. Clustering finds structure in unlabeled data, grouping similar records together.

python
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs

# Generate sample data with 3 natural clusters
X, _ = make_blobs(n_samples=300, centers=3, random_state=42)

kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
kmeans.fit(X)

print(f"Cluster centers:\n{kmeans.cluster_centers_}")
print(f"Cluster assignments for first 10 points: {kmeans.labels_[:10]}")

K-means groups data points into a specified number of clusters based on proximity to learned cluster centers. Choosing the right number of clusters (the n_clusters parameter) is itself a common challenge, often addressed using techniques like the elbow method, which isn’t covered in depth here but is worth researching once you’re comfortable with the basics.

Saving and Loading a Trained Model

Training a model is only useful if you can actually use it later without retraining from scratch every time. Scikit-learn models are typically saved using joblib, which handles NumPy arrays more efficiently than Python’s general-purpose pickle module.

python
import joblib

# Save a trained model (or pipeline) to disk
joblib.dump(pipeline, 'my_model.joblib')

# Load it back later, in the same or a different script
loaded_model = joblib.load('my_model.joblib')
predictions = loaded_model.predict(X_test)

Saving the entire pipeline, not just the final model, is important if your workflow includes preprocessing steps like scaling or encoding. A saved model without its accompanying preprocessing steps will produce incorrect predictions on raw, unscaled input data, since it expects data in the already-transformed format it was trained on.

What’s New in Scikit-Learn for 2026

Scikit-learn’s recent releases have added capability that older tutorials and Stack Overflow answers won’t reflect, so it’s worth knowing what’s changed if you’re learning from a mix of sources.

Array API support and GPU acceleration, introduced in version 1.8 (released December 2025) and expanded in 1.9 (released June 2026), let scikit-learn work directly with PyTorch tensors and CuPy arrays rather than only NumPy arrays. This means select estimators, including StandardScaler, PolynomialFeatures, RidgeCV, and GaussianMixture, can now run their computation on a GPU when a compatible array type is passed in, improving performance on larger datasets without switching to an entirely different library.

Continued Python version support has moved forward alongside these releases, with 1.9 supporting Python 3.11 through 3.14, alongside experimental support for free-threaded CPython, a build of Python designed to run without the Global Interpreter Lock’s traditional threading limitations.

New algorithms continue getting added even in a mature library. Classical multidimensional scaling (also known as Principal Coordinates Analysis) was added to the sklearn.manifold module in 1.8, giving users another dimensionality reduction option alongside longstanding tools like PCA and t-SNE.

The broader takeaway: scikit-learn isn’t a finished, static library. It continues improving performance and adding capability, and checking the release notes for the version you’re actually running is worth doing periodically, particularly if you’re working with datasets large enough that GPU acceleration would make a real difference.

Common Mistakes Beginners Make

Fitting the scaler on the full dataset before splitting. This leaks information from your test set into your preprocessing step, making your evaluation metrics look better than they’ll actually be on genuinely new data. Always split first, then fit preprocessing steps only on the training data.

Relying on accuracy alone for imbalanced classification problems. As covered above, accuracy can look excellent on a model that’s actually useless, particularly when one class is rare. Check precision, recall, and F1 score, not just accuracy, especially for real-world classification problems.

Skipping cross-validation and trusting a single train-test split. A single split can happen to be unusually easy or unusually hard by chance. Cross-validation gives a more reliable, stable estimate of true model performance.

Not using a Pipeline, and accidentally leaking data as a result. Manually managing preprocessing and modeling steps separately is exactly how the scaler-leakage mistake above tends to happen. Pipelines handle this correctly by default.

Assuming a more complex model is automatically a better model. A random forest or gradient boosting model isn’t always better than a simpler linear model, particularly on smaller datasets, and added complexity brings added risk of overfitting along with longer training time. Start simple, and add complexity only when the data and evaluation results actually justify it.

How to Continue Learning

Scikit-learn builds directly on the Python data science foundation, and getting genuinely comfortable with the surrounding libraries makes everything in this guide click faster.

If NumPy arrays and broadcasting still feel unfamiliar, our NumPy tutorial with examples covers exactly the foundation scikit-learn is built on top of. Since real projects rarely start with clean, ready-to-use arrays, our Pandas tutorial covers the data loading and cleaning work that typically happens before any of the code in this guide. And once you’ve got classical machine learning down, our deep learning tutorial is the natural next step for problems, like image and text data at scale, where scikit-learn’s classical algorithms start to hit their limits. For the definitive, always-current reference as new versions ship, scikit-learn’s own documentation and release highlights are worth bookmarking directly.

FAQs About Scikit-Learn

Is scikit-learn good for deep learning? No, not directly. Scikit-learn is built for classical machine learning algorithms on structured, tabular data. For neural networks and deep learning specifically, TensorFlow and PyTorch are the standard tools.

Do I need to know NumPy before learning scikit-learn? Basic familiarity helps significantly, since scikit-learn’s inputs and outputs are NumPy arrays (or Array API-compatible equivalents as of recent versions), and understanding array shapes and indexing makes debugging model code much easier.

What’s the difference between fit(), transform(), and fit_transform()? fit() learns parameters from data (like the mean and standard deviation for a scaler). transform() applies an already-learned transformation to data. fit_transform() does both in one step, typically used on training data, while test data should only use transform() with the parameters already learned from training data.

Can scikit-learn run on a GPU? As of version 1.8 and 1.9, select estimators support GPU computation through Array API compatibility with PyTorch tensors and CuPy arrays, though this isn’t universal across every algorithm in the library yet, and CPU remains the default for most workflows.

What’s the best first algorithm to learn in scikit-learn? Linear regression and logistic regression are the standard starting points, since they’re conceptually simple, fast to train, and directly illustrate scikit-learn’s core fit()/predict() pattern before moving to more complex models like random forests or gradient boosting.

How is scikit-learn different from TensorFlow or PyTorch? Scikit-learn focuses on classical machine learning algorithms with a consistent, simple API and is generally easier to get started with. TensorFlow and PyTorch are built specifically for deep learning and neural networks, offering far more flexibility for custom architectures, at the cost of a steeper learning curve for straightforward classical tasks.

Why does my model perform well on training data but poorly on test data? This is a classic sign of overfitting, where a model has essentially memorized patterns specific to the training data rather than learning patterns that generalize to new data. Cross-validation, simpler models, regularization, and gathering more training data are all common ways to address it.

Do I need to scale my data for every scikit-learn algorithm? No. Tree-based models like decision trees, random forests, and gradient boosting are generally insensitive to feature scale, since they split on individual feature thresholds rather than computing distances or gradients across all features at once. Distance-based and gradient-based algorithms, including k-nearest neighbors, logistic regression, and support vector machines, typically do benefit from scaling.

Conclusion

Scikit-learn’s real strength isn’t any single algorithm; it’s the consistency of the interface across dozens of them. Once fit(), predict(), and transform() feel natural, picking up a new algorithm is mostly a matter of reading its specific parameters, not relearning an entirely new way of working. The preprocessing, pipeline, and evaluation patterns covered in this guide matter just as much as the algorithms themselves, and they’re exactly the habits that separate a model that looks good in a notebook from one that actually holds up on new data.

Work through the examples in this guide directly rather than just reading them, and once they feel familiar, apply the same pattern to a real dataset you’re genuinely curious about. That hands-on repetition, more than any amount of additional reading, is what makes scikit-learn’s API start to feel like second nature.

Leave a Reply

Your email address will not be published. Required fields are marked *