Machine Learning·Lesson 2·13 min·0/4 exercises

The estimator API

Four methods, two kinds of number, and one very informative underscore

fitpredicttransformscoreget_paramstrailing underscore

Watch it happen

Play it through, or step back and forth yourself.

model = LogisticRegression(C=1.0, max_iter=1000)
model.fit(X_train, y_train)
reads the data · writes onto the object · returns itself
model.predict(X_test)
applies what was learned · changes nothing
model.score(X_test, y_test)
accuracy for classifiers, R² for regressors — and a trap
fit(X, y)learn
predict(X)apply
transform(X)apply, for preprocessors
score(X, y)one number
get_params() / set_params()the hyperparameters

scikit-learn's real achievement isn't any one algorithm — it's that all of them wear the same interface. Learn these four methods and you can drive a model you've never heard of.

The idea

scikit-learn's real achievement isn't any particular algorithm — it's that two hundred of them wear the same interface. Learn it once and you can drive a model you've never heard of, which is why this short lesson pays for itself repeatedly.

Construct, then fit

model = LogisticRegression(C=1.0, max_iter=1000)   # learns nothing
model.fit(X_train, y_train)                        # learns everything

The constructor only records your choices. Nothing is computed, no data is touched — you can build an estimator with no data in the room at all. All the work happens in fit, which reads the data and writes what it found onto the object.

Two properties of fit worth knowing. It returns the object itself, so model.fit(X, y).predict(X_test) chains. And it replaces rather than accumulates: fit a second time and the first fit is gone. (A handful of estimators offer partial_fit for genuine incremental learning; the default is a clean slate.)

The trailing underscore

This is the convention worth internalising, because it is load-bearing across the whole library. An attribute ending in _ was learned from the data. Before fit, it doesn't exist:

model = LogisticRegression()
hasattr(model, "coef_")      # False
model.coef_                   # AttributeError: not fitted yet

model.fit(X_train, y_train)
hasattr(model, "coef_")      # True
model.coef_                   # [[1.018, 0.173, 0.076, -0.125, 0.995]]
model.classes_                # [0 1]
model.n_features_in_          # 5

It holds everywhere, and it tells you where to look when you want to know what a model learned:

StandardScaler          mean_, scale_
SimpleImputer           statistics_
OneHotEncoder           categories_
DecisionTreeClassifier  tree_, feature_importances_
KMeans                  cluster_centers_, labels_
PCA                     components_, explained_variance_

It's also how NotFittedError works internally — scikit-learn checks for the underscored attributes. When you see that error, you called predict before fit, or on a copy that was never fitted.

The two kinds of number

Every model has numbers in it, and they come from two completely different places. Confusing them is the single most common source of muddle for people starting out.

HyperparametersParameters
You choose themThe data chooses them
Before fitting, in the constructorDuring fit
No underscoreTrailing underscore
C=1.0, max_depth=4coef_, tree_

Module 6 is entirely about choosing hyperparameters well; lesson 25 is about reading parameters to understand what the model learned. Keeping the two straight now saves a lot of confusion later.

model.get_params()               # everything you chose
model.set_params(C=0.1)          # change one, without rebuilding

get_params isn't just introspection — it's the mechanism GridSearchCV uses to try different settings, and how clone() makes a fresh unfitted copy. Because every estimator implements it, tools can manipulate models they know nothing about.

predict, transform, score

predict(X) applies what was learned to new rows and changes nothing. Classifiers also offer predict_proba(X), which gives the probabilities behind those labels — lesson 7 is about why you usually want those instead.

transform(X) is what preprocessors have where models have predict. Same contract: fit learns (the column medians, the category list), transform applies. And:

X_train_s = scaler.fit_transform(X_train)   # fit AND transform — training only
X_test_s  = scaler.transform(X_test)        # transform ONLY — never fit here

That asymmetry is not a style preference. Fitting the scaler on the test set lets the test set's means influence the training data — the test set stops being unseen. Lesson 13 makes it impossible to get wrong by putting everything in a Pipeline.

score(X, y) gives one number: accuracy for classifiers, R² for regressors. It's convenient, and it's a trap, because it picks a metric for you and the default is very often the wrong one. The whole of module 2 is about that.

Why it's worth caring about the interface

Because it composes. A Pipeline is an estimator; so is a ColumnTransformer, and a GridSearchCV. Each of them implements fit/predict, so they nest inside each other freely and every tool in the library accepts them. Swapping LogisticRegression for RandomForestClassifier inside a tuned pipeline is a one-word change — and that is a direct consequence of these four method names.

See it run

The lesson's code, ready to run and to fiddle with.

Putting the kettle on…

Starting up…

Worked example

not graded

Already written and ready to go — press Run to see what it does, then change a number, a column name, anything, and run it again.

trycalling model.predict(...) before fit and reading the NotFittedError carefully.

Press Run — the output appears here.

Your turn

4 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.

Build a LogisticRegression and do not fit it. Return whether it has a coef_ yet, as a bool.

your answer

Fit a SimpleImputer(strategy="median") on X[NUMERIC] and return what it learned — statistics_, rounded to 2 decimal places, as a list.

your answer

Sort the two kinds apart. Return a list of only the hyperparameters from ["C", "coef_", "max_iter", "classes_", "n_features_in_"] — the ones you set rather than the ones the data set.

your answer

Show that fit returns the object itself — which is what makes model.fit(X, y).predict(X) work. Return model.fit(...) is model as a bool.

your answer