Machine Learning Basics
Before diving into algorithms, it's crucial to understand the mathematical foundations and data manipulation libraries that power standard ML pipelines in Python: Numpy and Pandas.
NumPy Arrays
NumPy is the foundation of the Python data science stack. Its ndarray stores homogeneous typed data in contiguous memory, making vectorised operations orders of magnitude faster than Python lists.
import numpy as np
# Creating arrays
a = np.array([1, 2, 3, 4, 5]) # 1-D
A = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]) # 2-D (3×3)
print(A.shape) # (3, 3)
print(A.dtype) # int64
# Useful constructors
np.zeros((3, 4)) # all zeros
np.ones((2, 2)) # all ones
np.eye(3) # identity matrix
np.arange(0, 10, 2) # [0, 2, 4, 6, 8]
np.linspace(0, 1, 5) # [0, .25, .5, .75, 1]
Indexing & Slicing
A = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(A[0, 1]) # 2 — row 0, col 1
print(A[:, 1]) # [2, 5, 8] — entire column 1
print(A[1:, :2]) # [[4, 5], [7, 8]] — rows 1+, cols 0-1
# Boolean masking
print(A[A > 5]) # [6, 7, 8, 9]
Broadcasting
NumPy can operate on arrays of different shapes by "stretching" the smaller array — no Python loop needed:
row = np.array([1, 2, 3]) # shape (3,)
col = np.array([[10], [20], [30]]) # shape (3, 1)
# Broadcasting adds them as if both were (3, 3)
print(row + col)
# [[11 12 13]
# [21 22 23]
# [31 32 33]]
Statistical Functions
data = np.array([4, 7, 2, 9, 1, 5])
print(data.mean()) # 4.67
print(data.std()) # 2.62
print(data.min(), data.max()) # 1 9
print(np.median(data)) # 4.5
print(np.percentile(data, 75)) # 6.5
Matrix Operations
Linear algebra is the language of ML models. Every weight update, every dot-product attention score, every PCA decomposition is matrix arithmetic.
import numpy as np
A = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
B = np.array([[1, 2, 0],
[3, 4, 2],
[4, 7, 8]])
Element-wise Operations
print(A + B) # element-wise addition
print(A * B) # element-wise multiplication (NOT dot product)
print(A ** 2) # square every element
Matrix Multiplication (Dot Product)
print(A @ B) # preferred: @ operator (Python 3.5+)
print(np.dot(A, B)) # equivalent
# Output:
# [[ 19 31 28]
# [ 43 70 58]
# [ 67 109 88]]
Transpose & Inverse
print(A.T) # transpose — rows become columns
# Inverse (only for square, non-singular matrices)
M = np.array([[2, 1], [5, 3]])
print(np.linalg.inv(M))
# [[ 3. -1.]
# [-5. 2.]]
# Verify: M @ inv(M) ≈ identity
print(M @ np.linalg.inv(M)) # [[1, 0], [0, 1]]
Norms & Eigenvalues
v = np.array([3, 4])
print(np.linalg.norm(v)) # 5.0 (Euclidean / L2 norm)
eigenvalues, eigenvectors = np.linalg.eig(A)
print(eigenvalues) # used in PCA
Pandas DataFrames
A DataFrame is a labelled 2-D table — think a spreadsheet you can program. It's the standard container for tabular data before feeding it into an ML model.
import pandas as pd
# Create from dict
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Carol', 'Dave'],
'age': [25, 32, 28, 45],
'score': [88.5, 91.0, 76.3, 83.7],
})
print(df.head(2))
print(df.dtypes)
print(df.describe()) # count, mean, std, min, quartiles, max
Selecting & Filtering
# Select columns
print(df['age']) # Series
print(df[['name', 'score']]) # DataFrame
# Filter rows
print(df[df['age'] > 28])
print(df.query('score >= 85 and age < 40'))
# .loc (label-based) vs .iloc (integer-based)
print(df.loc[0, 'name']) # 'Alice'
print(df.iloc[1, 2]) # 91.0
Handling Missing Data
df2 = pd.DataFrame({'a': [1, None, 3], 'b': [4, 5, None]})
print(df2.isnull().sum()) # count NaNs per column
df2.fillna(df2.mean(), inplace=True) # fill with column mean
df2.dropna(inplace=True) # or drop rows with any NaN
GroupBy & Aggregation
sales = pd.DataFrame({
'region': ['North', 'South', 'North', 'South', 'North'],
'sales': [200, 150, 300, 250, 180],
})
summary = sales.groupby('region')['sales'].agg(['mean', 'sum', 'count'])
print(summary)
# mean sum count
# North 226.7 680 3
# South 200.0 400 2
Matplotlib
Matplotlib is Python's core plotting library. For ML workflows it's used to inspect data distributions, visualise training curves, and sanity-check model outputs.
Line & Scatter Plots
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 2 * np.pi, 100)
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
# Line plot
axes[0].plot(x, np.sin(x), label='sin(x)', color='steelblue')
axes[0].plot(x, np.cos(x), label='cos(x)', color='coral', linestyle='--')
axes[0].set_title('Line Plot')
axes[0].legend()
# Scatter plot
np.random.seed(42)
axes[1].scatter(np.random.randn(50), np.random.randn(50),
c='indigo', alpha=0.6, edgecolors='white')
axes[1].set_title('Scatter Plot')
plt.tight_layout()
plt.show()
Histograms & Box Plots
data = np.random.normal(loc=60, scale=15, size=500)
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
# Histogram — check distribution shape
axes[0].hist(data, bins=30, color='steelblue', edgecolor='white')
axes[0].set_title('Distribution of Scores')
axes[0].set_xlabel('Score')
# Box plot — spot outliers and IQR
axes[1].boxplot(data, vert=True, patch_artist=True,
boxprops=dict(facecolor='lightblue'))
axes[1].set_title('Box Plot')
plt.tight_layout()
plt.show()
Training Curve
# Visualising model training — the most common ML plot
epochs = range(1, 51)
train_loss = [1.0 * 0.92**e for e in epochs]
val_loss = [1.0 * 0.93**e + 0.05 for e in epochs]
plt.figure(figsize=(8, 4))
plt.plot(epochs, train_loss, label='Train loss')
plt.plot(epochs, val_loss, label='Val loss', linestyle='--')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training vs Validation Loss')
plt.legend()
plt.grid(alpha=0.3)
plt.show()
Scikit-learn Essentials
Scikit-learn provides a consistent API for the full ML workflow: split → preprocess → fit → evaluate. Every estimator exposes fit(), predict(), and score().
Train / Test Split
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2, # 20% held out
random_state=42, # reproducible
stratify=y, # preserve class proportions
)
print(X_train.shape, X_test.shape) # (120, 4) (30, 4)
Preprocessing
from sklearn.preprocessing import StandardScaler, LabelEncoder
# Standardise features: subtract mean, divide by std
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # fit on train only
X_test_scaled = scaler.transform(X_test) # apply same transform to test
# Encode string labels to integers
le = LabelEncoder()
y_encoded = le.fit_transform(['cat', 'dog', 'cat', 'bird'])
# [1, 2, 1, 0]
Pipelines
A Pipeline chains steps so that preprocessing never leaks test data into training:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
pipe = Pipeline([
('scaler', StandardScaler()),
('clf', LogisticRegression(max_iter=200)),
])
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)
print(classification_report(y_test, y_pred,
target_names=load_iris().target_names))
Evaluation Metrics
from sklearn.metrics import (
accuracy_score, precision_score, recall_score, f1_score,
confusion_matrix, mean_squared_error, r2_score
)
# Classification
print("Accuracy: ", accuracy_score(y_test, y_pred))
print("F1 macro:", f1_score(y_test, y_pred, average='macro'))
print(confusion_matrix(y_test, y_pred))
# Regression
from sklearn.linear_model import LinearRegression
reg = LinearRegression().fit(X_train, y_train)
y_reg = reg.predict(X_test)
print("RMSE:", mean_squared_error(y_test, y_reg, squared=False))
print("R²: ", r2_score(y_test, y_reg))
Always fit your scaler on training data only, then transform both train and test sets. Fitting on the full dataset leaks test statistics into training — a subtle but common mistake that inflates evaluation scores.
Feature Scaling Visualizer
See exactly how StandardScaler (z-score) and MinMaxScaler transform the same raw values. Add an outlier to watch how each scaler reacts.
Original Values
Scaled Values
Try it yourself — Row-normalise a matrix
Given X = np.array([[3,4],[1,0],[0,2]]), divide each row by its L2 norm so every row has unit length.
Show hint
norms = np.linalg.norm(X, axis=1, keepdims=True)X_norm = X / normsCheck:
np.linalg.norm(X_norm, axis=1) should be all 1.0Try it yourself — GroupBy aggregation
Create a DataFrame with year (2022/2023), product (A/B), and sales columns. Find total and mean sales per year.
Show hint
df.groupby('year')['sales'].agg(['sum', 'mean'])Knowledge Check
Test your understanding — pick the best answer, then click Check.
1. Why should you only fit a StandardScaler on training data?
2. What does A @ B compute in NumPy?
3. What does df.groupby('col').mean() return?