Below is a ready-to-use lab-based notebook structure.
Copy + paste each block into a Jupyter notebook, keeping the order.
In this notebook, we will learn machine learning step-by-step:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.preprocessing import StandardScaler
plt.style.use("ggplot")If load_boston is unavailable, use a CSV version.
# Try Scikit-Learn version
try:
from sklearn.datasets import load_boston
boston = load_boston()
X = pd.DataFrame(boston.data, columns=boston.feature_names)
y = pd.Series(boston.target, name="MEDV")
df = X.copy()
df["MEDV"] = y
print("Loaded from sklearn.")
except:
print("load_boston unavailable. Loading CSV...")
df = pd.read_csv("boston.csv")
df.rename(columns={"medv": "MEDV"}, inplace=True)
df.head()We check:
df.info()df.describe()In real machine learning projects, data is rarely clean.
So we intentionally add:
df_with_issues = df.copy()
n_rows = df_with_issues.shape[0]
# 3.1 β Add Missing Values in RM
np.random.seed(42)
missing_indices = np.random.choice(n_rows, size=int(0.01*n_rows), replace=False)
df_with_issues.loc[missing_indices, "RM"] = np.nan
# 3.2 β Add Outliers in LSTAT
outlier_indices = np.random.choice(n_rows, size=5, replace=False)
df_with_issues.loc[outlier_indices, "LSTAT"] = df_with_issues["LSTAT"].max() * 5
# 3.3 β Incorrect Scaling (CRIM)
scale_indices = np.random.choice(n_rows, size=10, replace=False)
df_with_issues.loc[scale_indices, "CRIM"] *= 1000
df_with_issues.head()Check for:
df_with_issues.isnull().sum()df_with_issues["LSTAT"].describe()df_with_issues["LSTAT"].hist(bins=30)
plt.title("LSTAT Distribution (with outliers)")
plt.show()Scatter plots help us see whether a feature correlates with the target.
plt.scatter(df_with_issues["RM"], df_with_issues["MEDV"])
plt.xlabel("RM")
plt.ylabel("MEDV")
plt.title("RM vs MEDV")
plt.show()plt.scatter(df_with_issues["LSTAT"], df_with_issues["MEDV"])
plt.xlabel("LSTAT")
plt.ylabel("MEDV")
plt.title("LSTAT vs MEDV")
plt.show()Steps:
df_clean = df_with_issues.copy()
df_clean["RM"].fillna(df_clean["RM"].mean(), inplace=True)Q1 = df_clean["LSTAT"].quantile(0.25)
Q3 = df_clean["LSTAT"].quantile(0.75)
IQR = Q3 - Q1
upper = Q3 + 1.5*IQR
lower = Q1 - 1.5*IQR
df_clean["LSTAT"] = df_clean["LSTAT"].clip(lower=lower, upper=upper)Q1c = df_clean["CRIM"].quantile(0.25)
Q3c = df_clean["CRIM"].quantile(0.75)
IQRc = Q3c - Q1c
upper_c = Q3c + 1.5 * IQRc
df_clean["CRIM"] = df_clean["CRIM"].clip(upper=upper_c)X = df_clean.drop(columns=["MEDV"])
y = df_clean["MEDV"]
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_scaled = pd.DataFrame(X_scaled, columns=X.columns)We evaluate the model on unseen test data.
X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y, test_size=0.2, random_state=42
)Linear Regression attempts to fit a straight hyperplane through the data.
lin_reg = LinearRegression()
lin_reg.fit(X_train, y_train)
print("Intercept:", lin_reg.intercept_)
pd.Series(lin_reg.coef_, index=X_train.columns)Use MSE and RΒ².
y_pred = lin_reg.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
mse, r2plt.scatter(y_test, y_pred)
plt.xlabel("Actual MEDV")
plt.ylabel("Predicted MEDV")
plt.title("Actual vs Predicted")
plt.show()Remember: Always apply the same pre-processing as training.
new_house = pd.DataFrame([{
"CRIM": 0.1, "ZN": 0, "INDUS": 8, "CHAS": 0, "NOX": 0.5,
"RM": 6, "AGE": 60, "DIS": 4, "RAD": 4, "TAX": 300,
"PTRATIO": 18, "B": 390, "LSTAT": 10
}])
new_scaled = scaler.transform(new_house)
lin_reg.predict(new_scaled)INDUS by 100 for some rowsβwhat happens in describe()?CRIM. What do you notice?df_with_issues.quantile([0.01, 0.99]) to inspect extreme values.Plot scatter plots for
Which seems more linear?