What’s one thing you learned? What’s still confusing?
Databases with Python: SQLite, SQLAlchemy & Data Access
Connect to databases, write SQL, use SQLAlchemy ORM, integrate Pandas with SQL.
Type Hints, Testing & Code Quality
Type hints, mypy, pytest basics, unittest.mock, PEP 8, and good docstrings.
Classes & Object-Oriented Programming
Create classes with __init__, methods, @property, __slots__, and inheritance basics.
Interactive Labs for This Track
Loop Visualizer
You're a factory robot repeating the same task on an assembly line — watch how loops automate repetitive work
List Slicing
You have a playlist of 50 songs — grab just tracks 10 through 20 with a single slice expression
Sorting Algorithms
You're organizing a library of 10,000 books — which sorting method is fastest?
Ask questions, share insights
fit/predict instead of compute. scikit-learn's fit/predict API is so well-designed that PyTorch, XGBoost, and Hugging Face all copied it.Every sklearn project follows the same five-step pattern:
# Step 1: Load data
from sklearn.datasets import load_iris
import numpy as np
iris = load_iris()
X = iris.data # features: sepal length, sepal width, petal length, petal width
y = iris.target # labels: 0=setosa, 1=versicolor, 2=virginica
print(f"Features shape: {X.shape}") # (150, 4) -- 150 samples, 4 features
print(f"Labels shape: {y.shape}") # (150,) -- 150 labels
print(f"Feature names: {iris.feature_names}")
print(f"Classes: {iris.target_names}") # ['setosa', 'versicolor', 'virginica']
# Step 2: Split into training and testing sets
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
print(f"Training set: {X_train.shape[0]} samples") # 120
print(f"Test set: {X_test.shape[0]} samples") # 30
# Step 3: Choose and create a model
from sklearn.tree import DecisionTreeClassifier
model = DecisionTreeClassifier(max_depth=3, random_state=42)
# Step 4: Train the model
model.fit(X_train, y_train)
# Step 5: Predict and evaluate
y_pred = model.predict(X_test)
from sklearn.metrics import accuracy_score
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy:.2%}") # ~96.67%We trained five models on the same Iris dataset (120 training samples, 30 test samples). Which model do you predict will have the HIGHEST accuracy?
Every sklearn model follows the same interface:
# This works for ANY sklearn model -- just swap the class
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
models = {
"Decision Tree": DecisionTreeClassifier(max_depth=3, random_state=42),
"Logistic Regression": LogisticRegression(max_iter=200, random_state=42),
"Random Forest": RandomForestClassifier(n_estimators=100, random_state=42),
"SVM": SVC(random_state=42),
"KNN (k=5)": KNeighborsClassifier(n_neighbors=5),
}
print("Model comparison on Iris dataset:")
print("-" * 40)
for name, model in models.items():
model.fit(X_train, y_train)
accuracy = model.score(X_test, y_test) # score() = predict() + accuracy
print(f" {name:25s} accuracy: {accuracy:.2%}")
# Decision Tree accuracy: 96.67%
# Logistic Regression accuracy: 100.00%
# Random Forest accuracy: 96.67%
# SVM accuracy: 96.67%
# KNN (k=5) accuracy: 96.67%fit() / predict() / score() API. This is the power of sklearn's consistent design -- you learned the pattern once, now you can use hundreds of algorithms.from sklearn.datasets import make_regression
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
# Generate synthetic regression data
X, y = make_regression(n_samples=200, n_features=1, noise=20, random_state=42)
# Split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Train
reg = LinearRegression()
reg.fit(X_train, y_train)
# Evaluate
y_pred = reg.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f"Slope (coefficient): {reg.coef_[0]:.2f}")
print(f"Intercept: {reg.intercept_:.2f}")
print(f"Mean Squared Error: {mse:.2f}")
print(f"R^2 Score: {r2:.4f}") # 1.0 = perfect, 0.0 = no better than mean# For Classification — re-load Iris because the regression demo above
# reassigned X_train/y_train. Always re-establish your splits before reusing
# the names!
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import classification_report, confusion_matrix
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, test_size=0.2, random_state=42, stratify=iris.target
)
tree = DecisionTreeClassifier(max_depth=3, random_state=42)
tree.fit(X_train, y_train)
y_pred = tree.predict(X_test)
print("Confusion Matrix:")
print(confusion_matrix(y_test, y_pred))
# (Rows = true class, cols = predicted class — a diagonal-heavy matrix
# is a good sign. Exact counts depend on the split.)
print("\nClassification Report:")
print(classification_report(y_test, y_pred, target_names=iris.target_names))
# Shows precision, recall, f1-score per class
# For Regression:
# MSE (Mean Squared Error) -- average of squared errors, penalizes large errors
# R^2 Score -- how much variance the model explains (1.0 = perfect)
# MAE (Mean Absolute Error) -- average of absolute errors, easier to interpretfrom sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
# Without pipeline (error-prone -- easy to forget a step)
# scaler = StandardScaler()
# X_train_scaled = scaler.fit_transform(X_train)
# X_test_scaled = scaler.transform(X_test) # must use transform, not fit_transform!
# With pipeline (clean, safe, reproducible)
pipe = Pipeline([
("scaler", StandardScaler()), # Step 1: normalize features
("pca", PCA(n_components=2)), # Step 2: reduce to 2 dimensions
("classifier", LogisticRegression()), # Step 3: classify
])
# The pipeline handles everything correctly
pipe.fit(X_train, y_train)
accuracy = pipe.score(X_test, y_test)
print(f"Pipeline accuracy: {accuracy:.2%}")
# Under the hood, pipe.fit() does:
# 1. scaler.fit_transform(X_train) -- learn mean/std, then scale
# 2. pca.fit_transform(X_train_scaled) -- learn components, then project
# 3. classifier.fit(X_train_projected, y_train) -- learn decision boundary
#
# And pipe.predict() does:
# 1. scaler.transform(X_test) -- scale using training stats
# 2. pca.transform(X_test_scaled) -- project using training components
# 3. classifier.predict(X_test_projected) -- make predictions# DATA LEAKAGE -- a subtle but devastating bug
# BAD: fitting the scaler on ALL data (including test)
# scaler.fit(X) # <-- sees test data statistics!
# X_scaled = scaler.transform(X)
# X_train, X_test = split(X_scaled) # test set is "contaminated"
# GOOD: pipeline ensures test data is never seen during fit
pipe = Pipeline([
("scaler", StandardScaler()),
("model", RandomForestClassifier(n_estimators=100, random_state=42)),
])
# fit() only sees training data, predict() applies the same transformations
pipe.fit(X_train, y_train)
print(f"No leakage accuracy: {pipe.score(X_test, y_test):.2%}")ColumnTransformer applies the right transformer to each column group and can be dropped into a Pipeline:import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
# Example dataset: mix of numeric and categorical features
data = pd.DataFrame({
"age": [25, 35, 45, 28, 52, 31],
"income": [50000, 80000, 120000, 62000, 95000, 71000],
"education": ["bachelor", "master", "phd", "bachelor", "master", "bachelor"],
"city": ["NYC", "SF", "NYC", "Austin", "SF", "NYC"],
"bought": [0, 1, 1, 0, 1, 1],
})
X = data.drop("bought", axis=1)
y = data["bought"]
# Identify column types
numeric_features = ["age", "income"]
categorical_features = ["education", "city"]
# ColumnTransformer: apply the right transformer to each group
preprocessor = ColumnTransformer(transformers=[
("num", StandardScaler(), numeric_features),
("cat", OneHotEncoder(handle_unknown="ignore"), categorical_features),
# Any columns NOT listed are dropped by default (set remainder="passthrough" to keep)
])
# Full pipeline: preprocess + model
full_pipeline = Pipeline([
("preprocessor", preprocessor),
("classifier", RandomForestClassifier(n_estimators=100, random_state=42)),
])
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
full_pipeline.fit(X_train, y_train)
print(f"Accuracy: {full_pipeline.score(X_test, y_test):.2%}")
# Inspect what the preprocessor produces
X_transformed = preprocessor.fit_transform(X_train)
print(f"Original shape: {X_train.shape}") # (4, 4)
print(f"Transformed shape: {X_transformed.shape}") # (4, N) where N = 2 + num_one_hot_categoriesColumnTransformer is the standard sklearn way to handle mixed-type data. The remainder parameter controls what happens to columns you did not list: "drop" (default) discards them, "passthrough" keeps them unchanged.from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
# Generate data with 3 natural clusters
X_blobs, true_labels = make_blobs(n_samples=300, centers=3, random_state=42)
# KMeans does NOT get the labels -- it discovers clusters on its own
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
kmeans.fit(X_blobs)
# Results
predicted_clusters = kmeans.labels_
centers = kmeans.cluster_centers_
print(f"Cluster centers:\n{centers}")
print(f"Inertia (lower = tighter clusters): {kmeans.inertia_:.2f}")
# Compare to true labels
from sklearn.metrics import adjusted_rand_score
ari = adjusted_rand_score(true_labels, predicted_clusters)
print(f"Adjusted Rand Index: {ari:.4f}") # 1.0 = perfect matchinertias = []
K_range = range(1, 10)
for k in K_range:
km = KMeans(n_clusters=k, random_state=42, n_init=10)
km.fit(X_blobs)
inertias.append(km.inertia_)
# The "elbow" in the plot indicates the best k
for k, inertia in zip(K_range, inertias):
bar = "#" * int(inertia / 100)
print(f"k={k}: inertia={inertia:>10.2f} {bar}")
# k=1: inertia= 13245.87 ################################################...
# k=2: inertia= 4935.21 ##################
# k=3: inertia= 1221.45 #### <-- elbow! Adding more clusters helps less
# k=4: inertia= 981.12 ###
# k=5: inertia= 814.33 ##Here is a full, production-ready workflow combining everything:
import numpy as np
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report
# 1. Load data
wine = load_wine()
X, y = wine.data, wine.target
print(f"Dataset: {X.shape[0]} samples, {X.shape[1]} features, {len(wine.target_names)} classes")
# 2. Split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 3. Build pipeline
pipeline = Pipeline([
("scaler", StandardScaler()),
("model", RandomForestClassifier(n_estimators=100, random_state=42)),
])
# 4. Cross-validation (more robust than a single train/test split)
cv_scores = cross_val_score(pipeline, X_train, y_train, cv=5, scoring="accuracy")
print(f"\nCross-validation scores: {cv_scores}")
print(f"Mean CV accuracy: {cv_scores.mean():.2%} (+/- {cv_scores.std():.2%})")
# 5. Train on full training set
pipeline.fit(X_train, y_train)
# 6. Final evaluation on test set
y_pred = pipeline.predict(X_test)
print(f"\nTest accuracy: {pipeline.score(X_test, y_test):.2%}")
print("\nDetailed Report:")
print(classification_report(y_test, y_pred, target_names=wine.target_names))
# 7. Feature importance (from the Random Forest inside the pipeline)
importances = pipeline.named_steps["model"].feature_importances_
feature_ranking = sorted(zip(wine.feature_names, importances), key=lambda x: -x[1])
print("Top 5 most important features:")
for name, importance in feature_ranking[:5]:
bar = "#" * int(importance * 100)
print(f" {name:30s} {importance:.4f} {bar}")Tests · Build a complete ML pipeline from data loading to evaluation!
bedrooms=3 vs square_feet=2000 — the large number dominates.from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# ❌ WRONG — data leakage: scaler sees test data before predicting
scaler_wrong = StandardScaler()
X_all_scaled = scaler_wrong.fit_transform(X) # fits on test data too!
# ✅ CORRECT — fit ONLY on training data
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # learn mean/std from train
X_test_scaled = scaler.transform(X_test) # apply SAME scale to testfit() trains, predict() generates outputs, score() measures accuracyPipeline([("scaler", StandardScaler()), ("model", RandomForest())]) prevents data leakage by ensuring test data is never seen during trainingDecisionTreeClassifier for RandomForestClassifier or SVM and the rest of your code stays identical. This is sklearn's greatest strengthWhat does model.fit(X_train, y_train) do?