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.

What is Machine Learning?

Machine learning is about learning a relationship from data instead of writing the rules by hand. Four terms come up constantly:

  • Features are the input variables.
  • Labels are the thing we are trying to predict.
  • A model defines the relationship between features and labels.
  • A model has two phases. Training means creating or learning the model. Inference means applying the trained model to unlabelled examples.

Types of Machine Learning

SupervisedUnsupervisedSemi-supervisedReinforcement
OverviewThe majority of algorithms. The machine is trained on well-labelled data, so inputs and outputs are matched.Only unlabelled data (inputs) is analysed. Learning happens without supervision.Some data is labelled and some is not. The goal is better results than labelled data alone, which suits real-world data.An agent interacts with its environment, acting and learning from errors or rewards.
ProcessA mapping function takes inputs and matches them to outputs, creating a target function.The inputs are used to build a model of the data.A combination of the supervised and unsupervised processes.Trial and error.
SubtypesClassification, regressionClustering, association, dimensionality reductionClassification, regression, clustering, associationReward-based
AlgorithmsLinear regression, random forest, SVMPCA, K-meansMixture models, semi-supervised SVMQ-learning

Supervised Learning: Classification vs Regression

ClassificationRegression
OverviewPredicting a discrete class label.Predicting a continuous quantity.
SubtypesBinary: data falls into two classes.
Multiclass: data falls into more than two classes.
Single regression: one dependent variable (y) and one independent variable (x).
Multivariate regression: one dependent variable (y) and two or more independent variables (x).
AlgorithmsNaive Bayes, logistic regressionLinear regression
OutputCategoricalContinuous
AssessmentAccuracy, confusion matrixRoot mean squared error
ExamplesBinary: is this email spam or not?
Multiclass: which dog breed is in the photo (pug, labrador, golden retriever, ...)?
Single: predict a house price (y) from its square footage (x).
Multivariate: predict the price (y) from square footage, number of rooms, nearby schools, and so on (x).

Unsupervised Learning: Clustering vs Association

ClusteringAssociation
OverviewGrouping objects so that those in the same group are more similar to each other than to objects in other groups.Finding associations between items in large commercial databases.
MethodsFive common families: partitioning, hierarchical, fuzzy, density-based and model-based.Rule mining over item co-occurrence.
AlgorithmsK-means, agglomerativeApriori
ExampleSplitting similar clothing into stacks.Finding which clothes someone often wears together.

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.

Scaler:

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 / norms
Check: np.linalg.norm(X_norm, axis=1) should be all 1.0
Try 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'])