Three method names that every model in the library shares - which is why swapping one model for another is a one-line change.
Overview
The problem it solves
scikit-learn is a Python library for machine learning. It covers classification, regression, clustering, dimensionality reduction and preprocessing. Every algorithm in it follows the same interface: three methods named fit, predict and transform.
That consistency is the library's main contribution. You learn the interface once. After that, switching from a linear regression to a random forest is a one-line change. The surrounding code stays the same.
The library sits on top of NumPy and SciPy. It expects numeric arrays as input and returns numeric arrays as output. It does not handle data loading, feature engineering from raw text, or deep learning. Those boundaries are deliberate. They keep the API small and predictable.
Most mistakes in applied machine learning are not about the algorithm. They are about how data reaches the algorithm and how the result is measured. scikit-learn's design reflects that. It spends more surface area on splitting data, building pipelines and scoring predictions than on any single model.
This page covers the interface itself. The three methods, the naming conventions, the shape rules and the two kinds of estimator. Once those are familiar, each later page adds one estimator or one evaluation technique. None of them changes how you call it.
The library ships with small datasets for testing. It also ships with generators that produce synthetic data of a chosen shape. Both are useful for checking that your code runs before pointing it at real data.
each machine-learning library has to answer the same question. How does a program hand data to an method and get an answer back. scikit-learn answered it once, in 2007. Then made each method in the library answer it the same way.
The result is that fit, predict and transform are almost the whole API. A linear regression, a random forest, a support-vector machine and a k-means clustering all expose the same three methods. The code around a model doesn't change when the model does.
The result is that fit, predict and transform are almost the whole API. A linear regression, a random forest, a support-vector machine and a k-means clustering all expose the same three methods. The code around a model doesn't change when the model does.
Worth knowing
fit learns from data, predict produces a target, transform produces new features. Every object in the library is some combination of those three.
An attribute ending in _ was learned during fit. One without was set by you.
X is 2-D with one row per sample and one column per feature; y is 1-D with one entry per sample.
A single feature still needs two dimensions - reshape(-1, 1) is the usual fix.
fit returns the estimator, which is why Model().fit(X, y) chains.
Calling predict before fit raises NotFittedError rather than guessing.
fit, predict, transform: A Practical Guide
scikit-learn has one interface and about two hundred methods behind it. Learning the interface is most of learning the library.
Every model is the same three methods
The library's whole design is that you learn the interface once, not once per algorithm.
example_01.pyscikit-learn
from sklearn.linear_model import LinearRegression
# Six houses: one feature (rooms), one target (price in thousands).
X = [[1], [2], [3], [4], [5], [6]]
y = [50, 70, 90, 110, 130, 150]
model = LinearRegression()
print("before fit :", hasattr(model, "coef_"))
model.fit(X, y)
print("after fit :", hasattr(model, "coef_"))
print("prediction for 7 rooms:", model.predict([[7]]))
Output
The trailing underscore means learned
Attributes ending in an underscore did not exist until fit ran. It is a naming convention, and it is the whole story of what fitting does.
example_02.pyscikit-learn
from sklearn.linear_model import LinearRegression
X = [[1], [2], [3], [4], [5], [6]]
y = [50, 70, 90, 110, 130, 150]
model = LinearRegression().fit(X, y)
print("coef_ :", model.coef_)
print("intercept_ :", round(model.intercept_, 6))
# Set before fit, by you. No underscore.
print("fit_intercept (a setting you chose):", model.fit_intercept)
Output
fit returns the estimator, so it chains
Every fit returns self. That is why you will see .fit(X, y) attached to the constructor on one line.
example_03.pyscikit-learn
from sklearn.linear_model import LinearRegression
X = [[1], [2], [3]]
y = [2, 4, 6]
model = LinearRegression()
same = model.fit(X, y)
print("fit returned the same object:", same is model)
print("so this one-liner works :",
LinearRegression().fit(X, y).predict([[4]]))
Output
Transformers use transform instead of predict
A model predicts a target. A transformer changes the features. The fit half is identical.
Same data, same calls, different algorithm - which is the payoff for having one interface.
example_05.pyscikit-learn
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
from sklearn.neighbors import KNeighborsRegressor
# Deliberately not a straight line, so the three models disagree.
X = [[1], [2], [3], [4], [5], [6]]
y = [50, 62, 90, 96, 130, 150]
for model in [LinearRegression(),
DecisionTreeRegressor(random_state=0),
KNeighborsRegressor(n_neighbors=2)]:
model.fit(X, y)
guess = model.predict([[3.5]])[0]
print("%-24s %6.1f" % (type(model).__name__, guess))
Output
X is always 2-D, y is always 1-D
The single most common error message on this track comes from getting this wrong, and it tells you exactly what it wanted.
example_06.pyscikit-learn
from sklearn.linear_model import LinearRegression
import numpy as np
X = np.array([[1], [2], [3]])
y = np.array([2, 4, 6])
print("X.shape:", X.shape, " y.shape:", y.shape)
flat = np.array([1, 2, 3])
try:
LinearRegression().fit(flat, y)
except ValueError as e:
print()
print("ValueError:", str(e).splitlines()[0])
print()
print("fix: flat.reshape(-1, 1) ->", flat.reshape(-1, 1).shape)
Output
fit learns, predict answers
fit(X, y) shows the estimator the data and the answers. It stores what it worked out on the object. predict(X) takes new data with no answers and makes them.
The split matters more than it looks. Fitting and predicting are separate calls because they happen at different times on different data. You fit once on data you've. Predict many times on data you didn't have when you fitted.
The underscore convention
scikit-learn marks learned fields with a trailing underscore. The rule is worth taking seriously because it cuts an estimator into two halves.
Fields without an underscore are settings: settings you chose, passed to the constructor, present before any data was seen. n_neighbors, max_depth, fit_intercept.
Fields with a trailing underscore are learned: they didn't exist before fit and they came from the data. coef_, intercept_, feature_importances_, classes_.
Reading an estimator so tells you what it was told and what it worked out. The two never get confused. It also means hasattr(model, "coef_") is a solid test for "has this been fitted". This Is just what the library's own check_is_fitted does.
Transformers change features rather than predicting
A transformer is the other half of the library. Instead of predict it has transform. Instead of making a target it makes a new version of X.
StandardScaler is the canonical one: fit finds the mean and standard deviation of each column, transform subtracts and cuts. OneHotEncoder, SimpleImputer and PCA are all the same shape.
fit_transform does both in one call and is the one you'll write most often on training data. It isn't merely shorthand. For some transformers it's faster than doing the two apart. This Is why it exists as its own method.
The rule that matters: fit on training data only, transform all. A scaler fitted on the test set has learned from data the model is supposed to have never seen. The score you get afterwards isn't a score.
The shapes
X is two-dimensional: rows are samples, columns are features. y is one-dimensional: one value per row of X.
That holds even when there's one feature. This Is where beginners meet their first error. A list of six numbers is six samples of nothing, not one sample of six features. Scikit-learn refuses to guess which you meant. reshape(-1, 1) turns a flat array into a column. The error message says so by name.
The two kinds of estimator
All in the library is one of two things. Telling them apart tells you which method to call.
A predictor ends in a target. LinearRegression, LogisticRegression, RandomForestClassifier, KMeans. You fit them and then predict. Classifiers also offer predict_proba. This Returns the chance of each class rather than the winning one.
A transformer ends in new features. StandardScaler, OneHotEncoder, SimpleImputer, PCA. You fit them and then transform. There's no target to give. There's no predict.
The two compose: a chain of transformers followed by one predictor is the shape of each real scikit-learn program. Pipeline exists to hold just that chain and give it the same fit/predict interface as a single estimator. That is why the API is worth learning first. A whole pipeline is used the same way as the simplest model in the library.
score, and what it means for each
each predictor has a score(X, y) method. The number it returns isn't the same quantity for all of them.
For a classifier, score is fit: the proportion of guesses that were right. For a regressor, it's R²: how much of the variation in the target the model accounts for. Here 1.0 is perfect and 0.0 is no better than always guessing the mean. R² can be minus. This Means the model is worse than that guess.
Both are conveniences rather than recommendations. Fit is misleading when the classes are imbalanced. R² says nothing about whether the errors are large in the units you care about. Later modules replace both with metrics that answer a exact question. The metrics module holds a few dozen of them.
What score is truly handy for is a quick check on the same data with the same model type. For the cross-validation helpers. This Call it by default when you don't name a metric.
Hyperparameters are set at construction
The constructor takes the settings, and there are often many of them with sensible defaults. RandomForestClassifier() works, and so does RandomForestClassifier(n_estimators=500, max_depth=8, random_state=0).
Two methods make those settings inspectable. get_params() returns each setting as a dict, including the defaults you didn't pass. This Is the fastest way to find out what an estimator can be configured with. set_params(**kw) changes them on an existing object.
Those two aren't conveniences either. They're what makes automated tuning possible. GridSearchCV works by calling set_params with each combination in turn. This Is why it can tune any estimator in the library, including ones written after it. Any step inside a pipeline.
The corollary is worth remembering: an estimator is fully given by its settings plus its fitted fields. Nothing else is hidden on it.
random_state, and reproducibility
any of it in the library that makes a random choice takes a random_state. Splitting data, resetting k-means, sampling features in a random forest, shuffling folds — all of them.
Passing an int makes the run reproducible: the same data and the same seed give the same result, each time, on any machine. Leaving it out gives a different answer on each run. This Makes two scores incomparable and a bug hopeless to copy.
The habit worth forming early is to pass random_state=0 to all that accepts it while you're learning or debugging. To think carefully before dropping it. The one time you truly want it left out is when you're on purpose measuring how much the result varies between runs. Which is a real question. One that a single seeded number can't answer.
When an estimator has not been fitted
Calling predict before fit raises NotFittedError rather than returning nonsense. The message names the estimator and tells you to call fit.
That check exists because the option is worse. An unfitted model has no coeffs. A library that quietly returned zeros or None would give a program that runs, makes numbers, and is fully meaningless.
It's also why the underscore rule matters in practice rather than only stylistically: check_is_fitted works by looking for fields ending in an underscore. An estimator you write yourself gets the same action for free by following the same rule.
Why one interface was the right decision
It's worth appreciating how unusual this is. Most machine-learning code before scikit-learn had a shape per method. One library wanted a mat and a separate label vector, another wanted them mixed, a third wanted a configuration file. Checking two methods meant rewriting the code around them. This Meant people checked far fewer than they should have.
Fixing the interface changed what is cheap. Swapping a model turns one line. Trying five is a loop rather than a project. Cross-validation can be written once and work with any of it. It only needs fit, predict and score. A pipeline can hold any steps, because each step honours fit and transform. A tuner can search any estimator, because get_params and set_params are universal.
None of that needed the methods to have any of it in common in math. A choice tree and a linear model share no theory at all. They share three method names. That turned out to be enough to build the entire ecosystem of helpers on top.
The hands-on consequence for someone learning: time spent on the API isn't cost before the notable part. It *is* the part that transfers. The estimators are on their own simple to use once the shape is familiar. The modules that follow spend most of their words on when each one is right and how to tell whether it worked, rather than on how to call it.
What the library deliberately does not do
Knowing the boundary saves looking for things that aren't there.
scikit-learn doesn't do deep learning. There's a small MLPClassifier, handy for a demo and not for real work. Any of it serious belongs in PyTorch or a similar library. It doesn't do GPUs. It doesn't do sequence models, text generation, or any of it with the word "neural" beyond that one estimator.
It doesn't handle data loading, cleaning or plotting. Data arrives as arrays or DataFrames that pandas made. Results are plotted with matplotlib. Those are separate libraries on purpose. The boundary is clean. Scikit-learn takes numeric arrays and returns numeric arrays.
It also doesn't do statistical inference. A linear model gives you coeffs and no p-values, no trust intervals and no hypothesis tests. The library is built around guess rather than explanation. statsmodels is the library for that question. Reaching for it's the right answer rather than a workaround.
Is the API stable? Remarkably so. Code written against fit/predict a decade ago still runs. This Is unusual in this field and is a large part of why the library is worth learning properly.
Things to try with fit, predict, transform
Run the first editor.hasattr(model, "coef_") is False before fit and True after. That is the whole of what fitting does, visible in one line.
Change the data. Make the tie non-linear. y = [1, 4, 9, 16, 25, 36]. And watch coef_ become a compromise rather than an exact fit.
Swap the model. In the fifth editor, add from sklearn.svm import SVR and put SVR() in the list. Nothing else changes.
Break the shape. In the last editor, read the error properly. It names the shape it got and the shape it wanted.
Questions people ask about fit, predict, transform
Do I need to know deep learning to use scikit-learn? No. Scikit-learn covers classical machine learning such as linear models, trees, clustering and preprocessing. Neural networks at scale are handled by libraries like PyTorch.
What is the difference between fit and fit_transform?fit_transform fits a transformer and returns the transformed data in one call. Use it on training data only; call plain transform on anything else.
Why does X have to be two-dimensional? Because every estimator expects rows as samples and columns as features. A single feature still needs reshape(-1, 1).
Does scikit-learn run on a GPU? Not by default. It runs on the CPU with NumPy and SciPy, and some estimators accept an n_jobs argument to use several cores.
Can I use pandas DataFrames with it? Yes. DataFrames are accepted as input and column names are kept as feature_names_in_ after fitting.
Where should a beginner start? Load a small dataset, split it, fit a LogisticRegression or LinearRegression, and call score on the held-out part. That loop is the whole library in miniature.
Where this leaves you
Three method names, one shape rule and one naming rule cover the surface of the entire library. All from here is a choice of estimator and, far more importantly, whether the number it reports can be believed.
Check yourself
0 of 4
Answer without scrolling back up.
What does a trailing underscore on an attribute mean?
Attributes like coef_ and classes_ do not exist until fit has run. Names without the underscore are settings you chose.
What shape must X be?
One row per sample, one column per feature - which is why a single feature still needs reshape(-1, 1).
What does a transformer have instead of predict?
Transformers produce new features rather than a target, and fit_transform does both steps in one call.
Why does fit return the estimator?
Every fit returns self, which is what makes the constructor-and-fit one-liner work.
Cheat sheet
fit, predict, transform
Learn the fit/predict/transform interface that every scikit-learn estimator shares, and see why one API covers two hundred algorithms.
Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.