Two integrated courses on one page: Coding for Data Science & Data Management (R, Python, SQL and MongoDB) and Statistical Theory & Mathematics. Every topic has an explanation, worked examples, exercises with hidden solutions and a quick quiz. Mark topics done as you go, then sit the timed mocks.
0%0 topics
30 NovPython project deadline (23:59 CET, last commit)
8 DecStatistical Theory project deadline (myAriel)
Not stated in the course pages: confirm these yourself
Written exam dates for both courses check, exam registration deadlines check, the pass mark and module weights for Coding check, the duration of the Statistical Theory test check, whether notes, a formula sheet or a calculator are allowed check, whether Maths homework counts check, whether a Coding mid-term will happen check, the dates of the Python project registration form check. This site never guesses these values.
Your progress
Exam formats
Unit A
Coding for Data Science & Data Management
One written, integrated exam in three modules. You declare beforehand which parts you take; the length depends on that.
Quizzes on relational modelling and NoSQL; SQL exercises; MongoDB find and aggregation exercises
Rules
You accept or reject only the final grade over the three modules, never one module.
A module can be retaken only after rejecting a final grade. To keep passed modules, ask prof. Montanelli to "freeze" them (his approval is needed).
R and Data Management are written only, in ordinary sessions.
Teachers may add a mid-term per unit check. Wooclap quizzes are not graded.
The Python project replaces only the written Python module, in December.
Not stated: December exam date, pass mark, module weights, notes allowed, registration deadline.
Unit B
Statistical Theory & Mathematics
Two separate written tests. Each must be ≥ 18/30; the final mark is the mean of the two. Both must be passed by the end of the academic year, otherwise both are retaken. Online registration is required.
Statistical Theory test
Part
Weight
Multiple choice
≈ 15–20%
Exercises
≈ 55–60%
Theory open questions (exempt with the project)
≈ 25%
Duration not stated check. The mock on this site assumes 2 hours.
Mathematics test
1.5 hours, 30 points (format from the course mock).
Multiple choice (≈ 2–4 points each) with option E "none of the other answers".
Open questions: show all your work. Partial credit for progress; bare correct answers get no credit.
Key dates and deadlines
From the briefs (course pages as of 30 Oct 2026). Red bar = hard deadline.
Date
Course
What
Mon 2 Nov
R
R lesson 16:30–18:30, G12 (swapped with the Python lab)
Tue 3 – Wed 4 Nov
DM
MongoDB aggregation pipeline
Thu 5 Nov
PythonMath
Python lab 16:30–18:30, G09 (data manipulation). Mathematics 10:30–12:30 and 14:30–16:30
Fri 6 Nov
Stat
Statistical Theory 10:30–12:30 and 14:30–16:30 (constructing CIs)
9, 16, 23 Nov
Python
Theory 14:30–16:30, labs 16:30–18:30 (scientific computing, visualisation, Streamlit)
Project registration form opens "for a couple of weeks"; invite the instructor to your GitHub repo
Mon 30 Nov, 23:59 CET
Python
Python project deadline (last GitHub commit counts; no extensions)
1, 2 or 3 Dec
Python
Project evaluation: 5–10 min presentation + questions on code and course topics (slot assigned after registration)
27 Nov, 3–4 Dec
Stat
Make-up classes if necessary
8 Dec
Stat
Statistical Theory project deadline: Rmarkdown that runs, only via myAriel
Wed 16 Dec, 9:30–11:30 check
Stat
Project presentation (looks like you booked Slot 4)
December check
Coding
Written exam: R (30 min), Python (30 min unless project), Data Management (60 min)
Not stated check
Stat/Math
Written tests for Statistical Theory and Mathematics; register online
January 2027 →
Python
Ordinary sessions, incl. the written Python exam if the project is skipped or rejected
Tasks
Open questions to resolve
Study plan
Ordered by deadlines and exam weight. Set your weekly hours and Python choice; hours per item update automatically. The written exam dates are unknown, so the last week is a template to shift once you know them.
A1. Data Management: relational, SQL, NoSQL, MongoDB
This module covers how data is stored and queried: the relational model (tables, keys, constraints), SQL for defining, changing and querying data on PostgreSQL, transactions, using a database from Python, and the NoSQL world with a focus on MongoDB (find queries and the aggregation pipeline). It is examined in the 60-minute Data Management part of the written exam: quizzes on (i) relational data modelling and (ii) NoSQL systems, plus SQL query exercises and MongoDB find and aggregation exercises. The course case study is an IMDB movie database, so every example here uses one small IMDB-style database (defined in A1.1). The exact date, pass mark and the weight of this module in the final grade are NOT STATED on the course page: check the noticeboard.
How to study this module
Order of value for the exam: SQL queries (A1.8 to A1.13) and MongoDB (A1.16, A1.17) are the query-writing exercises, so practise writing them by hand, without running them. The theory topics (A1.1 to A1.7, A1.15) feed the quizzes. Every SQL example was run on a real database (SQLite, with PostgreSQL differences noted) and every MongoDB example was run in mongosh; the outputs shown are real.
Check with the course
The teacher's IMDB case study on PostgreSQL may use different table and column names (for example an imdb schema with other attribute names). The ideas are the same; in the exam, read the given schema carefully and use its exact names.
A1.1 Databases, DBMS and our IMDB example
Why it matters. Every later topic assumes you know what a DBMS does for you (persistence, sharing, integrity, concurrency). This topic also defines the small IMDB database used in all examples of this module.
How it is examined. Quiz questions on relational data modelling: definitions (database, DBMS, data independence, schema vs instance, the three SQL sub-languages DDL/DML/DQL).
Explanation
Definition: Database and DBMS
A database is a collection of data that is organised, persistent (it survives after the program ends) and shared by many users and applications. A DBMS (Database Management System) is the software that manages the database: PostgreSQL, MySQL, SQLite, Oracle (relational) and MongoDB (NoSQL) are DBMSs.
Why not just keep data in CSV or JSON files read by your app? Because a DBMS gives you, for free:
Property
What it means
JS analogy
Large and persistent
Data larger than RAM, stored on disk, survives crashes
Not a const data = [] that dies on reload
Shared
Many users and programs read/write at the same time; the DBMS controls concurrency
Like many browser tabs writing to one backend safely
Reliable
Transactions and recovery after failures (ACID, see A1.7)
No "half-saved" state
Private / secure
Users get permissions (GRANT/REVOKE)
Auth middleware, but inside the DB
Efficient
Indexes and a query optimizer decide how to run your query
You write what you want (declarative), like CSS selectors, not loops
Data independence
Programs do not depend on how data is physically stored
Using an interface, not the implementation
Definition: Data independence
Physical independence: you can change how data is stored (files, indexes) without changing the queries or programs. Logical independence: you can change the logical schema (for example add a view or a column) without breaking existing external views and applications. Classic architecture: external level (views for users) → logical level (tables) → internal/physical level (files, indexes).
Definition: Schema vs instance (preview)
The schema is the structure (table names, column names and types, constraints). It changes rarely. The instance is the actual data at a given moment (the rows). It changes all the time. More in A1.2.
SQL (Structured Query Language) is split into sub-languages:
DDL (Data Definition Language): define the schema, e.g. CREATE TABLE, ALTER TABLE, DROP TABLE (A1.4).
DML (Data Manipulation Language): change the instance, e.g. INSERT, UPDATE, DELETE (A1.5).
DQL (Data Query Language): read data with SELECT (A1.8 onwards). Some books count SELECT as part of DML.
(Also DCL for permissions, GRANT/REVOKE, and transaction control, BEGIN/COMMIT/ROLLBACK.)
The example database used in this whole module
A tiny IMDB-style database with 5 tables. Underlined = primary key; arrows = foreign keys (A1.3, A1.6).
Schema in the usual textbook notation (underline = key, written here as …):
movie(id, title, year, length, budget) (length in minutes, budget in USD, may be NULL)
person(id, name, birth_year, sex)
crew(movie, person, role, character): who worked on which movie and how ('director', 'actor', 'writer'); character only for actors
rating(movie, source, score, votes): score 0-10 from a source ('IMDb', 'Metacritic')
genre(movie, genre): a movie can have many genres
Worked examples
The full DDL (you will learn every line in A1.3, A1.4, A1.6). It is valid PostgreSQL and also runs in SQLite:
CREATE TABLE movie (
id INTEGER PRIMARY KEY,
title VARCHAR(200) NOT NULL,
year INTEGER CHECK (year >= 1888),
length INTEGER CHECK (length > 0),
budget NUMERIC(12,0)
);
CREATE TABLE person (
id INTEGER PRIMARY KEY,
name VARCHAR(100) NOT NULL,
birth_year INTEGER,
sex CHAR(1) CHECK (sex IN ('M','F'))
);
CREATE TABLE crew (
movie INTEGER REFERENCES movie(id) ON DELETE CASCADE,
person INTEGER REFERENCES person(id),
role VARCHAR(20) NOT NULL,
character VARCHAR(100),
PRIMARY KEY (movie, person, role)
);
CREATE TABLE rating (
movie INTEGER REFERENCES movie(id) ON DELETE CASCADE,
source VARCHAR(20),
score NUMERIC(3,1) CHECK (score BETWEEN 0 AND 10),
votes INTEGER CHECK (votes >= 0),
PRIMARY KEY (movie, source)
);
CREATE TABLE genre (
movie INTEGER REFERENCES movie(id) ON DELETE CASCADE,
genre VARCHAR(20),
PRIMARY KEY (movie, genre)
);
The instance (all the data). Keep this page open while you do the exercises.
SELECT * FROM movie;
id title year length budget
-- --------------- ---- ------ ---------
1 Inception 2010 148 160000000
2 The Matrix 1999 136 63000000
3 Interstellar 2014 169 165000000
4 Pulp Fiction 1994 154 8000000
5 The Dark Knight 2008 152 185000000
6 Memento 2000 113 9000000
7 Parasite 2019 132 11400000
8 Tenet 2020 150
SELECT * FROM person;
id name birth_year sex
-- ----------------- ---------- ---
1 Christopher Nolan 1970 M
2 Leonardo DiCaprio 1974 M
3 Keanu Reeves 1964 M
4 Lana Wachowski 1965 F
5 Quentin Tarantino 1963 M
6 Uma Thurman 1970 F
7 Christian Bale 1974 M
8 Bong Joon-ho 1969 M
9 Anne Hathaway 1982 F
10 Carrie-Anne Moss 1967 F
11 Song Kang-ho 1967 M
12 Meryl Streep 1949 F
SELECT * FROM crew;
movie person role character
----- ------ -------- -----------
1 1 director
1 1 writer
1 2 actor Cobb
2 4 director
2 3 actor Neo
2 10 actor Trinity
3 1 director
3 1 writer
3 9 actor Brand
4 5 director
4 5 actor Jimmie
4 6 actor Mia Wallace
5 1 director
5 7 actor Bruce Wayne
6 1 director
6 10 actor Natalie
7 8 director
7 11 actor Kim Ki-taek
8 1 director
movie genre
----- --------
1 Sci-Fi
1 Action
2 Sci-Fi
2 Action
3 Sci-Fi
3 Drama
4 Crime
4 Drama
5 Action
5 Crime
6 Thriller
6 Mystery
7 Thriller
7 Drama
8 Action
8 Sci-Fi
Things to notice (they are used in later examples)
Tenet has budget NULL (unknown) and no ratings.
Meryl Streep is in person but has no crew rows.
Christopher Nolan directed 5 movies; Quentin Tarantino is both director and actor in Pulp Fiction.
Empty cells in the outputs are NULL (SQLite's text output prints NULL as an empty cell; PostgreSQL's psql also prints an empty cell by default).
SQLite prints 9.0 as 9; PostgreSQL prints 9.0 for a NUMERIC(3,1).
Check with the course
The data is a small sample made for practice (some values, like Tenet's missing budget, are on purpose). The list of DBMS properties and the three-level architecture follow the Atzeni et al. textbook (chapter 1), which is the course reference; the teacher's slides may group them a little differently.
Exercises
easy
Classify each statement as DDL, DML or DQL: (a) CREATE TABLE genre (...); (b) SELECT title FROM movie; (c) UPDATE movie SET budget = 0; (d) ALTER TABLE movie ADD COLUMN plot TEXT; (e) DELETE FROM rating; (f) DROP TABLE genre.
Show solution
(a) DDL, (b) DQL, (c) DML, (d) DDL, (e) DML (it removes rows, the table still exists), (f) DDL (it removes the table itself, schema and data).
easy
Is PostgreSQL a database or a DBMS? Is "the IMDB data on the teacher's server" a database or a DBMS?
Show solution
PostgreSQL is a DBMS (software). The IMDB data it manages is a database.
medium
The admin adds an index on movie(year) to make queries faster. No query has to change. Which kind of data independence is this? And if the admin adds a new column plot and old applications keep working?
Show solution
Adding an index changes only the physical storage: physical independence. Adding a column changes the logical schema while external applications still work: logical independence.
exam
Give three concrete problems you would have if 50 analysts shared the IMDB data as one CSV file on a shared drive, each editing it with a Python script, and name the DBMS feature that solves each one.
Show solution
Two people save at the same time and one overwrites the other's changes (lost update) → concurrency control / transactions.
A script crashes halfway through and the file is half-written → atomicity and recovery.
Someone writes a rating of 15 or a crew row for a movie that does not exist → integrity constraints (CHECK, foreign keys).
(Also: everyone can read/edit everything → access control; every query scans the whole file → indexes and query optimisation.)
Self-check quiz
Common mistakes
Calling PostgreSQL "a database". It is a DBMS; the data it manages is the database.
Thinking DELETE is DDL. DELETE removes rows (DML); DROP removes the table (DDL).
Mixing up physical and logical independence: physical = storage/indexes; logical = the table structure seen by applications.
Remember
Database = organised, persistent, shared data. DBMS = the software that manages it.
A DBMS gives persistence, sharing with concurrency control, reliability (transactions), security, efficiency, data independence.
Why it matters. PostgreSQL is a relational DBMS. The words relation, tuple, attribute, domain, schema and instance appear in every quiz on relational modelling.
How it is examined. Multiple-choice quizzes: definitions, "is this a valid relation?", degree and cardinality, schema vs instance, meaning of NULL.
Explanation
The relational model (Codd, 1970) represents all data as tables, which in theory are called relations.
Theory word
SQL / everyday word
Example
Relation
Table
movie
Attribute
Column
title
Tuple
Row
(1, 'Inception', 2010, 148, 160000000)
Domain
Type (set of allowed values)
INTEGER, VARCHAR(200)
Degree
Number of columns
movie has degree 5
Cardinality
Number of rows
movie has cardinality 8
Definition: Relation (mathematical view)
Given domains \(D_1, \dots, D_n\), a relation is a subset of their Cartesian product: \(r \subseteq D_1 \times D_2 \times \dots \times D_n\). So a relation is a set of tuples. In the relational model each position has a name (the attribute), so the order of columns does not matter.
Because a relation is a set:
there are no duplicate tuples;
the order of tuples does not matter (row 1 is not "first");
the order of attributes does not matter (they are identified by name);
each value is atomic (one value per cell, not a list: that is why genres are in a separate table genre, not a list inside movie).
Definition: Schema and instance
Relation schema: the name of the relation plus its attributes, e.g. \(\text{movie}(\text{id}, \text{title}, \text{year}, \text{length}, \text{budget})\). Database schema: the set of all relation schemas (plus constraints). Instance of a relation: the set of tuples it contains at a given moment. The schema is the intension (stable), the instance is the extension (changes over time).
Definition: NULL
NULL is a special marker meaning "no value": the value is unknown (Tenet's budget), does not exist (a director has no character), or we do not know which of the two. NULL is not 0 and not the empty string. Any comparison with NULL gives unknown (see A1.8).
How do tables connect? Not with pointers (like object references in JS) but with values: the row in crew stores the number 1 in column movie, and this value matches movie.id = 1. This "value-based" model is what makes the relational model independent from physical storage.
Tip for JS developers
A relation is like an array of objects that all have the same keys and primitive values only, where duplicates are forbidden and the array order is meaningless: think Set of frozen, flat objects, not Array.
Check with the course
Note: SQL tables are slightly different from theoretical relations: an SQL table can contain duplicate rows if it has no key, and a SELECT result can contain duplicates unless you use DISTINCT (A1.8). Quiz questions usually mean the theoretical model; read the wording ("relation" vs "SQL table").
Worked examples
Example 1: degree and cardinality. Relation rating has degree 4 (movie, source, score, votes). Its current instance has cardinality:
SELECT count(*) AS cardinality FROM rating;
cardinality
-----------
12
Example 2: same schema, different instance. After this insert the schema of movie is unchanged (same 5 attributes) but the instance changed (9 tuples now):
INSERT INTO movie (id, title, year) VALUES (9, 'Dune', 2021);
SELECT count(*) AS cardinality FROM movie;
cardinality
-----------
9
Example 3: is it a relation? Consider this table:
title
genres
Inception
Sci-Fi, Action
Inception
Sci-Fi, Action
It breaks two rules: duplicate tuple (same row twice) and a non-atomic value (a list in genres). The relational fix is our genre(movie, genre) table: one row per (movie, genre) pair.
Exercises
easy
Give the degree and the cardinality of person and of genre in our instance (see A1.1).
For each change, say whether it changes the schema or the instance: (a) insert a new rating; (b) add a column plot to movie; (c) set Tenet's budget; (d) add a CHECK constraint on votes.
Show solution
(a) instance, (b) schema, (c) instance, (d) schema (constraints are part of the schema).
medium
Two tables have the same rows but the columns are in a different order: (title, year) vs (year, title). Are they the same relation in the relational model? And are two tables with the same rows in a different row order the same?
Show solution
Yes in both cases. Attributes are identified by name, not position, and a relation is a set of tuples, so order of rows is irrelevant. (In SQL the column order matters only for display and for INSERT ... VALUES without a column list.)
medium
A colleague wants to store the cast of a movie as a column actors = 'Keanu Reeves; Carrie-Anne Moss' in movie. Which property of the relational model does this break, and what is the relational design?
Show solution
It breaks atomicity of values (first normal form): the cell contains a list. Relational design: a separate table such as our crew(movie, person, role, character) with one row per (movie, person, role), linked to movie and person by values (foreign keys). Note: a document database like MongoDB does allow arrays inside a record (A1.15).
exam
True or false, with a one-line reason: (a) a NULL budget means the budget is 0; (b) a relation can contain the same tuple twice; (c) the schema of a database changes every time a row is inserted; (d) a relation is a subset of the Cartesian product of its domains.
Show solution
(a) False: NULL = no value / unknown, not 0. (b) False: a relation is a set. (c) False: inserting changes the instance. (d) True: that is the formal definition.
Self-check quiz
Common mistakes
Saying "schema" when you mean the data. Schema = structure, instance = data.
Confusing degree (columns) with cardinality (rows).
Treating NULL as 0 or ''. It is a marker for "no value".
Thinking relations are linked by pointers. They are linked by matching values.
Remember
Relation = table, tuple = row, attribute = column, domain = set of allowed values.
A relation is a set of tuples: no duplicates, no order of rows, attributes identified by name, atomic values.
Schema = name + attributes (+ constraints), stable. Instance = current rows, changes.
Degree = number of attributes; cardinality = number of tuples.
A1.3 Integrity constraints and keys
Why it matters. Constraints are rules the DBMS enforces so the data stays correct. Keys identify rows and are the base of foreign keys and joins.
How it is examined. Relational-modelling quizzes: "which sets of attributes are superkeys / keys?", "which insert is rejected?", "can a primary key contain NULL?". You may also have to write NOT NULL, CHECK, PRIMARY KEY in DDL.
Explanation
Definition: Integrity constraint
A property that every instance must satisfy to be "legal". Intra-relational constraints involve one table: domain constraints, NOT NULL, CHECK (tuple constraints), UNIQUE, keys. Inter-relational constraints involve several tables: foreign keys / referential integrity (A1.6).
Domain constraint: a value must belong to the column type, e.g. year INTEGER.
NOT NULL: the column must have a value. title VARCHAR(200) NOT NULL.
CHECK (condition): a condition on the values of one row (tuple constraint). CHECK (score BETWEEN 0 AND 10). It can use several columns of the same row: CHECK (death_year IS NULL OR death_year >= birth_year).
UNIQUE: no two rows have the same value(s) in these columns (NULLs are allowed and, in PostgreSQL, several NULLs do not clash).
A set of attributes \(K\) is a superkey of relation \(r\) if no two distinct tuples of \(r\) have the same values on \(K\).
\(K\) is a key (also called candidate key) if it is a minimal superkey: if you remove any attribute from \(K\), it is no longer a superkey.
The primary key is the one key chosen by the designer to identify rows. It must be NOT NULL (in SQL, PRIMARY KEY = UNIQUE + NOT NULL). A table has at most one primary key; other keys can be declared UNIQUE.
Facts to remember
The set of all attributes is always a superkey (because a relation has no duplicate tuples). So every relation has at least one key.
Every superkey contains at least one key. Adding attributes to a superkey gives another superkey.
Keys come from the meaning of the data (the schema), not from one instance. One instance can only show that something is not a key (two rows clash), never prove that it is a key forever.
A key may be made of several attributes (composite key), e.g. rating(movie, source).
Examples in our schema:
movie: {id} is a key. {id, title} is a superkey but not a key (not minimal). {title} is not a key: two different movies can share a title (remakes).
rating: key {movie, source}, because one movie has one score per source. {movie} alone is not a key (Inception has 2 ratings).
crew: key {movie, person, role}: Nolan is both director and writer of Inception, so {movie, person} is not enough.
Tip
A CHECK constraint is satisfied when the condition is true or unknown. So CHECK (score BETWEEN 0 AND 10) accepts a NULL score. If you also want a value, add NOT NULL.
Worked examples
Each statement below breaks one constraint, so the DBMS rejects it. Output is SQLite's error message; PostgreSQL says the same thing in different words (shown in the comment).
-- duplicate primary key
-- PG: ERROR: duplicate key value violates unique constraint "movie_pkey"
INSERT INTO movie VALUES (1, 'Dune', 2021, 155, 165000000);
Error: UNIQUE constraint failed: movie.id
-- NOT NULL
-- PG: ERROR: null value in column "title" of relation "movie" violates not-null constraint
INSERT INTO movie (id, title, year) VALUES (9, NULL, 2021);
Error: NOT NULL constraint failed: movie.title
-- CHECK on one column
-- PG: ERROR: new row for relation "movie" violates check constraint "movie_length_check"
INSERT INTO movie VALUES (9, 'Dune', 2021, -5, 165000000);
Error: CHECK constraint failed: length > 0
-- CHECK with IN
INSERT INTO person VALUES (13, 'Zendaya', 1996, 'X');
Error: CHECK constraint failed: sex IN ('M','F')
-- composite primary key: Nolan is already director of movie 1
INSERT INTO crew VALUES (1, 1, 'director', NULL);
Columns you do not list get NULL (or their DEFAULT). This insert is accepted, because only title is NOT NULL:
INSERT INTO movie (id, title) VALUES (9, 'Dune');
SELECT * FROM movie WHERE id = 9;
id title year length budget
-- ----- ---- ------ ------
9 Dune
Naming a constraint (useful in error messages and to drop it later):
CREATE TABLE award (
id INTEGER PRIMARY KEY,
name VARCHAR(100) NOT NULL,
year INTEGER NOT NULL,
movie INTEGER,
CONSTRAINT award_year_ok CHECK (year >= 1929),
CONSTRAINT award_unique UNIQUE (name, year)
);
INSERT INTO award VALUES (1, 'Best Picture', 2020, 7);
INSERT INTO award VALUES (2, 'Best Picture', 2020, 1);
SQLite has an old quirk: it accepts NULL in a composite PRIMARY KEY column (e.g. INSERT INTO genre VALUES (8, NULL) succeeds). PostgreSQL correctly rejects it, because every PRIMARY KEY column is NOT NULL. Always reason with the PostgreSQL rule in the exam.
Exercises
easy
In person(id, name, birth_year, sex), which of these are superkeys, and which are keys: {id}, {id, name}, {name}, {name, birth_year}? Use the meaning of the data, not only the current rows.
Show solution
{id}: superkey and key (minimal). {id, name}: superkey, not a key (remove name and it is still a superkey). {name}: not a superkey in general (two people can share a name, even if our 12 rows have distinct names). {name, birth_year}: unlikely but still possible to clash, so normally not declared as key.
medium
Which of these inserts into rating are rejected, and by which constraint? Remember rating(movie, source) is the primary key, score has CHECK (score BETWEEN 0 AND 10).
(a) (3, 'Metacritic', 7.4, 46) (b) (3, 'IMDb', 8.0, 10) (c) (3, 'Letterboxd', 11, 10) (d) INSERT INTO rating (movie, source) VALUES (3, 'RottenT')
Show solution
(a) accepted (Interstellar has no Metacritic row yet). (b) rejected: primary key (3,'IMDb') already exists. (c) rejected by CHECK (11 > 10). (d) accepted: score and votes become NULL, and CHECK with NULL is unknown, which passes. Verified:
INSERT INTO rating VALUES (3, 'Letterboxd', 11, 10);
Error: CHECK constraint failed: score BETWEEN 0 AND 10
INSERT INTO rating (movie, source) VALUES (3, 'RottenT');
SELECT * FROM rating WHERE source = 'RottenT';
movie source score votes
----- ------- ----- -----
3 RottenT
medium
Write a CHECK constraint for a table person2(id, name, birth_year, death_year) that requires death after birth when the death year is known.
Show solution
CREATE TABLE person2 (
id INTEGER PRIMARY KEY,
name VARCHAR(100) NOT NULL,
birth_year INTEGER,
death_year INTEGER,
CHECK (death_year IS NULL OR death_year >= birth_year)
);
INSERT INTO person2 VALUES (1, 'Stanley Kubrick', 1928, 1999);
INSERT INTO person2 VALUES (2, 'Wrong Person', 1950, 1940);
Error: CHECK constraint failed: death_year IS NULL OR death_year >= birth_year
(Strictly, death_year >= birth_year alone already accepts NULL, because a CHECK that is unknown passes; writing IS NULL OR makes the intent clear.)
hard
Relation screening(cinema, room, date, time, movie): a room of a cinema shows one movie at a given date and time. Find the key(s). Is {cinema, date, time} a superkey?
Show solution
Key: {cinema, room, date, time}: these four together identify one screening, and removing any one of them allows clashes (two rooms at the same time, same room at two times, two cinemas with a "Room 1"...). {cinema, date, time} is not a superkey: two rooms of the same cinema can show films at the same time. {cinema, room, date, time, movie} is a superkey but not minimal.
exam
Given this instance of r(A, B, C): (1, x, 10), (2, x, 20), (3, y, 10). Based only on the instance, which single attributes and pairs could be keys? Why can the instance not prove a key?
Show solution
Single attributes: A (all distinct) could be a key; B is not (x repeats); C is not (10 repeats). Pairs: {B, C} has values (x,10),(x,20),(y,10): all distinct, so it could be a key (and it is minimal, since neither B nor C alone works). {A, B} and {A, C} are superkeys but not minimal (they contain A). An instance only shows the current data; a future insert could create a clash, so keys must come from the meaning of the data.
Self-check quiz
Common mistakes
Calling a non-minimal superkey a "key". Key = minimal superkey.
Deciding a key from one instance only. Keys come from the semantics (meaning) of the data.
Forgetting that PRIMARY KEY implies NOT NULL on every column of the key.
Thinking CHECK rejects NULL. A CHECK that evaluates to unknown passes; add NOT NULL if needed.
Thinking a CHECK can look at other rows or tables. A standard CHECK is about one row; other tables need a foreign key.
Remember
Intra-relational: domain, NOT NULL, CHECK, UNIQUE, keys. Inter-relational: foreign keys.
Superkey: no two rows agree on it. Key: minimal superkey. Primary key: the chosen key, NOT NULL, one per table.
All attributes together are always a superkey, so every relation has a key.
CHECK passes on true or unknown (NULL).
A1.4 SQL-DDL: creating tables, attributes and domains
Why it matters. DDL turns a relational design into real tables. You need to read DDL quickly in the exam to know the keys, the types and which columns can be NULL.
How it is examined. Quizzes on relational modelling (what a given CREATE TABLE allows), and possibly writing a short CREATE TABLE with keys and constraints.
exact number, p digits in total, s after the point
score NUMERIC(3,1) holds 0.0 … 99.9
REAL / DOUBLE PRECISION
approximate floating point
ratio REAL
CHAR(n)
fixed-length text, padded with spaces
sex CHAR(1)
VARCHAR(n)
variable-length text up to n characters
title VARCHAR(200)
TEXT
text of any length (PostgreSQL)
plot TEXT
BOOLEAN
true / false (/ NULL)
is_adult BOOLEAN DEFAULT false
DATE, TIME, TIMESTAMP
dates and times, literals like '2026-12-15'
check_date DATE
SERIAL (PostgreSQL)
INTEGER filled automatically from a sequence (auto-increment)
id SERIAL PRIMARY KEY
Column constraint vs table constraint. A constraint on a single column can be written after the column (id INTEGER PRIMARY KEY). A constraint on several columns must be written as a separate line (PRIMARY KEY (movie, source)). Both forms mean the same thing for one column.
Definition: User-defined domain (CREATE DOMAIN)
A named type with its own constraint, reusable in many tables. Standard SQL and PostgreSQL support it (SQLite does not):
-- PostgreSQL only
CREATE DOMAIN score_domain AS NUMERIC(3,1)
CHECK (VALUE BETWEEN 0 AND 10);
CREATE TABLE user_review (
id SERIAL PRIMARY KEY,
movie INTEGER REFERENCES movie(id),
score score_domain NOT NULL,
posted DATE DEFAULT CURRENT_DATE
);
Changing and removing tables
-- PostgreSQL only
ALTER TABLE movie ADD COLUMN plot TEXT; -- new column (NULL in existing rows)
ALTER TABLE movie DROP COLUMN plot; -- remove a column
ALTER TABLE movie ALTER COLUMN budget SET DEFAULT 0; -- change default
ALTER TABLE movie ALTER COLUMN title TYPE VARCHAR(300); -- change type
ALTER TABLE movie ADD CONSTRAINT budget_pos CHECK (budget > 0);
ALTER TABLE movie RENAME COLUMN length TO duration;
DROP TABLE genre; -- removes table: schema AND data
DROP TABLE movie CASCADE; -- also drops objects that depend on it (e.g. foreign keys)
Tip: schemas in PostgreSQL
In PostgreSQL a database contains schemas (namespaces) and a schema contains tables. The course IMDB database may use a schema, so you may see names like imdb.movie. CREATE SCHEMA imdb; creates one; SET search_path TO imdb; lets you write just movie. Do not confuse this "schema" (a namespace) with the "relational schema" (the structure).
Worked examples
Example 1: two new tables with keys and constraints (a country table and a many-to-many "which country produced which movie"):
CREATE TABLE country (
code CHAR(2) PRIMARY KEY,
name VARCHAR(60) NOT NULL UNIQUE
);
CREATE TABLE produced (
movie INTEGER REFERENCES movie(id) ON DELETE CASCADE ON UPDATE CASCADE,
country CHAR(2) REFERENCES country(code),
PRIMARY KEY (movie, country)
);
INSERT INTO country VALUES ('US','United States'), ('KR','South Korea'), ('GB','United Kingdom');
INSERT INTO produced VALUES (7,'KR'), (1,'US'), (1,'GB');
SELECT * FROM produced;
INSERT INTO produced VALUES (2,'IT'); -- 'IT' is not in country
movie country
----- -------
7 KR
1 US
1 GB
Error: FOREIGN KEY constraint failed
Example 2: adding a column with a default. Existing rows get the default value:
ALTER TABLE person ADD COLUMN nationality CHAR(2) DEFAULT 'US';
SELECT id, name, nationality FROM person WHERE id IN (1, 8);
id name nationality
-- ----------------- -----------
1 Christopher Nolan US
8 Bong Joon-ho US
(Of course Bong Joon-ho is Korean: a default is only a starting value, you then UPDATE the exceptions.)
Example 3: DROP vs DELETE. After DELETE FROM genre the table exists and is empty; after DROP TABLE genre the table is gone:
DELETE FROM genre;
SELECT count(*) AS n FROM genre;
DROP TABLE genre;
SELECT count(*) AS n FROM genre;
n
-
0
Error: no such table: genre
PostgreSQL vs SQLite
SQLite is very loose with types (it would store 'abc' in an INTEGER column) and only supports ALTER TABLE ... ADD COLUMN / RENAME / DROP COLUMN. PostgreSQL enforces types strictly and supports all the ALTER TABLE forms above. The blocks marked "PostgreSQL only" were checked against the PostgreSQL grammar (parser), not executed.
Exercises
easy
Which data type would you use for: (a) a movie title; (b) an ISO country code like 'IT'; (c) an average rating with one decimal (0.0 to 10.0); (d) a release date; (e) whether a movie is for adults?
Write the DDL for review(id, movie, author, score, posted): id auto-generated, movie must exist in movie, author and score required, score an integer 1-5, posted defaults to today, and one author can review a movie only once.
Show solution
-- PostgreSQL only
CREATE TABLE review (
id SERIAL PRIMARY KEY,
movie INTEGER NOT NULL REFERENCES movie(id),
author VARCHAR(50) NOT NULL,
score SMALLINT NOT NULL CHECK (score BETWEEN 1 AND 5),
posted DATE DEFAULT CURRENT_DATE,
UNIQUE (movie, author)
);
UNIQUE (movie, author) makes {movie, author} a second key (an alternative key). Using {movie, author} as primary key instead of id is also correct.
medium
Read this DDL and answer: can budget be NULL? Can two movies have the same title? Can length be 0?
-- template
CREATE TABLE movie (id INTEGER PRIMARY KEY, title VARCHAR(200) NOT NULL,
year INTEGER CHECK (year >= 1888), length INTEGER CHECK (length > 0), budget NUMERIC(12,0));
Show solution
budget: yes (no NOT NULL). Same title: yes (no UNIQUE on title). length 0: no (CHECK length > 0), but length NULL: yes.
hard
You must add the column imdb_code to movie, required and unique. Why does ALTER TABLE movie ADD COLUMN imdb_code VARCHAR(10) NOT NULL UNIQUE; fail on a table that already has rows (in PostgreSQL), and how do you do it in steps?
Show solution
Existing rows would get NULL in the new column, which violates NOT NULL, so PostgreSQL refuses. Steps:
-- PostgreSQL only
ALTER TABLE movie ADD COLUMN imdb_code VARCHAR(10); -- 1. nullable column
UPDATE movie SET imdb_code = 'tt' || lpad(id::text, 7, '0'); -- 2. fill existing rows
ALTER TABLE movie ALTER COLUMN imdb_code SET NOT NULL; -- 3. now enforce
ALTER TABLE movie ADD CONSTRAINT imdb_code_unique UNIQUE (imdb_code);
Self-check quiz
Common mistakes
Writing a composite key as two column constraints (movie INTEGER PRIMARY KEY, source VARCHAR PRIMARY KEY): that is an error (two primary keys). Use one table constraint PRIMARY KEY (movie, source).
Using FLOAT for money or scores that must be exact; use NUMERIC.
Confusing DROP TABLE (schema + data gone) with DELETE FROM (rows gone, table stays).
Forgetting the commas between column definitions or adding one after the last line (syntax error).
Remember
CREATE TABLE t (col type [DEFAULT v] [constraints], ..., [table constraints]);
Multi-column keys/UNIQUE/CHECK go in table constraints.
CREATE DOMAIN = reusable type + CHECK (PostgreSQL).
ALTER TABLE changes structure, DROP TABLE removes the table.
A1.5 SQL-DML: INSERT, UPDATE, DELETE
Why it matters. DML changes the instance. Each statement is checked against all constraints, and a wrong WHERE can change every row.
How it is examined. Short query-writing exercises ("increase by 10 the length of all movies directed by ...") and quiz questions on what a statement does or why it is rejected.
Explanation
-- template
INSERT INTO table (col1, col2, ...) VALUES (v1, v2, ...), (w1, w2, ...); -- one or more rows
INSERT INTO table (col1, ...) SELECT ... ; -- rows from a query
UPDATE table SET col1 = expr1, col2 = expr2 WHERE condition; -- change rows
DELETE FROM table WHERE condition; -- remove rows
INSERT: if you list the columns, unlisted columns get their DEFAULT or NULL. Without a column list you must give values for all columns in table order.
UPDATE: the SET expression can use the old value (votes = votes + 1000). WHERE chooses the rows; without WHERE, every row changes.
DELETE: removes the rows that satisfy WHERE; without WHERE the table becomes empty (but still exists).
The WHERE of UPDATE/DELETE can contain a subquery, e.g. WHERE id IN (SELECT movie FROM crew WHERE ...) (subqueries: A1.12).
Each statement is atomic: if one row of an UPDATE breaks a constraint, the whole statement fails and no row changes.
Tip: test the WHERE first
Before running UPDATE/DELETE, run SELECT * FROM table WHERE same_condition to see which rows will be touched. It is exactly like testing a .filter() before a .forEach() that mutates.
Worked examples
Insert two rows (only some columns; the others become NULL):
INSERT INTO movie (id, title, year, length)
VALUES (9, 'Dune', 2021, 155), (10, 'Oppenheimer', 2023, 180);
SELECT id, title, year, budget FROM movie WHERE id >= 8;
id title year budget
-- ----------- ---- ------
8 Tenet 2020
9 Dune 2021
10 Oppenheimer 2023
Update one row:
UPDATE movie SET budget = 205000000 WHERE title = 'Tenet';
SELECT id, title, budget FROM movie WHERE id = 8;
id title budget
-- ----- ---------
8 Tenet 205000000
Update using the old value:
UPDATE rating SET votes = votes + 1000 WHERE source = 'IMDb' AND movie = 7;
SELECT * FROM rating WHERE movie = 7;
Update with a subquery: add 10 minutes to every movie directed by Nolan (person 1):
UPDATE movie SET length = length + 10
WHERE id IN (SELECT movie FROM crew WHERE person = 1 AND role = 'director');
SELECT title, length FROM movie ORDER BY id;
title length
--------------- ------
Inception 158
The Matrix 136
Interstellar 179
Pulp Fiction 154
The Dark Knight 162
Memento 123
Parasite 132
Tenet 160
Delete some rows:
DELETE FROM rating WHERE source = 'Metacritic' AND score < 8;
SELECT movie, source, score FROM rating WHERE source = 'Metacritic';
A statement is atomic: adding 0.5 to every Metacritic score would push Parasite to 10.1, breaking the CHECK, so no row is updated:
UPDATE rating SET score = score + 0.5 WHERE source = 'Metacritic';
SELECT movie, score FROM rating WHERE source = 'Metacritic';
Error: CHECK constraint failed: score BETWEEN 0 AND 10
movie score
----- -----
1 7.4
2 7.3
4 9.5
5 8.4
7 9.6
Insert from a query (copy data into another table):
CREATE TABLE nolan_movie (id INTEGER PRIMARY KEY, title VARCHAR(200));
INSERT INTO nolan_movie (id, title)
SELECT m.id, m.title FROM movie m JOIN crew c ON c.movie = m.id
WHERE c.person = 1 AND c.role = 'director';
SELECT * FROM nolan_movie;
id title
-- ---------------
1 Inception
3 Interstellar
5 The Dark Knight
6 Memento
8 Tenet
PostgreSQL notes
PostgreSQL also has TRUNCATE table (fast "delete all rows") and RETURNING (e.g. DELETE FROM rating WHERE ... RETURNING * shows the deleted rows). Integer division is the same in both: 148 / 60 is 2; write 148 / 60.0 for a decimal result.
Exercises
easy
Insert the movie Dune (id 9, 2021, 155 minutes, budget unknown) with genres Sci-Fi and Adventure and an IMDb rating of 8.0 with 900000 votes.
Show solution
INSERT INTO movie (id, title, year, length) VALUES (9, 'Dune', 2021, 155);
INSERT INTO genre VALUES (9, 'Sci-Fi'), (9, 'Adventure');
INSERT INTO rating (movie, source, score, votes) VALUES (9, 'IMDb', 8.0, 900000);
SELECT m.title, g.genre FROM movie m JOIN genre g ON g.movie = m.id WHERE m.id = 9;
title genre
----- ---------
Dune Adventure
Dune Sci-Fi
The movie row must be inserted first: genre and rating reference it with a foreign key.
easy
Write the statement that sets the budget of every movie with unknown budget to 0. What is wrong with UPDATE movie SET budget = 0 WHERE budget = NULL?
Show solution
UPDATE movie SET budget = 0 WHERE budget IS NULL;
SELECT title, budget FROM movie WHERE id = 8;
title budget
----- ------
Tenet 0
budget = NULL is never true (it is unknown), so that version updates 0 rows.
medium
Increase all budgets by 10%. What happens to Tenet?
Show solution
UPDATE movie SET budget = budget * 1.1;
SELECT title, budget FROM movie WHERE id IN (4, 8);
title budget
------------ -------
Pulp Fiction 8800000
Tenet
NULL * 1.1 is NULL, so Tenet stays NULL. Arithmetic with NULL gives NULL.
hard
Delete all movies that have no rating. Which rows of other tables disappear too, and why? (Look at the DDL in A1.1.)
Show solution
DELETE FROM movie WHERE id NOT IN (SELECT movie FROM rating);
SELECT (SELECT count(*) FROM movie) AS movies,
(SELECT count(*) FROM crew) AS crew_rows,
(SELECT count(*) FROM genre) AS genre_rows;
movies crew_rows genre_rows
------ --------- ----------
7 18 14
Only Tenet is deleted (8 → 7 movies). Its 1 crew row and 2 genre rows are deleted too (19 → 18, 16 → 14) because crew.movie and genre.movie are declared ON DELETE CASCADE (A1.6).
exam
Statement: UPDATE rating SET score = score + 0.5 WHERE source = 'Metacritic';. How many rows are changed in our database and why?
Show solution
Zero. Parasite's Metacritic score would become 10.1, which violates CHECK (score BETWEEN 0 AND 10); a single SQL statement is atomic, so the whole UPDATE is rolled back (see the worked example above). A fix: SET score = LEAST(score + 0.5, 10) in PostgreSQL.
Self-check quiz
Common mistakes
Forgetting WHERE in UPDATE/DELETE: all rows change.
Writing WHERE col = NULL instead of WHERE col IS NULL.
Writing DELETE * FROM t (wrong: no column list in DELETE) or UPDATE t SET a = 1, WHERE ... (extra comma).
Inserting child rows (crew, rating) before the parent movie exists: foreign key violation.
Using double quotes for strings. In SQL, 'text' is a string; "text" is an identifier (a column/table name) in PostgreSQL.
Remember
INSERT INTO t (cols) VALUES (...), (...); or INSERT INTO t (cols) SELECT ...;
UPDATE t SET c = expr WHERE cond; and DELETE FROM t WHERE cond;
No WHERE = all rows. NULL needs IS NULL. Arithmetic with NULL gives NULL.
Each statement is atomic and checked against all constraints.
A1.6 Foreign keys and referential integrity
Why it matters. Foreign keys are how relational tables are connected. They also decide what happens to dependent rows when you delete or update a parent row.
How it is examined. Quizzes like "given these tables and policies, what happens after DELETE FROM movie WHERE ...?" and "which insert violates referential integrity?". You may have to write REFERENCES ... ON DELETE CASCADE.
Explanation
Definition: Foreign key and referential integrity
A foreign key is a set of attributes \(X\) of a table \(R_1\) (the referencing or child table) that refers to a key \(K\) (usually the primary key, or a UNIQUE column) of a table \(R_2\) (the referenced or parent table). Referential integrity holds when every non-NULL value of \(X\) in \(R_1\) appears as a value of \(K\) in \(R_2\). A row that refers to a non-existing parent is called dangling.
-- template
-- column form
movie INTEGER REFERENCES movie(id) ON DELETE CASCADE ON UPDATE CASCADE
-- table form (needed when the foreign key has several columns)
FOREIGN KEY (movie, source) REFERENCES rating(movie, source) ON DELETE SET NULL
What can violate referential integrity?
On the child: INSERT of a row with a non-existing parent, or UPDATE of the foreign key to a non-existing value. These are always rejected.
On the parent: DELETE of a referenced row, or UPDATE of its key. Here you choose a policy (reaction):
Policy
When a referenced parent row is deleted / its key updated
NO ACTION (default)
The operation is rejected with an error (checked at the end of the statement; can be deferred to the end of the transaction).
RESTRICT
Also rejected, checked immediately (cannot be deferred). In practice the same as NO ACTION for exam purposes.
CASCADE
The change propagates: delete → the child rows are deleted too; update → the child foreign key values are updated to the new key.
SET NULL
The child foreign key columns are set to NULL.
SET DEFAULT
The child foreign key columns are set to their default value.
The policies are separate for ON DELETE and ON UPDATE. If you write nothing, both are NO ACTION.
Choosing a policy (design rule of thumb)
CASCADE when the child has no meaning without the parent (a rating of a deleted movie). NO ACTION when deleting the parent should be blocked (do not delete a person who still has credits). SET NULL when the child should survive but lose the link (an award whose winner is removed).
Worked examples
Our DDL: crew.movie, rating.movie, genre.movie are ON DELETE CASCADE; crew.person has no policy (= NO ACTION).
1. Child insert with a missing parent is rejected (there is no movie 99):
INSERT INTO crew VALUES (99, 1, 'director', NULL);
Error: FOREIGN KEY constraint failed
2. CASCADE on delete: deleting Memento (id 6) also deletes its crew, rating and genre rows:
DELETE FROM movie WHERE id = 6;
SELECT (SELECT count(*) FROM crew WHERE movie = 6) AS crew_left,
(SELECT count(*) FROM rating WHERE movie = 6) AS rating_left,
(SELECT count(*) FROM genre WHERE movie = 6) AS genre_left;
3. NO ACTION on delete: Carrie-Anne Moss (id 10) still has crew rows, so she cannot be deleted. Meryl Streep (id 12) has none, so she can:
DELETE FROM person WHERE id = 10;
Error: FOREIGN KEY constraint failed
DELETE FROM person WHERE id = 12;
SELECT count(*) AS persons FROM person;
persons
-------
11
4. NO ACTION on update: crew.movie has ON DELETE CASCADE but nothing for update, so changing a referenced movie id is rejected:
UPDATE movie SET id = 100 WHERE id = 1;
Error: FOREIGN KEY constraint failed
5. SET NULL: an award keeps existing when its winner is removed:
CREATE TABLE award (
id INTEGER PRIMARY KEY,
name VARCHAR(100),
winner INTEGER REFERENCES person(id) ON DELETE SET NULL
);
INSERT INTO award VALUES (1, 'Best Director', 8), (2, 'Best Actor', 11);
DELETE FROM crew WHERE person = 8; -- first remove Bong Joon-ho's credits (NO ACTION on crew.person)
DELETE FROM person WHERE id = 8;
SELECT * FROM award;
id name winner
-- ------------- ------
1 Best Director
2 Best Actor 11
6. CASCADE on update:
CREATE TABLE country (code CHAR(2) PRIMARY KEY, name VARCHAR(60));
CREATE TABLE produced (
movie INTEGER REFERENCES movie(id),
country CHAR(2) REFERENCES country(code) ON UPDATE CASCADE,
PRIMARY KEY (movie, country)
);
INSERT INTO country VALUES ('UK', 'United Kingdom');
INSERT INTO produced VALUES (1, 'UK'), (3, 'UK');
UPDATE country SET code = 'GB' WHERE code = 'UK';
SELECT * FROM produced;
movie country
----- -------
1 GB
3 GB
PostgreSQL vs SQLite
SQLite only enforces foreign keys after PRAGMA foreign_keys = ON; (all examples here were run with it on). PostgreSQL always enforces them. PostgreSQL error text: ERROR: insert or update on table "crew" violates foreign key constraint "crew_movie_fkey" and ERROR: update or delete on table "person" violates foreign key constraint ... on table "crew".
Exercises
easy
Which of these statements violate referential integrity in our database? (a) INSERT INTO genre VALUES (9, 'Drama') (b) INSERT INTO rating VALUES (8, 'IMDb', 7.3, 600000) (c) INSERT INTO crew VALUES (2, 12, 'actor', 'Oracle') (d) UPDATE crew SET person = 50 WHERE movie = 7
Show solution
(a) violates: movie 9 does not exist. (b) OK: movie 8 (Tenet) exists. (c) OK: movie 2 and person 12 exist (the fact is invented but the database cannot know). (d) violates: person 50 does not exist (child updates are always checked).
medium
Starting from the original data, run DELETE FROM movie WHERE title = 'Pulp Fiction';. Give the new number of rows of movie, crew, rating, genre and person. (Before: 8, 19, 12, 16, 12.)
Show solution
DELETE FROM movie WHERE title = 'Pulp Fiction';
SELECT (SELECT count(*) FROM movie) AS m, (SELECT count(*) FROM crew) AS c,
(SELECT count(*) FROM rating) AS r, (SELECT count(*) FROM genre) AS g,
(SELECT count(*) FROM person) AS p;
m c r g p
- -- -- -- --
7 16 10 14 12
Pulp Fiction had 3 crew rows (Tarantino as director and actor, Uma Thurman), 2 ratings and 2 genres, all removed by CASCADE. person is unchanged: cascade goes from parent to child, never from crew to person.
medium
Why does DELETE FROM person WHERE name = 'Uma Thurman'; fail, and what must you do first?
Show solution
DELETE FROM person WHERE name = 'Uma Thurman';
Error: FOREIGN KEY constraint failed
crew.person is NO ACTION and she has a crew row. Delete her crew rows first, then the person:
DELETE FROM crew WHERE person = 6;
DELETE FROM person WHERE name = 'Uma Thurman';
SELECT count(*) AS persons FROM person;
persons
-------
11
hard
Design the policies for a table watchlist(user_id, movie) (a user's saved movies) and for crew.person. Justify. Write the DDL of watchlist assuming a table app_user(id).
Show solution
A watchlist entry has no meaning without its user or its movie: ON DELETE CASCADE for both. For crew.person, deleting a person who has credits is probably a mistake: keep NO ACTION (block it). If ids can change, add ON UPDATE CASCADE.
CREATE TABLE app_user (id INTEGER PRIMARY KEY, email VARCHAR(100) UNIQUE);
CREATE TABLE watchlist (
user_id INTEGER REFERENCES app_user(id) ON DELETE CASCADE ON UPDATE CASCADE,
movie INTEGER REFERENCES movie(id) ON DELETE CASCADE ON UPDATE CASCADE,
added DATE,
PRIMARY KEY (user_id, movie)
);
exam
Tables A(id), B(id, a REFERENCES A ON DELETE CASCADE), C(id, b REFERENCES B ON DELETE SET NULL), D(id, b REFERENCES B) (no policy). You delete one row of A that is referenced by 2 rows of B; one of those B rows is referenced by a row of C, and none by D. What happens? And what if one of the B rows were referenced by a row of D?
Show solution
Case 1: the A row is deleted; the 2 B rows are deleted (CASCADE); the C row that pointed to a deleted B row gets b = NULL (SET NULL). Everything succeeds. Case 2: deleting that B row would leave a dangling D row, and D's policy is NO ACTION, so the B delete is rejected; since everything happens inside one statement, the whole DELETE on A fails and nothing changes.
Self-check quiz
Common mistakes
Thinking CASCADE works "upwards" (deleting a crew row deletes the movie). It only goes from the referenced (parent) row to the referencing (child) rows.
Thinking a policy can make a child insert with a missing parent succeed. Policies only react to changes on the parent.
Forgetting that NO ACTION is the default.
Pointing a foreign key at a column that is not a key/UNIQUE in the parent: not allowed.
Forgetting that a NULL foreign key value is allowed (it refers to nothing) unless the column is NOT NULL.
Remember
FK value (if not NULL) must exist as a key value in the parent.
Child insert/update with a bad value: always rejected.
Parent delete/update: NO ACTION/RESTRICT (reject, default), CASCADE (propagate), SET NULL, SET DEFAULT.
Separate policies for ON DELETE and ON UPDATE.
A1.7 Transactions, concurrency and ACID
Why it matters. Many users change the database at the same time and machines crash. Transactions are how a DBMS keeps the data correct anyway.
How it is examined. Quizzes: the meaning of each ACID property, which property is violated in a scenario, what COMMIT/ROLLBACK do, typical concurrency anomalies.
Explanation
Definition: Transaction
A transaction is a sequence of operations that the DBMS treats as one logical unit of work. It starts with BEGIN (or START TRANSACTION) and ends with either COMMIT (make all changes permanent) or ROLLBACK (undo all changes). Without an explicit BEGIN, PostgreSQL runs each statement as its own transaction (autocommit).
Definition: ACID properties
Atomicity: all or nothing. Either all operations of the transaction take effect or none does (no half-done work, even after a crash). Mechanism: the log lets the DBMS undo.
Consistency: a transaction takes the database from a state that satisfies all integrity constraints to another such state. If a constraint is violated, the transaction (or statement) is rejected.
Isolation: concurrent transactions do not interfere; the result is as if they ran one after the other (serially). Mechanism: concurrency control (locks, snapshots).
Durability: once committed, changes survive failures (crash, power loss). Mechanism: the log written to disk before commit, plus recovery.
Concurrency anomalies (what isolation prevents)
Anomaly
Example on our data
Lost update
T1 reads votes = 100, T2 reads votes = 100, T1 writes 101, T2 writes 101. One vote is lost (should be 102).
Dirty read
T1 updates a score, T2 reads the new score, then T1 rolls back: T2 used a value that never officially existed.
Non-repeatable read
T1 reads Inception's score twice; between the reads T2 commits a change, so T1 sees two different values.
Phantom
T1 counts Sci-Fi movies twice; between the counts T2 inserts a new Sci-Fi movie, so the count changes.
SQL defines isolation levels: READ UNCOMMITTED, READ COMMITTED (PostgreSQL default), REPEATABLE READ, SERIALIZABLE (strongest: behaves like a serial execution). Higher level = fewer anomalies, but more waiting or aborted transactions.
JS analogy
A transaction is like building a new state object and swapping it in at the end (COMMIT), or throwing it away (ROLLBACK), instead of mutating the shared state step by step. Isolation is what async code without a mutex does not give you: two awaited read-modify-write sequences on the same value can lose an update.
Worked examples
ROLLBACK undoes everything since BEGIN. Move 100 votes from Inception to The Matrix, then change our mind:
BEGIN;
UPDATE rating SET votes = votes - 100 WHERE movie = 1 AND source = 'IMDb';
UPDATE rating SET votes = votes + 100 WHERE movie = 2 AND source = 'IMDb';
ROLLBACK;
SELECT movie, votes FROM rating WHERE source = 'IMDb' AND movie IN (1, 2);
movie votes
----- -------
1 2400000
2 2000000
COMMIT makes both changes permanent together (atomicity: never one without the other):
BEGIN;
UPDATE rating SET votes = votes - 100 WHERE movie = 1 AND source = 'IMDb';
UPDATE rating SET votes = votes + 100 WHERE movie = 2 AND source = 'IMDb';
COMMIT;
SELECT movie, votes FROM rating WHERE source = 'IMDb' AND movie IN (1, 2);
movie votes
----- -------
1 2399900
2 2000100
An error inside a transaction. Here the second insert breaks the primary key:
BEGIN;
INSERT INTO movie (id, title) VALUES (20, 'Barbie');
INSERT INTO movie (id, title) VALUES (20, 'Barbie again');
COMMIT;
SELECT id, title FROM movie WHERE id = 20;
Error: UNIQUE constraint failed: movie.id
id title
-- ------
20 Barbie
PostgreSQL behaves differently here (important)
SQLite only cancelled the failing statement and the COMMIT kept 'Barbie'. In PostgreSQL, after an error the whole transaction is aborted: every following statement fails with ERROR: current transaction is aborted, commands ignored until end of transaction block, and the final COMMIT actually performs a ROLLBACK. So in PostgreSQL the SELECT returns no row: all or nothing. Use PostgreSQL's behaviour in the exam.
A lost update, step by step (two sessions, PostgreSQL, default READ COMMITTED, application code that reads then writes):
-- template
-- T1 -- T2
BEGIN; BEGIN;
SELECT votes FROM rating WHERE movie = 7 AND source = 'IMDb'; -- both read 950000
SELECT votes FROM rating WHERE movie = 7 AND source = 'IMDb';
UPDATE rating SET votes = 950001 WHERE ...; -- value computed in the app
COMMIT;
UPDATE rating SET votes = 950001 WHERE ...;
COMMIT; -- final value 950001, one vote lost
Fixes: compute in SQL (SET votes = votes + 1, which re-reads the current committed value), lock the row with SELECT ... FOR UPDATE, or use SERIALIZABLE.
Exercises
easy
Match each scenario to the ACID property it is about: (a) the server loses power 1 second after COMMIT; after restart the change is there. (b) A transfer of votes updates the first row, then crashes; after restart neither row is changed. (c) An update that would give a score of 11 is rejected. (d) Two analysts update the same row at the same time and the result equals some serial order.
Name the anomaly: T1 computes the average IMDb score (8.71). T2 inserts a new IMDb rating and commits. T1 computes the average again in the same transaction and gets a different value.
Show solution
A phantom (a new row appeared in the set read by T1). If T2 had instead modified an existing row that T1 had read, it would be a non-repeatable read. Both are prevented by SERIALIZABLE (in PostgreSQL also REPEATABLE READ prevents them).
hard
Two sessions each run UPDATE rating SET votes = votes + 1 WHERE movie = 7 AND source = 'IMDb'; at the same time. Is an update lost? Explain.
Show solution
No. An UPDATE locks the row it changes. The second session waits until the first commits, then re-reads the current value and adds 1, so the final value is +2. The lost update happens when the application reads the value and writes back a value computed outside SQL.
exam
Explain the difference between consistency (the C in ACID) and integrity constraints. Can a transaction be temporarily inconsistent?
Show solution
Integrity constraints define which states are legal. Consistency means a committed transaction moves the database from a legal state to a legal state. Inside a transaction, intermediate states can break some rules (for example with DEFERRABLE foreign keys, checked only at COMMIT); what matters is the state at commit. Non-deferred constraints, as in our tables, are checked at the end of every statement.
Self-check quiz
Common mistakes
Mixing atomicity (all or nothing) and durability (committed changes survive crashes).
Thinking isolation means "transactions run one at a time". They run concurrently; the result must look serial.
Thinking ROLLBACK can undo a transaction that is already committed. It cannot.
Assuming PostgreSQL commits the good statements after an error inside a transaction block. It aborts the whole transaction.
Remember
BEGIN; ... COMMIT; or ... ROLLBACK;. Autocommit = one statement per transaction.
A = all or nothing (log/undo), C = legal state to legal state, I = concurrent but as if serial (concurrency control), D = committed changes survive (log/recovery).
Anomalies: lost update, dirty read, non-repeatable read, phantom. SERIALIZABLE prevents all.
A1.8 SELECT basics: WHERE, BETWEEN, LIKE, IS NULL, IN, ORDER BY, AS, DISTINCT
Why it matters. Every SQL exercise in the exam is a SELECT. This topic is the base for joins, grouping and subqueries.
How it is examined. SQL query-writing exercises on a given schema (IMDB-style), and quizzes like "how many rows does this query return?", especially with NULLs.
Explanation
-- template
SELECT [DISTINCT] expr1 [AS name1], expr2 ... -- 5. which columns (projection)
FROM table -- 1. where the rows come from
WHERE condition -- 2. keep only some rows (selection)
ORDER BY expr [ASC|DESC], ... -- 6. sort the result
LIMIT n; -- 7. keep the first n rows (PostgreSQL, SQLite)
The numbers show the logical order of evaluation (GROUP BY and HAVING, steps 3-4, come in A1.11). This explains a classic rule: an alias defined in SELECT can be used in ORDER BY but not in WHERE (WHERE runs before SELECT).
Operator
Meaning
Example
= <> != < <= > >=
comparison (<> is the standard "not equal")
year <> 2010
AND, OR, NOT
logic; NOT binds tighter than AND, which binds tighter than OR
(year < 2000 OR year > 2015) AND length > 140
BETWEEN a AND b
\(a \le x \le b\), both ends included
year BETWEEN 1999 AND 2008
LIKE pattern
% = any sequence (also empty), _ = exactly one character
title LIKE 'The %'
IN (v1, v2, ...)
equal to one of the values
birth_year IN (1970, 1974)
IS NULL, IS NOT NULL
test for NULL (the only correct way)
budget IS NULL
DISTINCT
remove duplicate rows from the result
SELECT DISTINCT role FROM crew
AS
rename a column (or table) in the result
length / 60.0 AS hours
Definition: Three-valued logic
A comparison with NULL (budget > 100 when budget is NULL, and even NULL = NULL) gives UNKNOWN, not true or false. WHERE keeps a row only if the condition is TRUE. Rules: NOT unknown = unknown; true OR unknown = true; false AND unknown = false; otherwise the result with unknown is unknown.
Tip for JS developers
SQL is declarative: SELECT title FROM movie WHERE year > 2000 is like movies.filter(m => m.year > 2000).map(m => m.title), but you do not say how to loop. Strings use single quotes; = compares (there is no ==).
Worked examples
AND:
SELECT title, year FROM movie WHERE year >= 2010 AND length > 140;
title year
------------ ----
Inception 2010
Interstellar 2014
Tenet 2020
BETWEEN includes both ends (1999 and 2008 are in):
SELECT title, year FROM movie WHERE year BETWEEN 1999 AND 2008 ORDER BY year;
title year
--------------- ----
The Matrix 1999
Memento 2000
The Dark Knight 2008
LIKE with % and _:
SELECT title FROM movie WHERE title LIKE 'The %';
title
---------------
The Matrix
The Dark Knight
SELECT name FROM person WHERE name LIKE '_e%'; -- second letter is 'e'
name
-----------------
Leonardo DiCaprio
Keanu Reeves
Meryl Streep
SELECT name FROM person WHERE name LIKE '%an%' ORDER BY name;
name
-----------------
Christian Bale
Christopher Nolan
Keanu Reeves
Lana Wachowski
Quentin Tarantino
Song Kang-ho
Uma Thurman
(Anne Hathaway and Carrie-Anne Moss are not there: LIKE is case-sensitive in PostgreSQL, and they only have An. Use ILIKE in PostgreSQL for case-insensitive matching.)
IS NULL vs = NULL:
SELECT title FROM movie WHERE budget IS NULL;
title
-----
Tenet
SELECT title FROM movie WHERE budget = NULL; -- always UNKNOWN: returns nothing
(no rows)
NOT and NULL. Tenet is in neither result: budget > 100000000 is unknown for it, and NOT unknown is still unknown:
SELECT title FROM movie WHERE NOT (budget > 100000000);
title
------------
The Matrix
Pulp Fiction
Memento
Parasite
Precedence: AND before OR. The first query means year < 2000 OR (year > 2015 AND length > 140):
SELECT title, year FROM movie WHERE year < 2000 OR year > 2015 AND length > 140;
title year
------------ ----
The Matrix 1999
Pulp Fiction 1994
Tenet 2020
SELECT title, year FROM movie WHERE (year < 2000 OR year > 2015) AND length > 140;
title year
------------ ----
Pulp Fiction 1994
Tenet 2020
IN, and ORDER BY on two columns (second column breaks ties):
SELECT name, birth_year FROM person
WHERE birth_year IN (1970, 1974)
ORDER BY birth_year DESC, name ASC;
name birth_year
----------------- ----------
Christian Bale 1974
Leonardo DiCaprio 1974
Christopher Nolan 1970
Uma Thurman 1970
DISTINCT:
SELECT DISTINCT role FROM crew;
role
--------
director
writer
actor
Expressions and aliases (alias used in ORDER BY):
SELECT title AS movie_title, length / 60.0 AS hours
FROM movie WHERE length > 150 ORDER BY hours DESC;
movie_title hours
--------------- ----------------
Interstellar 2.81666666666667
Pulp Fiction 2.56666666666667
The Dark Knight 2.53333333333333
Sorting with NULLs. PostgreSQL puts NULLs last in ascending order (first in DESC); SQLite does the opposite. Write NULLS LAST/NULLS FIRST to be explicit (works in both):
SELECT title, budget FROM movie ORDER BY budget NULLS LAST LIMIT 3;
The outputs here were produced by SQLite with PRAGMA case_sensitive_like = ON, so LIKE behaves as in PostgreSQL. Other differences: PostgreSQL shows 2.8166666666666667 for 169 / 60.0 (more digits), and has ILIKE and FETCH FIRST n ROWS ONLY (standard form of LIMIT).
Exercises
easy
Titles and years of the movies released between 2000 and 2015 (included) that last less than 150 minutes, newest first.
Show solution
SELECT title, year FROM movie
WHERE year BETWEEN 2000 AND 2015 AND length < 150
ORDER BY year DESC;
title year
--------- ----
Inception 2010
Memento 2000
easy
Names and birth years of the women born before 1966 or after 1980, oldest first.
Show solution
SELECT name, birth_year FROM person
WHERE sex = 'F' AND (birth_year < 1966 OR birth_year > 1980)
ORDER BY birth_year;
name birth_year
-------------- ----------
Meryl Streep 1949
Lana Wachowski 1965
Anne Hathaway 1982
Without the parentheses, the men born after 1980 would also qualify (AND binds tighter than OR).
medium
The distinct character names that contain a lowercase a, in alphabetical order (ignore crew rows with no character).
Show solution
SELECT DISTINCT character FROM crew
WHERE character IS NOT NULL AND character LIKE '%a%'
ORDER BY character;
character
-----------
Brand
Bruce Wayne
Kim Ki-taek
Mia Wallace
Natalie
IS NOT NULL is not strictly needed (NULL LIKE ... is unknown, so the row is dropped anyway) but it shows intent.
medium
For movies whose title does not start with "The " and whose budget is known: title and budget in millions (one decimal) as budget_musd, most expensive first.
Show solution
SELECT title, round(budget / 1000000.0, 1) AS budget_musd
FROM movie
WHERE budget IS NOT NULL AND title NOT LIKE 'The %'
ORDER BY budget_musd DESC;
(PostgreSQL prints 165.0, 11.4 etc. the same way here because the type is numeric.)
exam
How many rows does each query return on our data? (a) SELECT * FROM movie WHERE budget < 50000000 OR budget >= 50000000; (b) SELECT DISTINCT movie FROM rating; (c) SELECT title FROM movie WHERE year NOT IN (1994, 1999) AND budget IS NOT NULL;
Show solution
(a) 7, not 8: for Tenet both comparisons are unknown, and unknown OR unknown is unknown. (b) 7: every movie except Tenet has at least one rating. (c) 5: remove Pulp Fiction (1994), The Matrix (1999) and Tenet (NULL budget) from 8.
SELECT (SELECT count(*) FROM movie WHERE budget < 50000000 OR budget >= 50000000) AS a,
(SELECT count(DISTINCT movie) FROM rating) AS b,
(SELECT count(*) FROM movie WHERE year NOT IN (1994, 1999) AND budget IS NOT NULL) AS c;
a b c
- - -
7 7 5
Self-check quiz
Common mistakes
WHERE x = NULL: always empty. Use IS NULL.
Forgetting parentheses when mixing AND and OR.
Using a SELECT alias in WHERE (WHERE hours > 2): error, because WHERE is evaluated before SELECT. Repeat the expression instead.
Thinking BETWEEN excludes the ends. It includes them.
Using * instead of % in LIKE, or double quotes for strings.
Expecting rows in a given order without ORDER BY. Without ORDER BY the order is not guaranteed.
Remember
Logical order: FROM → WHERE → (GROUP BY → HAVING) → SELECT → ORDER BY → LIMIT.
BETWEEN includes both ends; LIKE: % many, _ one (case-sensitive in PostgreSQL).
NULL comparisons are UNKNOWN; WHERE keeps only TRUE rows; use IS [NOT] NULL.
DISTINCT removes duplicate result rows; AS renames.
A1.9 Cartesian product, inner joins and table aliases
Why it matters. Data about one fact is spread over several tables (movie title in movie, director name in person, the link in crew). Almost every exam SQL exercise needs a join.
How it is examined. SQL query-writing exercises with 2-4 tables, often with a self-join (the same table twice, with aliases). Quizzes on how many rows a Cartesian product or join returns.
Explanation
Definition: Cartesian product
FROM A, B (or FROM A CROSS JOIN B) combines every row of A with every row of B. If A has \(n\) rows and B has \(m\) rows, the result has \(n \times m\) rows and all columns of both. Most combinations are meaningless; a join keeps only the matching ones.
Definition: (Inner) join
A join = Cartesian product + a condition that links the tables, usually foreign key = primary key. Two equivalent syntaxes:
-- template
-- 1) implicit join (condition in WHERE)
SELECT ... FROM movie m, rating r WHERE m.id = r.movie AND ...;
-- 2) explicit join (preferred)
SELECT ... FROM movie m JOIN rating r ON m.id = r.movie WHERE ...;
JOIN means INNER JOIN: rows with no partner are dropped (Tenet has no rating, so it disappears from movie JOIN rating).
Table aliases: movie m or movie AS m. Then write m.title. Aliases are required when the same table appears twice (self-join) and when a column name is ambiguous (both crew and rating have movie).
Chain joins for 3+ tables: movie m JOIN crew c ON c.movie = m.id JOIN person p ON p.id = c.person.
Other forms: JOIN ... USING (col) when both tables have a column with the same name; NATURAL JOIN joins on all columns with equal names (dangerous: rarely what you want).
Method for exam questions
Which columns do I output? Which tables hold them?
Which tables do I need for the conditions?
Connect all those tables through the foreign keys (draw the path: movie ← crew → person).
Put the remaining conditions in WHERE. Add DISTINCT if the join can produce duplicates.
Worked examples
Cartesian product size: 8 movies × 12 ratings:
SELECT count(*) AS n FROM movie, rating;
n
--
96
Implicit vs explicit join, same result: ratings of at least 9 with the movie title.
SELECT m.title, r.source, r.score
FROM movie m, rating r
WHERE m.id = r.movie AND r.score >= 9;
title source score
--------------- ---------- -----
Pulp Fiction Metacritic 9.5
The Dark Knight IMDb 9
Parasite Metacritic 9.6
SELECT m.title, r.source, r.score
FROM movie AS m JOIN rating AS r ON m.id = r.movie
WHERE r.score >= 9;
title source score
--------------- ---------- -----
Pulp Fiction Metacritic 9.5
The Dark Knight IMDb 9
Parasite Metacritic 9.6
Three tables: every movie with its director.
SELECT m.title, p.name AS director
FROM movie m
JOIN crew c ON c.movie = m.id
JOIN person p ON p.id = c.person
WHERE c.role = 'director'
ORDER BY m.year;
title director
--------------- -----------------
Pulp Fiction Quentin Tarantino
The Matrix Lana Wachowski
Memento Christopher Nolan
The Dark Knight Christopher Nolan
Inception Christopher Nolan
Interstellar Christopher Nolan
Parasite Bong Joon-ho
Tenet Christopher Nolan
Self-join on crew: actors who acted in a movie directed by Christopher Nolan. We need crew twice (once for the director row, once for the actor row) and person twice (director name, actor name):
SELECT DISTINCT a.name
FROM crew c1
JOIN crew c2 ON c1.movie = c2.movie
JOIN person d ON d.id = c1.person
JOIN person a ON a.id = c2.person
WHERE c1.role = 'director' AND d.name = 'Christopher Nolan'
AND c2.role = 'actor'
ORDER BY a.name;
name
-----------------
Anne Hathaway
Carrie-Anne Moss
Christian Bale
Leonardo DiCaprio
Self-join on person: pairs of people born in the same year. p1.id < p2.id avoids pairing a person with themselves and listing each pair twice:
SELECT p1.name AS person1, p2.name AS person2, p1.birth_year
FROM person p1 JOIN person p2
ON p1.birth_year = p2.birth_year AND p1.id < p2.id;
person1 person2 birth_year
----------------- -------------- ----------
Christopher Nolan Uma Thurman 1970
Leonardo DiCaprio Christian Bale 1974
Carrie-Anne Moss Song Kang-ho 1967
Condition in ON + LIMIT: top 3 movies by IMDb score.
SELECT m.title, r.score
FROM movie m JOIN rating r ON m.id = r.movie AND r.source = 'IMDb'
ORDER BY r.score DESC
LIMIT 3;
title score
--------------- -----
The Dark Knight 9
Pulp Fiction 8.9
Inception 8.8
Check with the course
Both the implicit (FROM a, b WHERE ...) and explicit (JOIN ... ON) styles are correct SQL; the course slides list "Cartesian product and joins" and "JOIN with table aliases", so expect both. Output differences: PostgreSQL prints 9.0 where SQLite prints 9.
Exercises
easy
Title and genre of every movie released before 2000, sorted by title and genre.
Show solution
SELECT m.title, g.genre
FROM movie m JOIN genre g ON g.movie = m.id
WHERE m.year < 2000
ORDER BY m.title, g.genre;
title genre
------------ ------
Pulp Fiction Crime
Pulp Fiction Drama
The Matrix Action
The Matrix Sci-Fi
medium
For every acting role played by a woman: actress name, movie title and character.
Show solution
SELECT p.name, m.title, c.character
FROM person p
JOIN crew c ON c.person = p.id
JOIN movie m ON m.id = c.movie
WHERE c.role = 'actor' AND p.sex = 'F'
ORDER BY p.name;
name title character
---------------- ------------ -----------
Anne Hathaway Interstellar Brand
Carrie-Anne Moss The Matrix Trinity
Carrie-Anne Moss Memento Natalie
Uma Thurman Pulp Fiction Mia Wallace
medium
Titles of the movies whose director was born before 1968 and whose IMDb score is at least 8.7 (four tables).
Show solution
SELECT DISTINCT m.title
FROM movie m
JOIN crew c ON c.movie = m.id
JOIN person p ON p.id = c.person
JOIN rating r ON r.movie = m.id
WHERE c.role = 'director' AND p.birth_year < 1968
AND r.source = 'IMDb' AND r.score >= 8.7;
title
------------
The Matrix
Pulp Fiction
Lana Wachowski (1965) and Quentin Tarantino (1963) qualify; Nolan (1970) and Bong (1969) do not.
hard
Movies where the director also acts: title, director name and actor name.
Show solution
SELECT m.title, d.name AS director, a.name AS actor
FROM movie m
JOIN crew cd ON cd.movie = m.id AND cd.role = 'director'
JOIN person d ON d.id = cd.person
JOIN crew ca ON ca.movie = m.id AND ca.role = 'actor'
JOIN person a ON a.id = ca.person
WHERE d.id = a.id;
title director actor
------------ ----------------- -----------------
Pulp Fiction Quentin Tarantino Quentin Tarantino
Shorter: FROM crew cd JOIN crew ca ON cd.movie = ca.movie AND cd.person = ca.person with the two role conditions.
exam
Pairs of movies directed by Christopher Nolan where the second came out at most 2 years after the first (older title, newer title).
Show solution
SELECT a.title AS older, b.title AS newer
FROM movie a
JOIN crew ca ON ca.movie = a.id AND ca.role = 'director'
JOIN movie b ON b.year > a.year AND b.year - a.year <= 2
JOIN crew cb ON cb.movie = b.id AND cb.role = 'director' AND cb.person = ca.person
JOIN person p ON p.id = ca.person
WHERE p.name = 'Christopher Nolan'
ORDER BY a.year;
older newer
--------------- ---------
The Dark Knight Inception
The Dark Knight (2008) and Inception (2010). Note the join condition does not have to be an equality.
Self-check quiz
Common mistakes
Listing two tables in FROM and forgetting the join condition: you get the full Cartesian product.
Ambiguous column: SELECT movie FROM crew JOIN rating ... fails, both tables have movie. Prefix with the alias.
Using one alias for two roles: to talk about "the director" and "the actor" of a movie you need two copies of crew (and of person).
Forgetting DISTINCT when a join multiplies rows (a movie with 2 ratings appears twice).
After FROM movie AS m, writing movie.title: in PostgreSQL the alias replaces the name, so use m.title.
Remember
FROM A, B = Cartesian product, \(|A| \cdot |B|\) rows.
Aliases for readability and for self-joins (same table twice).
Path: movie ← crew → person; movie ← rating; movie ← genre.
A1.10 Outer joins: LEFT, RIGHT, FULL
Why it matters. An inner join silently drops rows without a partner. Questions like "all movies, with their rating if any" or "people who never worked on a movie" need outer joins.
How it is examined. SQL exercises ("list all ..., including those without ...", "find those with no ...") and quizzes about how many rows a LEFT/RIGHT/FULL join returns and where NULLs appear.
Explanation
Definition: Outer joins
A LEFT [OUTER] JOIN B ON cond: all rows of the inner join, plus every row of A with no match in B, padded with NULLs in B's columns.
A RIGHT [OUTER] JOIN B ON cond: the same, keeping all rows of B. (A RIGHT JOIN B = B LEFT JOIN A.)
A FULL [OUTER] JOIN B ON cond: inner join + unmatched rows of A + unmatched rows of B.
Shaded = rows kept. (Venn pictures are only an intuition: a join combines rows, it is not a set intersection.)
ON vs WHERE in a LEFT JOIN (very common exam trap)
A condition on the right table placed in ON decides which B rows are matched; unmatched A rows are still kept. The same condition in WHERE is applied after the join and removes the NULL-padded rows, turning the LEFT JOIN back into an inner join.
Anti-join pattern ("A rows with no B"): A LEFT JOIN B ON ... WHERE B.key IS NULL. (Alternatives with NOT EXISTS / NOT IN / EXCEPT: A1.12, A1.13.)
Worked examples
All movies with their Metacritic score, if any (condition in ON):
SELECT m.title, r.score
FROM movie m LEFT JOIN rating r
ON m.id = r.movie AND r.source = 'Metacritic'
ORDER BY m.id;
title score
--------------- -----
Inception 7.4
The Matrix 7.3
Interstellar
Pulp Fiction 9.5
The Dark Knight 8.4
Memento
Parasite 9.6
Tenet
Same condition in WHERE: the NULL rows are removed, we lost Interstellar, Memento, Tenet:
SELECT m.title, r.score
FROM movie m LEFT JOIN rating r ON m.id = r.movie
WHERE r.source = 'Metacritic'
ORDER BY m.id;
title score
--------------- -----
Inception 7.4
The Matrix 7.3
Pulp Fiction 9.5
The Dark Knight 8.4
Parasite 9.6
Anti-join: movies without any rating.
SELECT m.title
FROM movie m LEFT JOIN rating r ON m.id = r.movie
WHERE r.movie IS NULL;
title
-----
Tenet
RIGHT JOIN: people who are in no crew (person is on the right, so all persons are kept):
SELECT p.name, c.movie
FROM crew c RIGHT JOIN person p ON p.id = c.person
WHERE c.movie IS NULL;
name movie
------------ -----
Meryl Streep
FULL JOIN: compare our movies with a small list of Oscar winners. Matches in the middle, unmatched rows on both sides padded with NULL:
CREATE TABLE oscar (movie_title VARCHAR(200), year INTEGER);
INSERT INTO oscar VALUES ('Parasite', 2020), ('Oppenheimer', 2024);
SELECT m.title, o.movie_title, o.year AS oscar_year
FROM movie m FULL OUTER JOIN oscar o ON m.title = o.movie_title
WHERE m.year > 2015 OR m.year IS NULL;
(The WHERE just keeps the output short: recent movies plus the Oscar rows with no movie.)
LEFT JOIN + count: count(column) skips NULLs, so people with no credits get 0 (with count(*) they would get 1):
SELECT p.name, count(c.movie) AS n_credits
FROM person p LEFT JOIN crew c ON c.person = p.id
GROUP BY p.id, p.name
ORDER BY n_credits, p.name
LIMIT 3;
name n_credits
------------- ---------
Meryl Streep 0
Anne Hathaway 1
Bong Joon-ho 1
PostgreSQL vs SQLite
PostgreSQL has always supported LEFT, RIGHT and FULL joins. SQLite added RIGHT and FULL only in version 3.39 (these examples ran on 3.51). Results are identical.
Exercises
easy
Every movie with its number of ratings (0 if none), fewest first.
Show solution
SELECT m.title, count(r.movie) AS n_ratings
FROM movie m LEFT JOIN rating r ON r.movie = m.id
GROUP BY m.id, m.title
ORDER BY n_ratings, m.title;
title n_ratings
--------------- ---------
Tenet 0
Interstellar 1
Memento 1
Inception 2
Parasite 2
Pulp Fiction 2
The Dark Knight 2
The Matrix 2
medium
Names of the people who never directed a movie (use a LEFT JOIN).
Show solution
SELECT p.name
FROM person p LEFT JOIN crew c ON c.person = p.id AND c.role = 'director'
WHERE c.person IS NULL
ORDER BY p.name;
name
-----------------
Anne Hathaway
Carrie-Anne Moss
Christian Bale
Keanu Reeves
Leonardo DiCaprio
Meryl Streep
Song Kang-ho
Uma Thurman
The role condition must be in ON. In WHERE (WHERE c.role = 'director' AND c.person IS NULL) the result would be empty.
medium
One row per movie with two columns imdb and metacritic (NULL when missing).
Show solution
SELECT m.title, i.score AS imdb, mc.score AS metacritic
FROM movie m
LEFT JOIN rating i ON i.movie = m.id AND i.source = 'IMDb'
LEFT JOIN rating mc ON mc.movie = m.id AND mc.source = 'Metacritic'
ORDER BY m.id;
title imdb metacritic
--------------- ---- ----------
Inception 8.8 7.4
The Matrix 8.7 7.3
Interstellar 8.7
Pulp Fiction 8.9 9.5
The Dark Knight 9 8.4
Memento 8.4
Parasite 8.5 9.6
Tenet
Two LEFT JOINs to the same table with two aliases. Inner joins would keep only the 5 movies that have both scores.
hard
Movies from 2010 onwards with their Metacritic score, showing 0 when it is missing (use COALESCE(x, default), which returns the first non-NULL argument).
Show solution
SELECT m.title, COALESCE(r.score, 0) AS metacritic
FROM movie m
LEFT JOIN rating r ON r.movie = m.id AND r.source = 'Metacritic'
WHERE m.year >= 2010
ORDER BY m.title;
Here the condition on m.year is on the left table, so WHERE is correct.
exam
Without running: how many rows do these return? (a) movie m JOIN rating r ON r.movie = m.id (b) movie m LEFT JOIN rating r ON r.movie = m.id (c) person p LEFT JOIN crew c ON c.person = p.id (d) movie m FULL JOIN rating r ON r.movie = m.id
Show solution
(a) 12: one per rating row. (b) 13: the 12 matches + Tenet padded with NULL. (c) 20: 19 crew rows (each matches one person) + Meryl Streep. (d) 13: same as (b), because every rating references an existing movie (foreign key), so there are no unmatched ratings.
SELECT (SELECT count(*) FROM movie m JOIN rating r ON r.movie = m.id) AS a,
(SELECT count(*) FROM movie m LEFT JOIN rating r ON r.movie = m.id) AS b,
(SELECT count(*) FROM person p LEFT JOIN crew c ON c.person = p.id) AS c,
(SELECT count(*) FROM movie m FULL JOIN rating r ON r.movie = m.id) AS d;
a b c d
-- -- -- --
12 13 20 13
Self-check quiz
Common mistakes
Putting a condition on the right table in WHERE: the LEFT JOIN becomes an inner join.
Using count(*) after a LEFT JOIN to count matches: unmatched rows count as 1. Use count(b.column).
In the anti-join, testing a column that can be NULL anyway (like character). Test the join column or the key of the right table.
Thinking FULL JOIN = UNION. FULL JOIN combines columns side by side; UNION stacks rows (A1.13).
Remember
LEFT keeps all left rows, RIGHT all right rows, FULL both; missing side = NULLs.
Filters on the optional (right) table go in ON; filters on the kept table go in WHERE.
Anti-join: LEFT JOIN ... WHERE right.key IS NULL.
After a LEFT JOIN count with count(right.col); replace NULLs with COALESCE.
A1.11 Aggregate functions, GROUP BY and HAVING
Why it matters. "How many movies per director?", "average score per genre", "genres with at least 3 movies": these are the most typical analytical queries and very common exam exercises.
How it is examined. SQL exercises with GROUP BY/HAVING (often combined with joins), and quizzes on WHERE vs HAVING and on NULL handling in aggregates.
Explanation
Function
Result
NULLs
count(*)
number of rows
counts every row
count(col)
number of non-NULL values
ignored
count(DISTINCT col)
number of different non-NULL values
ignored
sum(col), avg(col)
sum, mean
ignored (avg = sum of non-NULL / count of non-NULL)
min(col), max(col)
smallest, largest (also for text and dates)
ignored
Without GROUP BY, an aggregate collapses the whole table into one row. With GROUP BY cols, rows with equal values on cols form a group and you get one row per group.
-- template
SELECT group_cols, aggregates -- 5
FROM ... JOIN ... -- 1
WHERE row_condition -- 2 filters ROWS, before grouping (no aggregates here)
GROUP BY group_cols -- 3
HAVING group_condition -- 4 filters GROUPS, after grouping (aggregates allowed)
ORDER BY ...; -- 6
The GROUP BY rule
When a query uses GROUP BY (or any aggregate), every column in SELECT (and HAVING, ORDER BY) must be either in the GROUP BY or inside an aggregate function. Otherwise there is no single value to show for the group. PostgreSQL raises ERROR: column "movie.title" must appear in the GROUP BY clause or be used in an aggregate function. (Exception in PostgreSQL: if you group by a table's primary key, you may select that table's other columns.)
JS analogy
GROUP BY is like Object.groupBy(rows, r => r.genre) followed by a reduce per group; HAVING is a .filter() on the groups; WHERE is a .filter() on the rows before grouping.
Worked examples
Whole-table aggregates (note count(*) = 8 but count(budget) = 7):
SELECT count(*) AS n_movies, count(budget) AS n_with_budget,
min(year) AS first, max(year) AS last,
avg(length) AS avg_len, sum(budget) AS total_budget
FROM movie;
n_movies n_with_budget first last avg_len total_budget
-------- ------------- ----- ---- ------- ------------
8 7 1994 2020 144.25 601400000
avg ignores NULL: it divides by 7, not 8.
SELECT avg(budget) AS avg_ignoring_null, sum(budget) / count(*) AS sum_over_all_rows
FROM movie;
SELECT source, count(*) AS n, avg(score) AS avg_score, max(score) AS best
FROM rating
GROUP BY source;
source n avg_score best
---------- - ---------------- ----
IMDb 7 8.71428571428571 9
Metacritic 5 8.44 9.6
HAVING: genres with at least 3 movies.
SELECT genre, count(*) AS n_movies
FROM genre
GROUP BY genre
HAVING count(*) >= 3
ORDER BY n_movies DESC, genre;
genre n_movies
------ --------
Action 4
Sci-Fi 4
Drama 3
Join + WHERE + GROUP BY + HAVING: people who directed more than one movie.
SELECT p.name, count(*) AS n_directed
FROM person p JOIN crew c ON c.person = p.id
WHERE c.role = 'director'
GROUP BY p.id, p.name
HAVING count(*) > 1;
name n_directed
----------------- ----------
Christopher Nolan 5
We group by p.id, p.name (not only name) because two different people could share a name.
WHERE and HAVING together: among movies from 2000 onwards, genres whose average length is above 140:
SELECT g.genre, avg(m.length) AS avg_len
FROM genre g JOIN movie m ON m.id = g.movie
WHERE m.year >= 2000
GROUP BY g.genre
HAVING avg(m.length) > 140
ORDER BY g.genre;
The GROUP BY rule broken. SQLite accepts this and returns an arbitrary-looking row; PostgreSQL rejects it. The correct way to get "the most recent movie" is a subquery (A1.12) or ORDER BY year DESC LIMIT 1.
SELECT title, max(year) FROM movie; -- WRONG in PostgreSQL
title max(year)
----- ---------
Tenet 2020
PostgreSQL vs SQLite
PostgreSQL returns avg of integers/numerics as numeric with many decimals (e.g. 144.2500000000000000, 8.7142857142857143); use round(avg(x), 2). In PostgreSQL round(value, digits) needs a numeric value (cast a double with ::numeric).
Exercises
easy
For each role in crew: number of rows and number of different people.
Show solution
SELECT role, count(*) AS n, count(DISTINCT person) AS n_people
FROM crew GROUP BY role ORDER BY role;
role n n_people
-------- - --------
actor 9 8
director 8 4
writer 2 1
medium
Number of movies and average length (1 decimal) per decade (1990, 2000, ...).
Show solution
SELECT year / 10 * 10 AS decade, count(*) AS n, round(avg(length), 1) AS avg_len
FROM movie
GROUP BY year / 10 * 10
ORDER BY decade;
Integer division: 2014 / 10 * 10 = 2010. (PostgreSQL also allows GROUP BY decade, using the alias.)
medium
People who worked on at least 2 different movies (in any role), with the number of movies.
Show solution
SELECT p.name, count(DISTINCT c.movie) AS n_movies
FROM person p JOIN crew c ON c.person = p.id
GROUP BY p.id, p.name
HAVING count(DISTINCT c.movie) >= 2
ORDER BY n_movies DESC, p.name;
name n_movies
----------------- --------
Christopher Nolan 5
Carrie-Anne Moss 2
With count(*) Tarantino would count 2 (director + actor in the same movie) and appear by mistake.
hard
Average IMDb score per genre (2 decimals), only for genres with at least 2 IMDb-rated movies, best first.
Show solution
SELECT g.genre, round(avg(r.score), 2) AS avg_imdb
FROM genre g JOIN rating r ON r.movie = g.movie
WHERE r.source = 'IMDb'
GROUP BY g.genre
HAVING count(*) >= 2
ORDER BY avg_imdb DESC;
For each director: total budget of the movies they directed and year of their latest movie, by total budget descending. What does Nolan's total ignore?
Show solution
SELECT p.name, sum(m.budget) AS total_budget, max(m.year) AS last_year
FROM person p
JOIN crew c ON c.person = p.id AND c.role = 'director'
JOIN movie m ON m.id = c.movie
GROUP BY p.id, p.name
ORDER BY total_budget DESC;
name total_budget last_year
----------------- ------------ ---------
Christopher Nolan 519000000 2020
Lana Wachowski 63000000 1999
Bong Joon-ho 11400000 2019
Quentin Tarantino 8000000 1994
sum ignores Tenet's NULL budget: 160 + 165 + 185 + 9 = 519 million. But max(year) still sees Tenet (2020).
Self-check quiz
Common mistakes
Aggregates in WHERE (WHERE count(*) > 1): error. Use HAVING.
Selecting a column that is neither grouped nor aggregated.
Using count(*) when you need count(DISTINCT ...) after a join that duplicates rows.
Forgetting that aggregates ignore NULL (avg is over non-NULL values; count(col) skips NULLs).
Putting a row filter in HAVING (works, but is slower and confusing). Row conditions → WHERE; group conditions → HAVING.
Remember
count/sum/avg/min/max; all ignore NULL except count(*).
GROUP BY → one row per group; SELECT only grouped columns or aggregates.
WHERE filters rows before grouping; HAVING filters groups after.
Order: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY.
A1.12 Nested queries: IN, EXISTS, ANY/ALL, scalar and correlated subqueries
Why it matters. Questions like "movies longer than the average", "people who never acted", "the best-rated movie of each genre" are naturally written with a query inside another query.
How it is examined. SQL query exercises (often the hardest one in the exam) and quizzes on the meaning of NOT IN, EXISTS, ALL with NULLs.
Explanation
A subquery is a SELECT in parentheses used inside another statement. Kinds:
Form
Meaning
x = (SELECT ...)
Scalar subquery: returns one value (one row, one column). Error if it returns more than one row.
x IN (SELECT ...)
x equals at least one value of the subquery result.
x NOT IN (SELECT ...)
x differs from all values. Trap: if the subquery returns a NULL, NOT IN is never true.
EXISTS (SELECT ...)
true if the subquery returns at least one row (the selected columns do not matter: SELECT * or SELECT 1).
NOT EXISTS (SELECT ...)
true if the subquery returns no row. Safe with NULLs.
x > ANY (SELECT ...)
true if x > at least one value (= ANY is the same as IN). SOME = ANY.
x >= ALL (SELECT ...)
true if x >= every value (true if the subquery is empty). <> ALL is the same as NOT IN.
FROM (SELECT ...) AS t
Subquery as a temporary table (needs an alias).
Definition: Correlated subquery
A subquery that uses a column of the outer query (e.g. WHERE r.movie = m.id where m is from the outer FROM). Conceptually it is re-evaluated for each row of the outer query, like a nested loop. A non-correlated subquery can be evaluated once.
JS analogy
EXISTS ≈ ratings.some(r => r.movie === m.id); NOT EXISTS ≈ !ratings.some(...); x >= ALL (...) ≈ values.every(v => x >= v) (and [].every(...) is true, just like ALL on an empty subquery).
Worked examples
Scalar subquery: movies longer than the average length (144.25).
SELECT title, length FROM movie
WHERE length > (SELECT avg(length) FROM movie);
title length
--------------- ------
Inception 148
Interstellar 169
Pulp Fiction 154
The Dark Knight 152
Tenet 150
IN: crime movies.
SELECT title FROM movie
WHERE id IN (SELECT movie FROM genre WHERE genre = 'Crime');
title
---------------
Pulp Fiction
The Dark Knight
NOT IN: people who never acted.
SELECT name FROM person
WHERE id NOT IN (SELECT person FROM crew WHERE role = 'actor');
name
-----------------
Christopher Nolan
Lana Wachowski
Bong Joon-ho
Meryl Streep
Correlated EXISTS: movies with a Metacritic score above 9.
SELECT m.title FROM movie m
WHERE EXISTS (SELECT * FROM rating r
WHERE r.movie = m.id AND r.source = 'Metacritic' AND r.score > 9);
title
------------
Pulp Fiction
Parasite
NOT EXISTS: movies without any rating.
SELECT m.title FROM movie m
WHERE NOT EXISTS (SELECT * FROM rating r WHERE r.movie = m.id);
title
-----
Tenet
Correlated scalar subquery (compare each Sci-Fi movie with the average of its own genre):
SELECT m.title, m.length
FROM movie m JOIN genre g ON g.movie = m.id
WHERE g.genre = 'Sci-Fi'
AND m.length > (SELECT avg(m2.length)
FROM movie m2 JOIN genre g2 ON g2.movie = m2.id
WHERE g2.genre = g.genre);
title length
------------ ------
Interstellar 169
Subquery in SELECT (one value per output row):
SELECT m.title,
(SELECT count(*) FROM crew c WHERE c.movie = m.id) AS crew_size
FROM movie m
ORDER BY crew_size DESC, m.title;
title crew_size
--------------- ---------
Inception 3
Interstellar 3
Pulp Fiction 3
The Matrix 3
Memento 2
Parasite 2
The Dark Knight 2
Tenet 1
ANY / ALL (PostgreSQL)
SQLite does not support ANY/ALL, so these were run in DuckDB (which uses PostgreSQL syntax) on the same data. The outputs are shown as plain tables.
-- PostgreSQL only
-- the longest movie(s): length >= every length
SELECT m.title, m.length FROM movie m
WHERE m.length >= ALL (SELECT length FROM movie);
Error: near "ALL": syntax error
m.title, m.length FROM movie m WHERE m.length >= ALL (SELECT length FROM movi
error here ---^
-- PostgreSQL only
-- movies more recent than at least one Crime movie (Crime years: 1994, 2008)
SELECT title, year FROM movie
WHERE year > ANY (SELECT m.year FROM movie m JOIN genre g ON g.movie = m.id
WHERE g.genre = 'Crime')
ORDER BY year;
Error: near "SELECT": syntax error
SELECT title, year FROM movie WHERE year > ANY (SELECT m.year FROM movie m JOI
error here ---^
ALL and NULL trap: Tenet's budget is NULL, so "budget >= ALL budgets" is never true (comparison with the NULL is unknown):
-- PostgreSQL only
SELECT title, budget FROM movie WHERE budget >= ALL (SELECT budget FROM movie);
-- 0 rows
SELECT title, budget FROM movie
WHERE budget >= ALL (SELECT budget FROM movie WHERE budget IS NOT NULL);
Error: near "ALL": syntax error
SELECT title, budget FROM movie WHERE budget >= ALL (SELECT budget FROM movie)
error here ---^
Error: near "ALL": syntax error
SELECT title, budget FROM movie WHERE budget >= ALL (SELECT budget FROM movie
error here ---^
The same trap with NOT IN: if the subquery contains a NULL, the result is empty. Here the subquery returns 8 and NULL:
SELECT name FROM person
WHERE id NOT IN (SELECT 8 UNION ALL SELECT NULL); -- 0 rows!
(no rows)
Why: for Nolan, 1 NOT IN (8, NULL) = 1 <> 8 AND 1 <> NULL = true AND unknown = unknown. Prefer NOT EXISTS when the subquery column may be NULL.
Check with the course
ANY/ALL are standard SQL and supported by PostgreSQL; they were verified with DuckDB because SQLite lacks them. A rewrite that works everywhere: x >= ALL (S) ≈ x >= (SELECT max(...) ...) when S has no NULLs and is not empty (careful: these differ when S is empty).
Exercises
easy
The oldest movie(s): title and year (use a scalar subquery).
Show solution
SELECT title, year FROM movie
WHERE year = (SELECT min(year) FROM movie);
title year
------------ ----
Pulp Fiction 1994
medium
Names of actors who acted in a Sci-Fi movie, using only nested IN (no JOIN).
Show solution
SELECT name FROM person
WHERE id IN (SELECT person FROM crew
WHERE role = 'actor'
AND movie IN (SELECT movie FROM genre WHERE genre = 'Sci-Fi'))
ORDER BY name;
name
-----------------
Anne Hathaway
Carrie-Anne Moss
Keanu Reeves
Leonardo DiCaprio
medium
Directors who never directed a Sci-Fi movie (use EXISTS and NOT EXISTS).
Show solution
SELECT p.name FROM person p
WHERE EXISTS (SELECT * FROM crew c WHERE c.person = p.id AND c.role = 'director')
AND NOT EXISTS (SELECT * FROM crew c JOIN genre g ON g.movie = c.movie
WHERE c.person = p.id AND c.role = 'director' AND g.genre = 'Sci-Fi')
ORDER BY p.name;
name
-----------------
Bong Joon-ho
Quentin Tarantino
hard
For each genre, the longest movie(s) of that genre: genre, title, length (correlated subquery).
Show solution
SELECT g.genre, m.title, m.length
FROM genre g JOIN movie m ON m.id = g.movie
WHERE m.length = (SELECT max(m2.length)
FROM movie m2 JOIN genre g2 ON g2.movie = m2.id
WHERE g2.genre = g.genre)
ORDER BY g.genre;
genre title length
-------- --------------- ------
Action The Dark Knight 152
Crime Pulp Fiction 154
Drama Interstellar 169
Mystery Memento 113
Sci-Fi Interstellar 169
Thriller Parasite 132
exam
People who worked (in any role) on every movie directed by Christopher Nolan. (Hint: "there is no Nolan movie on which this person did not work": double NOT EXISTS, the SQL way to express relational division.)
Show solution
SELECT p.name FROM person p
WHERE NOT EXISTS (
SELECT * FROM movie m
JOIN crew c0 ON c0.movie = m.id AND c0.role = 'director'
JOIN person n ON n.id = c0.person
WHERE n.name = 'Christopher Nolan'
AND NOT EXISTS (SELECT * FROM crew c WHERE c.movie = m.id AND c.person = p.id)
);
name
-----------------
Christopher Nolan
Only Nolan himself (trivially). Carrie-Anne Moss worked on Memento but not on the others. An alternative with grouping: count the distinct Nolan movies of each person and compare with the total number of Nolan movies (HAVING count(DISTINCT c.movie) = (SELECT count(*) ...)).
Self-check quiz
Common mistakes
NOT IN with a subquery that can return NULL: empty result. Use NOT EXISTS or filter IS NOT NULL inside.
Using = with a subquery that returns several rows: runtime error ("more than one row returned by a subquery used as an expression"). Use IN.
Forgetting the correlation condition in EXISTS (WHERE r.movie = m.id): EXISTS becomes true for every row (if any rating exists at all).
Forgetting the alias on a subquery in FROM: PostgreSQL requires (SELECT ...) AS t (since version 16 it is optional, but write it).
Thinking > ALL of an empty set is false. It is true.
Remember
Scalar (one value), IN / NOT IN (membership), EXISTS / NOT EXISTS (non-empty?), ANY / ALL (compare with some / every value).
Correlated = uses outer columns, evaluated per outer row.
NULL in the subquery breaks NOT IN and ALL; NOT EXISTS is safe.
"For all" questions = double NOT EXISTS.
A1.13 Set operators: UNION, INTERSECT, EXCEPT
Why it matters. Some questions are easiest as "this set AND/OR/MINUS that set": people who are both directors and actors, movies in genre A but not in B.
How it is examined. SQL exercises where a set operator is the natural solution, and quizzes on duplicates (UNION vs UNION ALL) and on compatibility rules.
Explanation
Operator
Result
Like (sets)
q1 UNION q2
rows in q1 or q2, duplicates removed
\(A \cup B\)
q1 UNION ALL q2
all rows of both, duplicates kept (faster)
concatenation
q1 INTERSECT q2
rows in both
\(A \cap B\)
q1 EXCEPT q2
rows in q1 and not in q2 (Oracle calls it MINUS)
\(A \setminus B\)
The two queries must be union-compatible: same number of columns, compatible types, matched by position (not by name).
The result's column names come from the first query.
ORDER BY can appear only once, at the end, and sorts the whole result.
Without ALL, the result is a set (no duplicates). PostgreSQL also has INTERSECT ALL and EXCEPT ALL.
For set operators, two NULLs are treated as equal (unlike =).
JS analogy
UNION ≈ [...new Set([...a, ...b])]; UNION ALL ≈ [...a, ...b]; INTERSECT ≈ a.filter(x => b.includes(x)) without duplicates; EXCEPT ≈ a.filter(x => !b.includes(x)) without duplicates.
Worked examples
UNION vs UNION ALL: ids of movies that are Sci-Fi or Drama. Interstellar (3) is both:
SELECT movie FROM genre WHERE genre = 'Sci-Fi'
UNION
SELECT movie FROM genre WHERE genre = 'Drama'
ORDER BY movie;
movie
-----
1
2
3
4
7
8
SELECT movie FROM genre WHERE genre = 'Sci-Fi'
UNION ALL
SELECT movie FROM genre WHERE genre = 'Drama'
ORDER BY movie;
movie
-----
1
2
3
3
4
7
8
INTERSECT: Sci-Fi and Action. EXCEPT: Sci-Fi but not Action.
SELECT movie FROM genre WHERE genre = 'Sci-Fi'
INTERSECT
SELECT movie FROM genre WHERE genre = 'Action'
ORDER BY movie;
movie
-----
1
2
8
SELECT movie FROM genre WHERE genre = 'Sci-Fi'
EXCEPT
SELECT movie FROM genre WHERE genre = 'Action';
movie
-----
3
People who are both director and actor (somewhere):
SELECT p.name FROM person p JOIN crew c ON c.person = p.id WHERE c.role = 'director'
INTERSECT
SELECT p.name FROM person p JOIN crew c ON c.person = p.id WHERE c.role = 'actor';
name
-----------------
Quentin Tarantino
EXCEPT as anti-join: movie ids with no rating.
SELECT id FROM movie
EXCEPT
SELECT movie FROM rating;
id
--
8
Mixing two tables: column name from the first query, constant column to tag the origin, one ORDER BY at the end:
SELECT title AS name, 'movie' AS kind FROM movie WHERE year > 2015
UNION
SELECT name, 'person' FROM person WHERE birth_year > 1980
ORDER BY name;
name kind
------------- ------
Anne Hathaway person
Parasite movie
Tenet movie
Not union-compatible (2 columns vs 1):
SELECT title, year FROM movie
UNION
SELECT name FROM person;
Error: SELECTs to the left and right of UNION do not have the same number of result columns
(PostgreSQL: ERROR: each UNION query must have the same number of columns.)
Check with the course
The brief says only "set operators (listed in the syllabus)": UNION, INTERSECT and EXCEPT are the standard ones in PostgreSQL. Without ORDER BY the row order of a set operation is not guaranteed (PostgreSQL and SQLite may print a different order), so the examples sort the result.
Exercises
easy
What does SELECT genre FROM genre WHERE movie = 1 UNION SELECT genre FROM genre WHERE movie = 2 return? And with UNION ALL?
Show solution
Inception and The Matrix are both Sci-Fi + Action. UNION: 2 rows (Action, Sci-Fi). UNION ALL: 4 rows (each twice).
SELECT genre FROM genre WHERE movie = 1
UNION
SELECT genre FROM genre WHERE movie = 2
ORDER BY genre;
genre
------
Action
Sci-Fi
medium
Titles of Drama movies that have a Metacritic rating (use INTERSECT).
Show solution
SELECT m.title FROM movie m JOIN genre g ON g.movie = m.id WHERE g.genre = 'Drama'
INTERSECT
SELECT m.title FROM movie m JOIN rating r ON r.movie = m.id WHERE r.source = 'Metacritic'
ORDER BY 1;
title
------------
Parasite
Pulp Fiction
ORDER BY 1 = sort by the first column. Careful: intersecting on titles would wrongly merge two different movies with the same title; intersecting on ids and then joining is more robust.
medium
Actors who acted only in movies released before 2000 (use EXCEPT).
Show solution
SELECT p.name FROM person p JOIN crew c ON c.person = p.id WHERE c.role = 'actor'
EXCEPT
SELECT p.name FROM person p JOIN crew c ON c.person = p.id
JOIN movie m ON m.id = c.movie WHERE m.year >= 2000
ORDER BY 1;
name
-----------------
Keanu Reeves
Quentin Tarantino
Uma Thurman
Carrie-Anne Moss is removed because of Memento (2000).
hard
One list called label with the titles of movies released before 1996 and the names of people born before 1964, alphabetically.
Show solution
SELECT title AS label FROM movie WHERE year < 1996
UNION
SELECT name FROM person WHERE birth_year < 1964
ORDER BY label;
Rewrite the EXCEPT query "movie ids with no rating" in two other ways, and explain one situation where EXCEPT and NOT IN give different results.
Show solution
SELECT m.id FROM movie m WHERE NOT EXISTS (SELECT * FROM rating r WHERE r.movie = m.id);
SELECT m.id FROM movie m LEFT JOIN rating r ON r.movie = m.id WHERE r.movie IS NULL;
id
--
8
id
--
8
If the subquery column contains a NULL, NOT IN returns nothing, while EXCEPT still works (it treats NULLs as equal and just removes matching rows). Also, EXCEPT removes duplicates from the result.
Self-check quiz
Common mistakes
Different number of columns or incompatible types in the two queries.
Putting ORDER BY in the first query (only one ORDER BY, at the very end).
Using UNION when you need to keep duplicates (e.g. before counting): use UNION ALL.
Expecting columns to match by name: they match by position.
Remember
UNION (∪, no duplicates), UNION ALL (keeps them), INTERSECT (∩), EXCEPT (−).
Same number of columns, compatible types, by position; names from the first query.
One ORDER BY at the end.
A1.14 Using the database from Python: drivers, pandas, Streamlit
Why it matters. In data science you rarely type SQL in a console: a Python script connects to PostgreSQL, runs queries and loads results into pandas for analysis or a dashboard.
How it is examined. The brief lists it in the syllabus ("PostgreSQL from Python, pandas with SQL, Streamlit") but the exam format names only quizzes, SQL and MongoDB exercises, so expect at most quiz questions on the concepts (driver, cursor, commit, parameters, read_sql). check with the teacher whether code from this part can appear.
Explanation
A driver (connector) is a library that speaks the DBMS network protocol. For PostgreSQL in Python: psycopg2 (classic) or psycopg (version 3). Python's standard DB-API defines the same pattern for all drivers.
Pattern: connect → get a cursor → execute(sql, params) → fetchone()/fetchall() → commit() (for changes) → close().
DB-API drivers open a transaction automatically: changes are lost unless you call conn.commit() (or use with conn:, which commits on success and rolls back on error).
Never build SQL with f-strings from user input (SQL injection). Pass parameters separately: placeholders are %s in psycopg2, ? in sqlite3, :name in SQLAlchemy text().
SQLAlchemy gives an engine from a URL (postgresql+psycopg2://user:password@host:5432/dbname); pandas uses it in pd.read_sql(query, engine) (result → DataFrame) and df.to_sql(name, engine) (DataFrame → table).
Streamlit turns a Python script into a web app (a simple frontend for the database): widgets like st.slider give values, you query with them, and st.dataframe shows the result. Run with streamlit run app.py.
JS analogy
psycopg2 ≈ pg (node-postgres): client.query('SELECT ... WHERE year >= $1', [2010]). SQLAlchemy ≈ a query builder/ORM like Knex/Prisma. pandas DataFrame ≈ an array of row objects with column-wise superpowers.
Worked examples
psycopg2 with PostgreSQL (not run here: no PostgreSQL server in the verification environment; the API is the standard one):
import psycopg2
conn = psycopg2.connect(host="localhost", port=5432, dbname="imdb",
user="postgres", password="secret")
cur = conn.cursor()
cur.execute("SELECT title, year FROM movie WHERE year >= %s ORDER BY year", (2010,))
for title, year in cur.fetchall(): # list of tuples
print(title, year)
cur.execute("UPDATE movie SET budget = %s WHERE title = %s", (205000000, "Tenet"))
conn.commit() # make the change permanent
cur.close()
conn.close()
The same pattern with sqlite3 (standard library, same DB-API), plus pandas. This one was run on our IMDB sample:
import sqlite3
import pandas as pd
conn = sqlite3.connect("imdb.db")
cur = conn.cursor()
# 1) query with a parameter (never build SQL with f-strings!)
cur.execute("SELECT title, year FROM movie WHERE year >= ? ORDER BY year", (2010,))
rows = cur.fetchall() # list of tuples
print(rows)
# 2) write inside a transaction
cur.execute("UPDATE movie SET budget = ? WHERE title = ?", (205000000, "Tenet"))
conn.commit() # without commit the change is lost
# 3) pandas: query result -> DataFrame
df = pd.read_sql("""
SELECT m.title, r.source, r.score
FROM movie m JOIN rating r ON r.movie = m.id
""", conn)
print(df.head(4))
print(df.pivot(index="title", columns="source", values="score").round(1))
conn.close()
[('Inception', 2010), ('Interstellar', 2014), ('Parasite', 2019), ('Tenet', 2020)]
title source score
0 Inception IMDb 8.8
1 Inception Metacritic 7.4
2 The Matrix IMDb 8.7
3 The Matrix Metacritic 7.3
source IMDb Metacritic
title
Inception 8.8 7.4
Interstellar 8.7 NaN
Memento 8.4 NaN
Parasite 8.5 9.6
Pulp Fiction 8.9 9.5
The Dark Knight 9.0 8.4
The Matrix 8.7 7.3
Note how SQL NULL / missing becomes NaN in pandas.
SQLAlchemy engine + named parameters + to_sql (run with a SQLite URL; for PostgreSQL only the URL changes):
import pandas as pd
from sqlalchemy import create_engine, text
engine = create_engine("sqlite:///imdb.db")
# PostgreSQL: create_engine("postgresql+psycopg2://user:pwd@localhost:5432/imdb")
with engine.connect() as conn:
df = pd.read_sql(text("SELECT title, length FROM movie WHERE length > :min_len"),
conn, params={"min_len": 150})
print(df)
summary = df.assign(hours=df.length / 60)
summary.to_sql("long_movies", engine, if_exists="replace", index=False)
print(pd.read_sql("SELECT * FROM long_movies", engine))
title length
0 Interstellar 169
1 Pulp Fiction 154
2 The Dark Knight 152
title length hours
0 Interstellar 169 2.816667
1 Pulp Fiction 154 2.566667
2 The Dark Knight 152 2.533333
A minimal Streamlit frontend (not run here; shown for the idea):
# app.py -> run with: streamlit run app.py
import pandas as pd
import streamlit as st
from sqlalchemy import create_engine, text
engine = create_engine("postgresql+psycopg2://user:pwd@localhost:5432/imdb")
st.title("IMDB explorer")
min_year = st.slider("Released from", 1990, 2025, 2000)
df = pd.read_sql(text("SELECT title, year FROM movie WHERE year >= :y ORDER BY year"),
engine, params={"y": min_year})
st.dataframe(df)
st.bar_chart(df.set_index("title")["year"])
Check with the course
Which driver the course uses (psycopg2, psycopg 3, or SQLAlchemy only) and the exact Streamlit app shown in class are NOT STATED in the brief. The concepts above (connection, cursor, parameters, commit, read_sql) are the same with any of them.
Exercises
easy
Put these in the right order: fetchall(), connect(), close(), cursor(), execute().
Show solution
connect() → cursor() → execute() → fetchall() → close() (and commit() before closing if you changed data).
medium
A script runs cur.execute("DELETE FROM rating WHERE source = 'Metacritic'") with psycopg2 and then ends without anything else. Are the rows deleted in the database?
Show solution
No. psycopg2 opened a transaction implicitly and it was never committed; when the connection closes, the transaction is rolled back. Call conn.commit() (or use with conn:).
medium
What is wrong with this code, and how do you fix it? cur.execute(f"SELECT * FROM movie WHERE title = '{user_input}'")
Show solution
SQL injection: with user_input = "x' OR '1'='1" the query returns every row (or worse, with a ; DROP TABLE ...). It also breaks on titles containing a quote. Fix: cur.execute("SELECT * FROM movie WHERE title = %s", (user_input,)). Note the comma: the parameters must be a tuple.
hard
Write Python code that loads, for each genre, the number of movies and the average IMDb score into a DataFrame (one SQL query, with SQLAlchemy and pandas), then prints the genre with the highest average.
Show solution
import pandas as pd
from sqlalchemy import create_engine
engine = create_engine("sqlite:///imdb.db") # or the PostgreSQL URL
df = pd.read_sql("""
SELECT g.genre, count(*) AS n, avg(r.score) AS avg_imdb
FROM genre g JOIN rating r ON r.movie = g.movie
WHERE r.source = 'IMDb'
GROUP BY g.genre
""", engine)
best = df.sort_values("avg_imdb", ascending=False).iloc[0]
print(best["genre"], round(best["avg_imdb"], 2))
Crime 8.95
Prefer doing the aggregation in SQL (the database is optimised for it, and less data travels to Python).
Self-check quiz
Common mistakes
Forgetting conn.commit() after INSERT/UPDATE/DELETE.
Building queries with f-strings or + (SQL injection, quoting bugs).
Passing a single parameter as (2010) instead of the tuple (2010,).
A1.15 NoSQL: data models, comparison with relational, the document model
Why it matters. NoSQL systems (like MongoDB) are used when data is huge, distributed or irregular. You must know when and why they differ from relational DBMSs.
How it is examined. The exam has a block of quizzes on NoSQL systems: the four families with examples, relational vs NoSQL, CAP and BASE vs ACID, schema flexibility, embedding vs referencing.
Explanation
Why NoSQL appeared ("Not only SQL", around 2009): web-scale applications needed (1) horizontal scaling (many cheap servers instead of one big one), (2) a flexible schema for irregular, fast-changing data (JSON from APIs, logs, user profiles), (3) high availability across data centres. The relational model with strict schemas, joins and full ACID across servers is hard to scale this way.
The four families
Model
Data looks like
Good for
Examples
Key-value
a huge hash map: key → opaque value (the DB does not look inside)
caching, sessions, shopping carts; very fast get/put by key
Redis, Amazon DynamoDB, Riak
Document
key → a structured document (JSON/BSON) with nested objects and arrays; the DB can query inside it
catalogues, content, user profiles, our movies with embedded cast and ratings
MongoDB, CouchDB
Column-family (wide-column)
rows with a key and many columns grouped in families; each row can have different columns; stored by column family
huge write-heavy data, time series, analytics at scale
Apache Cassandra, HBase, Google Bigtable
Graph
nodes and edges, both with properties
relationships: social networks, recommendations ("actors who worked with actors who worked with Nolan")
Neo4j
Key-value, document and column-family stores are called aggregate-oriented: they store a whole "aggregate" (e.g. a movie with its cast and ratings) as one unit, which is easy to distribute across servers. Graph databases are not aggregate-oriented.
Relational vs NoSQL (document)
Relational (PostgreSQL)
Document NoSQL (MongoDB)
Structure
tables, fixed schema defined first (schema-on-write)
collections of documents; flexible schema, documents in one collection can differ (often called "schemaless")
Values
atomic values only (1NF)
nested objects and arrays allowed
Relationships
normalised tables + foreign keys + joins
usually embedding (denormalised); references possible ($lookup exists but is used less)
Query language
SQL (standard)
product-specific API (MongoDB query language, aggregation pipeline)
Scaling
traditionally vertical (bigger server)
horizontal: sharding (split data across servers) + replication (copies)
Consistency
ACID transactions
atomic per document; often eventual consistency across replicas (BASE). MongoDB also offers multi-document transactions since version 4.0.
Integrity
constraints enforced by the DBMS (keys, FK, CHECK)
mostly the application's job (optional JSON-schema validation)
CAP theorem (Brewer)
A distributed data store cannot guarantee all three at the same time: Consistency (every read sees the latest write), Availability (every request gets a non-error answer), Partition tolerance (the system keeps working when the network splits the servers). Since network partitions do happen, during a partition a system must choose CP (refuse some requests to stay consistent) or AP (answer, possibly with stale data).
Definition: BASE (vs ACID)
Basically Available, Soft state, Eventual consistency: the system is always answering, its state may change without new input (replicas syncing), and if writes stop, all replicas eventually become consistent. BASE trades the strong guarantees of ACID for availability and scale.
Documents are JSON-like, stored as BSON (binary JSON, with extra types such as int, double, date, ObjectId). Max document size 16 MB.
Every document has a unique _id (the primary key); if you do not give one, MongoDB creates an ObjectId.
Embedding: put related data inside the document (cast and ratings inside the movie). One read gets everything; good for "contains" / one-to-few relationships read together. Referencing: store the other document's _id (like a foreign key). Better for many-to-many, very large or independently updated data. Design rule: model for the queries you will run.
JS analogy
A MongoDB document is literally a JS object (with a few extra types), and the shell mongosh is a JavaScript REPL. This is the part of the module where your JS background helps most.
Worked examples
One movie in the relational model needs rows in 5 tables (movie, crew, person, rating, genre). In the document model it is one document. This is the collection used in A1.16 and A1.17 (first document shown):
Flexible schema: in the same collection, Tenet has no ratings field at all, budget: null, an empty cast, and (on purpose, as "dirty data") its year stored as the string"2020". MongoDB accepts all of it; a relational table would not.
Embedding vs referencing for the director: we embedded just the director's name (director: "Christopher Nolan"). If we needed the director's birth year and bio in many queries, we could store people in a separate people collection and reference them: director_id: 1. Trade-off: embedding duplicates data (if Nolan's name changes we must update 5 documents), referencing needs a second query or $lookup.
Check with the course
Which NoSQL families and examples the teacher lists, and how CAP is presented, come from the course PDFs "NoSQL" and "MongoDB" which were not available: definitions here follow the standard literature. The sample collection is our own design (not necessarily the teacher's MongoDB IMDB collection).
Exercises
easy
Which NoSQL family fits best? (a) storing user sessions by session id; (b) a "friends of friends who liked this movie" recommendation; (c) a product catalogue where each product type has different attributes; (d) billions of sensor readings per day, written constantly.
Design a MongoDB document for a person that also stores the movies they worked on. What would you embed and what would you reference? Write one example document for Carrie-Anne Moss.
Show solution
Embed small, stable info read together (name, birth year, a short list of credits with movie id, title and role); reference the full movie by its _id.
The title is duplicated from the movie document (denormalisation) to avoid a second query when listing credits.
medium
During a network partition, an AP system and a CP system receive a read for data that was just updated on the other side of the partition. What does each do?
Show solution
AP: answers, possibly with the old (stale) value; replicas reconcile later (eventual consistency). CP: refuses the request or waits (error/timeout) rather than return a possibly stale value.
exam
True or false: (a) NoSQL means SQL can never be used. (b) In MongoDB all documents of a collection must have the same fields. (c) The CAP theorem says a distributed system can have at most two of C, A, P during a partition. (d) BASE systems guarantee that every read returns the latest write. (e) In a relational DB, many-to-many relationships need an extra table.
Show solution
(a) False: "Not only SQL"; some NoSQL systems even have SQL-like languages. (b) False: flexible schema. (c) True (in practice: with P, choose C or A). (d) False: eventual consistency. (e) True (like crew between movie and person, or genre).
Self-check quiz
Common mistakes
Saying "schemaless" means "no structure". The structure exists, it is just not enforced by the DBMS up front.
Mixing key-value and document stores: in key-value the value is opaque; in document stores you can query fields inside the value.
Reading CAP's "C" as ACID's "C". CAP consistency = all replicas show the latest write; ACID consistency = integrity constraints hold.
Thinking MongoDB has no transactions at all: single-document operations are atomic, and multi-document transactions exist since 4.0.
Remember
Four families: key-value (Redis), document (MongoDB), column-family (Cassandra), graph (Neo4j).
CAP: with partitions, choose consistency or availability. BASE: basically available, soft state, eventual consistency.
MongoDB: database → collection → document (BSON, _id) → field. Embed what you read together; reference what is shared or large.
A1.16 MongoDB: find queries and operators
Why it matters.find is MongoDB's SELECT ... WHERE. Queries on arrays and embedded documents (dot notation, $elemMatch) are where MongoDB differs most from SQL.
How it is examined. "MongoDB find query exercises": write the find for a request on a given collection (filter + projection, sometimes sort/limit), and quizzes on what a filter matches.
Explanation
// template
db.collection.find(filter, projection).sort({ field: 1 | -1 }).limit(n)
db.collection.findOne(filter, projection) // first matching document (or null)
db.collection.countDocuments(filter) // number of matching documents
filter: a document of conditions; {} matches everything. Several fields in one filter are combined with AND (implicit AND): { director: "Christopher Nolan", length: { $gt: 150 } }.
projection: which fields to return. { title: 1, year: 1 } = only these (+ _id, which is included unless you write _id: 0). { cast: 0 } = everything except cast. Do not mix 1 and 0 (except for _id: 0).
sort: 1 ascending, -1 descending; limit: keep the first n.
Operator
Meaning
Example
SQL
$eq
equal (usually implicit)
{ year: { $eq: 2010 } } = { year: 2010 }
=
$ne
not equal (also matches docs without the field)
{ director: { $ne: "Christopher Nolan" } }
<>
$gt $gte $lt $lte
>, ≥, <, ≤
{ year: { $gte: 1995, $lte: 2005 } }
BETWEEN
$in, $nin
value in / not in a list ($nin also matches docs without the field)
{ genres: "Crime" } on an array field matches if any element equals "Crime".
{ genres: ["Sci-Fi", "Action"] } matches only the exact array (same elements, same order).
Dot notation reaches inside: "ratings.score", "cast.name" (quotes are required).
Two conditions with dot notation on the same array ({ "ratings.source": "Metacritic", "ratings.score": { $gte: 9 } }) can be satisfied by different elements. To require the same element, use $elemMatch.
{ field: null } matches documents where the field is null or missing.
MongoServerError: Cannot do exclusion on field ratings in inclusion projection
Check with the course
Run on MongoDB 8 with mongosh. Older shells print the same documents with slightly different formatting. The brief lists $size and $type under the aggregation pipeline; they also work in find as shown. $all (array contains all values) and regular expressions ({ title: /^The / }) are not in the brief's list.
Exercises
easy
Titles and years of movies released from 1995 to 2005 (included), oldest first, without _id.
Yes: on an array, $ne means "no element equals", and it also matches documents without the field, so Tenet (no ratings) is included. (Equivalent: { "ratings.source": { $nin: ["Metacritic"] } }.)
exam
The two longest Nolan movies whose year is stored as a number: title and year.
[
{ title: 'Interstellar', year: 2014 },
{ title: 'The Dark Knight', year: 2008 }
]
"number" is an alias that matches int, long, double and decimal. You can sort on a field (length) that is not projected.
Self-check quiz
Common mistakes
Using dot notation with two conditions on the same array when the request is about one element: use $elemMatch.
Repeating the same key in one filter object ({ genres: "A", genres: "B" }): only the last one counts. Use $and (or $all).
Mixing inclusion and exclusion in the projection (only _id: 0 may be mixed).
Forgetting quotes around dotted field names: { ratings.score: 9 } is a JS syntax error.
Writing $not: { ... } at top level like $and. $not goes inside a field: { year: { $not: { $gte: 2000 } } }.
Forgetting that $ne, $nin, $not and { f: null } also match documents where the field is missing.
Remember
find(filter, projection).sort().limit(); fields in a filter are ANDed.
$eq $ne $gt $gte $lt $lte $in $nin on a field; $and $or with an array of conditions; $not wraps an operator on a field.
Array field: { arr: v } = any element; $elemMatch = one element meets all conditions; $size = length.
$exists for presence, $type for type, { f: null } = null or missing.
A1.17 MongoDB: the aggregation pipeline
Why it matters.find can only filter and project. Grouping, counting, averaging, reshaping and working element-by-element on arrays need the aggregation pipeline: MongoDB's equivalent of GROUP BY, HAVING and computed columns.
How it is examined. "MongoDB aggregation query exercises": write a pipeline for a request (taught on 3-4 November). Expect $match, $unwind, $group with $sum/$avg/$push, $project/$set with $size, $cond, $ifNull, $type, and $sort.
Explanation
A pipeline is an array of stages. Documents flow through the stages in order; each stage transforms the stream and passes it on (like chained .filter().map()... in JS, or a Unix pipe).
MongoServerError: Executor error during aggregate command on namespace: study_verify_a1.movies :: caused by :: The argument to $size must be an array, but was of type: missing
(Use { $unwind: { path: "$cast", preserveNullAndEmptyArrays: true } } to keep them, like a LEFT JOIN.)
Check with the course
The aggregation pipeline was scheduled for 3-4 November, after this page was written: compare with the class examples. $set is the newer name of $addFields (MongoDB 4.2+). $limit, $count, $max and preserveNullAndEmptyArrays are not in the brief's list; they are shown only as extras. Outputs from MongoDB 8 / mongosh; the order of documents with equal sort keys, and of $group output without $sort, is not guaranteed.
Exercises
easy
Number of movies per director, most first (ties by name).
For each movie: title, avg_score (average over its ratings) and label: "unrated" if it has no ratings, "top" if the average is at least 8.7, otherwise "good". Sort by title. Use $set, $cond and $type (or $ifNull).
For Tenet, $avg over a missing array gives null, whose $type is "null". Careful: without the first check, { $gte: [null, 8.7] } is simply false, so Tenet would be labelled "good". An alternative test: { $eq: [ { $ifNull: ["$avg_score", "none"] }, "none" ] }.
exam
For each genre: number of IMDb-rated movies, average IMDb score and titles; keep only genres with average ≥ 8.7; best first.
Same groups and numbers as the SQL version in A1.11 (Crime 8.95, Action 8.83, Sci-Fi 8.73, Drama 8.7), but with floating-point digits: Drama's exact average 8.7 comes out as 8.700000000000001. Here that is just above 8.7, so Drama is kept, but with other data a value like 8.699999999999999 would be dropped. Exact-threshold comparisons on float averages are fragile: round first ($round) if the boundary matters.
Self-check quiz
Common mistakes
Forgetting the $ in field paths: { $avg: "length" } averages the string "length" (result null). Write "$length".
Putting $ on field names in $match ({ "$year": 2010 }). In filters, the field name has no $.
Forgetting that $group outputs only _id and the accumulators: other fields (like title) are gone unless you $push them.
Using $project when you want to keep all other fields: use $set.
Grouping by array elements without $unwind: _id: "$genres" groups by the whole array.
$size on a field that may be missing: wrap it with $ifNull: ["$f", []].
Writing a comparison expression in query form inside $cond: use { $gte: ["$length", 150] }, not { length: { $gte: 150 } }.
This module teaches R from zero for a programmer who already knows JavaScript/TypeScript: the RStudio environment, R data structures (vectors, matrices and arrays, lists, data frames, environments), control structures and loop functionals, user-defined functions, import/export, base graphics and ggplot2, the tidyverse, random number generation, building packages and debugging, inference elements and Quarto/R Markdown. The written R module lasts 30 minutes and contains closed-form exercises and code exercises on R scripts, on both the lectures and the labs. In practice that means two skills: (1) reading a short R script and saying exactly what it returns or prints, and (2) writing a few lines of R that solve a small data task. Every topic below trains both. The exact number of questions, the point weights, whether notes are allowed and the exam date are check (NOT STATED on the course page).
How to use this module
Topics A2.1-A2.6 are the foundation (lessons 1-3): learn them first, because most "what does this return?" questions come from vectors, coercion, indexing and recycling. Then A2.7-A2.11 (lessons 3-4: control flow, loops, functionals, functions, import). Then plots (lessons 5-6). Topics A2.15-A2.17 cover the official syllabus items that were still to come at the time the brief was written. Remember: R is 1-based, vectorised, and copies values (copy-on-modify) instead of sharing references like JS objects.
A2.1 R and RStudio: help, workspace, naming, packages
Why it matters. This is lesson 1-2 material: how you run R code, find help, manage the objects in memory and load extra functionality from packages. Everything else builds on it.
How it is examined. Short closed-form questions: "which name is valid?", "what is the difference between install.packages() and library()?", "what does ls() return after this script?". Also the first lines of any code exercise (loading a package).
Explanation
R is a free language for statistics and data analysis (created by Ross Ihaka and Robert Gentleman in the 1990s as an open-source version of the S language). RStudio is an IDE (integrated development environment) for R. It has four panes: the script editor (your .R file), the console (where code actually runs), the environment/history pane (objects in memory) and the files/plots/packages/help pane. In the editor, Ctrl/Cmd+Enter sends the current line or selection to the console.
Assignment and printing
The assignment operator is <- (read "gets"). = also works at top level, but the course style (and most R code) uses <-; inside a function call, = names an argument (mean(x = v)). At the console, the value of an expression is auto-printed; an assignment is not printed, unless you wrap it in parentheses.
x <- 5 # nothing printed
x # auto-print
(y <- x * 2) # assignment + print
10 -> z # right assignment also exists (rare)
[1] 5
[1] 10
The [1] is not part of the value: it is the index of the first element shown on that line. In R even a single number is a vector of length 1.
Help
Command
What it does
?mean or help(mean)
Opens the help page of a function you know the name of.
??regression or help.search("regression")
Searches all installed help pages for a word.
example(mean)
Runs the examples at the bottom of the help page.
args(sample)
Shows the arguments of a function.
?"if", ?`+`
Help on keywords/operators needs quotes or backticks.
Workspace and working directory
The workspace (global environment) is the set of objects you created in the session. The working directory is the folder where R reads and writes files with relative paths.
Command
Meaning
ls()
Names of the objects in the workspace (character vector, sorted).
rm(x), rm(list = ls())
Remove one object / remove everything.
exists("x")
TRUE if an object called x can be found.
getwd(), setwd("path")
Get / set the working directory.
save.image(), save(x, y, file = "f.RData"), load("f.RData")
Save the whole workspace or some objects to an .RData file and load them back.
history(), sessionInfo()
Command history; R version and loaded packages.
Tip (RStudio projects)
An RStudio Project (.Rproj) sets the working directory to the project folder automatically, so you rarely need setwd(). Note that setwd("C:\Users\me") fails on Windows: in R strings the backslash is an escape character, so write "C:/Users/me" or "C:\\Users\\me".
Naming rules
Definition: syntactic name
A valid object name contains letters, digits, . and _; it must start with a letter or with a dot that is not followed by a digit; it must not be a reserved word (if, else, for, while, function, TRUE, FALSE, NULL, NA, Inf, NaN, repeat, break, next, in...). Names are case-sensitive: Data and data are different.
Valid: my_var, my.var, x2, .hidden. Invalid: 2x, _x, my-var (parsed as my - var), .2x, if. Names starting with a dot are hidden from ls() (use ls(all.names = TRUE)). A non-syntactic name can still be used with backticks: `my var` <- 1. Unlike JS, the dot has no special meaning in a name: my.var is just one name, not a property access. Avoid overwriting built-ins such as c, T, mean, data.
Packages
A package is a bundle of functions, data and documentation (like an npm package). The official repository is CRAN. Base R already loads a few packages (base, stats, graphics, utils, datasets, ...).
Step
Code
How often
JS analogy
Install (download to your library folder)
install.packages("dplyr") (name in quotes)
once per computer
npm install
Load (attach to the search path)
library(dplyr)
every session / script
import
Use one function without attaching
dplyr::filter(df, x > 1)
any time
import { filter } from
library(pkg) gives an error if the package is not installed (there is no package called ‘pkg’); require(pkg) gives a warning and returns FALSE. So if (!require(pkg)) install.packages("pkg") is a common idiom.
Masking: when two attached packages export the same name, the one attached last wins. Loading dplyr prints that filter and lag from package stats are masked. Use stats::filter to reach the masked one.
rm(list = ls()) # start clean
a <- 1; b <- "hi"; .secret <- 42
ls()
exists("secret")
exists(".secret")
rm(a)
ls()
[1] "a" "b"
[1] FALSE
[1] TRUE
[1] "b"
if (!require(ggplot2)) { # TRUE (silently) if already installed
install.packages("ggplot2")
library(ggplot2)
}
stats::sd(c(2, 4, 4, 4, 5, 5, 7, 9)) # call with namespace prefix
[1] 2.13809
Exercises
easy
Which of these names are valid: total_2024, 2nd_place, .temp, my-data, .5a, Mean.Value, else?
Show solution
Valid: total_2024, .temp, Mean.Value. Invalid: 2nd_place (starts with a digit), my-data (minus sign: R reads my - data), .5a (dot followed by a digit), else (reserved word).
easy
What is printed by this script (run in a fresh session)?
x <- 3
x * 2
y <- x + 1
(z <- y^2)
Show solution
[1] 6
[1] 16
Line 1 and 3 are assignments (not printed). Line 2 is auto-printed. Line 4 is an assignment in parentheses, so its value \(4^2 = 16\) is printed.
medium
A script starts with library(tidyverse) and fails with Error in library(tidyverse) : there is no package called ‘tidyverse’. Explain the cause and the fix. Then explain why install.packages(tidyverse) (without quotes) also fails.
Show solution
The package is not installed on this computer. Run once install.packages("tidyverse"), then library(tidyverse). Without quotes, R evaluates tidyverse as an object name; no such object exists, so you get object 'tidyverse' not found. library() accepts an unquoted name because it uses non-standard evaluation, install.packages() does not.
exam
What does the last line print?
rm(list = ls())
Data <- 1:3
data_2 <- "a"
.cfg <- TRUE
`my var` <- 10
ls()
Show solution
[1] "Data" "data_2" "my var"
.cfg is hidden (starts with a dot). The non-syntactic name created with backticks is a normal object. ls() returns the names sorted; "Data" comes before "data_2" because it is a prefix of it when case is ignored (and in the C locale upper case sorts first anyway). The key points are: 3 names, .cfg missing.
Self-check quiz
Common mistakes
Calling install.packages() inside every script run (slow, and not needed): install once, library() every session.
Forgetting quotes in install.packages("pkg").
Thinking [1] is part of the output value. It is only an index label.
Using backslashes in Windows paths: "C:\data" is an error. Use /.
Naming objects c, T, df or data: it works but shadows built-ins and confuses readers (and T <- 0 breaks code that uses T for TRUE).
Remember
<- assigns; assignments are not printed; (x <- 1) prints.
?f help on a function, ??word full-text search.
ls(), rm(), getwd(), setwd(), save()/load().
Names: start with letter (or dot not followed by digit), case-sensitive, no reserved words.
install.packages("x") once; library(x) each session; pkg::fun to avoid masking.
A2.2 Basic data types and coercion
Why it matters. Every R value has a type, and R silently converts (coerces) types when you mix them. Most "what does this return?" traps come from coercion.
How it is examined. Closed-form: "what is typeof(...) / class(...)?", "what does c(1, "a", TRUE) give?", "what is sum(c(TRUE, FALSE, TRUE))?" (lesson 2, lab 1).
Explanation
R has six atomic types. In practice you meet four.
typeof()
class()
Examples
JS analogy
"logical"
"logical"
TRUE, FALSE, NA
boolean
"integer"
"integer"
5L, 1:3, length(x)
(none: JS has one number type)
"double"
"numeric"
5, 3.14, 1e-3, Inf
number
"character"
"character"
"a", 'hello'
string
"complex"
"complex"
2+3i
-
"raw"
"raw"
as.raw(255)
Uint8Array byte
Definition: typeof vs class vs mode
typeof(x) is the internal storage type. class(x) is the "object-oriented" type used to choose methods (e.g. "data.frame", "factor", "matrix" "array"). mode(x) is an older classification ("numeric" for both integer and double). Note: 5 is a double; you need 5L for an integer. is.numeric() is TRUE for both integers and doubles.
Special values
NA: missing value ("not available"). It has a type: NA is logical; NA_integer_, NA_real_, NA_character_ exist. Almost any operation with NA gives NA. Test with is.na(x), never x == NA.
NULL: the empty object (length 0, "nothing"). Like JS null/undefined but it disappears inside c(): c(1, NULL, 2) is c(1, 2). Test with is.null(x).
NaN ("not a number", e.g. 0/0), Inf and -Inf (e.g. 1/0). is.na(NaN) is TRUE, but is.nan(NA) is FALSE.
Coercion
Rule: implicit coercion order
An atomic vector holds one type only. When you combine types, everything is converted to the most flexible one: logical < integer < double < complex < character. TRUE becomes 1, FALSE becomes 0; numbers become strings like "1", "2.5"; TRUE becomes "TRUE".
Explicit coercion uses as.logical(), as.integer(), as.numeric() (= as.double()), as.character(). If the conversion is impossible you get NA with a warningNAs introduced by coercion (not an error). as.integer() truncates toward zero (as.integer(3.9) is 3, as.integer(-3.9) is -3). as.logical() accepts "TRUE", "true", "T", "FALSE", "false", "F"; any other string gives NA; numbers: 0 is FALSE, anything else TRUE.
Comparisons also coerce: 1 == "1" is TRUE (the number becomes "1"), and "10" < "9" is TRUE because strings compare character by character. Arithmetic with a string is an error: "1" + 1 gives non-numeric argument to binary operator (unlike JS, where "1" + 1 is "11").
Arithmetic facts
/ always returns a double: 5L / 2L is 2.5. Integer division %/%, remainder %% (both floor-based: -5 %/% 2 is -3, -5 %% 2 is 1; JS -5 % 2 is -1).
[1] "double"
[1] "numeric"
[1] "integer"
[1] "integer"
[1] "integer"
[1] "double"
[1] "1" "a" "TRUE"
[1] 1 1 0
[1] 1.5 2.0
[1] 3
[1] 3.5
[1] 3
[1] NA
Warning message:
NAs introduced by coercion
Note how R prints c(1.5, 2.0) with the same number of decimals for every element, and character vectors with quotes and padded to the same width.
NA + 1; NA > 3; NA == NA
NA & FALSE # FALSE whatever NA is
NA | TRUE # TRUE whatever NA is
c(1, NULL, 3); length(NULL); length(NA)
1/0; -1/0; 0/0
is.na(c(1, NA, NaN))
[1] NA
[1] NA
[1] NA
[1] FALSE
[1] TRUE
[1] 1 3
[1] 0
[1] 1
[1] Inf
[1] -Inf
[1] NaN
[1] FALSE TRUE TRUE
[1] "FALSE" "2" "b"
[1] NA TRUE FALSE NA
[1] FALSE TRUE TRUE
[1] 4
Line 1: the character type wins, the logical becomes "FALSE" (not "0"!) because each element is converted directly to character. Line 2: only the recognised strings convert; "yes" and "0" are NA (a string "0" is not a number for as.logical). Line 4: \(1 + 1 \cdot 3 = 4\).
Strings are compared character by character: "9" is greater than "1...", so max(x) is "9" and the sorted order is "10" "100" "9". After conversion to numbers the maximum is 100. In x == 10 the number 10 is coerced to "10", so only the first element matches.
Self-check quiz
Common mistakes
Believing 5 is an integer. It is a double; write 5L.
Testing missing values with x == NA (always NA). Use is.na(x).
Expecting "1" + 1 to work as in JS. It is an error in R.
Converting a factor or a character with numbers using the wrong function (see A2.6 for factors).
Confusing NULL (nothing, length 0) with NA (a missing value, length 1).
Remember
Types: logical, integer (L), double, character (+ complex, raw).
NA propagates, except NA & FALSE = FALSE and NA | TRUE = TRUE.
/ gives double; %/%, %% are floor division and modulo.
A2.3 Vectors: creation, indexing, recycling, NA
Why it matters. The vector is the basic R data structure: a number is a vector of length 1, a data frame column is a vector. R operations are vectorised: they work on whole vectors at once, without loops.
How it is examined. This is the most frequent source of closed-form questions: indexing (x[-1], x[x > 2], x[0]), seq/rep, recycling, operator precedence of :, and NA handling (lessons 2-3).
Explanation
Creating vectors
Code
Result
c(4, 7, 1)
[1] 4 7 1
2:6, 5:1
[1] 2 3 4 5 6, [1] 5 4 3 2 1 (integer)
seq(1, 10, by = 3)
[1] 1 4 7 10
seq(0, 1, length.out = 5)
[1] 0.00 0.25 0.50 0.75 1.00
rep(c(1, 2), times = 3)
[1] 1 2 1 2 1 2
rep(c(1, 2), each = 2)
[1] 1 1 2 2
rep(c("a", "b"), times = c(3, 1))
[1] "a" "a" "a" "b"
numeric(3), character(2), logical(2)
[1] 0 0 0, [1] "" "", [1] FALSE FALSE
seq_len(3), seq_along(c("x","y"))
[1] 1 2 3, [1] 1 2
Precedence of :
: binds tighter than *, +, - (binary) but looser than ^ and unary minus. So 1:n-1 is (1:n) - 1, 2*1:3 is 2 * (1:3), -2:2 is (-2):2, 1:3^2 is 1:9.
Indexing with [ ] (1-based!)
Kind
Example (x <- c(10, 20, 30, 40))
Result
positive integers
x[2], x[c(1, 3)], x[c(1, 1)]
20; 10 30; 10 10
negative = drop
x[-1], x[-c(1, 2)], x[-length(x)]
20 30 40; 30 40; 10 20 30
logical
x[x > 15], x[c(TRUE, FALSE)]
20 30 40; 10 30 (recycled)
names
v <- c(a = 1, b = 2); v["b"]
named b 2
out of range
x[6]
NA (no error!)
zero
x[0]
numeric(0) (empty)
empty
x[]
all elements
You cannot mix positive and negative indices: x[c(-1, 2)] is an error. Assigning beyond the end extends the vector and fills the gap with NA: x <- 1:3; x[5] <- 9L gives 1 2 3 NA 9. which(cond) returns the positions where a logical vector is TRUE; which.max()/which.min() return the position of the (first) max/min.
Vectorised operations and recycling
Arithmetic and comparison are element-wise: c(1, 2, 3) * 2 is 2 4 6; c(1, 2, 3) + c(10, 20, 30) is 11 22 33. If lengths differ, the shorter vector is recycled (repeated) to the length of the longer one. If the longer length is not a multiple of the shorter, R still computes but gives a warning: longer object length is not a multiple of shorter object length.
JS contrast
In JS, [1,2,3] * 2 is NaN and you need .map(). In R, nearly every function is already vectorised: sqrt(x), x > 2, paste("id", 1:3), ifelse(). Writing a loop where a vectorised call exists is considered bad style.
Useful functions
length, sum, mean, median, var, sd, min, max, range, prod, cumsum, cumprod, diff, rev, sort (values), order (positions that would sort), rank, unique, duplicated, table, %in%, head(x, n), tail(x, n), round, abs, exp, log (natural), log10, log(x, base = 2). Built-in vectors: letters, LETTERS, month.name, pi.
Missing values
Summary functions return NA if any element is NA, unless you set na.rm = TRUE. sum(is.na(x)) counts the missing values; x[!is.na(x)] removes them; mean(is.na(x)) is the proportion missing.
Worked examples
x <- c(3, 8, NA, 5, 1)
length(x)
mean(x)
mean(x, na.rm = TRUE)
x[!is.na(x) & x > 2]
which(x > 2)
sort(x) # NA removed by default
order(c(30, 10, 20))
cumsum(1:4)
1:6 + c(10, 100)
1:5 + 1:2
[1] 5
[1] NA
[1] 4.25
[1] 3 8 5
[1] 1 2 4
[1] 1 3 5 8
[1] 2 3 1
[1] 1 3 6 10
[1] 11 102 13 104 15 106
[1] 2 4 4 6 6
Warning message:
In 1:5 + 1:2 :
longer object length is not a multiple of shorter object length
Note: x[x > 2] on a vector with NA would keep an NA in the result (NA > 2 is NA, and indexing with NA gives NA). which() ignores NAs, so x[which(x > 2)] is a safe alternative.
v <- c(a = 1, b = 2, c = 3)
v * 10
names(v)
v[c("c", "a")]
3 %in% v; c(1, 9) %in% v
a b c
10 20 30
[1] "a" "b" "c"
c a
3 1
[1] TRUE
[1] TRUE FALSE
Exercises
easy
x <- c(5, 2, 9, 4). Give the output of x[2], x[-2], x[x %% 2 == 0], x[5], rev(x).
Show solution
[1] 2
[1] 5 9 4
[1] 2 4
[1] NA
[1] 4 9 2 5
medium
Let n <- 4. What is 1:n-1? 1:(n-1)? -1:2? 2^(0:3)? seq(10, 1, by = -4)?
The logical index y > 1 is TRUE NA TRUE FALSE; an NA index returns NA. which() keeps only the TRUE positions (1 and 3).
hard
Without running: x <- 1:10. What is x[c(TRUE, FALSE, FALSE)]? What is x[x > 3 & x %% 2 == 1]? What is sum(x[-(1:8)])?
Show solution
[1] 1 4 7 10
[1] 5 7 9
[1] 19
The logical index is recycled: TRUE at positions 1, 4, 7, 10. Last: drops elements 1-8, so 9 + 10 = 19.
exam
Write one line (no loop) that, for temps <- c(12.5, NA, 18.0, 25.3, 9.8, NA, 30.1), returns (a) the number of non-missing values above 15, (b) the positions of the missing values, (c) the mean of the non-missing values rounded to 1 decimal.
Using 0-based thinking: x[0] is an empty vector, not the first element.
Thinking x[-1] is the last element (as in Python). In R it removes the first element. Last element: x[length(x)] or tail(x, 1).
Forgetting brackets around arithmetic in ranges: 1:n-1.
Ignoring the recycling warning: it usually means a bug.
Confusing sort(x) (sorted values) and order(x) (indices).
Using 1:length(x) when x may be empty: it gives 1 0. Use seq_along(x).
Remember
Create: c(), :, seq(), rep().
Index: positive (keep), negative (drop), logical (filter), names; out of range gives NA.
Everything is vectorised; shorter vectors are recycled (warning if not a multiple).
na.rm = TRUE in summaries; is.na() to test; which() drops NAs.
A2.4 Matrices and arrays
Why it matters. A matrix is a vector with two dimensions; an array has any number of dimensions. They appear in statistics (design matrices, correlation tables) and are the input of apply().
How it is examined. "Print the matrix created by matrix(1:6, nrow = 2)", "what is m[2, ]?", "what is the class of m[, 1]?", element-wise * versus matrix product %*% (lessons 2-3).
Explanation
matrix(data, nrow, ncol, byrow = FALSE) builds a matrix. Like any atomic vector it has one type only. By default R fills column by column (column-major order); use byrow = TRUE to fill row by row.
m <- matrix(1:6, nrow = 2)
m
matrix(1:6, nrow = 2, byrow = TRUE)
dim(m); nrow(m); ncol(m); length(m)
m[2, 3] is one element (6). m[2, ] is the whole 2nd row, m[, 2] the whole 2nd column. Negative and logical indices work as with vectors.
drop = TRUE (the default): if the result has only one row or one column, R drops the dimension and returns a plain vector. Write m[, 2, drop = FALSE] to keep a 2×1 matrix.
A single index, m[5], treats the matrix as the underlying vector in column-major order: m[5] is 5. m[m > 3] returns a vector of the elements greater than 3.
Rows and columns can have names: dimnames(m), rownames(m), colnames(m); then m["r1", "B"] works.
Building and combining
cbind() binds vectors/matrices as columns, rbind() as rows. dim(x) <- c(2, 3) turns a vector into a matrix. t(m) transposes.
fast row/column summaries (same as apply(m, 1, sum) etc.)
outer(1:3, 1:3)
outer product (multiplication table)
Arrays
array(1:24, dim = c(2, 3, 4)) is a 2×3×4 array (2 rows, 3 columns, 4 "layers"). Index with three positions: a[i, j, k]. Filling is still "first index fastest": element a[i, j, k] is at vector position \(i + (j-1)\cdot 2 + (k-1)\cdot 6\). a[, , 1] is the first layer (a 2×3 matrix). In R ≥ 4.0, class(matrix(1:4, 2)) is "matrix" "array": a matrix is a 2-D array.
Worked examples
m <- matrix(1:6, nrow = 2)
m[2, ]
m[, 2]
m[, 2, drop = FALSE]
m[m > 3]
m %*% t(m)
m * c(10, 100) # recycled down the columns
Check the product: row 1 of m is (1, 3, 5), so \(1\cdot1+3\cdot3+5\cdot5 = 35\); row 1 times row 2: \(1\cdot2 + 3\cdot4 + 5\cdot6 = 44\); row 2 with itself: \(4+16+36 = 56\).
\(A^2\): row 1 = (2·2 + 1·0, 2·1 + 1·3) = (4, 5); row 2 = (0·2 + 3·0, 0·1 + 3·3) = (0, 9). The vector c(1, 1) is treated as a column; the result is a 2×1 matrix.
exam
Write code that builds a 3×3 matrix with the numbers 1 to 9 filled by row, sets every element greater than 6 to 0, and returns the column sums. Give the result.
Rows are (1,2,3), (4,5,6), (0,0,0) after replacement, so column sums are 5, 7, 9. (colSums returns a double vector; values print as whole numbers.)
Self-check quiz
Common mistakes
Assuming row-wise filling (as you would write a nested JS array). R fills by column unless byrow = TRUE.
Using * for matrix multiplication. * is element-wise; use %*%.
Forgetting that m[, 1] is a vector, not a 1-column matrix (drop = TRUE).
Mixing numbers and strings in cbind()/matrix(): everything becomes character. Use a data frame for mixed types.
Remember
matrix(x, nrow, ncol, byrow = FALSE): column-major fill, one type.
m[i, j]; empty index = all; drop = FALSE keeps dimensions.
* element-wise, %*% matrix product, t(), solve().
array(x, dim = c(...)) for 3+ dimensions; first index varies fastest.
A2.5 Lists
Why it matters. A list is R's general container: each element can have a different type and length (like a JS object or array of anything). Data frames are lists, model results (t.test(), lm()) are lists, lapply() returns a list.
How it is examined. The classic closed-form trap: l[1] versus l[[1]] versus l$a, what length() returns, what assigning NULL does (lesson 2).
Explanation
l <- list(a = 1, b = "x", c = 1:3)
str(l)
List of 3
$ a: num 1
$ b: chr "x"
$ c: int [1:3] 1 2 3
Definition: the three extraction operators
l[i] (single brackets) returns a list (a sub-list, "the train with fewer carriages"). l[[i]] returns the element itself ("the contents of one carriage"). l$name is a shortcut for l[["name"]] (no quotes, and it does partial matching of names).
Code
Returns
class()
l[3] or l["c"]
a list of length 1 containing 1:3
"list"
l[[3]], l[["c"]], l$c
the vector 1 2 3
"integer"
l$c[2] = l[["c"]][2]
2
"integer"
l[c(1, 2)]
a list with a and b
"list"
l[[c(3, 2)]]
recursive: l[[3]][[2]] = 2
"integer"
l$zzz
NULL (no error)
"NULL"
l[[5]]
error: subscript out of bounds
-
Modifying
Add or replace: l$d <- TRUE, l[["e"]] <- c("p", "q").
Remove an element: l$a <- NULL (the list gets shorter). To store a NULL as a value, use l["a"] <- list(NULL).
unlist(l) flattens to an atomic vector (with coercion and generated names like c1 c2 c3).
length(l) is the number of top-level elements, names(l) their names.
JS contrast: copy-on-modify
In JS, const b = a; b.x = 9 also changes a (shared reference). In R, b <- a; b$x <- 9 leaves a unchanged: R copies on modification. The same holds when you pass a list to a function and modify it inside: the caller's list does not change. (Environments, A2.7, are the exception.)
Worked examples
l <- list(a = 1, b = "x", c = 1:3)
l["b"]
l[["b"]]
length(l$c)
l$a <- NULL
names(l)
unlist(list(a = 1, b = 2:3))
unlist(list(1, "a", TRUE))
$b
[1] "x"
[1] "x"
[1] 3
[1] "b" "c"
a b1 b2
1 2 3
[1] "1" "a" "TRUE"
x <- list(1:4, "hi", c(TRUE, FALSE)). Give length(x), length(x[[1]]), class(x[2]), class(x[[2]]), x[[3]][2].
Show solution
3, 4, "list", "character", FALSE.
medium
What is printed?
l <- list(n = 10, v = c(2, 4))
l$v[2] <- 40
l$w <- NULL
l[["n"]] <- NULL
length(l)
l
Show solution
[1] 1
$v
[1] 2 40
Setting a non-existing element w to NULL does nothing; setting n to NULL deletes it. Only v is left, modified in place.
medium
Why does sum(l[2]) fail while sum(l[[2]]) works, with l <- list("a", 1:3)?
Show solution
l[2] is a list containing the vector, and sum() does not accept a list: error invalid 'type' (list) of argument. l[[2]] extracts the integer vector, so the sum is 6.
exam
Given exams <- list(r = c(24, 27), py = c(30), dm = c(18, 22, 26)), write one line that returns the number of grades per subject and one line that returns the overall mean of all grades. Give the results.
Show solution
sapply(exams, length)
mean(unlist(exams))
r py dm
2 1 3
[1] 24.5
All grades: 24, 27, 30, 18, 22, 26, sum 147, mean \(147/6 = 24.5\). (lengths(exams) also gives the counts.)
Self-check quiz
Common mistakes
Using [ ] when you want the element: l[1] is still a list.
Expecting an error from l$missing: it silently returns NULL (typos go unnoticed).
Expecting l$x <- NULL to store NULL: it removes x.
Expecting reference semantics as in JS objects.
Remember
list() holds anything; str() shows its structure.
[ ] gives a sub-list, [[ ]] and $ give the element.
NULL assignment deletes; unlist() flattens with coercion.
A2.6 Data frames and factors
Why it matters. The data frame is the table you analyse: rows are observations, columns are variables (each column one type). Factors are R's type for categorical variables. The lab datasets (birds_aircraft, diabetes) are read into data frames.
How it is examined. Code exercises like "select the patients older than 50 and compute the mean glucose by group", and closed-form questions about df[ , ] indexing, str() output, drop, and factor levels/codes (lessons 2-4, labs 1-2).
Explanation
Definition: data frame
A list of vectors of equal length, shown as a table. Because it is a list, length(df) is the number of columns, and df$col/df[["col"]] extract a column. Because it is a table, df[i, j] works like a matrix.
name age city
1 Ann 28 MI
2 Bob 35 RM
3 Cy 42 MI
4 Dan 19 TO
'data.frame': 4 obs. of 3 variables:
$ name: chr "Ann" "Bob" "Cy" "Dan"
$ age : num 28 35 42 19
$ city: chr "MI" "RM" "MI" "TO"
[1] 4 3
[1] 4
[1] 3
[1] 3
[1] "name" "age" "city"
Since R 4.0.0 stringsAsFactors = FALSE is the default, so text columns stay character. (Older code and slides may show factors here.)
Indexing
Code
Result
df$age, df[["age"]], df[, "age"], df[, 2]
a vector (drop = TRUE for a single column)
df["age"], df[2], df[, 2, drop = FALSE]
a data frame with 1 column
df[2, ]
a data frame with 1 row (rows are never dropped to a vector)
df[df$age > 30, ]
rows where age > 30 (note the comma!)
df[df$city == "MI", c("name", "age")]
filter rows and select columns
subset(df, age > 30, select = c(name, age))
same, with column names used directly
df[order(df$age), ], df[order(-df$age), ]
sort ascending / descending
Add a column: df$senior <- df$age >= 35. Remove: df$senior <- NULL. Add rows: rbind(df, data.frame(name = "Eve", age = 50, city = "RM")). Join two data frames on a key: merge(df1, df2, by = "id") (inner join; all.x = TRUE for a left join). Explore: head(df), tail(df), summary(df), View(df), colMeans(df[, num_cols]), table(df$city).
Group summaries in base R: aggregate(age ~ city, data = df, FUN = mean), or tapply(df$age, df$city, mean) (A2.9).
Factors
Definition: factor
A factor stores a categorical variable as integer codes plus a character vector of levels. By default the levels are the sorted unique values. typeof(f) is "integer", class(f) is "factor".
[1] low high med low
Levels: high low med
[1] "high" "low" "med"
[1] 2 1 3 2
[1] 3
[1] 1 3 2 1
f2
low med high
2 1 1
Assigning a value that is not a level gives NA with a warning (invalid factor level, NA generated).
factor(x, levels = ..., labels = ...) renames levels; ordered = TRUE (or ordered()) makes an ordinal factor where < works.
droplevels(f) removes unused levels (e.g. after filtering). Unused levels still appear in table() with count 0.
Trap:as.numeric(factor(c("10", "20", "10"))) returns the codes1 2 1. To get the numbers back: as.numeric(as.character(f)) or as.numeric(levels(f))[f].
cut(x, breaks) turns numbers into a factor of intervals; relevel(f, ref = "med") changes the first (reference) level.
Worked examples
df[df$age > 30, ]
df[df$age > 30, "name"]
df[2, ]
mean(df$age[df$city == "MI"])
aggregate(age ~ city, data = df, FUN = mean)
table(df$city)
name age city
2 Bob 35 RM
3 Cy 42 MI
[1] "Bob" "Cy"
name age city
2 Bob 35 RM
[1] 35
city age
1 MI 35
2 RM 35
3 TO 19
MI RM TO
2 1 1
Notice that filtered rows keep their original row names (2, 3). table() of an unnamed vector prints an empty first line (where a variable name would be).
Exercises
easy
With df above, give the class of: df$city, df["city"], df[, 3], df[3, ]. What is length(df)?
Show solution
"character", "data.frame", "character", "data.frame". length(df) is 3 (number of columns, because a data frame is a list of columns).
medium
Write base-R code that returns the names of people from MI sorted by age descending, and code that adds a column decade equal to the age rounded down to a multiple of 10.
[1] 2 1 3 2
[1] "M"
Warning message:
In `[<-.factor`(`*tmp*`, 2, value = "XL") :
invalid factor level, NA generated
[1] M <NA> L M
Levels: S M L
The codes follow the given level order S=1, M=2, L=3. "XL" is not a level, so element 2 becomes NA (printed <NA> inside factors). The warning text is shown for completeness; on the exam "NA with a warning" is enough.
hard
v <- factor(c("5", "10", "5", "20")). Give levels(v), as.numeric(v), as.numeric(as.character(v)), and sum(as.numeric(v)).
Show solution
Levels are sorted as strings: "10" "20" "5". Codes: 3 1 3 2. Real numbers: 5 10 5 20. sum(as.numeric(v)) is \(3+1+3+2 = 9\), a silent bug (the right sum is 40).
exam
A data frame diab has columns age (numeric), glucose (numeric, with some NA) and outcome (0/1). Write base-R code to: (a) count missing glucose values; (b) compute the mean glucose of patients over 50, ignoring NA; (c) compute mean glucose by outcome; (d) convert outcome to a factor with labels "neg" and "pos".
Marking idea: correct logical indexing with $, na.rm = TRUE in the right place (extra arguments of tapply are passed to mean), and levels/labels matched in order.
Self-check quiz
Common mistakes
Forgetting the comma: df[df$age > 30] selects columns with a logical vector, which is wrong (and errors if lengths differ). Write df[df$age > 30, ].
Writing df[age > 30, ] without df$: age is not a variable in the workspace (only subset() and dplyr let you omit it).
Expecting df[, "x"] to be a data frame (it is a vector, drop = TRUE).
Converting a numeric factor with as.numeric() directly.
Assuming factor levels follow the order of appearance: they are sorted alphabetically unless you give levels.
Remember
Data frame = list of equal-length columns; length(df) = number of columns.
df[rows, cols]; single column drops to vector; df["col"] keeps a data frame.
Filter df[cond, ], sort df[order(x), ], group aggregate()/tapply().
Factor = integer codes + levels (sorted by default). as.numeric(as.character(f)).
A2.7 Environments and scoping
Why it matters. The official syllabus lists environments among the data structures. An environment is where R looks up names; it explains how functions find variables, why <<- exists, and how packages avoid name clashes.
How it is examined. Closed-form questions on scoping ("what does f() return when x is defined both globally and inside?"), on <<-, and on the fact that environments are modified in place.
Explanation
Definition: environment
An environment is a collection of name → value bindings (a "frame") plus a pointer to a parent (enclosing) environment. When R looks up a name, it searches the current environment, then its parent, then the parent's parent, ... up to the empty environment. If the name is not found: Error: object 'x' not found.
globalenv() (printed <environment: R_GlobalEnv>) is the workspace. Its parents are the attached packages (see search()), then base, then the empty environment.
Every function call creates a new, temporary environment for its arguments and local variables. Its parent is the environment where the function was defined (lexical scoping), not where it was called. This is the same rule as JS closures.
Reference semantics: unlike lists, environments are not copied. If two names point to the same environment, or a function modifies an environment passed as argument, everyone sees the change (like JS objects).
Definition: <<- (super-assignment)
x <<- value does not create a local variable. It searches the parent environments for an existing x and modifies the first one it finds; if none exists, it creates x in the global environment. Normal <- inside a function always creates/modifies a local variable.
JS contrast
R has no let/const and no block scope: a variable created inside if or for braces at top level is a global variable, and the loop variable still exists after the loop. Only functions create new scopes.
Worked examples
x <- 1
f <- function() { x <- 2; g() }
g <- function() x # g was defined globally, so it sees the global x
f()
h <- function() { x <- 2; k <- function() x; k() }
h() # k was defined inside h, so it sees h's x
(The calls themselves print nothing: an assignment returns its value invisibly.)
e <- new.env()
e$v <- 1
set_v <- function(env) env$v <- 99
set_v(e)
e$v # 99: environments are modified in place
l <- list(v = 1)
set_l <- function(lst) lst$v <- 99
set_l(l)
l$v # 1: the list was copied inside the function
assign("w", 5, envir = e)
sort(ls(e)); get("w", envir = e)
exists("w"); exists("w", envir = e)
[1] 99
[1] 1
[1] "v" "w"
[1] 5
[1] FALSE
[1] TRUE
(exists("w") is FALSE assuming there is no global w.)
Exercises
easy
What is printed?
a <- 10
f <- function(a) { a <- a * 2; a }
f(3)
a
Show solution
[1] 6
[1] 10
The argument a is local to the call; the global a is untouched.
medium
What is printed?
for (i in 1:3) { tmp <- i * 10 }
i
tmp
if (TRUE) { inside <- "yes" }
inside
Show solution
[1] 3
[1] 30
[1] "yes"
No block scope: loop variable and variables created in braces at top level are global.
hard
What is printed?
y <- 1
outer_f <- function() {
y <- 5
inner_f <- function() y <<- y + 1
inner_f()
y
}
outer_f()
y
Show solution
[1] 6
[1] 1
<<- inside inner_f finds y in the enclosing environment (the call of outer_f, where y = 5) and sets it to 6. The global y is not reached.
exam
Write a function make_account(balance) that returns a list of two functions, deposit(x) and get(), sharing the same balance. Show that two accounts are independent.
Each call of make_account creates its own environment holding balance; both inner functions are defined there, so they share it (a closure).
Self-check quiz
Common mistakes
Thinking a function sees the variables of its caller (dynamic scoping). R uses the environment where the function was defined.
Using <- inside a function and expecting the global variable to change.
Overusing <<-: functions that change globals are hard to debug. Prefer returning values.
Expecting block scope as in JS let.
Remember
Environment = bindings + parent; lookup goes up the parents.
Lexical scoping: parent of a call = environment of the function's definition.
<- local, <<- modifies an existing variable in a parent (or creates a global).
Environments have reference semantics; lists and vectors are copied on modify.
A2.8 Control structures, logical operators and split()
Why it matters. Lesson 3 material: decisions in scripts and functions, vectorised conditions, and splitting data by groups.
How it is examined. "What does ifelse(...) return?", "& versus &&", "what does switch() return for an unmatched value?", "what is the output of split()?"; plus code exercises that classify values.
Explanation
if / else
if (x > 0) {
"positive"
} else if (x == 0) {
"zero"
} else {
"negative"
}
The condition must be oneTRUE/FALSE. A condition of length > 1 is an error (since R 4.2: the condition has length > 1); an NA condition is an error (missing value where TRUE/FALSE needed).
if is an expression and returns a value: y <- if (x > 0) "pos" else "neg" (like the JS ternary). If the condition is FALSE and there is no else, the value is NULL (invisible).
At top level, else must be on the same line as the closing }; otherwise R finishes the if at the end of the line and else alone is a syntax error.
Logical operators
Operator
Works on
Use
&, |, !
vectors, element-wise
filtering: x[x > 1 & x < 5]
&&, ||
single values only, short-circuit
inside if(): if (!is.null(x) && x > 0)
xor(a, b)
vectors
exclusive or
any(), all()
a logical vector → one value
if (any(is.na(x)))
isTRUE(x), isFALSE(x)
anything
TRUE only for a single non-NA TRUE
which()
logical vector
positions of TRUE
Since R 4.3, &&/|| with a vector of length > 1 is an error. Edge cases: all(logical(0)) is TRUE, any(logical(0)) is FALSE; any(c(NA, TRUE)) is TRUE but any(c(NA, FALSE)) is NA.
Vectorised conditional: ifelse()
ifelse(test, yes, no) works element-wise on a whole vector (the "special conditional operator" of lesson 3). Where test is NA the result is NA. Nest it for 3 classes, or use cut() or dplyr::case_when().
switch()
switch(type,
mean = mean(x),
median = median(x),
stop("unknown type")) # unnamed last argument = default
With a character first argument, R returns the value of the matching name. An empty argument falls through to the next one: switch("a", a = , b = "A or B") gives "A or B". Without a match and without a default, switch returns NULL invisibly. With a numbern, it returns the n-th argument: switch(2, "x", "y", "z") is "y".
Stopping with errors
stop("message") raises an error, warning("message") a warning, stopifnot(is.numeric(x), length(x) > 0) checks conditions (A2.16).
split()
split(x, f) divides a vector or data frame x into a named list of groups defined by f (converted to a factor, so groups come out in level order). unsplit() reverses it. Typical pattern (split-apply-combine): sapply(split(df$value, df$group), mean).
Worked examples
x <- c(-2, 0, 3, NA, 7)
ifelse(x > 0, "pos", "non-pos")
ifelse(is.na(x), 0, x)
x > 0 & x < 5
any(x > 5); all(x > -5); all(x > -5, na.rm = TRUE)
which(x > 0)
switch("b", a = "one", b = "two", "other")
switch("z", a = "one", b = "two", "other")
What does ifelse(c(4, 9, 1, 6) %% 2 == 0, "even", "odd") return? And ifelse(c(1, NA, 3) > 2, 1, 0)?
Show solution
[1] "even" "odd" "odd" "even"
[1] 0 NA 1
medium
For v <- c(2, 5), say what happens: (a) if (v > 3) "big"; (b) v > 3 & v < 10; (c) v > 3 && v < 10; (d) if (any(v > 3)) "some big".
Show solution
(a) error: the condition has length > 1 (R ≥ 4.2; older R used only the first element with a warning). (b) [1] FALSE TRUE. (c) error in R ≥ 4.3 ('length = 2' in coercion to 'logical(1)'). (d) [1] "some big".
medium
What is printed?
f <- function(d) switch(d, sat = , sun = "weekend", "weekday")
f("sat"); f("mon"); f("sun")
r <- if (3 > 5) "yes"
is.null(r)
sat has an empty value, so it falls through to sun. An if without else whose condition is FALSE returns NULL.
hard
df <- data.frame(team = c("A", "B", "A", "C", "B"), pts = c(3, 1, 0, 3, 3)). What do names(split(df$pts, df$team)) and sapply(split(df$pts, df$team), mean) return? What is class(split(df, df$team)$A)?
Show solution
[1] "A" "B" "C"
A B C
1.5 2.0 3.0
A: (3+0)/2 = 1.5; B: (1+3)/2 = 2; C: 3. Splitting a data frame gives a list of data frames, so the class is "data.frame".
exam
Write a function bmi_class(w, h) that is vectorised (works on vectors of weights in kg and heights in m) and returns "under" if BMI < 18.5, "normal" if 18.5 ≤ BMI < 25, "over" otherwise, with BMI = w / h². What does bmi_class(c(50, 70, 90), c(1.80, 1.75, 1.70)) return?
BMIs: 50/3.24 = 15.43, 70/3.0625 = 22.86, 90/2.89 = 31.14. A version with if would fail on vectors; ifelse (or cut(bmi, c(-Inf, 18.5, 25, Inf), right = FALSE, labels = ...)) is required.
Self-check quiz
Common mistakes
Using if on a vector instead of ifelse().
Using && for element-wise filtering, or & inside if with vectors.
Putting else on a new line at top level.
Forgetting that == with NA gives NA, which breaks if.
Expecting split() groups in order of first appearance: they come in sorted (factor level) order.
Remember
if needs one non-NA logical; it returns a value.
&| vectorised; &&|| scalar, short-circuit.
ifelse() for vectors; switch() for many named cases (fall-through, default).
any, all, which; split(x, g) → named list of groups.
A2.9 Loops and loop functionals (apply, lapply, sapply, tapply, mapply)
Why it matters. Loops repeat work; the apply family ("loop functionals", lesson 4) does the same in one call and returns the results collected in a vector, matrix or list. They are the R version of JS map/reduce/groupBy.
How it is examined. "What does this loop print / what is x after the loop?", "what does apply(m, 1, sum) return?", "is the result of sapply a vector, a matrix or a list?", and code exercises: "rewrite this loop with sapply" or "compute group means with tapply".
Explanation
Loops
for (i in 1:3) { ... } # i takes each value of the vector (like JS for...of)
for (name in c("a", "b")) { ... }
while (cond) { ... } # repeat while cond is TRUE
repeat { ...; if (done) break } # infinite loop, exit with break
next # skip to next iteration (JS continue)
Inside a loop (or a function), values are not auto-printed: for (i in 1:3) i prints nothing. Use print(i) or cat(i, "\n").
Loop over positions with seq_along(x) (safe for empty x; 1:length(x) gives 1 0 when x is empty).
Pre-allocate the result (res <- numeric(n), vector("list", n)) and fill res[i], instead of growing with res <- c(res, val) (slow: copies every time).
A for loop changes the loop variable for good: after for (i in 1:3), i is 3. Modifying the sequence inside the loop does not change the iterations.
The apply family
Function
Input
Output
JS idea
apply(X, MARGIN, FUN)
matrix/array (or data frame, converted to matrix); MARGIN = 1 rows, 2 columns, c(1, 2) cells
vector, or matrix if FUN returns length > 1 (results become columns)
rows.map(f)
lapply(X, FUN)
vector or list
always a list of the same length
arr.map(f)
sapply(X, FUN)
vector or list
simplified: vector if every result has length 1; matrix if all have the same length > 1; otherwise a list
arr.map(f)
vapply(X, FUN, FUN.VALUE)
vector or list
like sapply but with a declared result type (safer)
typed map
tapply(X, INDEX, FUN)
a vector + a grouping vector
one result per group (named)
groupBy + reduce
mapply(FUN, x, y, ...) / Map()
several vectors in parallel
simplified like sapply / list
zip + map
Reduce(f, x), Filter(f, x)
vector or list
a single accumulated value / the elements where f is TRUE
reduce, filter
Extra arguments after FUN are passed to it: sapply(lst, mean, na.rm = TRUE). Applied to a data frame, lapply/sapply iterate over the columns (a data frame is a list of columns). Anonymous functions: function(x) x^2, or the short form \(x) x^2 (R ≥ 4.1), like a JS arrow function.
Worked examples
total <- 0
for (i in 1:5) {
if (i %% 2 == 0) next
total <- total + i
}
total
n <- 1
while (n < 100) n <- n * 3
n
i <- 0
repeat { i <- i + 1; if (i^2 > 50) break }
i
[1] 9
[1] 243
[1] 8
m <- matrix(1:6, nrow = 2)
apply(m, 1, sum)
apply(m, 2, max)
apply(m, 1, function(r) r * 2) # note: result is transposed (3 x 2)
The last call cannot be simplified (results of lengths 1, 2, 3), so sapply returns a list. With a character input, sapply uses the strings as names (USE.NAMES = TRUE).
N S
90 60
N S
3 2
[1] 4 10 18
[[1]]
[1] 1 1 1
[[2]]
[1] 2 2
[[3]]
[1] 3
[1] 1 3 6 10
[1] 5 3
Exercises
easy
What is x after this code? x <- 0; for (k in c(2, 5, 1)) x <- x * 10 + k
Show solution
0 → 2 → 25 → 251. x is 251.
medium
Write a loop with pre-allocation that stores the first 10 Fibonacci numbers (1, 1, 2, ...) in fib. Give the printed result.
Show solution
fib <- numeric(10)
fib[1:2] <- 1
for (i in 3:10) fib[i] <- fib[i - 1] + fib[i - 2]
fib
[1] 1 1 2 3 5 8 13 21 34 55
With 10 or more elements R reserves room for the label [10], so the first label is printed as [1] with a leading space (try 1:10).
medium
For each call say whether the result is a vector, a matrix or a list, and its dimensions/length: (a) sapply(1:4, function(i) i %% 2 == 0); (b) sapply(1:4, function(i) c(min = i, max = i * 2)); (c) lapply(1:4, sqrt); (d) sapply(1:4, function(i) rep(i, i)); (e) apply(matrix(1:12, 3), 2, range).
Show solution
(a) logical vector of length 4: FALSE TRUE FALSE TRUE. (b) a 2×4 matrix with row names min, max. (c) a list of length 4. (d) a list of length 4 (different lengths). (e) a 2×4 matrix (each column's min and max become a column).
hard
What is printed?
df <- data.frame(a = c(1, 2, 3), b = c(10, 20, 30))
sapply(df, mean)
apply(df, 1, sum)
for (i in 1:3) i * 2
res <- for (i in 1:3) i * 2
is.null(res)
Show solution
a b
2 20
[1] 11 22 33
[1] TRUE
sapply runs on the columns; apply(df, 1, sum) sums each row. The loop prints nothing, and a for loop returns NULL (invisibly).
exam
A data frame birds has columns species (character), height (numeric, may contain NA) and damage (0/1). (a) With tapply, compute the mean height per species ignoring missing values. (b) With sapply, count the missing values in every column. (c) Rewrite (a) as a for loop over the species.
Show solution
# (a)
tapply(birds$height, birds$species, mean, na.rm = TRUE)
# (b)
sapply(birds, function(col) sum(is.na(col)))
# (c)
sp <- sort(unique(birds$species))
res <- numeric(length(sp)); names(res) <- sp
for (s in sp) {
res[s] <- mean(birds$height[birds$species == s], na.rm = TRUE)
}
res
Marking idea: correct grouping vector in tapply; na.rm = TRUE passed through; anonymous function in sapply; pre-allocated, named result in the loop.
Self-check quiz
Common mistakes
Expecting a loop body to print values automatically.
Forgetting that apply(m, 1, f) with a vector-valued f returns the transpose (results as columns).
Assuming sapply always returns a vector: it can return a matrix or a list (and list() for an empty input). Use vapply to be sure.
Using apply on a data frame with mixed types: it is converted to a character matrix first.
Swapping arguments in tapply(X, INDEX, FUN): values first, groups second.
Remember
for (x in v), while, repeat + break, next; pre-allocate.
tapply(values, groups, FUN) → one value per group. mapply → parallel over several vectors.
Extra arguments after FUN go to FUN (na.rm = TRUE).
A2.10 User-defined functions
Why it matters. Lessons 4 and labs 1-2 ("functions and technicalities"). Almost every code exercise asks you to write a small function; closed-form questions test default arguments, return values and lazy evaluation.
How it is examined. "Write a function that ... and call it on ...", "what does f(2) return?", "what happens if an argument is never used?".
Explanation
name <- function(arg1, arg2 = default) {
# body
result # the value of the LAST expression is returned
}
A function is an object like any other (first-class, as in JS): you can assign it, pass it to sapply, return it from another function.
Return value: the last evaluated expression. return(x) exits early. To return several things, return a list (or a named vector). If the last expression is an assignment, the value is returned invisibly (not printed at the console). invisible(x) does the same on purpose.
Argument matching, in this order: exact name, then partial name (prefix), then position. power(p = 3, x = 2) works in any order. Arguments after ... can only be matched by full name.
Defaults can refer to other arguments (function(x, n = length(x))). They are evaluated inside the function, at the moment they are first used.
missing(arg) tells you if the caller supplied an argument.
... (dots) collects any number of extra arguments; forward them (plot(x, ...)) or capture them (list(...), ...length()). Like JS rest/spread (...args).
Definition: lazy evaluation
R evaluates an argument only when the function body first uses it (it is passed as a "promise"). An argument that is never used is never evaluated, so even stop() passed to it does nothing. A missing argument without default only causes an error when used: argument "y" is missing, with no default.
Definition: closure
A function created inside another function keeps access to the environment where it was created (A2.7). R calls every user function a "closure" (typeof(f) is "closure"). Function factories such as make_power(p) use this.
Good practice
Check inputs at the start (stopifnot(is.numeric(x)) or if (...) stop("...")), return instead of printing, avoid modifying global variables, write vectorised bodies so the function works on whole vectors.
Worked examples
power <- function(x, p = 2) x^p
power(3); power(3, 3); power(p = 3, x = 2); power(1:4)
f <- function(x, y) if (missing(y)) "no y" else x + y
f(1); f(1, 2)
g <- function(a, b) a * 2
g(5, stop("never evaluated"))
h <- function(x, y = x * 2) { x <- 10; y }
h(1)
The body is vectorised, so no loop is needed. (Naming the argument c works but shadows the function c() inside; a better name is temp.)
medium
What is printed?
f <- function(x) { x[1] <- 100; x }
v <- 1:3
f(v)
v
k <- function(n = 5, step) n * 2
k()
k(step = stop("boom"))
Show solution
[1] 100 2 3
[1] 1 2 3
[1] 10
[1] 10
The function modifies a local copy. step is never used, so its absence (or the stop) is never evaluated.
medium
What does s(4) return, and what does s(-1) return?
s <- function(x) {
if (x < 0) return("negative")
out <- sqrt(x)
paste("root:", out)
}
Show solution
[1] "root: 2"
[1] "negative"
hard
What is printed?
adders <- lapply(1:3, function(i) function(x) x + i)
adders[[2]](10)
compose <- function(f, g) function(x) g(f(x))
h <- compose(sqrt, function(z) z * 10)
h(16)
Show solution
[1] 12
[1] 40
Each inner function remembers its own i (since R 3.2.0 lapply forces the argument, so adders[[2]] has i = 2). h(16) = 10 * sqrt(16) = 40.
exam
Write summary_stats(x, na.rm = TRUE) that stops with an error if x is not numeric and otherwise returns a named numeric vector with n (number of non-missing values), mean and sd. Show the call on c(2, 4, 4, 4, 5, 5, 7, 9, NA).
Show solution
summary_stats <- function(x, na.rm = TRUE) {
if (!is.numeric(x)) stop("x must be numeric")
if (na.rm) x <- x[!is.na(x)]
c(n = length(x), mean = mean(x), sd = sd(x))
}
summary_stats(c(2, 4, 4, 4, 5, 5, 7, 9, NA))
n mean sd
8.00000 5.00000 2.13809
All elements of a numeric vector share one format, so 8 and 5 are printed with 5 decimals like the sd \( \sqrt{32/7} \approx 2.13809\). Marking idea: input check, NA handling, named return value, no printing inside the function.
Self-check quiz
Common mistakes
Printing the result inside the function (print(res)) instead of returning it.
Ending the body with an assignment and wondering why nothing is shown.
Writing return x (JS style). In R, return is a function: return(x).
Expecting a function to change its argument in the caller (copy-on-modify).
Forgetting that defaults are evaluated lazily, inside the function.
Remember
f <- function(x, y = 1) { ...; value }; last value is returned.
Match by name, partial name, then position; ... collects extras.
Lazy evaluation: unused arguments are never evaluated; defaults evaluated at first use.
Closures remember their defining environment; return a list for multiple outputs.
A2.11 Importing and exporting data, data from the web, quantmod
Why it matters. Every lab starts by reading a CSV (birds_aircraft_database.csv, diabetes_dataset.csv). Lesson 4 covered CSV and other formats, reading from a URL, and downloading financial series with quantmod.
How it is examined. Writing the import line with the right arguments (header, sep, na.strings), knowing what write.csv writes (row names!), and the difference between .csv, .rds and .RData.
Explanation
Text files
Function
Defaults
Use for
read.csv(file)
header = TRUE, sep = ",", dec = "."
standard CSV
read.csv2(file)
sep = ";", dec = ","
Italian/European Excel CSV (3,5 as decimal)
read.table(file, header = FALSE, sep = "")
whitespace separated, no header by default
generic text tables, sep = "\t" for TSV
readr::read_csv(file)
tidyverse; returns a tibble, faster, prints column types
tidyverse workflow
readxl::read_excel("f.xlsx", sheet = 1)
package readxl
Excel files
Important arguments: na.strings = c("", "NA", "?") (which strings mean missing), stringsAsFactors (default FALSE since R 4.0), skip (lines to skip), nrows, colClasses, encoding/fileEncoding. The file can be a path relative to the working directory, an absolute path, or a URL: read.csv("https://.../data.csv") reads directly from the web. download.file(url, destfile) saves a copy first. file.choose() opens a dialog.
After reading, always inspect: str(df), head(df), summary(df), dim(df), colSums(is.na(df)).
Writing
write.csv(df, "out.csv", row.names = FALSE). By default row.names = TRUE: the row names (1, 2, 3...) are written as an extra first column with an empty header, which comes back as a column named X when you re-read with read.csv.
you choose the name when reading; keeps types, factors, attributes
.RData/.rda (several objects)
save(a, b, file = "f.RData"), save.image()
load("f.RData")
restores objects with their original names (can overwrite existing ones)
Other formats via packages: haven (SPSS, Stata, SAS), jsonlite::fromJSON(), DBI for databases.
Financial data from the web: quantmod
library(quantmod)
getSymbols("AAPL", src = "yahoo", from = "2024-01-01", to = "2024-12-31")
# creates an object called AAPL in the workspace (auto.assign = TRUE)
class(AAPL) # "xts" "zoo": a time series indexed by date
head(AAPL) # columns AAPL.Open AAPL.High AAPL.Low AAPL.Close AAPL.Volume AAPL.Adjusted
close <- Cl(AAPL) # also Op(), Hi(), Lo(), Vo(), Ad()
ret <- dailyReturn(AAPL)
chartSeries(AAPL) # financial chart
x <- getSymbols("MSFT", auto.assign = FALSE) # return the data instead
getSymbols() itself returns the name of the symbol ("AAPL") and, as a side effect, creates the object. The data requires an internet connection; sources (Yahoo, FRED: src = "FRED") may change, so the exact columns can differ check against the lesson 4 code.
Worked examples
df <- data.frame(id = 1:2, score = c(7.5, 8))
write.csv(df, "scores.csv") # row names written too
back <- read.csv("scores.csv")
names(back)
write.csv(df, "scores.csv", row.names = FALSE)
names(read.csv("scores.csv"))
saveRDS(df, "scores.rds")
same <- readRDS("scores.rds")
identical(df, same)
[1] "X" "id" "score"
[1] "id" "score"
[1] TRUE
The file written by the first write.csv looks like this:
"","id","score"
"1",1,7.5
"2",2,8
# Lab-style import (file in the working directory, "?" used for missing values)
diab <- read.csv("diabetes_dataset.csv", na.strings = c("", "NA", "?"))
str(diab)
colSums(is.na(diab))
Exercises
easy
Write the line to read sales.csv, where values are separated by semicolons and decimals use a comma (12,50).
A colleague runs write.csv(df, "a.csv") then df2 <- read.csv("a.csv"), with df having 3 columns. What is ncol(df2)? How do you fix it on writing and on reading?
Show solution
ncol(df2) is 4 (the extra column X with the old row names). Fix when writing: row.names = FALSE. Fix when reading: read.csv("a.csv", row.names = 1) (use the first column as row names) or drop it: df2$X <- NULL.
medium
What is the difference between these two scripts, in terms of which objects exist afterwards?
# A
save(df, file = "d.RData"); rm(df); load("d.RData")
# B
saveRDS(df, "d.rds"); rm(df); data2 <- readRDS("d.rds")
Show solution
After A, an object named df exists again (load restores original names). After B, the data is in data2 and there is no df.
exam
Write code that: downloads https://example.org/birds.csv directly into a data frame (empty strings are missing), keeps only rows where SPECIES is not missing, and saves the result as birds_clean.csv without row names and as birds_clean.rds.
File not found: the file is not in the working directory. Check getwd() and list.files().
Using read.csv on a semicolon file: you get one single column. Use read.csv2.
Missing values coded as "?" or "" make a numeric column character. Use na.strings.
Forgetting row.names = FALSE in write.csv.
Writing x <- load("f.RData"): load returns only the names of the loaded objects.
Remember
read.csv (comma), read.csv2 (semicolon, decimal comma), read.table; URLs work as file names.
write.csv(df, file, row.names = FALSE).
saveRDS/readRDS one object; save/load many objects with names.
quantmod::getSymbols("TICKER") creates an xts object named after the ticker.
A2.12 Base graphics: plot(), layers, layouts, histograms, statistical plots
Why it matters. Lesson 5 and lab 2: quick plots to explore data (time series of production, distributions of diabetes variables).
How it is examined. You cannot draw on a written exam, so questions are about code: "which function adds a line to an existing plot?", "what does par(mfrow = c(2, 3)) do?", "complete the code to draw a histogram with a density curve", "what does type = "l" mean?".
Explanation
Definition: high-level vs low-level plotting functions
A high-level function opens a new plot (axes, scales): plot(), hist(), boxplot(), barplot(), pairs(), qqnorm(), curve(). A low-level function adds to the current plot: points(), lines(), abline(), text(), legend(), title(), axis(), qqline(), rug(). Calling a low-level function with no open plot gives the error plot.new has not been called yet. Base graphics work like painting: you cannot remove something once drawn, only redraw.
colour: name ("red"), number, rgb(), hex "#1f77b4"
pch
point symbol (0-25; 16 = filled circle, 19 = solid circle)
cex
size multiplier of points/text
lty, lwd
line type (1 solid, 2 dashed, 3 dotted) and width
log = "y"
logarithmic axis
plot() is generic: plot(x, y) scatterplot; plot(y) values against index 1..n; plot(y ~ x, data = df) formula interface; plot(df) for a numeric data frame gives a scatterplot matrix; plot(factor) gives a bar chart; plot(ts_object) a time-series line.
Adding layers
points(x, y, col = "red"), lines(x, y, lty = 2)
abline(h = 0) horizontal, abline(v = 5) vertical, abline(a = 1, b = 2) line \(y = a + bx\), abline(lm(y ~ x)) regression line
text(x, y, labels), legend("topright", legend = c("A", "B"), col = c("black", "red"), lty = 1)
Multi-plot layouts
par() sets graphical parameters. par(mfrow = c(2, 3)) splits the device into 2 rows × 3 columns, filled row by row (mfcol fills column by column). Settings persist until changed, so save and restore: old <- par(mfrow = c(1, 2)); ...; par(old). layout(matrix(...)) allows unequal panels. Other par options: mar (margins), bg, las (axis label orientation).
Distributions and classical statistical plots
Code
Shows
hist(x, breaks = 20, main = "", col = "grey")
histogram of counts; breaks = suggested number of bins or a vector of break points
hist(x, freq = FALSE) or probability = TRUE
density scale (total area 1), needed before adding a density curve
lines(density(x))
kernel density estimate on top (density() computes, lines() draws; plot(density(x)) alone also works)
curve(dnorm(x, mean(v), sd(v)), add = TRUE)
overlay a theoretical normal density
boxplot(x), boxplot(y ~ group, data = df)
median, quartiles, whiskers (1.5 IQR), outliers; one box per group
barplot(table(f))
bar chart of counts of a categorical variable (barplot needs the counts, not raw data)
pie(table(f))
pie chart (rarely recommended)
qqnorm(x); qqline(x)
normal Q-Q plot to check normality
pairs(df[, 1:4])
scatterplot matrix
Saving to a file: open a device, plot, close it: png("plot.png", width = 800, height = 600); plot(x); dev.off() (also pdf(), jpeg()).
Worked examples
x <- 1:10
y <- x^2
plot(x, y, type = "b", pch = 19, col = "blue",
main = "Square vs cube", xlab = "x", ylab = "value")
lines(x, x^1.5, lty = 2, col = "red")
abline(h = 50, col = "grey")
legend("topleft", legend = c("x^2", "x^1.5"),
col = c("blue", "red"), lty = c(1, 2), pch = c(19, NA))
set.seed(1)
v <- rnorm(200, mean = 10, sd = 2)
old <- par(mfrow = c(1, 3)) # 1 row, 3 panels
hist(v, freq = FALSE, breaks = 15, main = "Histogram + density")
lines(density(v), lwd = 2)
curve(dnorm(x, 10, 2), add = TRUE, lty = 2)
boxplot(len ~ supp, data = ToothGrowth, main = "Boxplot by group")
qqnorm(v); qqline(v)
par(old) # restore the layout
ToothGrowth is a built-in dataset (columns len, supp, dose), like mtcars, iris, airquality.
Exercises
easy
Match: (1) type = "l", (2) pch, (3) lty = 2, (4) abline(v = 3), (5) par(mfrow = c(2, 2)) with: (a) dashed line, (b) 2×2 grid of plots, (c) draw lines instead of points, (d) vertical line at x = 3, (e) point symbol.
Show solution
1-c, 2-e, 3-a, 4-d, 5-b.
medium
This code fails or does not show the density curve properly. Find the two problems.
lines(density(mtcars$mpg))
hist(mtcars$mpg)
Show solution
(1) lines() is low-level: it needs an existing plot, so call hist() first (else: plot.new has not been called yet). (2) The histogram must be on the density scale (freq = FALSE), otherwise the density curve (values around 0.05) is flattened at the bottom of a count axis.
After par(mfrow = c(2, 2)) you call hist(a); hist(b); plot(a, b). Where is each plot, and where would a fifth plot go?
Show solution
hist(a) top-left, hist(b) top-right, plot(a, b) bottom-left (filled by rows). The bottom-right stays empty until the 4th plot; a 5th high-level plot starts a new page at top-left.
exam
Using the built-in airquality data frame (columns Ozone, Temp, Month), write code that draws side by side: (a) a scatterplot of Ozone against Temp with filled red points, title and axis labels, and the least-squares line; (b) boxplots of Temp by Month.
Show solution
old <- par(mfrow = c(1, 2))
plot(Ozone ~ Temp, data = airquality, pch = 16, col = "red",
main = "Ozone vs temperature", xlab = "Temp (F)", ylab = "Ozone (ppb)")
abline(lm(Ozone ~ Temp, data = airquality), lwd = 2)
boxplot(Temp ~ Month, data = airquality, main = "Temperature by month")
par(old)
Rows with NA Ozone are dropped automatically by the formula interface and by lm().
Self-check quiz
Common mistakes
Calling points()/lines() before plot().
Adding a density curve on a count-scale histogram.
Calling barplot(df$city) on raw strings instead of barplot(table(df$city)).
Forgetting to reset par(mfrow): later plots stay in the grid.
Forgetting dev.off() after png()/pdf(): the file stays empty.
Remember
High-level (plot, hist, boxplot, barplot) start a plot; low-level (points, lines, abline, legend, text) add to it.
type, pch, col, lty, lwd, main, xlab, xlim.
par(mfrow = c(r, c)) grid by rows; restore with par(old).
hist(x, freq = FALSE); lines(density(x)).
A2.13 Tidyverse: tibbles, dplyr verbs and pipes (data manipulation)
Why it matters. Lesson 6 introduced the tidyverse and tibbles; "data manipulation with R" is an official syllabus item (the book R for Data Science is the reference). dplyr makes table operations readable, like SQL or chained JS array methods.
How it is examined. "Tibble or data frame: what does x[, 1] return?", "what does this pipeline return (how many rows/columns, which values)?", "write a pipeline that filters, groups and summarises".
Explanation
library(tidyverse) attaches the core packages: ggplot2 (plots), dplyr (manipulation), tidyr (reshaping), readr (import), tibble, purrr (functional programming, map()), stringr (strings), forcats (factors) and, since tidyverse 2.0, lubridate (dates). "Tidy data": each variable a column, each observation a row, each value a cell.
Tibbles versus data frames
Behaviour
data.frame
tibble
Printing
all rows (up to a limit), no types
first 10 rows, only the columns that fit, column types (<chr>, <dbl>, <int>, <lgl>, <fct>), dimensions
x[, "col"] (one column)
a vector (drop = TRUE)
a tibble (never drops)
x$co (partial name)
partial matching: returns x$col
NULL + warning Unknown or uninitialised column
Recycling at creation
recycles if lengths are multiples
only length-1 values are recycled
Refer to a column just created
no
yes: tibble(x = 1:3, y = x * 2)
Row names
yes
no (use a column)
Non-syntactic names
changed (my var → my.var)
kept (`my var`)
Convert with as_tibble(df) and as.data.frame(tb). readr::read_csv() returns a tibble. Extracting one column as a vector from a tibble: tb$col, tb[["col"]] or pull(tb, col).
Pipes
x |> f(y) means f(x, y): the left side becomes the first argument. |> is the native pipe (R ≥ 4.1); %>% is the magrittr pipe loaded with the tidyverse (it also accepts . as a placeholder: x %>% lm(y ~ z, data = .); the native pipe's placeholder is _, must be used with a named argument, R ≥ 4.2). Read a pipe as "then". JS analogy: arr.filter(...).map(...).
The main dplyr verbs
Verb
Does
SQL / JS analogy
filter(df, cond1, cond2)
keep rows (commas = AND); rows where the condition is NA are dropped
WHERE / .filter
select(df, a, b), select(-c), starts_with("x"), a:c
keep/drop/reorder columns
SELECT a, b
mutate(df, new = expr)
add or modify columns
computed column / .map
arrange(df, a, desc(b))
sort rows
ORDER BY / .sort
summarise(df, m = mean(x), n = n())
collapse to one row (per group)
aggregate / .reduce
group_by(df, g) (+ ungroup())
following verbs work per group
GROUP BY
count(df, g)
number of rows per group (column n)
COUNT(*) ... GROUP BY
rename(df, new = old), distinct(), slice(1:3), slice_max(x, n = 2), pull(x)
misc
left_join(x, y, by = "id"), inner_join, full_join, anti_join
Inside the verbs you write column names directly, without df$ and without quotes ("data masking"). dplyr keeps the input class: a data.frame in gives a data.frame out (but group_by creates a grouped tibble). tidyr reshapes: pivot_longer(df, cols = c(a, b), names_to = "var", values_to = "val") (wide → long) and pivot_wider() (long → wide); drop_na() removes rows with NA.
The same group summary in base R: aggregate(salary ~ dept, data = emp, FUN = mean) or tapply(emp$salary, emp$dept, mean). (.default in case_when needs dplyr ≥ 1.1; older code writes TRUE ~ "low".)
Exercises
easy
Rewrite without pipes: emp |> filter(salary > 2600) |> arrange(salary). How many rows does it return and who is first?
Show solution
arrange(filter(emp, salary > 2600), salary). Bob (2500) is removed, so 4 rows; the first is Dan (2800).
medium
With emp as a tibble and d <- as.data.frame(emp), what is the class of: emp[, 2], d[, 2], emp$dept, d["dept"]? What does d$sal return, and emp$sal?
Show solution
emp[, 2]: tibble ("tbl_df" "tbl" "data.frame"); d[, 2]: character vector; emp$dept: character vector; d["dept"]: data.frame. d$sal partially matches salary and returns the 5 salaries; emp$sal returns NULL with a warning.
medium
What does this return (number of rows and the values)?
filter drops the NA row and the 1, leaving 3 and 4. Group a contains an NA, so its sum is NA unless na.rm = TRUE.
hard
Explain why tibble(x = 1:4, y = 1:2) fails while data.frame(x = 1:4, y = 1:2) works, and what the second gives for y.
Show solution
Tibbles only recycle vectors of length 1 (to avoid hidden bugs): error Tibble columns must have compatible sizes. data.frame recycles 1:2 because 4 is a multiple of 2, so y is 1 2 1 2.
exam
Using the diabetes data (tibble diab with age, bmi, glucose, outcome 0/1), write a dplyr pipeline that removes rows with missing glucose, adds age_group ("<40", "40-60", ">60"), and returns per age group and outcome the number of patients and mean BMI, sorted by mean BMI descending.
Column names unquoted inside verbs; NA rows dropped by filter.
A2.14 ggplot2: aesthetic mappings, geoms, facets, combining plots with patchwork
Why it matters. Lesson 6: ggplot2 is the standard R plotting system, built on the "grammar of graphics": a plot is data + aesthetic mappings + geometric layers (+ scales, facets, theme).
How it is examined. Reading and completing ggplot code: "what is wrong with this code?", "which geom draws ...?", "colour inside or outside aes()?", "how do you put two plots side by side with patchwork?".
Explanation
ggplot(data = df, mapping = aes(x = var1, y = var2, colour = group)) +
geom_point() +
labs(title = "...", x = "...", y = "...") +
theme_minimal()
ggplot() creates the plot object with the data and (optionally) the global mapping. Layers are added with + (not with a pipe). The + must be at the end of a line, not at the start of the next one.
A ggplot is an object: p <- ggplot(...) + ... draws nothing until printed (p or print(p)). You can add more later: p + geom_smooth().
Definition: aesthetic mapping
aes() maps a variable (a column) to a visual property: x, y, colour/color, fill, size, shape, alpha, linetype, group. A legend is created automatically. To set a property to a constant, put it outsideaes(), as an argument of the geom: geom_point(colour = "blue"). Writing aes(colour = "blue") maps the constant string "blue" as if it were a category: points get the default first colour (a salmon red) and a legend titled "colour" with the entry "blue".
Common geoms
Geom
Needs
Plot
geom_point()
x, y
scatterplot (geom_jitter() adds random noise)
geom_line()
x, y
line (connects in x order), e.g. time series
geom_smooth(method = "lm")
x, y
trend line with confidence band (default method: loess/gam)
geom_histogram(bins = 30) or binwidth = 5
x
histogram (counts); aes(y = after_stat(density)) for density scale
ggsave("plot.png", p, width = 7, height = 5) (default: the last plot).
patchwork
The patchwork package combines ggplot objects with operators: p1 + p2 (side by side), p1 | p2 (side by side), p1 / p2 (stacked), (p1 | p2) / p3 (two on top, one below), p1 + p2 + plot_layout(ncol = 1), + plot_annotation(title = "...", tag_levels = "A"). It works only with ggplot objects (not base plots; for those use par(mfrow)).
factor(cyl) is used because cyl is numeric (4, 6, 8): mapped as a number it would give a continuous colour gradient instead of 3 distinct colours, and a boxplot needs a discrete x. With colour = factor(cyl) in the global aes, geom_smooth fits one line per group.
counts <- data.frame(city = c("MI", "RM", "TO"), n = c(12, 7, 4))
ggplot(counts, aes(x = city, y = n)) + geom_col() # heights given
ggplot(df, aes(x = city)) + geom_bar() # counts the rows of df
Exercises
easy
Which geom for: (a) the distribution of one numeric variable; (b) counts of a categorical variable from raw rows; (c) bar heights already computed in a column; (d) a numeric variable by group; (e) a time series?
ggplot(mtcars, aes(x = hp, y = mpg))
+ geom_point(aes(colour = "red"))
|> labs(title = "HP vs MPG")
Show solution
(1) + at the start of a line: the first line is a complete statement, so the plot is printed empty and + geom_point(...) fails on its own. Put + at the end of each line. (2) colour = "red" inside aes() maps a constant: points are not red (default palette colour) and a useless legend appears; write geom_point(colour = "red"). (3) Layers are added with +, not |>.
ggplot(mtcars, aes(x = hp, y = mpg)) +
geom_point(colour = "red") +
labs(title = "HP vs MPG")
medium
What is the difference between ggplot(df, aes(x, y, colour = g)) + geom_point() + geom_smooth(method = "lm") and ggplot(df, aes(x, y)) + geom_point(aes(colour = g)) + geom_smooth(method = "lm")?
Show solution
In the first, colour = g is global: points are coloured and geom_smooth draws one regression line per group. In the second, colour only applies to the points; geom_smooth fits a single line to all the data.
exam
Using the iris dataset, write code for: p1 = scatterplot of Petal.Length vs Sepal.Length coloured by Species; p2 = histograms of Sepal.Width with one panel per species; then combine them with p1 on top and p2 below using patchwork, and save as iris.png.
geom_bar counts, geom_col uses y; facet_wrap(~ v) for small multiples.
patchwork: | or + side by side, / stacked.
A2.15 Random number generation and the d/p/q/r distribution functions
Why it matters. An official syllabus topic: simulation is how data scientists check methods, build examples and resample data. R has a uniform naming system for every probability distribution.
How it is examined. "What does pnorm(0) return?", "which function gives a quantile?", "why use set.seed()?", "write a simulation that estimates a probability". Exact random values are not predictable without running R, so exam questions focus on lengths, ranges, reproducibility and the d/p/q functions.
Explanation
Definition: the d / p / q / r prefixes
For a distribution with R name dist (norm, unif, binom, pois, exp, t, chisq, f, gamma, beta, ...): ddist(x, ...) = density \(f(x)\) (for discrete distributions the probability \(P(X = x)\)); pdist(q, ...) = cumulative distribution function \(F(q) = P(X \le q)\) (use lower.tail = FALSE for \(P(X > q)\)); qdist(p, ...) = quantile, the inverse of p: the value \(q\) with \(F(q) = p\); rdist(n, ...) = n random draws.
Distribution
Random draws
Parameters (defaults)
Normal
rnorm(n, mean = 0, sd = 1)
note: sd, not variance
Uniform
runif(n, min = 0, max = 1)
Binomial
rbinom(n, size, prob)
size = number of trials; rbinom(n, 1, p) = Bernoulli
Poisson
rpois(n, lambda)
Exponential
rexp(n, rate = 1)
mean = 1/rate
Student t, chi-squared
rt(n, df), rchisq(n, df)
Seeds and reproducibility
Computers generate pseudo-random numbers from a deterministic algorithm (R's default is the Mersenne-Twister). set.seed(123) fixes the starting state, so the same code produces the same numbers every time (unlike JS Math.random(), which cannot be seeded). Two calls after one seed give different numbers (the state moves on); re-setting the seed repeats the sequence. Note: sample() results changed in R 3.6.0 (new default sample.kind = "Rejection"), so old seeded outputs may differ.
sample()
sample(x): a random permutation of x.
sample(x, size): without replacement by default (size must be ≤ length(x), else error cannot take a sample larger than the population when 'replace = FALSE').
sample(x, size, replace = TRUE, prob = c(...)): with replacement and given probabilities.
Trap: if x is a single number \(n \ge 1\), sample(x, k) samples from 1:n. sample(10, 1) is a number from 1 to 10, not 10.
replicate(B, expr) evaluates an expression B times and collects the results (like sapply with a dummy index) - the standard tool for simulations.
set.seed(42); a <- runif(3)
set.seed(42); b <- runif(3)
identical(a, b) # same seed, same numbers
length(rnorm(5)); all(runif(100) < 1)
set.seed(1)
coin <- sample(c("H", "T"), 1000, replace = TRUE)
mean(coin == "H") # close to 0.5 (exact value depends on the seed)
# Monte Carlo estimate of P(Z > 2), Z ~ N(0, 1)
mean(rnorm(1e5) > 2) # close to 0.0228
# Sampling distribution of the mean (central limit theorem)
means <- replicate(1000, mean(runif(30)))
hist(means) # bell-shaped around 0.5
[1] TRUE
[1] 5
[1] TRUE
Exercises
easy
Which function answers each question for \(X \sim N(100, 15^2)\)? (a) \(P(X \le 130)\); (b) the 90th percentile; (c) 50 simulated values; (d) the density at 100.
Show solution
(a) pnorm(130, 100, 15) (\(\approx 0.977\)); (b) qnorm(0.9, 100, 15); (c) rnorm(50, 100, 15); (d) dnorm(100, 100, 15). Note the third argument is the standard deviation 15, not the variance 225.
medium
Give the exact values: pnorm(0), qnorm(0.5), dbinom(0, 5, 0.2), pbinom(5, 5, 0.3), qunif(0.1, 10, 20), dunif(3, 0, 4).
Show solution
0.5, 0, \(0.8^5 = 0.32768\), 1 (5 is the maximum), 11, 0.25.
medium
For each line, say what is certain about the output: x <- sample(1:5); y <- sample(5, 3); z <- rbinom(4, 10, 0.5); sample(1:3, 5).
Show solution
x: the numbers 1-5 each once, in random order (sort(x) is 1 2 3 4 5). y: 3 distinct integers from 1-5. z: 4 integers between 0 and 10. The last line is an error: sample larger than the population without replacement.
exam
Write a reproducible simulation (seed 2024, 10 000 repetitions) that estimates the probability that the sum of two fair dice is at least 10, and compare with the exact value.
Exact value \(6/36 = 1/6 \approx 0.1667\). The vectorised version is preferred to a loop; replicate(B, sum(sample(1:6, 2, replace = TRUE)) >= 10) also works.
Self-check quiz
Common mistakes
Passing the variance to rnorm/pnorm instead of the sd.
Confusing p (probability from a value) and q (value from a probability).
Using dnorm as a probability: for continuous variables it is a density, \(P(X = x) = 0\).
Forgetting replace = TRUE when simulating dice or bootstrap samples.
Setting the seed inside a loop (every iteration then repeats the same numbers).
Remember
d density/pmf, p CDF, q quantile, r random draws.
set.seed() for reproducibility.
sample(x, size, replace, prob); sample(n) means sample(1:n).
replicate(B, expr) + mean(condition) = Monte Carlo probability.
A2.16 Building R packages and debugging
Why it matters. Official syllabus item ("building R packages and debugging"), still to come at the time the brief was written. Packages are how R code is shared and reused; debugging tools save time in every lab.
How it is examined. Likely conceptual closed-form questions: "which file lists the package dependencies?", "what does browser() do?", "what does traceback() show?", "what does this tryCatch return?". check the lecture slides for the exact tools the teacher used (devtools vs package.skeleton()).
which functions are exported (export(f)) and which are imported from other packages. Usually generated by roxygen2.
R/
the .R files with the function definitions
man/
documentation .Rd files (help pages), generated from roxygen comments
tests/, data/, vignettes/
unit tests (testthat), datasets, long-form docs
Workflow with devtools / usethis / roxygen2
usethis::create_package("~/mypkg") # skeleton: DESCRIPTION, NAMESPACE, R/
usethis::use_r("stats_helpers") # creates R/stats_helpers.R
# write functions with roxygen comments:
#' Coefficient of variation
#'
#' @param x A numeric vector.
#' @return sd(x) / mean(x).
#' @export
cv <- function(x) sd(x) / mean(x)
devtools::document() # roxygen2: writes man/*.Rd and NAMESPACE
devtools::load_all() # load the package for interactive testing
devtools::check() # R CMD check: finds errors, warnings, notes
devtools::install() # install it; then library(mypkg)
usethis::use_package("dplyr") # add to Imports in DESCRIPTION
usethis::use_testthat() # test infrastructure
Base R alternative: package.skeleton(name = "mypkg", list = c("cv")) creates the folders from functions in the workspace; then from a terminal R CMD build mypkg and R CMD INSTALL mypkg_0.1.0.tar.gz. Inside a package, call other packages' functions as pkg::fun() and declare them in Imports; never call library() in package code.
Conditions: errors, warnings, messages
stop("msg"): error, stops execution. warning("msg"): continues, warning shown at the end. message("msg"): informational text (to stderr; can be suppressed with suppressMessages()).
stopifnot(cond1, cond2): error if any condition is not all TRUE.
tryCatch(expr, error = function(e) ..., warning = function(w) ..., finally = ...): handle conditions (like JS try/catch/finally). conditionMessage(e) gives the text. try(expr, silent = TRUE) returns an object of class "try-error" on failure.
Debugging tools
Tool
What it does
print()/cat()
quick inspection of values (the "printf" method)
traceback()
after an error: shows the call stack (which function called which) that led to it
browser()
put inside a function: execution pauses there and you get an interactive prompt Browse[1]> in the function's environment (like a JS debugger; statement)
debug(f) / undebug(f); debugonce(f)
enter the browser at the start of every call / the next call only of f
options(error = recover)
on error, choose a frame of the stack to inspect
RStudio breakpoints
click left of a line number (file must be sourced)
Commands at the Browse> prompt: n next line, s step into a function call, f finish the current loop/function, c continue, Q quit, where show the stack; typing a variable name prints it.
Worked examples
safe_log <- function(x) {
tryCatch(log(x),
warning = function(w) { message("warning: ", conditionMessage(w)); NA },
error = function(e) { message("error: ", conditionMessage(e)); NA })
}
safe_log(100)
safe_log(-1)
safe_log("a")
r <- tryCatch(stop("boom"), error = function(e) conditionMessage(e), finally = cat("done\n"))
r
[1] 4.60517
warning: NaNs produced
[1] NA
error: non-numeric argument to mathematical function
[1] NA
done
[1] "boom"
f <- function(x) g(x)
g <- function(x) h(x)
h <- function(x) x + "1"
f(1)
traceback()
Error in x + "1" : non-numeric argument to binary operator
3: h(x) at #1
2: g(x) at #1
1: f(1)
Read the traceback from the top: the error happened in h, called by g, called by f(1). (The "at #1" part, with source line information, appears only for sourced code; exact formatting varies.)
Exercises
easy
In which package file do you (a) declare that your package needs dplyr, (b) list exported functions, (c) put the function code, (d) store help pages?
What does the roxygen tag @export do, and which command turns roxygen comments into files?
Show solution
@export makes the function visible to users after library(mypkg) (it writes export(fun) into NAMESPACE); without it the function is internal (reachable only with mypkg:::fun). devtools::document() (which runs roxygen2) generates NAMESPACE and man/*.Rd.
medium
What is printed?
res <- tryCatch({
x <- as.integer("seven")
x * 2
}, warning = function(w) -1L, error = function(e) -2L)
res
out <- try(sqrt("4"), silent = TRUE)
class(out)
Show solution
[1] -1
[1] "try-error"
as.integer("seven") raises a warning (NAs introduced by coercion), which tryCatch catches immediately: the handler's value -1 is returned and x * 2 is never run. sqrt("4") is an error (non-numeric argument to mathematical function), so try returns a "try-error" object.
exam
A function mean_by_group(df, value, group) returns wrong numbers. Describe two different ways to inspect its local variables during execution, and write a version that stops with a clear message if value is not a numeric column of df.
Show solution
(1) Insert browser() in the body, rerun, and use n/c and print variables at the prompt; (2) debugonce(mean_by_group) then call it; (also: RStudio breakpoints, or print() statements).
mean_by_group <- function(df, value, group) {
if (!value %in% names(df)) stop("column '", value, "' not found")
if (!is.numeric(df[[value]])) stop("column '", value, "' must be numeric")
tapply(df[[value]], df[[group]], mean, na.rm = TRUE)
}
df[[value]] (not df$value) is needed because value holds the column name as a string.
Self-check quiz
Common mistakes
Calling library() inside package code instead of declaring Imports and using pkg::fun.
Editing NAMESPACE by hand when roxygen2 manages it (it gets overwritten).
Forgetting to remove browser() calls before sharing code.
Using df$value when value is a variable holding a column name: use df[[value]].
Thinking tryCatch(..., warning = ) lets the code continue after the warning: the handler replaces the rest of the expression.
A2.17 Inference elements in R, and reports with Quarto / R Markdown
Why it matters. The official syllabus lists "inference elements" and "Quarto/Rmd". You need to run a test, read its output and extract numbers from it; and you need to produce reproducible reports (code + text + results in one document). The theory behind tests is taught in the Statistical Theory course; here the focus is the R side.
How it is examined. Probably closed-form: "which value in this t.test output is the p-value / CI?", "how do you extract the confidence interval?", "which chunk option hides the code?". check the slides when these lessons are given: the exact tests covered are NOT STATED.
Explanation
Tests return htest lists
Call
Tests
t.test(x, mu = 5)
one-sample t-test, \(H_0: \mu = 5\)
t.test(x, y) or t.test(value ~ group, data = df)
two-sample t-test; default is Welch (var.equal = FALSE)
t.test(x, y, paired = TRUE)
paired t-test (on the differences)
alternative = "less" / "greater", conf.level = 0.99
one-sided test, other confidence level (default two-sided, 0.95)
prop.test(x = 45, n = 100, p = 0.5)
test for a proportion (chi-squared approximation, continuity correction by default); binom.test is the exact version
The result is a list of class "htest": res$statistic, res$parameter (degrees of freedom), res$p.value, res$conf.int, res$estimate, res$null.value, res$method. Printing it gives the formatted report.
Definition: confidence interval for a mean (t)
\[ \bar x \pm t_{n-1,\,1-\alpha/2}\,\frac{s}{\sqrt n} \]
In R: mean(x) + c(-1, 1) * qt(0.975, df = n - 1) * sd(x) / sqrt(n), which equals t.test(x)$conf.int. Decision rule: reject \(H_0\) at level \(\alpha\) if the p-value \(< \alpha\).
Worked examples
x <- c(5.1, 4.9, 5.3, 5.0, 5.2)
res <- t.test(x, mu = 5)
res
One Sample t-test
data: x
t = 1.4142, df = 4, p-value = 0.2302
alternative hypothesis: true mean is not equal to 5
95 percent confidence interval:
4.903676 5.296324
sample estimates:
mean of x
5.1
By hand: \(\bar x = 5.1\), \(s = \sqrt{0.1/4} = 0.1581\), \(s/\sqrt 5 = 0.0707\), \(t = 0.1/0.0707 = 1.414\); \(t_{4,0.975} = 2.776\), so the CI is \(5.1 \pm 0.196\). The p-value 0.23 > 0.05: do not reject \(H_0: \mu = 5\); consistently, 5 lies inside the CI.
One-sided p-value = half the two-sided one here (the estimate is on the side of the alternative). The paired test uses the differences 2, 4, 2, 2, 3, 3 (mean 2.667, sd 0.816), so \(t = 2.667/(0.816/\sqrt6) = 8\). For prop.test: \(X^2 = (|45-50|-0.5)^2/25 = 0.81\) with 1 df.
Quarto and R Markdown
An R Markdown file (.Rmd) or a Quarto file (.qmd, the newer system) mixes: a YAML header (metadata between --- lines), Markdown text, and code chunks that are executed when the document is rendered (knitted). The output (HTML, PDF, Word, slides) contains text, code, results and plots, so the analysis is reproducible.
---
title: "Diabetes analysis"
author: "Your name"
format: html # R Markdown uses: output: html_document
---
## Data
```{r}
#| label: load
#| echo: false
diab <- read.csv("diabetes_dataset.csv")
```
The data has `r nrow(diab)` patients and the mean BMI is `r round(mean(diab$bmi, na.rm = TRUE), 1)`.
```{r}
#| fig-width: 6
hist(diab$bmi)
```
Chunk option
Effect
echo: false
run the code, show results, hide the code
eval: false
show the code, do not run it
include: false
run the code, show nothing (neither code nor results)
message: false, warning: false
hide messages / warnings (e.g. from library())
fig-width, fig-cap
figure size and caption
Quarto writes options as #| key: value lines inside the chunk; classic R Markdown writes them in the header: ```{r load, echo=FALSE} (both styles work in .Rmd with recent knitr). Inline code `r expr` inserts a computed value into the text. Render with the RStudio Render/Knit button, quarto render file.qmd in a terminal, or rmarkdown::render("file.Rmd"). Rendering runs in a fresh session: the document must load its own packages and data (objects in your workspace are not visible).
Exercises
easy
Write the call for: (a) testing whether the mean of w is 70 with a 99% CI; (b) comparing glucose between the two levels of outcome in data frame diab; (c) extracting only the p-value of (b).
Show solution
t.test(w, mu = 70, conf.level = 0.99) # (a)
t.test(glucose ~ outcome, data = diab) # (b) Welch by default
t.test(glucose ~ outcome, data = diab)$p.value # (c)
medium
A t.test print shows p-value = 0.0132 and 95 percent confidence interval: 1.2 8.9 for \(H_0: \mu = 0\). What is your decision at 5% and at 1%? Is it consistent with the CI?
Show solution
At 5%: reject (0.0132 < 0.05), and indeed 0 is outside the 95% CI. At 1%: do not reject (0.0132 > 0.01); the 99% CI (wider, not shown) would contain 0.
medium
Which chunk options: (a) a chunk that loads packages and should produce no output at all; (b) a chunk whose plot appears but whose code is hidden; (c) a chunk shown as an example but not executed?
Show solution
(a) include: false (or message: false + warning: false if you still want the code shown); (b) echo: false; (c) eval: false.
exam
Compute by hand and in R (without t.test) the 95% CI for the mean of y <- c(12, 15, 11, 14, 13, 16). Use \(t_{5,0.975} = 2.571\).
n <- length(y)
mean(y) + c(-1, 1) * qt(0.975, n - 1) * sd(y) / sqrt(n)
Self-check quiz
Common mistakes
Forgetting that the two-sample t.test is Welch by default (write var.equal = TRUE for the pooled test).
Using t.test(x, y) on paired data (before/after on the same subjects): add paired = TRUE.
Reading "do not reject" as "\(H_0\) is true".
A Quarto document that uses an object created only in the console: rendering fails with object not found.
Confusing echo: false (hide code) and include: false (hide everything).
Remember
t.test(x, mu =), t.test(y ~ g, data =), paired = TRUE, prop.test, chisq.test.
Results are lists: $p.value, $conf.int, $estimate, $statistic.
Quarto/Rmd = YAML + Markdown + chunks; echo, eval, include; inline `r x`; render in a clean session.
Unit A · Coding for DS & DM · exam module: 30 min (or the Python project)
A3. Python: language, NumPy, pandas, scikit-learn and the project
The Python module (prof. Kheirati Roonizi, labs by Dr. Sergio Picascia) goes from programming basics (types, control flow, strings, lists, dictionaries, functions, OOP) to data science tools (NumPy, pandas, visualisation, scikit-learn, Streamlit). The written module lasts 30 minutes: quizzes on Python code fragments (mostly "what does this code print?") plus open-ended questions on data analysis. Instead of the written module you can do the individual Python project (deadline 30 Nov 2026, 23:59 CET, last commit; evaluation 1-3 Dec). The last topic (A3.17) is the project guide.
How to study this module (you know JS/TS)
Python and JS share many ideas (references to objects, closures, first-class functions), so the traps are in the differences: // and /, slicing, mutable default arguments, class vs instance attributes, is vs ==.
For every code block, predict the output before you read it. That is exactly the exam skill.
pandas and scikit-learn are new for you: spend most time on A3.12 to A3.15.
All outputs on this page were produced by really running the code (Python 3.13, NumPy 2.x, pandas 3.x, scikit-learn 1.x). Small display differences with older versions are noted where they matter.
Check with the course
NOT STATED: the exact December exam date, the point weights of the Python questions, whether notes are allowed, the project registration form dates. Check the noticeboard and the exam registration system.
A3.1 Computational thinking, I/O, variables and types
Why it matters. Every quiz fragment uses numbers, strings and print. Small rules (/ always gives a float, // rounds down, True + True == 2) are classic exam traps.
How it is examined. Predict-the-output quizzes on short fragments: arithmetic, type conversion, print with sep/end, f-strings.
Explanation
Definition: Computational thinking
Solving a problem in a way a computer can execute: decomposition (split the problem into small parts), pattern recognition (find what repeats), abstraction (keep only the important details) and algorithm design (a finite, ordered list of precise steps).
A variable in Python is a name that refers to an object (like a let in JS that always holds a reference). Python is dynamically typed (the type belongs to the object, not to the name) and strongly typed ("3" + 4 is an error, unlike JS where it gives "34").
Type
Example
Note (vs JS)
int
7, -3, 10**20
Unlimited size (no Number.MAX_SAFE_INTEGER).
float
3.0, 1e-3
64-bit like JS numbers; 0.1 + 0.2 != 0.3 too.
str
'hi', "hi", """multi"""
Immutable. No separate char type.
bool
True, False
Capitalised. A subclass of int: True == 1.
NoneType
None
Like null; there is no undefined.
Arithmetic operators
/ true division, always a float: 6 / 2 is 3.0.
// floor division: rounds down (towards minus infinity), so -7 // 2 is -4, not -3.
% modulo: the result has the sign of the divisor, so -7 % 2 is 1 (in JS -7 % 2 is -1). Always a == (a // b) * b + a % b.
If one operand is a float, the result is a float: 7 // 2.0 is 3.0.
Input and output
input(prompt) reads one line from the keyboard and always returns a str; convert with int() or float(). print(*values, sep=' ', end='\n') prints values separated by sep and followed by end.
Tip: f-strings = JS template literals
f"{name} is {age}" is like `${name} is ${age}`. Format specs after a colon: f"{x:.2f}" (2 decimals), f"{n:5d}" (width 5), f"{x=}" (prints x= and the value).
x = "3"
print(x * 3) # repetition, not multiplication
print(int(x) * 3)
print("3" + 4)
333
9
Traceback (most recent call last):
...
TypeError: can only concatenate str (not "int") to str
Multiple assignment and swapping work without a temporary variable (the right side is evaluated first):
a, b = 1, 2
a, b = b, a + b
print(a, b)
x = y = [] # both names refer to the SAME list
x.append(1)
print(y)
2 3
[1]
Exercises
easy
Without running: what do 17 // 5, 17 % 5, 17 / 5 and 2 ** -1 give?
Show solution
3, 2, 3.4, 0.5 (a negative exponent gives a float).
medium
Write a program that reads a temperature in Celsius with input and prints it in Fahrenheit with one decimal (\(F = C \cdot 9/5 + 32\)). What happens if you forget the conversion of the input?
Without float(), c is a string and c * 9 repeats it 9 times; then / 5 raises TypeError: unsupported operand type(s) for /: 'str' and 'int'.
medium
Predict the output:
print(-9 // 4, -9 % 4, 9 // -4, 9 % -4)
Show solution
-3 3 -3 -3
Floor division rounds down: \(-9/4 = -2.25 \to -3\); then -9 - (-3)(4) = 3. And \(9/-4 = -2.25 \to -3\); 9 - (-3)(-4) = -3 (sign of the divisor).
exam
Predict the output:
x = 5
y = x
x += 1
print(x, y, bool(""), bool("0"), bool(0.0), None == 0)
Show solution
6 5 False True False False
Integers are immutable: x += 1 makes x point to a new object, y still points to 5. The string "0" is non-empty, so it is truthy (same as JS). None == 0 is False.
Self-check quiz
Common mistakes
Expecting 6 / 2 to be 3 (it is 3.0).
Thinking // truncates towards zero like JS Math.trunc: it floors (-7 // 2 == -4).
Forgetting that input() returns a string.
Writing true/null (JS) instead of True/None.
Assuming round(2.5) == 3: it is 2 (round half to even).
Remember
/ gives float; // floors; % takes the sign of the divisor.
Names refer to objects; a, b = b, a swaps.
Strong typing: "3" + 4 is a TypeError; "3" * 3 is "333".
print(..., sep=, end=); f-strings with :.2f.
A3.2 Control flow: if, loops, range, truthiness
Why it matters. Loops and conditions are inside almost every quiz fragment. The output often depends on one detail: where range stops, what break skips, what and/or return.
How it is examined. "How many times is X printed?", "what is the final value of total?", fragments with for ... else and short-circuit operators.
Explanation
Blocks are defined by indentation (4 spaces), not braces. A colon : opens a block.
x = 15
if x % 15 == 0:
print("FizzBuzz")
elif x % 3 == 0:
print("Fizz")
else:
print(x)
FizzBuzz
range and for
range(start, stop, step) produces start, start+step, ... and stops before stop. range(5) is 0..4. A Python for is like JS for...of: it iterates over the values of any iterable. Use enumerate for index + value and zip for parallel iteration.
print(list(range(5)), list(range(2, 10, 3)), list(range(5, 0, -2)), list(range(3, 3)))
for i, ch in enumerate("abc", start=1):
print(i, ch, end="; ")
print()
for name, age in zip(["Ann", "Bob", "Cy"], [31, 25]): # zip stops at the shortest
print(name, age)
break exits the loop, continue jumps to the next iteration. A loop can have an else block: it runs only if the loop ended without break. There is no do-while and no i++.
for n in [3, 7, 9, 11]:
if n % 2 == 0:
print("found even", n)
break
else:
print("no even number")
i = 0
while i < 10:
i += 1
if i % 3:
continue
print(i, end=" ")
no even number
3 6 9
Truthiness and boolean operators
Falsy values: False, None, 0, 0.0, "", [], (), {}, set(), range(0). Everything else is truthy (careful: in JS [] is truthy, in Python it is falsy). and/or return one of the operands (like JS &&/||), and they short-circuit. not always returns a bool.
print(0 or "default", [] or None, 3 and 4, 0 and 1/0, not [])
print(1 < 2 < 3, 3 > 2 > 1 == 1, 1 < 3 > 2) # chained comparisons
x = 5
print("big" if x > 3 else "small") # conditional expression
default None 4 0 True
True True True
big
0 and 1/0 never evaluates 1/0 (short-circuit), so there is no error. a < b < c means a < b and b < c (in JS 3 > 2 > 1 is false!).
Definition: == vs is
== compares values (calls __eq__). is compares identity (same object in memory). Use is only for None: if x is None. [1] == [1] is True but [1] is [1] is False.
Python 3.10+ also has match/case (structural pattern matching), similar to switch but without fall-through. It is rarely examined.
Worked examples
total = 0
for i in range(1, 6):
if i == 4:
break
total += i
else:
total += 100
print(total)
count = 0
for i in range(3):
for j in range(3):
if j == i:
continue
count += 1
print(count)
6
6
First loop: 1+2+3 = 6, then break at 4, so else is skipped. Second: 9 pairs minus 3 with i == j = 6.
n = 10
steps = 0
while n != 1: # Collatz sequence
n = n // 2 if n % 2 == 0 else 3 * n + 1
steps += 1
print(steps)
6
Exercises
easy
How many numbers does range(10, 0, -3) produce, and which?
Show solution
Four: 10, 7, 4, 1 (the stop value 0 is excluded).
medium
Write a loop that prints the first prime number greater than 90 using for ... else to test primality.
Show solution
n = 91
while True:
for d in range(2, int(n ** 0.5) + 1):
if n % d == 0:
break # not prime
else:
print(n) # no divisor found: prime
break
n += 1
97
medium
Predict the output:
print(None or 0 or "", "a" and "" and "b", not None == 0)
Show solution
True
or returns the first truthy operand or, if none, the last one ("", printed as nothing, so the line starts with two spaces: two empty strings plus two separators). and returns the first falsy operand (""). not None == 0 is not (None == 0) = True because not has lower precedence than ==.
exam
Predict the output:
s = 0
for i in range(5):
if i % 2 == 0:
continue
for j in range(i):
s += j
print(s, i)
Show solution
3 4
Only i = 1 (adds 0) and i = 3 (adds 0+1+2 = 3) run the inner loop, so s = 3. The loop variable keeps its last value after the loop: i = 4 (Python has no block scope).
Self-check quiz
Common mistakes
Thinking range(1, 5) includes 5.
Believing the loop else runs "when the loop did not run": it runs whenever there was no break (also for an empty loop).
Expecting and/or to return True/False: they return an operand.
Using is to compare numbers or strings (works by accident for small ints, then fails).
Forgetting that loop variables survive the loop.
Remember
range(a, b, s) excludes b; for iterates over values.
Loop else = "no break happened".
Empty containers, 0, "", None are falsy.
x or default; chained comparisons a < b < c.
A3.3 Strings and slicing
Why it matters. Slicing is the single most common predict-the-output topic: the same syntax works on strings, lists, tuples, NumPy arrays and (with iloc) pandas.
How it is examined. Fragments like s[-3:], s[::-2], s[5:1], string methods that return a new string.
Explanation
A string is an immutable sequence of characters. Index from 0; negative indices count from the end (s[-1] is the last character, like JS s.at(-1)).
Definition: Slice s[start:stop:step]
Takes the characters at positions start, start+step, ...excludingstop. Defaults: start = beginning, stop = end, step = 1. With a negative step the defaults flip: start = end, stop = before the beginning. Slices never raise IndexError; out-of-range bounds are clipped. A single index out of range does raise IndexError.
Positive indices on top, negative below. s[1:4] = "YTH".
P N YTH PY HON HON
PTO YHN NOHTYP OHTY
'' '' PYTHON
Traceback (most recent call last):
...
IndexError: string index out of range
s[4:1] is empty (step +1 cannot go from 4 to 1). s[-2:0:-1] goes backwards from O and stops before index 0.
s = "data"
s[0] = "D"
Traceback (most recent call last):
...
TypeError: 'str' object does not support item assignment
Strings are immutable: build a new string instead, e.g. "D" + s[1:] or s.capitalize().
Common methods (all return a NEW string or value)
t = " Hello, World "
print(repr(t.strip()), t.lower(), t.upper())
u = t.strip()
print(u.replace("l", "L"), u.find("o"), u.find("z"), u.count("l"))
print(u.split(", "), "a,b,,c".split(","), " many spaces ".split())
print("-".join(["2026", "11", "30"]), "ab" * 3, "lo" in u, u.startswith("He"))
print(len(u), u.index("W"), "42".isdigit(), "3.5".isdigit(), "abc".title())
'Hello, World' hello, world HELLO, WORLD
HeLLo, WorLd 4 -1 3
['Hello', 'World'] ['a', 'b', '', 'c'] ['many', 'spaces']
2026-11-30 ababab True True
12 7 True False Abc
Tip: JS vs Python string methods
trim() → strip(); toUpperCase() → upper(); includes(x) → x in s; arr.join("-") → "-".join(arr) (the separator is the object!); indexOf → find (returns -1) or index (raises ValueError). split() with no argument splits on any whitespace and drops empty pieces.
s = "banana"
s.upper() # result thrown away!
print(s)
s = s.upper()
print(s, s.count("AN"), s.replace("A", "", 2))
print(ord("a"), chr(98), "b" > "a", "Z" < "a", "10" < "9")
banana
BANANA 2 BNNA
97 b True True True
String comparison is lexicographic by character code: "10" < "9" because "1" < "9".
Exercises
easy
Given s = "university", write slices that give "uni", "ty", "ytisrevinu" and "uiest".
Show solution
s = "university"
print(s[:3], s[-2:], s[::-1], s[::2])
uni ty ytisrevinu uiest
medium
Write a function is_palindrome(text) that ignores case and spaces: "Never odd or even" is a palindrome.
s = "Data Science"
words = s.split()
print(words[1][::-1][:3], s.lower().count("a"), "-".join(s[:4]))
Show solution
ecn 2 D-a-t-a
"Science"[::-1] is "ecneicS", first 3 → "ecn". join on a string iterates over its characters.
Self-check quiz
Common mistakes
Including stop in a slice (s[1:3] has 2 characters).
Calling s.upper() and expecting s to change.
Expecting s[5:1] to reverse: you need a negative step s[5:1:-1].
Writing list.join("-") (JS habit) instead of "-".join(list); and joining non-strings ("-".join([1, 2]) is a TypeError).
Remember
s[a:b:c]: b excluded; negative step flips defaults; slices never fail.
s[::-1] reverses; s[-n:] = last n characters.
Strings are immutable: methods return new strings.
sep.join(list_of_str), s.split(), find → -1, index → error.
A3.4 Lists and tuples: mutability, aliasing, copies
Why it matters. Aliasing (two names, one list) is the favourite trap of Python quizzes, and it is the same idea you will meet again with NumPy views and pandas.
How it is examined. Fragments where a list is modified through another name, append vs extend, sort() returning None, [[0]*3]*3.
Explanation
A list[1, "a", 2.5] is an ordered, mutable sequence (like a JS array). A tuple(1, "a", 2.5) is an ordered, immutable sequence: you cannot add, remove or replace its elements. Tuples are used for fixed records, for returning several values from a function, and as dictionary keys. A one-element tuple needs a comma: (5,); (5) is just the int 5.
Operation
Effect
Returns
L.append(x)
adds x as ONE element at the end
None
L.extend(it) / L += it
adds each element of it
None
L.insert(i, x)
inserts before position i
None
L.pop() / L.pop(i)
removes last / at i
the removed element
L.remove(x)
removes the first x (ValueError if absent)
None
L.sort()
sorts in place
None
sorted(L)
does not change L
a new list
L.reverse() / L[::-1]
in place / new list
None / list
L + M, L * 2
new list
list
Definition: Aliasing
Assignment b = a never copies: it makes b a second name for the same object. If the object is mutable, a change through b is visible through a. Copies: a[:], list(a), a.copy() (all shallow: the outer list is new but the inner objects are shared), copy.deepcopy(a) (copies recursively).
Worked examples
a = [1, 2, 3]
b = a # alias
c = a[:] # shallow copy
b.append(4)
c.append(99)
print(a, b, c, a is b, a == c)
import copy
m = [[1, 2], [3, 4]]
s = m.copy()
d = copy.deepcopy(m)
m[0].append(9) # change an inner list
m.append([5]) # change the outer list
print(s)
print(d)
[[1, 2, 9], [3, 4]]
[[1, 2], [3, 4]]
Tuples and unpacking
t = (1, [2, 3], "x")
t[1].append(4) # allowed: the list inside is mutable
print(t, type((5)), type((5,)), len(()))
first, *rest = [10, 20, 30, 40]
x, (y, z) = 1, (2, 3)
print(first, rest, x, y, z)
t[0] = 100
[expr for x in iterable if cond] is like iterable.filter(cond).map(expr) in JS.
nums = [1, 2, 3, 4, 5, 6]
print([n * n for n in nums if n % 2 == 0])
print([(i, j) for i in range(2) for j in range(3) if i != j])
print(["even" if n % 2 == 0 else "odd" for n in nums[:3]])
print(sum(nums), max(nums), min(nums), nums.index(4), 7 in nums, nums[-2:])
What is len([1, [2, 3], (4, 5, 6)])? And len([1, 2] + [3])?
Show solution
3 (nested containers count as one element) and 3.
medium
Predict the output:
a = [1, 2, 3]
b = a
a = a + [4]
print(a, b)
c = [1, 2, 3]
d = c
c += [4]
print(c, d)
Show solution
[1, 2, 3, 4] [1, 2, 3]
[1, 2, 3, 4] [1, 2, 3, 4]
a = a + [4] creates a new list and rebinds a; b still has the old one. c += [4] extends the list in place, so d sees the change. This difference is a classic exam question.
medium
Write a list comprehension that flattens [[1, 2], [3], [4, 5, 6]] into [1, 2, 3, 4, 5, 6].
Show solution
nested = [[1, 2], [3], [4, 5, 6]]
print([x for sub in nested for x in sub])
[1, 2, 3, 4, 5, 6]
The for clauses are written in the same order as nested loops.
exam
Predict the output:
x = [3, 1, 2]
y = x.sort()
z = sorted(x, reverse=True)
x.append(z)
print(y, x[-1] is z, len(x), x[3][0])
Show solution
None True 4 3
sort() returns None. append(z) stores a reference to the same list z.
Both entries of table are the same list object, so both show the 5. Rebinding the name row afterwards does not affect the list objects already stored in table.
Self-check quiz
Common mistakes
L = L.sort() → L becomes None.
Confusing append (one element) with extend (many).
Thinking b = a copies; thinking a.copy() copies nested lists.
Creating a 2D list with [[0]*n]*m.
Writing (5) for a one-element tuple.
Believing a tuple's content can never change: a list inside a tuple can.
Remember
Assignment = new name, same object. Copy with [:]/.copy() (shallow) or deepcopy.
a = a + [x] rebinds; a += [x] mutates.
In-place methods return None; sorted() returns a new list.
Tuples: immutable, (x,), unpacking a, *rest = ....
A3.5 Dictionaries and sets
Why it matters. Dictionaries are Python's main "advanced data structure": counting, grouping, JSON records, and the mental model behind a pandas row or column.
How it is examined. Fragments with items(), keys(), values(), get() (listed in the Oct 12 lecture), duplicate keys, key order, set operations and comprehensions.
Explanation
Definition: Dictionary
A mutable mapping {key: value}. Keys must be hashable (immutable: int, str, float, tuple of immutables; not list, dict or set). Since Python 3.7 dicts keep insertion order. Assigning to an existing key replaces the value but keeps the key's original position.
Compared with a JS object: keys can be any hashable type (1 and "1" are different keys), there is no dot access (d["name"], not d.name), and a missing key raises KeyError instead of giving undefined. Note 1, 1.0 and True are equal and have the same hash, so they are the same key.
Operation
Meaning
d[k]
value, or KeyError
d.get(k, default=None)
value, or the default (never an error)
d.keys(), d.values(), d.items()
live "views" (they reflect later changes); items() gives (key, value) tuples
k in d
tests the keys, not the values
d.pop(k), del d[k]
remove a key
d.setdefault(k, v)
returns d[k]; if missing, first sets it to v
d.update(other), d | other
merge (later values win)
Definition: Set
An unordered collection of unique hashable elements: {1, 2, 3}. The empty set is set(), because {} is an empty dict. Operators: | union, & intersection, - difference, ^ symmetric difference. Membership x in s is fast (hashing), unlike in a list.
Worked examples
ages = {"ann": 31, "bob": 25}
ages["cy"] = 40
ages["ann"] = 32 # update keeps position
print(ages)
print(list(ages.keys()), list(ages.values()))
for name, age in ages.items():
print(name, age, end=" | ")
print()
print(ages.get("zoe"), ages.get("zoe", 0), "bob" in ages, 25 in ages)
print(ages["zoe"])
{1: 'bool'}: the three keys are equal, so there is one key; it keeps the first key object (1) and the last value.
Counting and grouping, three ways:
words = "the cat and the horse and a bat".split()
counts = {}
for w in words:
counts[w] = counts.get(w, 0) + 1
print(counts)
from collections import Counter, defaultdict
print(Counter(words).most_common(2))
by_len = defaultdict(list)
for w in words:
by_len[len(w)].append(w)
print(dict(by_len))
prices = {"apple": 0.5, "kiwi": 0.25, "mango": 2.0}
print({k: v * 2 for k, v in prices.items() if v < 1})
print({v: k for k, v in prices.items()}) # invert
print({len(k) for k in prices}) # set comprehension
print(sorted(prices, key=prices.get), max(prices, key=prices.get))
The printed order of a set of strings is not guaranteed (it can change between runs); for small ints it usually looks sorted. In a quiz, an option that depends on set order is suspicious.
Exercises
easy
What is the difference between d["x"] and d.get("x") when "x" is not a key?
Show solution
d["x"] raises KeyError: 'x'; d.get("x") returns None (or the default given as second argument).
medium
Given a list of (student, grade) pairs, build a dict student → list of grades and then a dict student → average.
Show solution
pairs = [("ann", 28), ("bob", 24), ("ann", 30), ("bob", 18), ("cy", 27)]
grades = {}
for s, g in pairs:
grades.setdefault(s, []).append(g)
avg = {s: sum(gs) / len(gs) for s, gs in grades.items()}
print(grades)
print(avg)
This is exactly a "group by student, then mean": in pandas it is df.groupby("student")["grade"].mean() (A3.13).
medium
Predict the output:
d = {"x": 1, "y": 2}
k = d.keys()
d["z"] = 3
print(len(k), "z" in k, list(d.items())[-1])
Show solution
3 True ('z', 3)
keys() is a live view, so it sees "z".
exam
Predict the output:
inv = {}
for k, v in {"a": 1, "b": 2, "c": 1}.items():
inv[v] = k
s = {1, 2, 3} & {2, 3, 4}
print(inv, len(s | {9}), {}.get("a", "none"))
Show solution
{1: 'c', 2: 'b'} 3 none
Key 1 is first set to "a", then overwritten by "c" (keeping its first position).
Self-check quiz
Common mistakes
x in d checks keys, not values (use x in d.values()).
{} is an empty dict, not a set.
Using a list as a key (TypeError: unhashable type: 'list'); use a tuple.
Modifying a dict's size while iterating over it (RuntimeError); iterate over list(d) instead.
Relying on the printed order of a set.
Remember
Dicts keep insertion order; duplicate keys: last value wins, first position kept.
get(k, default) never fails; items() for key-value loops.
Keys must be hashable; 1 == 1.0 == True is one key.
Sets: unique, unordered, | & - ^, set() for empty.
A3.6 Functions: arguments, scope, lambdas
Why it matters. Functions are the unit of code organisation required by the project (criterion 2: functions with docstrings). The mutable default argument is the most famous Python quiz trap.
How it is examined. Predict the output of fragments with default arguments, *args/**kwargs, global, functions without return, closures.
Explanation
def area(width, height=1):
"""Return the area of a rectangle.
Parameters: width, height (numbers). Returns: width * height.
"""
return width * height
def greet(name):
print("hi", name) # no return statement
print(area(3), area(3, 4), area(height=2, width=5))
r = greet("Ann")
print(r)
3 12 10
hi Ann
None
A function without return returns None (JS returns undefined). The text in triple quotes is the docstring (purpose, parameters, return value), shown by help(area).
Kinds of parameters
Positional and keyword arguments: f(1, b=2). Positional ones must come first.
Default values: def f(a, b=10). Parameters with defaults come after those without.
*args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict (like JS rest ...args, but split in two).
At the call site, * and ** unpack: f(*[1, 2], **{"c": 3}) (like JS spread).
A () , {}
B (1, 2, 3) , {'x': 9, 'y': 'z'}
C (4,) ; {'k': 1}
Definition: Mutable default argument trap
Default values are evaluated once, when the def runs, not at every call. A mutable default (list, dict) is therefore shared by all calls that use it. In JS, default parameters are re-evaluated at each call, so this surprises JS developers. Fix: use None as default and create the list inside.
A name is looked up in the Local scope, then Enclosing functions, then Global (module), then Built-ins. Python has function scope, no block scope. If a function assigns to a name anywhere in its body, that name is local in the whole function, unless declared global (module variable) or nonlocal (enclosing function variable).
x = 10
def show():
print(x) # reads the global
def change():
global x
x = 20
def broken():
print(x) # x is local here (assigned below) -> error
x = 30
show(); change(); show()
broken()
10
20
Traceback (most recent call last):
...
UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
Mutating a global object does not need global; only rebinding the name does. Arguments are passed "by object reference" (like JS): a function can mutate a list you pass it, but assigning to the parameter does not affect the caller.
def modify(lst, n):
lst.append(n) # visible to the caller
lst = [0] # rebinding: only local
n = n + 1 # ints are immutable
nums, k = [1], 5
modify(nums, k)
print(nums, k)
[1, 5] 5
Lambdas, closures, higher-order functions
lambda x: x * 2 is an anonymous function with a single expression (like x => x * 2). Typical use: the key of sorted, max, min, and pandas apply. map and filter return lazy iterators: wrap them in list() to see the values.
people = [("ann", 31), ("bob", 25), ("cy", 31)]
print(sorted(people, key=lambda p: (-p[1], p[0])))
print(list(map(lambda x: x ** 2, [1, 2, 3])), list(filter(lambda x: x % 2, range(6))))
print(type(map(str, [1])))
def make_multiplier(k):
def mult(x):
return x * k # k comes from the enclosing scope (closure)
return mult
double = make_multiplier(2)
print(double(21))
fs = [lambda: i for i in range(3)] # late binding!
print([f() for f in fs])
fs = [lambda i=i: i for i in range(3)] # fix: capture with a default
print([f() for f in fs])
Closures look up variables when called, not when created: all three lambdas see the final i = 2. (In JS, let in a for creates a new binding per iteration, so the JS equivalent prints 0, 1, 2.)
Exercises
easy
What does print(print("a")) output?
Show solution
print(print("a"))
a
None
The inner print shows a and returns None, which the outer one prints.
medium
Write stats(*values) that returns a tuple (min, max, mean) and returns None if no values are given. Add a docstring.
Show solution
def stats(*values):
"""Return (min, max, mean) of the given numbers, or None if there are none."""
if not values:
return None
return min(values), max(values), sum(values) / len(values)
print(stats(4, 8, 6), stats())
lo, hi, mean = stats(1, 2)
print(lo, hi, mean)
(4, 8, 6.0) None
1 2 1.5
medium
Predict the output:
def f(a, b=2, *c, d=4, **e):
return a, b, c, d, e
print(f(1))
print(f(1, 5, 6, 7, d=8, z=9))
The default dict is shared: 1, 2, then a fresh dict gives 1, then the shared one again gives 3.
exam
Predict the output:
total = 0
def add(n):
total = n
return total
def outer():
v = 1
def inner():
nonlocal v
v += 10
inner(); inner()
return v
print(add(5), total, outer())
Show solution
5 0 21
add creates a local total; the global stays 0. nonlocal lets inner rebind v of outer.
Self-check quiz
Common mistakes
Using [] or {} as a default value.
Forgetting return and getting None.
Assigning to a global inside a function without global (UnboundLocalError).
Printing map(...) directly (you see <map object>).
Expecting closures in a loop to capture the current value.
Remember
Defaults are evaluated once: use None for mutable defaults.
*args → tuple, **kwargs → dict; after *args parameters are keyword-only.
LEGB; assignment makes a name local; global/nonlocal to rebind.
No return → None; lambdas for short key= functions.
Why it matters. OOP was the topic of the Oct 26 lecture (class, object, instance, attribute, method, magic methods __init__, __str__, __len__, __del__, inheritance). The project also asks for classes with docstrings.
How it is examined. Predict-the-output fragments: class vs instance attributes, what print(obj) shows, len(obj), when __del__ runs, == between objects.
Explanation
Definitions
Class: a blueprint (a new type). Object / instance: a concrete value created from a class: p = Point(1, 2) (no new keyword).
Attribute: a variable attached to an object (p.x) or to the class. Method: a function defined in the class; its first parameter is the instance, by convention self (explicit, unlike JS this).
Magic (dunder) methods: methods with double underscores that Python calls for you: __init__ (initialiser, like constructor), __str__ (used by print/str), __repr__ (developer representation, used in the REPL and inside lists), __len__ (len(obj)), __eq__ (==), __del__ (called when the object is about to be destroyed).
class Dog:
species = "Canis familiaris" # class attribute: shared by all dogs
def __init__(self, name, tricks=None):
self.name = name # instance attributes
self.tricks = tricks if tricks is not None else []
def learn(self, trick):
self.tricks.append(trick)
return self # allows chaining
def __str__(self):
return f"Dog {self.name} ({len(self)} tricks)"
def __len__(self):
return len(self.tricks)
rex = Dog("Rex")
rex.learn("sit").learn("roll")
print(rex)
print(len(rex), rex.species, Dog.species, rex.tricks)
print(Dog.learn(rex, "jump") is rex, len(rex)) # rex.learn(x) == Dog.learn(rex, x)
Dog Rex (2 tricks)
2 Canis familiaris Canis familiaris ['sit', 'roll']
True 3
Class attributes vs instance attributes
Reading obj.attr looks first in the instance, then in the class. Assigningobj.attr = v always creates/changes an instance attribute, which then shadows the class one. But mutating a mutable class attribute through an instance changes the shared object.
class Counter:
count = 0
shared = []
def __init__(self):
Counter.count += 1 # update the class attribute
a, b = Counter(), Counter()
a.count = 100 # new instance attribute on a only
a.shared.append("x") # mutates the ONE shared list
print(Counter.count, a.count, b.count)
print(b.shared, a.__dict__, b.__dict__)
2 100 2
['x'] {'count': 100} {}
__str__ vs __repr__, __eq__
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f"Point({self.x}, {self.y})"
def __str__(self):
return f"({self.x}, {self.y})"
p, q = Point(1, 2), Point(1, 2)
print(p, [p], str(p), repr(p))
print(p == q, p is q) # no __eq__: == falls back to identity
class Point2(Point):
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
print(Point2(1, 2) == Point2(1, 2))
Inside a list, Python uses __repr__ for the elements. Without __str__, print falls back to __repr__; without both, you get something like <__main__.Point object at 0x...>. Note: defining __eq__ without __hash__ makes instances unhashable (cannot be dict keys or set elements).
__del__
__del__ is called when the object's reference count drops to zero (in CPython, usually immediately). del x only removes the namex; if another name still refers to the object, __del__ does not run yet.
class Resource:
def __init__(self, name):
self.name = name
print("open", name)
def __del__(self):
print("close", self.name)
r = Resource("A")
alias = r
del r
print("after del r")
del alias
print("end")
open A
after del r
close A
end
Tip: other class tools you may meet
@property (a method read like an attribute), @staticmethod (no self), @classmethod (receives the class cls, often used as alternative constructor), and @dataclass (auto-generates __init__, __repr__, __eq__). A leading underscore _x means "private by convention"; Python has no real private attributes (__x only triggers name mangling).
from dataclasses import dataclass
@dataclass
class Movie:
title: str
year: int
rating: float = 0.0
@property
def decade(self):
return self.year // 10 * 10
@classmethod
def from_csv_line(cls, line):
t, y, r = line.split(",")
return cls(t, int(y), float(r))
m = Movie.from_csv_line("Alien,1979,8.5")
print(m, m.decade, m == Movie("Alien", 1979, 8.5))
A small class like those in the course, with validation and several magic methods:
class Playlist:
"""A named, ordered collection of song titles."""
def __init__(self, name, songs=None):
self.name = name
self.songs = list(songs) if songs else []
def add(self, song):
if song in self.songs:
raise ValueError(f"{song!r} already in playlist")
self.songs.append(song)
def __len__(self):
return len(self.songs)
def __str__(self):
return f"{self.name}: " + ", ".join(self.songs)
def __getitem__(self, i): # enables pl[i] and for-loops
return self.songs[i]
pl = Playlist("Road", ["A", "B"])
pl.add("C")
print(pl, len(pl), pl[-1], [s.lower() for s in pl])
pl.add("A")
Road: A, B, C 3 C ['a', 'b', 'c']
Traceback (most recent call last):
...
ValueError: 'A' already in playlist
Exercises
easy
What is the difference between a class and an instance? Between an instance attribute and a class attribute?
Show solution
A class is the template/type; an instance is one object built from it. Instance attributes (self.x = ..., usually in __init__) belong to one object; class attributes (assigned in the class body) are shared by all instances and accessed as ClassName.attr or through an instance if not shadowed.
medium
Write a class BankAccount with owner, balance (default 0), methods deposit(amount) and withdraw(amount) (raise ValueError if the balance is insufficient), and a __str__.
self.items.append mutates the shared class list. self.n += 1 means self.n = self.n + 1: it reads the class value 0 and creates an instance attribute 1, leaving A.n at 0.
exam
Predict the output:
class Box:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
def __repr__(self):
return f"Box{self.items}"
b = Box([1, 2])
c = Box(b.items)
c.items.append(3)
print(len(b), [b], bool(Box([])))
Show solution
3 [Box[1, 2, 3]] False
b and c share the same list (aliasing). bool(obj) uses __len__ when there is no __bool__: length 0 → False.
Self-check quiz
Common mistakes
Forgetting self in the method definition (TypeError: ... takes 0 positional arguments but 1 was given) or writing name instead of self.name.
Using a mutable class attribute (items = []) when each instance needs its own list.
__str__ must return a string, not print it.
Thinking del x always calls __del__: only when the last reference disappears.
Expecting == to compare attributes without defining __eq__.
Remember
obj.method(x) = Class.method(obj, x).
Lookup: instance first, then class. Assignment through self creates an instance attribute.
print → __str__ (fallback __repr__); lists show __repr__; len → __len__.
__del__ runs when the reference count reaches zero.
A3.8 Inheritance, super(), overriding and MRO
Why it matters. Inheritance closed the Oct 26 OOP lecture. Quiz fragments love to ask which version of a method runs, and what happens when a child forgets to call the parent's __init__.
How it is examined. Predict-the-output with overridden methods, super() calls, class attributes redefined in subclasses, isinstance, multiple inheritance order.
Explanation
Definition: Inheritance
class Child(Parent): creates a subclass that inherits all attributes and methods of Parent. The child can add new methods and override (redefine) existing ones. super().method(...) calls the parent's version. Every class ultimately inherits from object.
Method lookup is dynamic: self.speak() inside a parent method calls the child'sspeak if the object is a child (polymorphism, same as JS classes).
class Animal:
sound = "..."
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} says {self.sound}"
def describe(self):
return "I am an animal. " + self.speak()
class Cat(Animal):
sound = "meow" # overrides the class attribute
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # run the parent initialiser
self.breed = breed
def speak(self): # override
return super().speak().upper() + "!"
for a in [Animal("x"), Cat("Tom"), Dog("Rex", "lab")]:
print(a.describe())
print(isinstance(Dog("a", "b"), Animal), issubclass(Cat, Dog), type(Cat("t")).__name__)
I am an animal. x says ...
I am an animal. Tom says meow
I am an animal. REX SAYS ...!
True False Cat
Forgetting super().__init__
If a child defines __init__, the parent's __init__ is not called automatically (unlike JS, where super() is mandatory in a derived constructor before using this). Attributes set by the parent are then missing:
class Base:
def __init__(self):
self.ready = True
class Child(Base):
def __init__(self):
self.extra = 1 # no super().__init__()
c = Child()
print(c.extra)
print(c.ready)
1
Traceback (most recent call last):
...
AttributeError: 'Child' object has no attribute 'ready'
Multiple inheritance and the MRO
A class can have several parents: class C(A, B). Python searches methods in the Method Resolution Order (MRO), computed with the C3 algorithm: the class itself, then parents left to right, with each class appearing before its own parents. See it with C.__mro__ or C.mro(). super() means "next class in the MRO", not simply "my parent".
class A:
def who(self): return "A"
class B(A):
def who(self): return "B" + super().who()
class C(A):
def who(self): return "C" + super().who()
class D(B, C):
def who(self): return "D" + super().who()
print(D().who())
print([k.__name__ for k in D.__mro__])
DBCA
['D', 'B', 'C', 'A', 'object']
In B.who, super() goes to C (the next in D's MRO), not to A. That is why the result is DBCA.
Worked examples
class Shape:
def area(self):
raise NotImplementedError("subclasses must implement area")
def __str__(self):
return f"{type(self).__name__} with area {self.area():.2f}"
class Rect(Shape):
def __init__(self, w, h):
self.w, self.h = w, h
def area(self):
return self.w * self.h
class Square(Rect):
def __init__(self, side):
super().__init__(side, side)
class Circle(Shape):
def __init__(self, r):
self.r = r
def area(self):
return 3.14159 * self.r ** 2
shapes = [Rect(2, 3), Square(2), Circle(1)]
for s in shapes:
print(s)
print(sum(s.area() for s in shapes))
print(Shape().area())
Rect with area 6.00
Square with area 4.00
Circle with area 3.14
13.14159
Traceback (most recent call last):
...
NotImplementedError: subclasses must implement area
Tip: abstract base classes
For a real "must implement" contract use from abc import ABC, abstractmethod: a class with an @abstractmethod cannot be instantiated (TypeError). Similar to an abstract class in TypeScript.
Exercises
easy
What is overriding? What does super() return?
Show solution
Overriding = a subclass defines a method with the same name as one in a parent, replacing it for the subclass's objects. super() returns a proxy object that forwards method calls to the next class in the MRO (for single inheritance, the parent).
medium
Create Employee(name, salary) with annual() returning 12 * salary, and Manager(name, salary, bonus) overriding annual() to add the bonus, reusing the parent's method.
class P:
def __init__(self):
self.v = self.init_value()
def init_value(self):
return 1
class Q(P):
def init_value(self):
return 2
print(P().v, Q().v)
Show solution
1 2
P.__init__ runs for Q(), but self.init_value() is looked up on the actual object, a Q.
exam
Predict the output:
class X:
tag = "x"
def show(self):
return self.tag
class Y(X):
tag = "y"
class Z(Y):
def show(self):
return "z" + super().show()
z = Z()
z.tag = "i"
print(X().show(), Y().show(), Z().show(), z.show())
Show solution
x y zy zi
Z inherits tag = "y" from Y. For z, the instance attribute "i" shadows it.
Self-check quiz
Common mistakes
Overriding __init__ and forgetting super().__init__(...).
Calling super().__init__(self, ...): self is passed automatically.
Thinking a parent method always calls the parent's version of other methods (dynamic dispatch uses the object's class).
Assuming super() = direct parent in multiple inheritance (it follows the MRO).
Remember
class Child(Parent); override by redefining; extend with super().method().
The child's __init__ replaces the parent's unless you call super().__init__.
MRO: class, then parents left to right, each before its parents; Class.__mro__.
isinstance(obj, Parent) is True for subclass instances.
A3.9 Exceptions, files and modules
Why it matters. The project's Input/Output criterion explicitly asks you to "handle loading errors cleanly", and the organisation criterion asks for modules that the notebook/app imports. Exceptions also appear in quizzes ("which error is raised?", "what does finally print?").
How it is examined. Predict the output of try/except/else/finally blocks; name the exception type; project discussion on how your code loads data and is split into modules.
Explanation
Exceptions
def safe_div(a, b):
try:
result = a / b
except ZeroDivisionError:
print("division by zero")
return None
except TypeError as e:
print("bad types:", e)
return None
else: # runs only if NO exception happened in try
print("ok")
return result
finally: # ALWAYS runs, even after return
print("done")
print(safe_div(6, 3))
print(safe_div(1, 0))
print(safe_div("a", 2))
ok
done
2.0
division by zero
done
None
bad types: unsupported operand type(s) for /: 'str' and 'int'
done
None
Exception
Typical cause
ZeroDivisionError
1 / 0, 1 // 0, 1 % 0
TypeError
wrong type: "a" + 1, calling with wrong number of arguments
ValueError
right type, bad value: int("abc")
IndexError / KeyError
list index out of range / missing dict key
AttributeError
[1].push(2) (no such attribute/method)
NameError
undefined variable
FileNotFoundError
opening a file that does not exist (subclass of OSError)
Raise your own with raise ValueError("message") (JS: throw new Error()). except Exception catches almost everything; catching a specific exception is better practice. The first matching except wins, so put subclasses before parents. Define custom errors with class MyError(Exception): pass.
Files
Use with open(path, mode, encoding="utf-8") as f:; the with block (a context manager) closes the file automatically, even after an error. Modes: "r" read (default), "w" write (truncates!), "a" append, "x" create-only, add "b" for binary.
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("line one\n")
f.write("line two\n")
with open("notes.txt", "a", encoding="utf-8") as f:
print("line three", file=f)
with open("notes.txt", encoding="utf-8") as f:
for i, line in enumerate(f, 1):
print(i, line.rstrip("\n"))
with open("notes.txt") as f:
print(f.readlines())
1 line one
2 line two
3 line three
['line one\n', 'line two\n', 'line three\n']
CSV and JSON with the standard library (in the project you will mostly use pandas, A3.12):
import csv, json
rows = [{"city": "Milan", "temp": 14.5}, {"city": "Rome", "temp": 18.0}]
with open("cities.csv", "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=["city", "temp"])
w.writeheader()
w.writerows(rows)
with open("cities.csv") as f:
back = list(csv.DictReader(f))
print(back[0], type(back[0]["temp"])) # csv gives strings!
with open("cities.json", "w") as f:
json.dump(rows, f, indent=2)
with open("cities.json") as f:
data = json.load(f)
print(data[1]["temp"] + 1, json.dumps({"a": None, "b": True}))
A clean loader for the project ("handle loading errors cleanly"):
from pathlib import Path
import json
def load_config(path):
"""Load a JSON file; return an empty dict if it is missing or invalid."""
path = Path(path)
try:
with path.open(encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
print(f"Warning: {path} not found, using defaults")
except json.JSONDecodeError as e:
print(f"Warning: {path} is not valid JSON ({e.msg})")
return {}
Path("bad.json").write_text("{oops")
print(load_config("missing.json"), load_config("bad.json"), load_config("cities.json")[0])
Warning: missing.json not found, using defaults
Warning: bad.json is not valid JSON (Expecting property name enclosed in double quotes)
{} {} {'city': 'Milan', 'temp': 14.5}
Modules and packages
A module is any .py file; a package is a folder of modules (usually with an __init__.py). Import styles: import math, import numpy as np, from math import sqrt. Avoid from x import *. Like ES modules, a module's top-level code runs once, on the first import.
# file: src/weather/stats.py
def mean(values):
"""Return the arithmetic mean of a non-empty list."""
return sum(values) / len(values)
if __name__ == "__main__": # True only when run as: python stats.py
print(mean([1, 2, 3])) # quick manual test, NOT run on import
__name__ is "__main__" when the file is executed directly, and the module's name (e.g. "weather.stats") when it is imported. In your notebook: from weather.stats import mean. This is how "the notebook imports and uses the project code" (project rule).
Worked examples
def parse_ages(items):
good, bad = [], []
for x in items:
try:
age = int(x)
if age < 0:
raise ValueError(f"negative age {age}")
except ValueError as e:
bad.append((x, str(e)))
else:
good.append(age)
return good, bad
print(parse_ages(["31", "abc", "-4", "7"]))
([31, 7], [('abc', "invalid literal for int() with base 10: 'abc'"), ('-4', 'negative age -4')])
Exercises
easy
Name the exception: int("3.5"), [1, 2][2], {"a": 1}["b"], None + 1.
finally runs before the function actually returns.
medium
Write a function count_words(path) that returns the number of words in a text file, or -1 if the file does not exist.
Show solution
def count_words(path):
try:
with open(path, encoding="utf-8") as f:
return len(f.read().split())
except FileNotFoundError:
return -1
with open("t.txt", "w") as f:
f.write("to be or\nnot to be")
print(count_words("t.txt"), count_words("nope.txt"))
6 -1
exam
Predict the output:
out = []
for x in [1, 0, "2"]:
try:
out.append(10 // x)
except ZeroDivisionError:
out.append("Z")
except Exception as e:
out.append(type(e).__name__)
finally:
out.append("|")
print("".join(map(str, out)))
Show solution
10|Z|TypeError|
Self-check quiz
Common mistakes
A bare except: that hides real bugs (and even Ctrl-C). Catch specific errors.
Opening with "w" when you meant "a": the file is emptied.
Forgetting that csv reads everything as strings.
Putting test code at module top level instead of under if __name__ == "__main__":, so it runs on every import.
Hard-coding absolute paths (/Users/me/...) in the project: use relative paths or pathlib.
Remember
try / except E as e / else (no error) / finally (always).
with open(..., encoding="utf-8") as f closes the file for you.
Why it matters. The exercise classes started with the shell, and the project lab sequence covers project organisation with Git/GitHub. Criterion 1 of the project is GitHub usage (README, .gitignore, frequent atomic commits with descriptive messages), and the deadline is defined by your last commit.
How it is examined. Mostly in the project evaluation (your history is inspected; you may be asked what a command does). Possible conceptual quiz questions in the written exam (check: not stated whether Git appears in the written module).
Explanation
Shell essentials
As a developer you already know most of this; the course covered finding and running programs, navigating the file system and creating directories.
pwd # print working directory
ls -la # list files (including hidden ones like .gitignore)
cd projects/weather # change directory; cd .. goes up; cd ~ goes home
mkdir -p data/raw # create directories (with parents)
which python3 # where is the program that runs?
python3 --version
python3 -m venv .venv # create a virtual environment
source .venv/bin/activate # activate it (Windows: .venv\Scripts\activate)
pip install pandas numpy matplotlib
pip freeze > requirements.txt # pin installed versions
python -m src.weather.stats # run a module of your package
Tip: venv ≈ node_modules
A virtual environment is a per-project folder of installed packages, like node_modules; requirements.txt plays the role of package.json + lockfile. Never commit .venv/, just like you never commit node_modules/.
Git model
Definition: The three areas
Working directory (your files) → git add → staging area (what goes into the next commit) → git commit → repository (history of snapshots). git push sends commits to a remote (GitHub); git pull fetches and merges remote commits.
Command
What it does
git init / git clone URL
create a repo / copy a remote repo
git status, git diff
what changed (unstaged); git diff --staged for staged
git add file / git add -p
stage a file / stage chosen hunks (for atomic commits)
git commit -m "msg"
record a snapshot
git log --oneline
history
git branch x, git switch x
create / move to a branch
git remote add origin URL, git push -u origin main
connect to GitHub and push
git restore file, git rm --cached file
discard local changes / stop tracking a file but keep it on disk
Worked examples
Starting a project repo (output from a real run; the commit hash is shortened):
git init -b main
mkdir -p data src
echo "x" > data/big.csv
echo "print(1)" > src/main.py
printf "data/\n__pycache__/\n.venv/\n" > .gitignore
git status --short --untracked-files=all
?? .gitignore
?? src/main.py
data/big.csv does not appear: it is ignored. Then:
Atomic: one logical change per commit ("Add loader for OWID CSV with error handling"), not "update" or "final version 3".
Frequent: commit every working step; a history with one giant commit on Nov 30 looks bad and suggests copied code.
Message style: imperative mood, short first line (about 50 characters), optional body explaining why.
Never commit secrets (API keys) or large data: put them in .gitignore and document how to download the data.
Check with the course
Submission: the repo may be private, but you must invite the instructor as a collaborator on GitHub (Settings → Collaborators). The instructor's GitHub username is not in the brief: check the course page / lab slides.
Exercises
easy
You edited loader.py and plots.py for two unrelated reasons. How do you make two atomic commits?
Show solution
git add loader.py
git commit -m "Handle missing values in loader"
git add plots.py
git commit -m "Add monthly temperature line plot"
medium
You accidentally committed data/ratings.csv (500 MB). How do you stop tracking it without deleting it locally?
Show solution
echo "data/" >> .gitignore
git rm --cached data/ratings.csv
git commit -m "Stop tracking raw data; document download in README"
Note: the file is still in the old commits. If it was never pushed, it is simpler to amend/reset the commit; if GitHub rejected the push (files > 100 MB are refused), you must rewrite history (e.g. git reset to before the commit, then recommit without the file).
medium
What is the difference between git pull and git clone? Between git add and git commit?
Show solution
clone copies a remote repo the first time; pull updates an existing local repo with new remote commits. add stages changes; commit records the staged snapshot in history.
exam
The deadline is the last commit before 30 Nov 2026 23:59 CET. You make a commit at 23:50 locally but push at 00:10. Explain the risk.
Show solution
A commit's date is set on your machine when you commit, and Git dates can be edited, so the instructor may also look at when the code reached GitHub. The rule says updates after the deadline are ignored; do not test the edge. Push well before the deadline (assumption: aim for the evening of Nov 29), and verify on github.com that the last commit is visible.
Self-check quiz
Common mistakes
Committing .venv/, __pycache__/, .ipynb_checkpoints/, large data or API keys.
Adding a path to .gitignore after it is already tracked (you also need git rm --cached).
Messages like "fix", "wip", "asdf".
Forgetting to push (the instructor sees only GitHub).
Remember
working dir → add → staging → commit → push to GitHub.
.gitignore: data, venv, caches, secrets.
Small, frequent, descriptive commits; invite the instructor; push early.
Why it matters. "Efficient numeric handling with NumPy" is in the official syllabus, and pandas is built on NumPy. Project criterion 5 (scientific computing) asks for numerical work with numpy or scipy.
How it is examined. Predict the output/shape: broadcasting, axis=0 vs axis=1, integer dtype truncation, slices that are views, boolean masks.
Explanation
Definition: ndarray
A NumPy array is an n-dimensional grid of values of one single dtype (e.g. int64, float64, bool), stored contiguously in memory. Attributes: shape (tuple of sizes), ndim, size, dtype. Operations are element-wise and vectorised (the loop runs in C), so they are much faster than Python loops.
import numpy as np
a = np.array([1, 2, 3, 4])
b = np.array([[1, 2, 3], [4, 5, 6]])
print(a.dtype, a.shape, b.shape, b.ndim, b.size)
print(a * 2, a + a, a ** 2, a > 2)
print([1, 2] * 2, np.array([1, 2]) * 2) # list vs array!
print(np.array([1, 2.5]).dtype, np.array([1, "a"]).dtype, np.array([True, 2]))
print(np.arange(0, 1, 0.25), np.linspace(0, 1, 5), np.zeros((2, 2)), np.ones(3, dtype=int))
Mixed types are upcast to one common dtype (int+float → float, anything+str → string: <U21 means "Unicode string of up to 21 characters"; bool+int → int). An int array stays int when you assign a float into it (silent truncation):
x = np.array([1, 2, 3])
x[0] = 9.99
print(x, x / 2, x // 2)
print(np.array([1, 2, 3]).mean(), np.array([1, 2, 3]).sum() / 2)
[9 2 3] [4.5 1. 1.5] [4 1 1]
2.0 3.0
Indexing, slicing, views vs copies
Basic slicing returns a view: a new array object that shares memory with the original (unlike Python list slices, which copy!). Boolean masks and integer-list ("fancy") indexing return copies. Use .copy() when you need independence.
[12.5 18. 25.3] 2 0.4
['ok' 'ok' 'hot' 'hot' 'ok']
[12.5 18. 25.3 30.1 10. ]
Traceback (most recent call last):
...
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
Axis
Definition: axis
axis=0 = "along the rows", i.e. the operation collapses the rows and you get one result per column. axis=1 collapses the columns: one result per row. The axis you pass is the one that disappears from the shape.
To combine arrays of different shapes, NumPy compares shapes from the right. Two dimensions are compatible if they are equal or one of them is 1 (missing dimensions count as 1). Size-1 dimensions are stretched (virtually, no copy). Otherwise: ValueError: operands could not be broadcast together.
Right-aligned: (2,3) vs (4,1,3) → (1,2,3) vs (4,1,3) → (4,2,3).
medium
Given a 2-D array of exam scores (rows = students, columns = exams), compute each student's mean, each exam's max, and the number of scores ≥ 18 per exam.
Why it matters. pandas is the core of the syllabus's data part (Wes McKinney's book is the course reference) and of project criterion 4 (data manipulation). Open exam questions on data analysis will expect pandas vocabulary.
How it is examined. Predict the output of loc/iloc selections, filters and Series arithmetic (index alignment → NaN); open questions like "how would you clean this dataset?".
Explanation
Definitions
Series: a 1-D array of values with an index (labels). Like a NumPy array + a dict.
DataFrame: a 2-D table; each column is a Series and all columns share the same row index. Think "array of JS objects", but stored column by column.
Index: the row labels (default 0..n-1). Operations between pandas objects align on labels, not on positions.
Check with the course: pandas version
The outputs below come from pandas 3.x. If the lab uses pandas 2.x, text columns show dtype: object instead of str, and some copy/view behaviour differs (pandas 3 always uses "Copy-on-Write": a selection never modifies the original). The logic of every example is the same.
import pandas as pd
import numpy as np
s = pd.Series([10, 20, 30], index=["a", "b", "c"])
print(s)
print(s["b"], s.iloc[0], s[s > 15].tolist(), s.index.tolist())
a 10
b 20
c 30
dtype: int64
20 10 [20, 30] ['a', 'b', 'c']
title year genre rating
0 Alien 1979 SciFi 8.5
1 Up 2009 Anim 8.3
2 Heat 1995 Crime NaN
3 Coco 2017 Anim 8.4
4 Rocky 1976 Drama 8.1
(5, 4) ['title', 'year', 'genre', 'rating']
title str
year int64
genre str
rating float64
dtype: object
Note: np.nan (missing value) forces the column to float64. First looks at a dataset: df.head(), df.tail(), df.info(), df.describe(), df["col"].value_counts().
['Up', 'Coco']
['Heat', 'Rocky']
['Alien']
title rating age classic
0 Alien 8.5 47 yes
3 Coco 8.4 9 no
1 Up 8.3 17 no
4 Rocky 8.1 50 yes
2 Heat NaN 31 no
NaN goes last when sorting (option na_position).
Index alignment (classic trap)
a = pd.Series([1, 2, 3], index=["x", "y", "z"])
b = pd.Series([10, 20, 30], index=["y", "z", "w"])
print(a + b)
print(a.add(b, fill_value=0))
print(a.values + b.values) # NumPy arrays: by position
w NaN
x NaN
y 12.0
z 23.0
dtype: float64
w 30.0
x 1.0
y 12.0
z 23.0
dtype: float64
[11 22 33]
The result has the union of the labels (sorted); labels present in only one Series give NaN. The int values become float because of NaN.
city 1
temp 2
dtype: int64
12.5 25.0 2 4
city temp
0 Milan 14.0
city temp
0 Milan 14.0
1 Rome 12.5
2 unknown 11.0
3 Turin 12.5
pandas aggregations skip NaN by default (mean of [14, 11] = 12.5), unlike NumPy's np.mean on an array with NaN. count() counts non-missing values; len counts rows.
Reading and writing CSV
df.to_csv("movies.csv", index=False) # index=False: do not write 0..n-1 as a column
back = pd.read_csv("movies.csv")
print(back.shape, back["rating"].isna().sum())
sub = pd.read_csv("movies.csv", usecols=["title", "year"], nrows=2)
print(sub)
try:
pd.read_csv("does_not_exist.csv")
except FileNotFoundError as e:
print("cannot load:", type(e).__name__)
(5, 6) 1
title year
0 Alien 1979
1 Up 2009
cannot load: FileNotFoundError
loc[20:30] includes 30; iloc[1:3] is positions 1 and 2. Here both give the same rows.
medium
Replace missing ratings in df with the median rating of the same genre if possible, otherwise with the overall median. (Hint: groupby(...).transform("median"), see A3.13.)
"Crime" has no other rating, so the overall median (8.35) is used.
exam
Predict the output:
p = pd.Series([1, 2, 3])
q = pd.Series([10, 20, 30], index=[1, 2, 3])
r = p + q
print(r.tolist(), r.count(), r.sum())
Show solution
[nan, 12.0, 23.0, nan] 2 35.0
Alignment on labels 0..3: only 1 and 2 are in both (2+10, 3+20). sum skips NaN.
exam
Open question: you load a CSV of 50,000 weather records and see that temp has dtype object/str instead of float. List possible causes and how you would investigate and fix them.
Show solution
Causes: decimal commas ("14,5"), placeholders like "?", "N/A" or "-", units in the cell ("14 °C"), stray spaces, a repeated header row. Investigate: pd.to_numeric(df["temp"], errors="coerce") and look at the rows that became NaN (df[converted.isna() & df["temp"].notna()]), or value_counts() of non-numeric strings. Fix at load time with decimal=",", na_values=[...], or clean with .str.replace then convert; report how many rows were affected and how you handled them (drop vs impute), because that is a design choice the examiner may ask about.
Self-check quiz
Common mistakes
loc slice includes the end label; iloc slice excludes it.
Using and/or in masks: use &, |, ~ with parentheses.
Chained assignment df[df.x > 0]["y"] = 1: does not modify df (warning in pandas 2, silently nothing in pandas 3). Use df.loc[df.x > 0, "y"] = 1.
Forgetting index=False in to_csv (an extra "Unnamed: 0" column appears on reload).
Expecting Series arithmetic to work by position (it aligns labels).
Testing missing values with == np.nan: use isna().
Remember
DataFrame = columns of Series sharing one index.
loc = labels (end included), iloc = positions (end excluded).
Masks with & | ~; isin, query.
Arithmetic aligns on index → NaN for non-matching labels.
Why it matters. "Clean, transform, reshape and prepare data with pandas" is project criterion 4, and grouping/joining is the pandas version of SQL GROUP BY and JOIN (module A1), so the ideas transfer in both directions.
How it is examined. Predict the result of a groupby or merge on a tiny table (number of rows, NaN after a left join); open questions: "which operations would you use to compute X?".
Explanation
Definition: split-apply-combine
df.groupby(keys)splits rows into groups by the values of keys, applies a function to each group (aggregation like mean, transformation, filter), and combines the results. The group keys become the index of the result (use as_index=False or .reset_index() to keep them as columns).
city
Milan 30
Rome 10
Turin 5
Name: units, dtype: int64
n total avg_price
city
Milan 3 61.6 2.07
Rome 2 26.5 2.75
Turin 1 7.5 1.50
city month units
0 Milan Feb 12
1 Milan Jan 18
2 Rome Feb 3
3 Rome Jan 7
4 Turin Jan 5
SQL equivalent of the second query: SELECT city, COUNT(*) AS n, SUM(revenue) AS total, AVG(price) AS avg_price FROM sales GROUP BY city. The equivalent of HAVING is filtering the aggregated result, e.g. g[g["total"] > 20].
transform vs agg
agg returns one row per group; transform returns a result aligned with the original rows (same length), useful to add group statistics as a column.
city units city_avg_units share
0 Milan 10 10.0 0.33
1 Rome 7 5.0 0.70
2 Milan 12 10.0 0.40
3 Rome 3 5.0 0.30
4 Turin 5 5.0 1.00
5 Milan 8 10.0 0.27
merge (join) and concat
pd.merge(left, right, on="key", how=...) works like SQL joins: how="inner" (default: only matching keys), "left", "right", "outer" (all keys, NaN where missing). If a key appears several times on both sides, you get all combinations. df.join(other) joins on the index. pd.concat([df1, df2]) stacks rows (or columns with axis=1).
mid title stars
0 1 Alien 5
1 1 Alien 4
2 2 Up 3
mid title stars
0 1 Alien 5.0
1 1 Alien 4.0
2 2 Up 3.0
3 3 Heat NaN
5
(4, 2)
apply and map
Series.map(func_or_dict) transforms each value; Series.apply(func) is similar; df.apply(func, axis=1) calls func on each row (slow: prefer vectorised operations when possible). The .str and .dt accessors give vectorised string and date methods.
mid title code len kind label
0 1 Alien ALI 5 scifi Alien#1
1 2 Up UP 2 anim Up#2
2 3 Heat HEA 4 NaN Heat#3
['Monday', 'Tuesday'] 1
Reshaping: pivot_table and melt
Definition: long vs wide format
Long (tidy) format: one row per observation, e.g. (city, month, units). Wide format: one row per entity, one column per variable value, e.g. a column per month. pivot_table goes long → wide (with an aggregation for duplicates); melt goes wide → long. Plotting libraries such as seaborn prefer long format.
title genre score vs_genre
0 A x 7.1 -0.5
1 B y 8.0 0.0
2 C x 9.2 1.6
3 D x 6.5 -1.1
4 E y 8.0 0.0
exam
Predict the output:
d = pd.DataFrame({"g": ["a", "b", "a", "b", "a"], "v": [1, 2, 3, np.nan, 5]})
r = d.groupby("g")["v"].agg(["count", "size", "mean"])
print(r)
Show solution
count size mean
g
a 3 3 3.0
b 1 2 2.0
count skips NaN, size counts rows; the mean of group b ignores NaN.
exam
Open question: you have a table temps(city, date, temp) with daily data for 5 cities over 30 years. Describe (with pandas operations) how to get, for each city, the average temperature per decade, and how to present it.
Show solution
1) Parse dates: pd.to_datetime(df["date"]) (or parse_dates in read_csv). 2) Create decade = df["date"].dt.year // 10 * 10. 3) Handle missing temps (check isna().sum(); aggregations skip NaN, but report the coverage per group, e.g. with count). 4) df.groupby(["city", "decade"])["temp"].mean(). 5) .unstack("city") or pivot_table(index="decade", columns="city", values="temp") for a wide table. 6) Present as a line plot (x = decade, one line per city) and the table; comment on trends, noting that daily means per decade smooth out seasonality.
Self-check quiz
Common mistakes
Forgetting that group keys become the index (then df["city"] fails): use reset_index().
Using agg when you need a per-row column (transform).
Merging on keys with duplicates and getting more rows than expected (many-to-many). Check with validate="one_to_many".
Using apply(axis=1) for simple arithmetic that could be vectorised.
pivot (no aggregation) fails with duplicate index/column pairs; use pivot_table.
Remember
groupby(k)[col].agg(...) → one row per group; transform → same length as df.
map for values, .str/.dt accessors, apply(axis=1) for rows.
pivot_table: long → wide; melt: wide → long.
A3.14 Data analysis and visualisation (describe, matplotlib, seaborn)
Why it matters. The written module has open-ended questions on data analysis, and the project has criteria for visualisation (6) and presentation (7). You must be able to explain which summary or plot answers a question, and why.
How it is examined. Open questions ("describe how you would explore this dataset", "which plot would you use to compare X across groups?", "how do you detect outliers?"), plus reading the output of describe().
Explanation
Exploratory data analysis (EDA) checklist
Structure: shape, dtypes, head(), info(). Are the types right (dates, numbers)?
hours score
count 200.00 200.00
mean 5.40 72.79
std 3.02 18.33
min 0.00 37.00
25% 2.90 60.92
50% 5.60 72.10
75% 8.20 83.32
max 10.00 250.00
group
A 95
B 61
C 44
Name: count, dtype: int64
{'A': 0.48, 'B': 0.3, 'C': 0.22}
Read describe(): the max of score (250) is far from the 75% quartile, a hint of an outlier. Mean vs median (50%) tells you about skewness: mean much larger than median suggests right skew.
Definition: IQR outlier rule
With quartiles \(Q_1\), \(Q_3\) and \(IQR = Q_3 - Q_1\), a value is flagged as an outlier if it is below \(Q_1 - 1.5\,IQR\) or above \(Q_3 + 1.5\,IQR\). This is the rule used for the whiskers of a box plot.
hours group score
3 0.2 A 250.0
0.894 0.561
mean median std
group
A 72.0 72.6 13.8
B 72.3 71.1 13.2
C 71.0 71.4 12.7
A single outlier lowered the Pearson correlation (from 0.894 without it to 0.561 with it, here): always check plots, not only numbers. Pearson correlation measures linear association, between -1 and 1; use method="spearman" for monotonic, rank-based association.
Choosing a plot
Question
Plot
matplotlib / seaborn
Distribution of one numeric variable
histogram, box plot
ax.hist, sns.histplot, sns.boxplot
Relationship of two numeric variables
scatter (+ trend line)
ax.scatter, sns.scatterplot, sns.regplot
Evolution over time
line plot
ax.plot, sns.lineplot
Compare a quantity across categories
bar chart (or box plot for distributions)
ax.bar, sns.barplot
Many pairwise correlations
heatmap
sns.heatmap(df.corr(numeric_only=True))
matplotlib: Figure and Axes
A Figure is the whole image; an Axes is one plot inside it (with its x/y axes). The object-oriented style fig, ax = plt.subplots() is clearer than the global plt.plot style, especially with several panels. A good plot has a title, axis labels with units, a legend if there are several series, and no misleading axis cuts.
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 3, figsize=(12, 3.5))
axes[0].hist(clean["score"], bins=20, edgecolor="white")
axes[0].set(title="Score distribution", xlabel="score", ylabel="students")
for g, part in clean.groupby("group"):
axes[1].scatter(part["hours"], part["score"], s=10, label=g)
axes[1].set(title="Study hours vs score", xlabel="hours per week", ylabel="score")
axes[1].legend(title="group")
means = clean.groupby("group")["score"].mean()
axes[2].bar(means.index, means.values)
axes[2].set(title="Mean score by group", ylabel="mean score")
fig.tight_layout()
fig.savefig("eda.png", dpi=120) # in a notebook: plt.show() or just display fig
print(type(fig).__name__, axes.shape)
Figure (3,)
The same with seaborn (built on matplotlib, works directly with DataFrames in long format):
import seaborn as sns
fig, ax = plt.subplots(figsize=(5, 3.5))
sns.boxplot(data=clean, x="group", y="score", ax=ax)
ax.set_title("Score by group")
fig.savefig("box.png")
print(ax.get_xlabel(), ax.get_ylabel())
passed False True
group
A 0.41 0.59
B 0.48 0.52
C 0.45 0.55
Exercises
easy
Which plot would you use to show (a) how global CO2 emissions changed from 1950 to 2020, (b) whether GDP per capita is related to energy use per capita across countries, (c) the distribution of book ratings?
Show solution
(a) Line plot (time on x). (b) Scatter plot, probably with log scales because GDP is very skewed; maybe colour by continent. (c) Histogram (or box plot if comparing genres).
medium
Given describe() output with mean = 3.9, 50% = 2.1, max = 97 for a column "minutes late", what do you conclude and what would you do next?
Show solution
Strong right skew (mean much larger than median) with extreme values. Next: histogram (maybe log scale), check the extreme rows (errors or genuine delays?), report the median and quantiles rather than the mean, and decide explicitly whether to keep, cap or remove outliers (and say why).
medium
Write code that flags outliers in any numeric Series with the IQR rule and returns a boolean mask. Test on [1, 2, 2, 3, 3, 3, 4, 40].
Show solution
def iqr_outliers(s, k=1.5):
"""Return a boolean Series: True where s is outside [Q1 - k*IQR, Q3 + k*IQR]."""
q1, q3 = s.quantile(0.25), s.quantile(0.75)
iqr = q3 - q1
return (s < q1 - k * iqr) | (s > q3 + k * iqr)
s = pd.Series([1, 2, 2, 3, 3, 3, 4, 40])
print(s[iqr_outliers(s)].tolist(), s.quantile(0.25), s.quantile(0.75))
[40] 2.0 3.25
exam
Open question (exam style): "A dataset contains the daily number of bike rentals and the daily temperature for two years. Describe an analysis to study whether temperature affects rentals. Mention the Python tools you would use and two possible pitfalls."
Show solution
Model answer. Load with pd.read_csv(..., parse_dates=["date"]); check types, missing days, duplicates. Describe both variables (describe, histograms). Plot rentals over time (line) to see seasonality and trend. Scatter plot rentals vs temperature (maybe coloured by season / weekday) and compute Pearson and Spearman correlation (df.corr()). Group temperatures into bins (pd.cut) and compare mean rentals per bin (bar or box plot). Optionally fit a simple linear regression (np.polyfit or scikit-learn) to quantify the effect.
Pitfalls: confounders (season, holidays, rain also change rentals: correlation is not causation); non-linear relationship (very hot days may reduce rentals); outliers or data errors; days with missing data.
Why it matters. The official syllabus ends with "performance metrics, classification and clustering algorithms; scikit-learn". Expect open questions such as "compute precision and recall from this confusion matrix" or "why do we split into train and test sets?".
How it is examined. Open data-analysis questions and code-fragment quizzes on the fit/predict API and metrics. (The project does not require a machine learning model.)
Explanation
Definitions
Supervised learning: learn from examples with known answers (labels \(y\)). Classification: \(y\) is a category (spam / not spam). (Regression: \(y\) is a number.)
Unsupervised learning: no labels; find structure. Clustering: group similar observations.
Features \(X\): a 2-D array/DataFrame of shape (n_samples, n_features). Target \(y\): 1-D, length n_samples.
The estimator API (same for every model)
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
X, y = load_iris(return_X_y=True, as_frame=True) # 150 flowers, 4 features, 3 species
print(X.shape, y.value_counts().to_dict())
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y)
model = KNeighborsClassifier(n_neighbors=5) # 1. choose model + hyperparameters
model.fit(X_train, y_train) # 2. learn from training data
y_pred = model.predict(X_test) # 3. predict unseen data
print(X_train.shape, X_test.shape, y_pred[:8])
print(round(model.score(X_test, y_test), 3)) # accuracy for classifiers
Why split? A model evaluated on the data it was trained on looks better than it is (overfitting: it memorises noise). The test set estimates performance on new data. stratify=y keeps class proportions equal in both parts; random_state makes the split reproducible. Transformers (scalers) follow the same API: fit on training data only, then transform both sets; fitting on the whole dataset leaks test information (data leakage).
Three classifiers to know
Model
Idea
Key hyperparameter
Note
k-nearest neighbours (KNeighborsClassifier)
predict the majority class among the k closest training points
n_neighbors (small k → overfit, large k → underfit)
distance-based: scale features
Logistic regression (LogisticRegression)
linear model that outputs class probabilities via the sigmoid \(\sigma(z) = 1/(1+e^{-z})\)
C (inverse regularisation)
predict_proba; interpretable coefficients
Decision tree (DecisionTreeClassifier)
sequence of if/else splits on features chosen to make groups pure (Gini / entropy)
max_depth
no scaling needed; deep trees overfit
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
models = {
"kNN (scaled)": make_pipeline(StandardScaler(), KNeighborsClassifier(5)),
"logistic": make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000)),
"tree depth 2": DecisionTreeClassifier(max_depth=2, random_state=0),
"tree full": DecisionTreeClassifier(random_state=0),
}
for name, m in models.items():
m.fit(X_train, y_train)
print(f"{name:13s} train={m.score(X_train, y_train):.3f} test={m.score(X_test, y_test):.3f}")
kNN (scaled) train=0.981 test=0.911
logistic train=0.981 test=0.911
tree depth 2 train=0.971 test=0.889
tree full train=1.000 test=0.978
The full tree is perfect on the training set (1.000): a perfect training score is a warning sign of overfitting, so always compare with the test score. Here it happens to do well on the test set too, but with only 45 test flowers one flower = 0.022 of accuracy, so differences of 1-3 flowers between models are mostly noise (use cross-validation for a fairer comparison, exercise 3). A Pipeline chains the scaler and the model so that fit applies the scaler correctly (fit on train only).
Performance metrics for classification
Definition: Confusion matrix (binary)
Rows = true class, columns = predicted class (scikit-learn convention). With "positive" = class 1: TP (true positives), FN (positives predicted negative), FP (negatives predicted positive), TN.
Precision: of the items I flagged positive, how many really are? Recall (sensitivity): of the real positives, how many did I find? \(F_1\) is their harmonic mean.
With 99% negatives (e.g. fraud), a model that always says "negative" has 99% accuracy and 0 recall. For imbalanced classes look at precision, recall, F1 and the confusion matrix. Which error is worse (FP or FN) depends on the application: medical screening wants high recall; a spam filter wants high precision.
For multi-class problems, macro avg is the plain average over classes; weighted avg weights each class by its support (number of true instances).
Clustering: k-means and silhouette
Definition: k-means
Choose \(k\). Initialise \(k\) centroids; repeat: (1) assign each point to the nearest centroid, (2) move each centroid to the mean of its points; stop when assignments no longer change. It minimises the within-cluster sum of squares (inertia_). Results depend on initialisation (n_init, random_state) and on feature scale; clusters are assumed roughly spherical.
Definition: Silhouette score
For a point \(i\): \(a\) = mean distance to points of its own cluster, \(b\) = mean distance to points of the nearest other cluster, \(s_i = (b - a)/\max(a, b) \in [-1, 1]\). The silhouette score is the mean of \(s_i\): near 1 = well separated, near 0 = overlapping, negative = probably wrong cluster. Used to choose \(k\).
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
Xs = StandardScaler().fit_transform(X) # unsupervised: no y used
for k in [2, 3, 4, 5]:
km = KMeans(n_clusters=k, n_init=10, random_state=0).fit(Xs)
print(k, round(km.inertia_, 1), round(silhouette_score(Xs, km.labels_), 3))
km3 = KMeans(n_clusters=3, n_init=10, random_state=0).fit(Xs)
print(pd.crosstab(km3.labels_, y, rownames=["cluster"], colnames=["species"]))
Inertia always decreases with \(k\) (look for an "elbow"); silhouette here prefers \(k=2\) (two iris species overlap a lot), although the true number of species is 3. Clustering finds structure in the features, not necessarily the labels you have in mind.
Worked examples
Reading a small decision tree and predicting one new flower (with class probabilities):
from sklearn.tree import export_text
tree = models["tree depth 2"]
print(export_text(tree, feature_names=list(X.columns)))
new = pd.DataFrame([[6.0, 2.9, 4.5, 1.5]], columns=X.columns)
print(tree.predict(new), models["logistic"].predict_proba(new).round(3))
Classes are 0 = setosa, 1 = versicolor, 2 = virginica. The tree's first split on petal width separates setosa perfectly; the logistic model gives probability 0.757 to versicolor and 0.225 to virginica.
Exercises
easy
A spam filter checks 100 emails: 20 are spam. It flags 18 emails, of which 15 are spam. Compute precision, recall, accuracy, F1.
Why must you scale features before kNN or k-means, but not before a decision tree?
Show solution
kNN and k-means use distances: a feature measured in large units (income in euros) dominates one in small units (age in years). Scaling (e.g. StandardScaler: mean 0, std 1) gives every feature equal weight. A tree splits on one feature at a time with thresholds, which are unaffected by monotonic rescaling.
medium
Find the best n_neighbors in 1..15 for scaled kNN on iris with 5-fold cross-validation on the training set.
Show solution
from sklearn.model_selection import cross_val_score
scores = {k: cross_val_score(make_pipeline(StandardScaler(), KNeighborsClassifier(k)),
X_train, y_train, cv=5).mean() for k in range(1, 16)}
best = max(scores, key=scores.get)
print(best, round(scores[best], 3))
14 0.971
Choose hyperparameters with cross-validation on the training data; use the test set only once, at the end.
exam
Predict the output:
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(["a", "b", "a", "a", "b"], ["a", "a", "a", "b", "b"])
print(cm, cm.trace() / cm.sum())
Show solution
[[2 1]
[1 1]] 0.6
Labels sorted: a, b. True a predicted a: 2; true a predicted b: 1; true b predicted a: 1; true b predicted b: 1. The diagonal sum / total = accuracy 3/5.
exam
Open question: "Explain how k-means works and how you would choose k. Give one limitation."
Show solution
Algorithm as in the definition (assign to nearest centroid / recompute means, until stable). Choose k with the elbow of inertia vs k, and/or the maximum silhouette score, plus domain knowledge. Limitations: k must be fixed in advance; sensitive to initialisation (use n_init), to outliers and to feature scales; assumes convex, similar-size clusters; only numeric features.
Self-check quiz
Common mistakes
Evaluating on the training set.
Fitting the scaler on all data before splitting (leakage).
Passing a 1-D feature array: X must be 2-D (use df[["col"]] or .reshape(-1, 1)).
Swapping precision and recall; reading the confusion matrix with predicted classes as rows.
Using accuracy alone on imbalanced classes.
Interpreting cluster numbers as meaningful labels (cluster 0 is not "class 0").
Remember
model = Model(params); model.fit(X_train, y_train); model.predict(X_test).
train_test_split(..., test_size, random_state, stratify); scale inside a pipeline.
Precision = TP/(TP+FP), recall = TP/(TP+FN), F1 = harmonic mean; CM rows = truth.
k-means: assign/update loop; choose k by elbow/silhouette; silhouette ∈ [-1, 1].
A3.16 Streamlit dashboards and coding with LLMs
Why it matters. The lab sequence ends with a Streamlit dashboard, and the project may be presented as a Streamlit app ("optional but appreciated"). The course also has a lecture on coding with LLMs, and the project's AI policy decides whether you pass the discussion.
How it is examined. Mainly in the project evaluation (your app, and questions on your code and choices). Possibly conceptual questions in the written exam (check).
Explanation
Streamlit in five ideas
A Streamlit app is a normal Python script (app.py) run with streamlit run app.py; it opens in the browser.
The script runs top to bottom on every interaction (every widget change triggers a full rerun). Think "React re-render of the whole page", but the state is recomputed by re-executing the script.
Widgets return their current value: year = st.slider("Year", 1950, 2020, 2000).
Expensive work (loading a CSV, calling an API) must be cached with @st.cache_data, otherwise it repeats at every rerun.
st.session_state keeps values across reruns (like a component state) when you need it.
# app.py (run with: streamlit run app.py)
import streamlit as st
import matplotlib.pyplot as plt
from weather.data import load_city_data # YOUR package: the app only orchestrates
from weather.analysis import yearly_means
st.set_page_config(page_title="Weather Report", layout="wide")
st.title("Weather Report: 80 years of temperatures")
st.markdown("Data: Open-Meteo Historical Weather API.")
@st.cache_data
def get_data(city):
return load_city_data(city) # runs once per city, then cached
city = st.sidebar.selectbox("City", ["Milan", "Rome", "Oslo"])
years = st.sidebar.slider("Years", 1940, 2025, (1980, 2025))
df = get_data(city)
df = df[(df["year"] >= years[0]) & (df["year"] <= years[1])]
col1, col2 = st.columns(2)
col1.metric("Mean temperature", f"{df['temp'].mean():.1f} °C")
col2.metric("Days of data", len(df))
fig, ax = plt.subplots()
yearly_means(df).plot(ax=ax)
ax.set(xlabel="year", ylabel="°C")
st.pyplot(fig)
st.dataframe(df.head(20))
Function
Shows
st.title, st.header, st.markdown, st.write
text (st.write renders almost anything, including DataFrames)
Both are accepted; Streamlit is "optional but appreciated". The rule that matters for both: the notebook/app must import and use your project code; it should not contain all the logic. A clean notebook that tells a story is better than a half-working app. If you know React, Streamlit is quick to learn, but budget a day for it.
Coding with LLMs: the course AI policy
The policy (from the project rules)
Allowed: using LLM tools (ChatGPT, Claude, Gemini) as assistants: asking for alternative approaches, finding issues, template (boilerplate) code.
Not allowed: generating whole scripts or the core logic.
You must test and validate any generated code and fully understand every part: explaining your code and design choices in the evaluation is mandatory for passing.
Good uses: "explain this pandas error", "what is the difference between merge and join?", "review my function for edge cases", "suggest a .gitignore for a Python project", "how do I cache data in Streamlit?". Bad uses: "write my analysis of Eurovision voting", pasting the whole assignment, accepting code you cannot explain line by line.
Practical rules: verify every claim by running code (LLMs invent function arguments, especially for pandas versions); keep commits small so the history shows your progress; write the docstrings yourself (it forces understanding); keep a short log of what you asked an assistant, so you can answer "did you use AI, and how?" honestly.
Worked examples
A loader that fails gracefully in Streamlit (criterion 3: handle loading errors cleanly):
import streamlit as st
import pandas as pd
@st.cache_data
def load(path):
return pd.read_csv(path, parse_dates=["date"])
try:
df = load("data/weather_milan.csv")
except FileNotFoundError:
st.error("Data file not found. Run `python scripts/download_data.py` first (see README).")
st.stop()
st.success(f"Loaded {len(df):,} rows")
The same pure logic is testable without Streamlit: keep functions such as yearly_means(df) in your package and call them from both the app and a quick test.
import pandas as pd
def yearly_means(df):
"""Return a Series: mean of the 'temp' column per 'year'."""
return df.groupby("year")["temp"].mean()
demo = pd.DataFrame({"year": [2020, 2020, 2021], "temp": [10.0, 14.0, 13.0]})
print(yearly_means(demo).to_dict())
{2020: 12.0, 2021: 13.0}
Exercises
easy
Why does a Streamlit app that loads a 200 MB CSV feel slow at every click, and how do you fix it?
Show solution
The whole script re-runs at every widget interaction, so the CSV is re-read each time. Put the loading in a function decorated with @st.cache_data (and/or load a smaller preprocessed file, e.g. Parquet or a filtered CSV).
medium
Sketch (no core logic) the structure of a Streamlit app for "Douze Points" with a sidebar to choose a year range and a country, and three sections.
Show solution
Sidebar: st.slider for the year range, st.selectbox for the country. Section 1 "Overview": st.metrics (contests, countries, total points). Section 2 "Who votes for whom": a heatmap from a function in your package (e.g. voting_matrix(df, years)). Section 3 "Country profile": points received over time (st.line_chart), top friends. The functions (voting_matrix, ...) live in src/ with docstrings; the app only calls them.
medium
You asked an LLM for help and it suggested df.groupby("country").mean(numeric=True). It fails. What do you do?
Show solution
Check the documentation / help(pd.core.groupby.DataFrameGroupBy.mean): the real parameter is numeric_only=True. Test the fix on a tiny DataFrame whose result you can compute by hand. This is exactly the "test and validate" requirement: LLMs often invent or misremember parameters.
exam
In the evaluation the examiner asks: "Did you use AI tools? Show me one place where you did and explain the code." Prepare a good answer structure.
Show solution
Be honest and specific: "I used it to (a) understand an error with read_csv encodings and (b) get a .gitignore template. The loading function is mine: here is why I used errors='coerce' and how many rows it affected." Then walk through the code line by line, explain alternatives you considered and why you rejected them, and show the commit where you added it.
Self-check quiz
Common mistakes
Putting all the analysis inside app.py or the notebook (violates the "import and use the project code" rule).
No caching for data loading in Streamlit.
Committing API keys used in the app (use environment variables or .streamlit/secrets.toml, which must be in .gitignore).
Using LLM-generated core code you cannot explain: this can make you fail the project discussion.
Remember
Streamlit = script re-run on every interaction; widgets return values; @st.cache_data.
App/notebook orchestrates; logic lives in your modules.
AI: assistant yes, core logic no; test everything; explain everything.
A3.17 The Python project: rules, criteria, templates, proposals, presentation
Why it matters. The project replaces the 30-minute written Python module in the December session. It is the only Python task with a hard deadline: 30 November 2026, 23:59 CET (last GitHub commit), no extensions.
How it is examined. A short presentation (about 5-10 minutes) on 1, 2 or 3 December, then questions on your project code and on the topics of the course (so A3.1-A3.16 still matter!). Missing the discussion means the project is rejected.
Explanation
The rules at a glance
Rule
Detail
Who
Individual (no groups). You may propose your own idea by email to prof. Montanelli and Dr. Picascia.
Deadline
30 Nov 2026, 23:59 CET, based on the last GitHub commit. Firm; later updates are ignored.
Submission
GitHub repository (may be private) + invite the instructor as a collaborator.
Registration
Online form open "for a couple of weeks in November"; exact dates check (NOT STATED; announced during the course).
Evaluation
Dec 1 (14:30-16:30), Dec 2 (14:30-18:30) or Dec 3 (16:30-18:30). Slot assigned after registration closes; swaps with colleagues possible (tell the instructor). Probably in presence, possibly online.
Format
Jupyter notebook or Streamlit app (optional but appreciated). It must import and use the project code. Example of a complete project: vgrank.
Grade
Replaces the Python module result. If not satisfied, reject immediately and take the written Python exam in an ordinary session from January 2027.
Incompatibility
In December you cannot take both the project and the written Python exam. You can still take R and Data Management written in December.
FAQ
Pick a project you like, not an "easy" one; you may change parts of a proposal if the criteria are met; plotly, scipy and scikit-learn are allowed; a subset of a large dataset is fine; do not commit big data (ignore it, explain or script the download); no ML model required; code quality matters most; quality and appropriateness of analysis matter more than quantity.
Check with the course
NOT STATED: registration form dates and link; the instructor's GitHub username to invite; how the project grade combines with R and DM; the exact plane range and customs time for "Island Hopper" and other numeric details of each proposal (read the official project README for each trace).
A suggested timeline (assumption: you start now, 30 Oct)
When
Goal
Oct 30 - Nov 2
Choose a proposal; explore the data in a scratch notebook; create the repo with README skeleton, .gitignore, requirements.txt; invite the instructor.
Nov 3 - 9
Data loading module with clean error handling (criterion 3); download script; first cleaning functions.
Nov 10 - 16
Data manipulation with pandas (4) and numerical/statistical work with NumPy/SciPy (5). Register when the form opens (check dates).
Nov 17 - 23
Plots (6); the notebook or Streamlit app that tells the story (7).
Presentation: [ ] the notebook/app guides the audience: question → data → method → results → conclusions; markdown text between code cells.
Repository structure template
my-project/
├── README.md
├── requirements.txt
├── .gitignore
├── data/ # ignored by git (raw/ and processed/)
│ └── .gitkeep # optional: keep the empty folder
├── scripts/
│ └── download_data.py # reproducible download of the data
├── src/
│ └── myproject/
│ ├── __init__.py
│ ├── io.py # loading + saving, error handling
│ ├── cleaning.py # pandas transformations
│ ├── analysis.py # numpy/scipy computations
│ └── plots.py # functions that return matplotlib figures
├── notebooks/
│ └── report.ipynb # OR app.py for Streamlit: imports from myproject
└── tests/ # optional but a plus: small checks of your functions
└── test_cleaning.py
To make import myproject work from the notebook, either install the package in editable mode (pip install -e . with a minimal pyproject.toml), or run Jupyter from the repo root and add src to the path. Explain your choice in the README.
README template
# Project title
One-paragraph summary: the question you answer and the main finding.
## Data
- Source: name + link (+ licence), date of download.
- Size / subset used and why.
- How to get it: `python scripts/download_data.py` (data is not in the repo).
## Libraries
pandas, numpy, scipy, matplotlib, seaborn (exact versions in requirements.txt).
## Setup
```bash
git clone https://github.com/<you>/<repo>.git
cd <repo>
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python scripts/download_data.py
```
## Run
- Notebook: `jupyter lab notebooks/report.ipynb`
- or app: `streamlit run app.py`
## Project structure
Short description of each module in src/.
## Results
2-3 bullet points + one key figure.
## Author and AI use
Name, course, academic year. How AI assistants were used (if at all).
.gitignore and requirements.txt templates
# .gitignore
data/
*.csv
*.parquet
.venv/
venv/
__pycache__/
*.pyc
.ipynb_checkpoints/
.DS_Store
.env
.streamlit/secrets.toml
cache/ # e.g. FastF1 or API caches
# requirements.txt (generate with: pip freeze > requirements.txt, then keep only what you use)
# example versions from this site's test environment - use YOUR versions
numpy==2.5.4
pandas==3.1.0
scipy==1.18.1
matplotlib==3.11.2
seaborn==0.13.2
requests # pin it too, e.g. requests==<your version>
streamlit # only if you build an app; pin it too
A docstring template (structure only; the body is yours):
def load_city_weather(city, start, end, cache_dir="data/raw"):
"""Load daily weather for one city, downloading it if not cached.
Parameters
----------
city : str
City name, e.g. "Milan".
start, end : str
Dates in ISO format "YYYY-MM-DD" (inclusive).
cache_dir : str, optional
Folder where downloaded files are stored.
Returns
-------
pandas.DataFrame
One row per day with columns date, temp_mean, precipitation.
Raises
------
ValueError
If the city is unknown.
"""
...
print(load_city_weather.__doc__.splitlines()[0])
Load daily weather for one city, downloading it if not cached.
The 8 proposals: tips (no solutions)
Proposal
Data
Watch out for
Ideas that hit the criteria
1. Powering the Planet
Our World in Data energy and CO2 CSVs (GitHub); optionally World Bank API
Rows for aggregates (World, continents, income groups) mixed with countries: filter them; totals vs per-capita; many missing early years
Energy mix over time (stacked area), GDP vs CO2 "decoupling", growth rates and trends with NumPy/SciPy, comparing per-capita figures
2. Weather Report
Open-Meteo Historical Weather API (free, from 1940) + its geocoding for coordinates
API limits: download once, cache files in data/ (ignored); handle network errors; long ranges may need several requests
Trend per decade (linear regression with SciPy), anomalies vs a baseline period, extremes (hot days per year), seasonality plots, city comparison
3. Around the Globe Quiz
World countries dataset; World Bank API
It is a program, not only an analysis: still cover pandas, NumPy and plots; reproducible randomness (seed); four distinct answers
Classes (Question, Quiz) with docstrings; difficulty of distractors from numeric similarity; difficulty-weighted score; plots of score statistics
4. Page Turners
goodbooks-10k (10,000 books, 6 million ratings, tags)
Ratings file is large: do not commit, maybe sample; tags are noisy ("to-read"); join keys differ between files (check the dataset docs)
Rating distributions, popularity vs average rating, genre from tags, rating-count-aware rankings
5. Box, Box
FastF1 library (lap timing)
Enable the FastF1 cache folder and ignore it; lap times are timedeltas; pit-stop / safety-car laps distort averages
Stint and tyre degradation analysis (slopes with NumPy), teammate comparison, lap-time distributions
6. Douze Points
Eurovision voting history since 1956
Scoring rules changed over the years (normalise points); countries appear/disappear or change name; finals vs semi-finals
Do not rank by naive average ("How Not To Sort By Average Rating"): few reviews make extreme averages; check how the data encodes reviews without a rating
A ranking that accounts for the number of ratings (justify and explain the formula yourself), comparison with naive ranking, nutrition/ingredient analysis
8. Island Hopper
OurAirports dataset
Filter airport types/closed airports; pairwise distances for many airports are expensive (vectorise, prune); the cost has three parts (1 h per 500 km, 1 h per refuel stop, customs between countries: check values in the README)
Great-circle distances with NumPy, graph shortest path (your own implementation is best for the discussion), farthest airport within 10 stops, map plots
Tip: choosing (you are a frontend developer)
Choose the topic you find most interesting (FAQ). With a JS background, proposals with an API (2, 5) or with algorithmic logic (3, 8) may feel natural; but the criteria reward pandas manipulation, NumPy/SciPy computation and clear plots just as much, so whatever you choose, make sure there is a real tabular dataset to clean and analyse. A Streamlit app is a good way to show your strengths, if the analysis is solid first.
Presentation outline (5-10 minutes)
Question (30 s): what you wanted to find out and why.
Data (1 min): source, size, how it is loaded (show the loading function and its error handling).
Preparation (1-2 min): the main cleaning/reshaping decisions and their effect (e.g. "removed 3% of rows with missing temperature").
Analysis and results (3 min): 2-3 key plots, each with one sentence of conclusion; the NumPy/SciPy computation behind them.
Code tour (1-2 min): repository structure, one module, how the notebook/app imports it, commit history.
Limits and next steps (30 s).
Likely examiner questions
"Walk me through this function. What does it return? What happens if the file is missing / the API is down?"
"Why did you use a left join here? How many rows did you lose or gain?"
"Why did you drop (or fill) these missing values? What would change otherwise?"
"What does axis=1 mean here? Is this a view or a copy? What is the difference between loc and iloc?"
"Why this plot type? What does this figure show?"
"Why did you create this class? What are its attributes and methods?"
"Show me the commit where you added X." / "How did you use AI tools?"
Course questions: mutable default arguments, aliasing, class vs instance attributes, broadcasting, groupby, precision vs recall...
AI policy (repeat, it decides the outcome)
LLMs are allowed as assistants (alternative approaches, finding issues, template code), not to generate whole scripts or the core logic. Test and validate any generated code and fully understand every part: explaining your code and design choices is mandatory for passing. That is why this page gives structure and checklists, but not the analysis code of any proposal.
Worked examples
Bad vs good commit history (criterion 1):
# bad
a1b2c3d final
d4e5f6a update
0718293 asdf
# good
3c4d5e6 Add README with data source and setup instructions
7f8a9b0 Add loader for OWID energy CSV with FileNotFoundError handling
1a2b3c4 Filter out aggregate regions (World, continents) in cleaning
5d6e7f8 Compute yearly growth rates of renewables with numpy
9a0b1c2 Add stacked area plot of energy mix per country
Exercises
easy
Which three files should be in the root of your repository from the very first commit, and why?
Show solution
README.md (criterion 1: what, data, libraries, setup/run), .gitignore (so data/venv are never committed, even by mistake) and requirements.txt (criterion 2, reproducibility).
medium
Find at least five problems in this repository:
project/
├── analysis_FINAL_v3.ipynb # 2,000 lines, all functions defined here
├── ratings.csv # 450 MB, committed
├── .venv/ # committed
├── utils.py # def f(x), def g(df, a, b) - no docstrings
└── README.md # "my project"
Show solution
All logic in the notebook: the notebook must import project code (organisation).
Large data committed (and GitHub refuses files over 100 MB): ignore it, script the download.
.venv/ committed: ignore it, use requirements.txt.
No requirements.txt, no .gitignore.
Unclear names and no docstrings in utils.py.
README without sources, setup or run instructions.
Bad file name versioning ("FINAL_v3") instead of using Git history.
medium
Write the docstring (not the body) for a function bayesian_rank(df, min_votes) in "Rate My Recipe" that returns recipes sorted by a rating-count-aware score.
Show solution
def bayesian_rank(df, min_votes):
"""Rank recipes by a score that accounts for the number of ratings.
Parameters
----------
df : pandas.DataFrame
One row per recipe with columns 'recipe_id', 'mean_rating', 'n_ratings'.
min_votes : int
Weight of the prior: how many ratings a recipe needs before its own
mean dominates the global mean.
Returns
-------
pandas.DataFrame
Copy of df with an added 'score' column, sorted by score (descending).
"""
...
Being able to explain why the formula you choose works (and its parameters) is part of the evaluation; derive and justify it yourself.
exam
Rehearsal: answer in 3-4 sentences, as in the evaluation: "Your loader catches Exception. Why is that a problem, and what would you do instead?"
Show solution
Catching Exception hides unrelated bugs (a typo raising NameError would look like a "loading error"). I would catch the specific errors I expect: FileNotFoundError for a missing file, pd.errors.ParserError / UnicodeDecodeError for a malformed file, requests.exceptions.RequestException for network problems. For each I give a clear message (and in Streamlit st.error + st.stop()), and let other errors surface so I can fix them.
Self-check quiz
Common mistakes
Starting in the last week; one huge commit at the end.
Forgetting to invite the instructor or to register for the evaluation (check the form dates).
All logic in the notebook/app.
Committing data or secrets.
Analysis without a question, or plots without labels.
Code generated by an LLM that you cannot explain.
Forgetting that the discussion also covers course topics.
Remember
Deadline 30 Nov 2026 23:59 CET, last commit; evaluation 1-3 Dec; 5-10 min talk + questions.
This module follows Prof. Leorato's lectures and Casella & Berger (C&B), Statistical Inference, 2nd ed.
The written test mixes multiple choice (about 15-20%), exercises (about 55-60%) and
open theory questions (about 25%); if your optional project is submitted and evaluated you are exempt from the theory questions.
You need at least 18/30, and the final course mark is the mean of this test and the Mathematics test. The duration, the number of questions per type,
and whether a formula sheet or calculator is allowed are NOT STATED: check with the teacher.
Because most of the marks come from exercises, every topic below has many worked problems. Work through the topics in order:
B1.1-B1.7 are the probability background, B1.8-B1.14 point estimation, B1.15-B1.20 intervals and tests, B1.21 the project.
B1.1 Probability review: axioms, conditional probability, Bayes, counting
Why it matters. Every later result (distributions, likelihood, tests) is built on these rules. Lecture 1 (C&B 1.1-1.3).
How it is examined. Usually inside a bigger exercise (a Bayes-rule step, a counting step) or as a multiple-choice question; the axioms can appear as a short theory question.
Explanation
A random experiment has outcomes we cannot predict. We model it with three objects.
Definition: probability space
The sample space \(S\) is the set of all possible outcomes. An event is a subset \(A \subseteq S\).
The collection of events \(\mathcal{B}\) is a sigma-algebra: it contains \(\emptyset\), it is closed under complement (\(A\in\mathcal B \Rightarrow A^c\in\mathcal B\)) and under countable unions.
A probability function \(P\) on \(\mathcal B\) satisfies the Kolmogorov axioms:
(1) \(P(A)\ge 0\) for every event; (2) \(P(S)=1\); (3) if \(A_1,A_2,\dots\) are pairwise disjoint, \(P\left(\bigcup_i A_i\right)=\sum_i P(A_i)\).
Law of total probability: if \(C_1,C_2,\dots\) is a partition of \(S\) (disjoint, union \(=S\)), then \(P(A)=\sum_i P(A\cap C_i)=\sum_i P(A\mid C_i)P(C_i)\).
Definition: conditional probability and independence
If \(P(B)\gt 0\): \(\displaystyle P(A\mid B)=\frac{P(A\cap B)}{P(B)}\). Events \(A,B\) are independent if \(P(A\cap B)=P(A)P(B)\) (equivalently \(P(A\mid B)=P(A)\)).
\(A_1,\dots,A_n\) are mutually independent if for every sub-collection \(P(A_{i_1}\cap\dots\cap A_{i_k})=\prod_j P(A_{i_j})\). Pairwise independence is weaker.
Theorem (Bayes' rule)
If \(C_1,C_2,\dots\) is a partition of \(S\) and \(P(A)\gt 0\):
\[ P(C_i\mid A)=\frac{P(A\mid C_i)\,P(C_i)}{\sum_j P(A\mid C_j)\,P(C_j)}. \]
Counting
With equally likely outcomes, \(P(A)=\#A/\#S\). Number of ways to choose \(r\) objects from \(n\):
without replacement
with replacement
ordered
\(\dfrac{n!}{(n-r)!}\)
\(n^r\)
unordered
\(\dbinom{n}{r}=\dfrac{n!}{r!(n-r)!}\)
\(\dbinom{n+r-1}{r}\)
Worked examples
Example 1 (Bayes, diagnostic test). A disease has prevalence 1%. A test is positive with probability 0.95 for sick people (sensitivity) and negative with probability 0.90 for healthy people (specificity). Find \(P(\text{sick}\mid +)\).
\[ P(D\mid +)=\frac{0.95\cdot 0.01}{0.95\cdot 0.01+0.10\cdot 0.99}=\frac{0.0095}{0.0095+0.099}=\frac{0.0095}{0.1085}\approx 0.0876. \]
Only about 8.8%: when the disease is rare, most positives are false positives.
Example 2 (counting, C&B lottery). Choose 6 numbers from 44. Number of possible tickets: ordered without replacement \(44\cdot43\cdots39=5{,}082{,}517{,}440\); ordered with replacement \(44^6=7{,}256{,}313{,}856\); unordered without replacement \(\binom{44}{6}=7{,}059{,}052\); unordered with replacement \(\binom{49}{6}=13{,}983{,}816\).
Example 3 (total probability). Machine A makes 60% of items with 2% defective, machine B makes 40% with 5% defective.
\(P(\text{def})=0.6\cdot0.02+0.4\cdot0.05=0.032\), and \(P(A\mid\text{def})=0.012/0.032=0.375\).
Exercises
easy
\(P(A)=0.5\), \(P(B)=0.4\), \(P(A\cap B)=0.2\). Find \(P(A\cup B)\), \(P(A\mid B)\), \(P(A^c\cap B^c)\). Are \(A\) and \(B\) independent?
Show solution
\(P(A\cup B)=0.5+0.4-0.2=0.7\). \(P(A\mid B)=0.2/0.4=0.5\). By De Morgan \(P(A^c\cap B^c)=1-P(A\cup B)=0.3\). Since \(P(A)P(B)=0.2=P(A\cap B)\), yes, independent (also \(P(A\mid B)=P(A)\)).
easy
Three fair coins are tossed. Find the probability of at least one head.
An urn has 5 red and 3 blue balls. Draw 2 without replacement. Find \(P(\text{both red})\) and \(P(\text{2nd red}\mid\text{1st blue})\).
Show solution
\(P(\text{both red})=\binom52/\binom82=10/28=5/14\approx0.357\) (or \(\frac58\cdot\frac47=\frac{20}{56}\)). After a blue ball is removed there are 5 red out of 7: \(5/7\).
medium
Using machine A/B from Example 3: an item is defective. What is the probability it came from B? Explain why the answer is bigger than 0.4.
Show solution
\(P(B\mid\text{def})=0.02/0.032=0.625\). It is bigger than the prior 0.4 because B has the higher defect rate, so observing a defect is evidence for B.
hard
Toss two fair coins. \(A\)= first is H, \(B\)= second is H, \(C\)= both coins show the same face. Show \(A,B,C\) are pairwise independent but not mutually independent.
Show solution
\(S=\{HH,HT,TH,TT\}\), each 1/4. \(P(A)=P(B)=P(C)=1/2\). \(A\cap B=\{HH\}\), \(A\cap C=\{HH\}\), \(B\cap C=\{HH\}\): each has probability \(1/4=\frac12\cdot\frac12\), so pairwise independent. But \(A\cap B\cap C=\{HH\}\) has probability \(1/4\neq 1/8=P(A)P(B)P(C)\). Not mutually independent.
exam
(a) Prove Bonferroni's inequality \(P(A\cap B)\ge P(A)+P(B)-1\) from the axioms. (b) If \(P(A)=0.9\) and \(P(B)=0.8\), what is the smallest possible \(P(A\cap B)\)?
Show solution
(a) \(P(A\cup B)=P(A)+P(B)-P(A\cap B)\) and \(P(A\cup B)\le 1\), so \(P(A)+P(B)-P(A\cap B)\le 1\), i.e. \(P(A\cap B)\ge P(A)+P(B)-1\).
(b) \(0.9+0.8-1=0.7\). It is attained when \(A\cup B=S\).
Self-check quiz
Common mistakes
Confusing \(P(A\mid B)\) with \(P(B\mid A)\) (the "prosecutor's fallacy"). Use Bayes' rule to switch.
Thinking "disjoint" means "independent". Disjoint events with positive probability are never independent: \(P(A\cap B)=0\neq P(A)P(B)\).
Checking only pairs when the question asks for mutual independence.
Using ordered counts in the numerator and unordered counts in the denominator.
Remember
Three axioms: non-negative, \(P(S)=1\), countable additivity.
\(P(A\mid B)=P(A\cap B)/P(B)\); total probability; Bayes = total probability in the denominator.
B1.2 Random variables, CDF, pmf and pdf (and Buffon's needle)
Why it matters. A statistical model is a family of distributions of random variables; you must move easily between CDF, pmf and pdf. Lectures 2-3 (C&B 1.4-1.6, 2.1).
How it is examined. Typical exercise openings: "find \(c\) so that \(f\) is a density", "find the CDF", "compute \(P(a\lt X\le b)\)", "find the density of \(Y=g(X)\)". Buffon's needle is also a project proposal.
Explanation
Definition: random variable and CDF
A random variable (r.v.) is a function \(X:S\to\mathbb R\) from the sample space to the real numbers. Its cumulative distribution function (CDF) is
\(F_X(x)=P(X\le x)\) for all \(x\in\mathbb R\).
Theorem (when is \(F\) a CDF?)
\(F\) is a CDF if and only if (a) \(\lim_{x\to-\infty}F(x)=0\) and \(\lim_{x\to\infty}F(x)=1\); (b) \(F\) is non-decreasing; (c) \(F\) is right-continuous.
Two r.v.s are identically distributed if they have the same CDF (they need not be equal!).
Definition: pmf and pdf
Discrete r.v. (countably many values): probability mass function \(f_X(x)=P(X=x)\), with \(f_X\ge0\), \(\sum_x f_X(x)=1\), and \(F_X(x)=\sum_{t\le x}f_X(t)\) (a step function).
Continuous r.v.: probability density function \(f_X\ge0\) with \(F_X(x)=\int_{-\infty}^x f_X(t)\,dt\), so \(f_X=F_X'\) where the derivative exists, and \(\int_{-\infty}^{\infty}f_X=1\).
Then \(P(a\lt X\le b)=F(b)-F(a)=\int_a^b f_X\), and \(P(X=x)=0\) for every single \(x\). A density value can be bigger than 1: it is not a probability.
Transformations \(Y=g(X)\)
CDF method (always works): write \(F_Y(y)=P(g(X)\le y)\) as a probability about \(X\), then differentiate.
Theorem (monotone transformation, C&B 2.1.5)
If \(X\) has pdf \(f_X\) and \(g\) is strictly monotone and differentiable on the support, then
\[ f_Y(y)=f_X\big(g^{-1}(y)\big)\,\left|\frac{d}{dy}g^{-1}(y)\right| \quad\text{on the image of the support.} \]
Probability integral transform
If \(X\) is continuous with CDF \(F_X\), then \(U=F_X(X)\sim\text{Uniform}(0,1)\). Conversely \(F^{-1}(U)\) has CDF \(F\): this is how computers simulate (e.g. \(-\log(U)/\lambda\sim\text{Exp}(\lambda)\)).
Buffon's needle
Parallel lines are drawn at distance \(d\). A needle of length \(\ell\le d\) is dropped at random. Let \(X\)= distance from the needle centre to the nearest line, \(X\sim U(0,d/2)\), and \(\Theta\)= acute angle with the lines, \(\Theta\sim U(0,\pi/2)\), independent. The needle crosses a line iff \(X\le \frac{\ell}{2}\sin\Theta\). Hence
\[ P(\text{cross})=\int_0^{\pi/2}\frac{(\ell/2)\sin\theta}{d/2}\cdot\frac{2}{\pi}\,d\theta=\frac{2\ell}{\pi d}. \]
With \(n\) drops and \(C\) crossings, \(\hat p=C/n\) estimates \(2\ell/(\pi d)\), so \(\hat\pi=\dfrac{2\ell n}{d\,C}\) estimates \(\pi\) (a method-of-moments estimator; by the delta method its variance is approximately \(\pi^2(1-p)/(np)\)).
Needle centre at distance \(X\) from the nearest line; it crosses when \(X\le(\ell/2)\sin\Theta\).
Worked examples
Example 1. \(f(x)=cx^2\) on \([0,2]\). Normalise: \(\int_0^2cx^2dx=8c/3=1\Rightarrow c=3/8\). CDF \(F(x)=x^3/8\) on \([0,2]\) (0 before, 1 after). \(P(X\gt1)=1-1/8=7/8\).
Example 2 (CDF method). \(X\sim U(0,1)\), \(Y=X^2\). For \(0\lt y\lt1\): \(F_Y(y)=P(X\le\sqrt y)=\sqrt y\), so \(f_Y(y)=\frac{1}{2\sqrt y}\).
Example 3 (monotone formula). \(X\sim U(0,1)\), \(Y=-\log X\). \(g^{-1}(y)=e^{-y}\), \(|d g^{-1}/dy|=e^{-y}\), so \(f_Y(y)=1\cdot e^{-y}\), \(y\gt0\): \(Y\sim\text{Exp}(1)\).
Example 4 (simulation of Buffon in R, not run here).
set.seed(1)
n <- 1e5; l <- 1; d <- 2
x <- runif(n, 0, d/2) # centre-to-line distance
theta <- runif(n, 0, pi/2) # angle
C <- sum(x <= (l/2) * sin(theta))
pi_hat <- 2 * l * n / (d * C)
pi_hat # should be close to 3.14
Exercises
easy
\(f(x)=cx\) for \(x=1,2,3,4\). Find \(c\), \(P(X\ge3)\) and \(F(2.5)\).
\(X\sim U(0,1)\). Find the pdf of \(Y=-2\log X\). Which known distribution is it?
Show solution
\(g^{-1}(y)=e^{-y/2}\), derivative magnitude \(\frac12e^{-y/2}\): \(f_Y(y)=\frac12e^{-y/2}\), \(y\gt0\). Exponential with mean 2, which is also \(\chi^2_2\).
hard
Buffon: \(\ell=1\), \(d=2\). (a) Find \(P(\text{cross})\). (b) In 1000 drops you see 310 crossings. Estimate \(\pi\).
\(X\) has pdf \(f(x)=\frac12\) on \((-1,1)\). Find the pdf of \(Y=X^2\) (careful: \(g\) is not monotone).
Show solution
For \(0\lt y\lt1\): \(F_Y(y)=P(-\sqrt y\le X\le\sqrt y)=\frac12\cdot2\sqrt y=\sqrt y\). So \(f_Y(y)=\frac1{2\sqrt y}\) on \((0,1)\). (Same as Example 2: both branches contribute.)
Self-check quiz
Common mistakes
Forgetting the support: always state where the pdf is positive and give the CDF on the whole real line (0 / formula / 1).
Forgetting the absolute value of the Jacobian, or using the monotone formula when \(g\) is not monotone.
For discrete r.v.s, \(P(X\lt x)\neq P(X\le x)\); for continuous r.v.s they are equal.
Why it matters. Means and variances are the language of estimators (bias, MSE, CLT). The MGF is the fast way to get moments and to find the distribution of sums. Lectures 3 and 6 (C&B 2.2-2.3).
How it is examined. Compute \(E[X]\), \(\text{Var}(X)\) from a pmf/pdf; derive moments from an MGF; identify a distribution from its MGF; Chebyshev bounds.
Explanation
Definition: expectation
\(E[g(X)]=\sum_x g(x)f_X(x)\) (discrete) or \(\int g(x)f_X(x)\,dx\) (continuous), provided \(E|g(X)|\lt\infty\). Otherwise the expectation does not exist (example: Cauchy).
Properties: \(E[aX+b]=aE[X]+b\); \(E[X+Y]=E[X]+E[Y]\) always (independence not needed); if \(g\le h\) then \(E g(X)\le E h(X)\).
Definition: moments and variance
\(n\)-th moment \(\mu_n'=E[X^n]\); \(n\)-th central moment \(\mu_n=E[(X-\mu)^n]\) with \(\mu=E X\).
Variance \(\text{Var}(X)=E[(X-\mu)^2]=E[X^2]-(E X)^2\); standard deviation \(\sqrt{\text{Var}(X)}\). \(\text{Var}(aX+b)=a^2\text{Var}(X)\).
Definition: moment generating function
\(M_X(t)=E[e^{tX}]\), if it is finite for all \(t\) in a neighbourhood of 0.
Theorem (MGF facts)
(1) \(E[X^n]=M_X^{(n)}(0)\) (the \(n\)-th derivative at 0).
(2) \(M_{aX+b}(t)=e^{bt}M_X(at)\).
(3) If \(X_1,\dots,X_n\) are independent, \(M_{\sum X_i}(t)=\prod_i M_{X_i}(t)\).
(4) Uniqueness: if two MGFs agree on a neighbourhood of 0, the distributions are the same.
(5) If \(M_{X_n}(t)\to M_X(t)\) near 0 then \(X_n\to X\) in distribution (used for the CLT).
Inequalities (C&B 3.6)
Markov: if \(X\ge0\), \(P(X\ge a)\le E[X]/a\). Chebyshev: \(P(|X-\mu|\ge k)\le \sigma^2/k^2\). Chebyshev gives the weak law of large numbers (B1.14).
Worked examples
Example 1 (Exponential). \(f(x)=\lambda e^{-\lambda x}\), \(x\gt0\). \(M(t)=\int_0^\infty e^{tx}\lambda e^{-\lambda x}dx=\dfrac{\lambda}{\lambda-t}\) for \(t\lt\lambda\).
\(M'(t)=\lambda/(\lambda-t)^2\Rightarrow E X=1/\lambda\); \(M''(t)=2\lambda/(\lambda-t)^3\Rightarrow EX^2=2/\lambda^2\), so \(\text{Var}X=2/\lambda^2-1/\lambda^2=1/\lambda^2\).
Example 2 (Binomial via MGF). Bernoulli: \(M(t)=1-p+pe^t\). Sum of \(n\) independent Bernoulli: \(M(t)=(1-p+pe^t)^n\), which is the Binomial\((n,p)\) MGF. \(M'(0)=np\), \(M''(0)=np+n(n-1)p^2\), so \(\text{Var}=np(1-p)\).
Example 3 (discrete). \(X\in\{0,1,2\}\) with probabilities \(0.2,0.5,0.3\): \(EX=1.1\), \(EX^2=0.5+1.2=1.7\), \(\text{Var}X=1.7-1.21=0.49\).
Exercises
easy
If \(EX=2\), \(\text{Var}X=3\), find \(E[3-2X]\), \(\text{Var}(3-2X)\), \(E[X^2]\).
Show solution
\(3-4=-1\); \(4\cdot3=12\) (the constant disappears, the sign is squared); \(EX^2=\text{Var}+(EX)^2=7\).
easy
\(f(x)=2x\) on \((0,1)\). Find \(EX\) and \(\text{Var}X\).
\(M^{(n)}(0)=EX^n\); MGF of independent sum = product; MGF identifies the distribution.
Chebyshev: \(P(|X-\mu|\ge k)\le\sigma^2/k^2\).
B1.4 Common families of distributions and exponential families
Why it matters. Almost every exercise starts with "let \(X_1,\dots,X_n\) be a sample from [family]". You must know the pmf/pdf, support, mean and variance by heart, and recognise exponential families (they make sufficiency, MLE, UMP tests easy). Lectures 3, 4, 6 (C&B 3.1-3.4).
How it is examined. MC questions ("which is an exponential family?"), "write the density in exponential-family form and find the natural sufficient statistic", and as the base of every estimation exercise. The course has a "distributions sheet": compare it with the table below.
Useful facts: \(\Gamma(\alpha)=\int_0^\infty t^{\alpha-1}e^{-t}dt\), \(\Gamma(n)=(n-1)!\), \(\Gamma(1/2)=\sqrt\pi\), \(B(\alpha,\beta)=\Gamma(\alpha)\Gamma(\beta)/\Gamma(\alpha+\beta)\).
Special cases: Exponential = Gamma\((1,\beta)\); \(\chi^2_p\) = Gamma\((p/2,2)\); Uniform(0,1) = Beta(1,1). Negative binomial\((r,p)\) = number of failures before the \(r\)-th success.
Exponential and geometric are memoryless: \(P(X\gt s+t\mid X\gt s)=P(X\gt t)\).
Check with the course
C&B writes the exponential with scale \(\beta\) (pdf \(\frac1\beta e^{-x/\beta}\), mean \(\beta\)); here and in most exercises we use the rate \(\lambda=1/\beta\). Gamma also has a rate version (\(\beta\) replaced by \(1/\beta\)). The geometric can also be defined as number of failures (support \(0,1,\dots\), mean \((1-p)/p\)). Always check which version the exercise uses; the course distributions sheet decides.
Location and scale families
If \(f\) is a pdf, then \(\frac1\sigma f\!\left(\frac{x-\mu}\sigma\right)\) is a pdf for every \(\mu\in\mathbb R,\sigma\gt0\) (location-scale family). \(X=\mu+\sigma Z\) with \(Z\sim f\). Examples: normal, Cauchy, uniform.
Exponential families
Definition: exponential family (C&B 3.4)
A family of pdfs/pmfs is an exponential family if it can be written
\[ f(x\mid\boldsymbol\theta)=h(x)\,c(\boldsymbol\theta)\exp\Big(\sum_{i=1}^k w_i(\boldsymbol\theta)\,t_i(x)\Big), \]
with \(h(x)\ge0\), \(c(\boldsymbol\theta)\gt0\), and the support not depending on \(\boldsymbol\theta\).
\(\eta_i=w_i(\boldsymbol\theta)\) are the natural parameters. If the dimension of \(\boldsymbol\theta\) is smaller than \(k\) it is a curved exponential family.
Theorem (moments of \(t(X)\), one-parameter case)
\(E\!\left[\frac{\partial w(\theta)}{\partial\theta}t(X)\right]=-\frac{\partial}{\partial\theta}\log c(\theta)\). With natural parameter \(\eta\) and \(f=h(x)\exp(\eta t(x)-A(\eta))\): \(E[t(X)]=A'(\eta)\), \(\text{Var}(t(X))=A''(\eta)\).
Not exponential families: Uniform\((0,\theta)\) (support depends on \(\theta\)), Cauchy location, shifted exponential \(e^{-(x-\theta)}\mathbb 1\{x\ge\theta\}\).
Worked examples
Example 1 (Poisson). \(f(x\mid\lambda)=\frac{1}{x!}\,e^{-\lambda}\exp(x\log\lambda)\): \(h(x)=1/x!\) on \(\{0,1,\dots\}\), \(c(\lambda)=e^{-\lambda}\), \(w(\lambda)=\log\lambda\), \(t(x)=x\). Check the theorem: \(E[X/\lambda]=-\frac{d}{d\lambda}(-\lambda)=1\Rightarrow EX=\lambda\).
Write Beta\((\alpha,\beta)\) as a two-parameter exponential family; give \(t_1,t_2\).
Show solution
\(f=\frac{1}{B(\alpha,\beta)}\exp\big((\alpha-1)\log x+(\beta-1)\log(1-x)\big)\mathbb 1_{(0,1)}(x)\): \(h=\mathbb 1_{(0,1)}\), \(c=1/B(\alpha,\beta)\), \(w_1=\alpha-1\), \(t_1=\log x\), \(w_2=\beta-1\), \(t_2=\log(1-x)\). Support \((0,1)\) is fixed.
hard
Is \(N(\theta,\theta^2)\), \(\theta\gt0\), an exponential family? Is Uniform\((0,\theta)\)?
Show solution
\(f=\frac{1}{\sqrt{2\pi}\theta}e^{-1/2}\exp\!\big(-\frac{x^2}{2\theta^2}+\frac{x}{\theta}\big)\): yes, a curved exponential family (\(k=2\) statistics \(x^2,x\), but only one parameter). Uniform\((0,\theta)\): \(f=\frac1\theta\mathbb 1\{0\lt x\lt\theta\}\); the indicator depends on \(\theta\) and cannot be written as \(h(x)c(\theta)\exp(\cdot)\): not an exponential family.
exam
For the exponential family \(f(x\mid\theta)=\theta x^{\theta-1}\), \(0\lt x\lt1\), \(\theta\gt0\): identify \(h,c,w,t\) and use the theorem to find \(E[\log X]\).
B1.5 Joint, marginal and conditional distributions; covariance and correlation
Why it matters. A sample is a vector of r.v.s; the likelihood is a joint density. Covariance is needed for variances of sums. Lecture 4 (C&B 4.1, 4.2, 4.4, 4.5).
How it is examined. Classic exercise: given a joint pmf table or joint pdf, find marginals, conditionals, check independence, compute covariance and correlation.
Explanation
Definitions
Joint pmf \(f(x,y)=P(X=x,Y=y)\) or joint pdf with \(P((X,Y)\in A)=\iint_A f(x,y)\,dx\,dy\).
Marginal: \(f_X(x)=\sum_y f(x,y)\) or \(\int f(x,y)\,dy\).
Conditional: \(f(y\mid x)=f(x,y)/f_X(x)\) when \(f_X(x)\gt0\).
Independence: \(f(x,y)=f_X(x)f_Y(y)\) for all \(x,y\). Shortcut (C&B Lemma 4.2.7): \(X,Y\) are independent iff \(f(x,y)=g(x)h(y)\) for some functions \(g,h\) — and the support must be a product set (a rectangle).
Covariance and correlation
\(\text{Cov}(X,Y)=E[(X-\mu_X)(Y-\mu_Y)]=E[XY]-\mu_X\mu_Y\); \(\rho_{XY}=\dfrac{\text{Cov}(X,Y)}{\sigma_X\sigma_Y}\in[-1,1]\), with \(|\rho|=1\) iff \(Y=aX+b\) a.s.
Independent \(\Rightarrow\) \(\text{Cov}=0\) and \(E[g(X)h(Y)]=E g(X)\,E h(Y)\). The converse is false.
Theorem (iterated expectation and total variance)
\(E[X]=E\big[E[X\mid Y]\big]\) and \(\text{Var}X=E\big[\text{Var}(X\mid Y)\big]+\text{Var}\big(E[X\mid Y]\big)\).
Bivariate normal \((\mu_X,\mu_Y,\sigma_X^2,\sigma_Y^2,\rho)\): marginals are normal, \(Y\mid X=x\sim N\big(\mu_Y+\rho\frac{\sigma_Y}{\sigma_X}(x-\mu_X),\ \sigma_Y^2(1-\rho^2)\big)\), and here \(\rho=0\) does imply independence.
Worked examples
Example 1. \(f(x,y)=x+y\) on \((0,1)^2\). Marginal \(f_X(x)=\int_0^1(x+y)dy=x+\frac12\) (same for \(Y\)).
\(EX=\int_0^1x(x+\frac12)dx=\frac13+\frac14=\frac7{12}\). \(EX^2=\frac14+\frac16=\frac5{12}\), \(\text{Var}X=\frac5{12}-\frac{49}{144}=\frac{11}{144}\).
\(E[XY]=\int\!\!\int xy(x+y)=\frac16+\frac16=\frac13\). \(\text{Cov}=\frac13-\frac{49}{144}=-\frac1{144}\), \(\rho=\frac{-1/144}{11/144}=-\frac1{11}\). Not independent (\(x+y\) is not a product).
Example 2 (hierarchical model). \(Y\sim\text{Poisson}(\lambda)\), \(X\mid Y\sim\text{Bin}(Y,p)\). \(EX=E[E(X\mid Y)]=E[Yp]=\lambda p\). In fact \(X\sim\text{Poisson}(\lambda p)\) (C&B Example 4.4.1, insects and eggs).
\(f(x,y)=2\) on \(0\lt x\lt y\lt1\). Find the marginals, \(f(x\mid y)\) and \(E[X\mid Y=y]\). Independent?
Show solution
\(f_X(x)=\int_x^12\,dy=2(1-x)\); \(f_Y(y)=\int_0^y2\,dx=2y\). \(f(x\mid y)=2/(2y)=1/y\) on \((0,y)\): Uniform\((0,y)\), so \(E[X\mid Y=y]=y/2\). Not independent: the support is a triangle, not a rectangle (and \(2\neq 4y(1-x)\)).
medium
In Example 2 compute \(\text{Var}X\) with the total-variance formula.
\(X_1,\dots,X_n\) i.i.d. with variance \(\sigma^2\). Find \(\text{Cov}(\bar X,X_i-\bar X)\). What does this say for a normal sample?
Show solution
\(\text{Cov}(\bar X,X_i)=\frac1n\sum_j\text{Cov}(X_j,X_i)=\sigma^2/n\); \(\text{Cov}(\bar X,\bar X)=\sigma^2/n\). Difference: 0. For a normal sample \((\bar X,X_i-\bar X)\) is jointly normal, so zero covariance means independence; this is the key step for \(\bar X\perp S^2\) (B1.6).
Self-check quiz
Common mistakes
Integrating over the wrong limits when the support is not a rectangle (draw it!).
Concluding independence from zero covariance (only valid for jointly normal variables).
Forgetting the \(2ab\,\text{Cov}\) term in \(\text{Var}(aX+bY)\).
B1.6 Random samples; sample mean and variance; sampling from the normal (\(\chi^2\), \(t\), \(F\))
Why it matters. Every confidence interval and test for normal data uses the facts in this topic. Lectures 5-6 (C&B 5.1-5.3).
How it is examined. "Find \(E\bar X\), \(\text{Var}\bar X\)", "show \(S^2\) is unbiased", "find the distribution of [combination of normal variables]", "compute \(P(\bar X\gt c)\) or a quantile of \(S^2\)" using tables.
Explanation
Definitions
A statistical model is a family \(\{f(x\mid\theta):\theta\in\Theta\}\) with unknown parameter \(\theta\). A random sample of size \(n\) is \(X_1,\dots,X_n\) independent and identically distributed (i.i.d.) with pdf \(f(x\mid\theta)\); joint pdf \(\prod_{i=1}^nf(x_i\mid\theta)\).
A statistic \(T=T(X_1,\dots,X_n)\) is any function of the sample that does not involve unknown parameters; its distribution is the sampling distribution.
Sample mean \(\bar X=\frac1n\sum X_i\). Sample variance \(S^2=\frac1{n-1}\sum(X_i-\bar X)^2\).
Theorem (any distribution with mean \(\mu\), variance \(\sigma^2\))
Proof of \(ES^2=\sigma^2\): \(E\sum X_i^2=n(\sigma^2+\mu^2)\), \(E[n\bar X^2]=n(\sigma^2/n+\mu^2)=\sigma^2+n\mu^2\). Difference \((n-1)\sigma^2\); divide by \(n-1\).
Three derived distributions
Chi-squared: if \(Z_1,\dots,Z_p\) i.i.d. \(N(0,1)\), \(\sum Z_i^2\sim\chi^2_p\) (= Gamma\((p/2,2)\); mean \(p\), variance \(2p\)). Independent \(\chi^2\)s add: \(\chi^2_p+\chi^2_q=\chi^2_{p+q}\).
Student t: \(Z\sim N(0,1)\), \(V\sim\chi^2_p\) independent: \(T=\dfrac{Z}{\sqrt{V/p}}\sim t_p\). Symmetric, heavier tails than \(N(0,1)\), \(t_p\to N(0,1)\) as \(p\to\infty\); \(t_1\) is Cauchy.
F: \(U\sim\chi^2_p\), \(V\sim\chi^2_q\) independent: \(F=\dfrac{U/p}{V/q}\sim F_{p,q}\). Also \(1/F\sim F_{q,p}\) and \(T^2\sim F_{1,p}\) if \(T\sim t_p\).
Theorem (sampling from the normal, C&B 5.3.1)
If \(X_1,\dots,X_n\) i.i.d. \(N(\mu,\sigma^2)\):
(a) \(\bar X\) and \(S^2\) are independent; (b) \(\bar X\sim N(\mu,\sigma^2/n)\); (c) \(\dfrac{(n-1)S^2}{\sigma^2}\sim\chi^2_{n-1}\). Consequently
\[ \frac{\bar X-\mu}{S/\sqrt n}\sim t_{n-1}, \qquad \frac{S_1^2/\sigma_1^2}{S_2^2/\sigma_2^2}\sim F_{n_1-1,\,n_2-1}\ \text{(two independent normal samples).} \]
Why \(n-1\) degrees of freedom: the \(n\) deviations \(X_i-\bar X\) sum to zero, so only \(n-1\) are free.
Other sums via MGFs: sum of \(n\) i.i.d. Exp(rate \(\lambda\)) is Gamma(shape \(n\), rate \(\lambda\)); sum of independent normals is normal.
Worked examples
Example 1. \(n=16\) from \(N(50,64)\). \(P(\bar X\gt52)=P\big(Z\gt\frac{52-50}{8/4}\big)=P(Z\gt1)\approx0.1587\).
Example 2. \(n=10\) from \(N(\mu,4)\). Find \(c\) with \(P(S^2\gt c)=0.05\): \(P\big(\chi^2_9\gt 9c/4\big)=0.05\Rightarrow 9c/4=\chi^2_{9;0.95}=16.919\Rightarrow c=7.52\).
Example 3. \(n=9\): \(P\big(|\bar X-\mu|\gt 2.306\,S/3\big)=P(|t_8|\gt2.306)=0.05\) since \(t_{8;0.975}=2.306\).
Prove \(\text{Var}\bar X=\sigma^2/n\) and explain why independence is needed.
Show solution
\(\text{Var}(\frac1n\sum X_i)=\frac1{n^2}\big[\sum\text{Var}X_i+\sum_{i\neq j}\text{Cov}(X_i,X_j)\big]=\frac1{n^2}n\sigma^2\): the covariances are 0 by independence.
medium
\(X_1,\dots,X_4\) i.i.d. \(N(0,1)\). Find the distribution of \(T=\dfrac{X_1+X_2}{\sqrt{X_3^2+X_4^2}}\) and of \(\dfrac{X_1^2}{X_2^2}\).
Two independent normal samples with equal variances, \(n_1=n_2=11\). Find \(c\) with \(P(S_1^2/S_2^2\gt c)=0.05\).
Show solution
With \(\sigma_1=\sigma_2\) the ratio is \(F_{10,10}\): \(c=F_{10,10;0.95}\approx2.978\).
exam
\(X_1,\dots,X_n\) i.i.d. \(N(\mu,\sigma^2)\). Find \(\text{Var}(S^2)\), using \(\text{Var}(\chi^2_{n-1})=2(n-1)\).
Show solution
\(S^2=\frac{\sigma^2}{n-1}V\) with \(V\sim\chi^2_{n-1}\): \(\text{Var}S^2=\frac{\sigma^4}{(n-1)^2}2(n-1)=\frac{2\sigma^4}{n-1}\). (Used again for MSE in B1.12.)
Self-check quiz
Common mistakes
Using \(n\) instead of \(n-1\) degrees of freedom for \((n-1)S^2/\sigma^2\) and for the \(t\) statistic.
Dividing by \(\sigma\) instead of \(\sigma/\sqrt n\) when standardising \(\bar X\).
Forgetting that the independence of \(\bar X\) and \(S^2\) holds for normal samples only.
Remember
\(E\bar X=\mu\), \(\text{Var}\bar X=\sigma^2/n\), \(ES^2=\sigma^2\) for any i.i.d. sample.
B1.7 Order statistics and the empirical distribution function
Why it matters. The maximum \(X_{(n)}\) is the MLE and sufficient statistic for Uniform\((0,\theta)\); the median is the robust estimator for the Cauchy project; the empirical CDF is the basic nonparametric estimator. Lectures 5 and 7 (C&B 5.4, 5.3.2 area).
How it is examined. "Find the CDF/pdf of the max (or min)", "compute \(E X_{(n)}\)", "compute \(F_n(x)\) from data and its mean/variance".
Explanation
Definition: order statistics
Sort the sample: \(X_{(1)}\le X_{(2)}\le\dots\le X_{(n)}\). \(X_{(1)}\) is the minimum, \(X_{(n)}\) the maximum, \(R=X_{(n)}-X_{(1)}\) the range; the sample median is \(X_{((n+1)/2)}\) for odd \(n\), the average of the two middle values for even \(n\).
Theorem (distributions, continuous i.i.d. sample with CDF \(F\), pdf \(f\))
\(F_{X_{(n)}}(x)=F(x)^n\), \(f_{X_{(n)}}(x)=nF(x)^{n-1}f(x)\).
\(F_{X_{(1)}}(x)=1-(1-F(x))^n\), \(f_{X_{(1)}}(x)=n(1-F(x))^{n-1}f(x)\).
\(j\)-th order statistic: \(f_{X_{(j)}}(x)=\dfrac{n!}{(j-1)!\,(n-j)!}f(x)F(x)^{j-1}(1-F(x))^{n-j}\).
Reason for the max: \(X_{(n)}\le x\) iff all \(X_i\le x\), and by independence \(P=F(x)^n\). For the min: \(X_{(1)}\gt x\) iff all \(X_i\gt x\).
Definition: empirical distribution function (ECDF)
\(\displaystyle F_n(x)=\frac1n\sum_{i=1}^n\mathbb 1\{X_i\le x\}\) = proportion of observations \(\le x\). It is a step function jumping by \(1/n\) at each observation.
Properties of \(F_n(x)\) for a fixed \(x\)
\(nF_n(x)\sim\text{Bin}(n,F(x))\). So \(E F_n(x)=F(x)\) (unbiased), \(\text{Var}F_n(x)=F(x)(1-F(x))/n\to0\) (consistent), and by the CLT \(\sqrt n(F_n(x)-F(x))\to N(0,F(x)(1-F(x)))\).
Glivenko-Cantelli: \(\sup_x|F_n(x)-F(x)|\to0\) almost surely (uniform convergence).
Worked examples
Example 1 (max of uniforms). \(X_i\sim U(0,\theta)\): \(F_{X_{(n)}}(x)=(x/\theta)^n\), \(f=nx^{n-1}/\theta^n\), \(E X_{(n)}=\int_0^\theta x\,nx^{n-1}/\theta^n\,dx=\frac{n}{n+1}\theta\). So \(X_{(n)}\) underestimates \(\theta\) slightly.
Example 2 (min of exponentials). \(X_i\sim\text{Exp}(\lambda)\): \(P(X_{(1)}\gt x)=(e^{-\lambda x})^n=e^{-n\lambda x}\), so \(X_{(1)}\sim\text{Exp}(n\lambda)\), mean \(1/(n\lambda)\).
Example 3 (ECDF). Data \(3,1,4,1,5\). \(F_5(0.5)=0\), \(F_5(1)=2/5\), \(F_5(3.5)=3/5\), \(F_5(4)=4/5\), \(F_5(5)=1\).
Data: \(2.1, 0.7, 3.3, 2.1, 1.5, 4.0\). Compute \(F_6(2.1)\), \(F_6(2)\), the median and the range.
Show solution
Sorted: 0.7, 1.5, 2.1, 2.1, 3.3, 4.0. \(F_6(2.1)=4/6\), \(F_6(2)=2/6\). Median \((2.1+2.1)/2=2.1\). Range \(4.0-0.7=3.3\).
easy
\(X_1,\dots,X_5\) i.i.d. \(U(0,1)\). Find \(P(X_{(5)}\le0.8)\) and \(P(X_{(1)}\gt0.1)\).
Show solution
\(0.8^5=0.32768\); \(0.9^5=0.59049\).
medium
\(X_i\sim U(0,1)\). Show \(X_{(j)}\sim\text{Beta}(j,n-j+1)\) and deduce \(EX_{(j)}\).
Show solution
With \(F(x)=x\), \(f=1\): \(f_{(j)}(x)=\frac{n!}{(j-1)!(n-j)!}x^{j-1}(1-x)^{n-j}\), which is Beta\((j,n-j+1)\) since \(B(j,n-j+1)=\frac{(j-1)!(n-j)!}{n!}\). Mean \(\frac{j}{n+1}\).
medium
For \(n=3\) i.i.d. \(U(0,1)\), find the pdf of the sample median and its variance.
Show solution
\(j=2\): \(f(x)=\frac{3!}{1!1!}x(1-x)=6x(1-x)\), Beta(2,2). Variance \(\frac{2\cdot2}{4^2\cdot5}=\frac1{20}\). (Compare \(\text{Var}\bar X=\frac1{12\cdot3}=\frac1{36}\): for uniform data the mean is better.)
hard
\(n=50\), true \(F(x_0)=0.3\). Give the mean and standard deviation of \(F_{50}(x_0)\) and an approximate 95% range for it.
Show solution
Mean 0.3, variance \(0.3\cdot0.7/50=0.0042\), sd \(\approx0.0648\). Approx. \(0.3\pm1.96\cdot0.0648=(0.173,0.427)\).
exam
\(X_i\sim U(0,\theta)\). Show that \(P(|X_{(n)}-\theta|\gt\varepsilon)\to0\) for every \(\varepsilon\gt0\), and that \(n(\theta-X_{(n)})\) converges in distribution to an exponential with mean \(\theta\).
Show solution
For \(0\lt\varepsilon\lt\theta\): \(P(X_{(n)}\lt\theta-\varepsilon)=(1-\varepsilon/\theta)^n\to0\) (and \(X_{(n)}\le\theta\) always). \(P(n(\theta-X_{(n)})\gt t)=P(X_{(n)}\lt\theta-t/n)=(1-\frac{t}{n\theta})^n\to e^{-t/\theta}\): Exp with mean \(\theta\). Note the rate \(n\), not \(\sqrt n\): this is a non-regular model.
Self-check quiz
Common mistakes
Writing \(F_{X_{(1)}}=F^n\) for the minimum (that is the max). For the min use the survival function.
Forgetting that \(F_n\) is right-continuous: at a data point it already includes that point.
Order statistics are not independent even though the \(X_i\) are.
Remember
Max: \(F^n\). Min: \(1-(1-F)^n\). \(j\)-th: multinomial-type formula; for U(0,1), Beta\((j,n-j+1)\).
B1.8 Sufficient statistics and the factorization theorem
Why it matters. A sufficient statistic keeps all the information about \(\theta\) in the sample; MLEs, Bayes estimators, UMVU estimators and UMP tests all depend on the data only through it. Lecture 7 (C&B 6.1, 6.2.1); the course has extra exercises on sufficiency.
How it is examined. Almost certain: "find a sufficient statistic for \(\theta\) using the factorization theorem" (often a part (a) of a longer exercise); sometimes "show from the definition that \(T\) is (not) sufficient" or a theory question "state the factorization theorem".
Explanation
Definition: sufficient statistic
A statistic \(T(\mathbf X)\) is sufficient for \(\theta\) if the conditional distribution of the sample \(\mathbf X\) given \(T(\mathbf X)=t\) does not depend on \(\theta\).
Intuition: once you know \(T\), the rest of the data is pure noise whose distribution you know; it cannot tell you anything more about \(\theta\).
Theorem (Factorization theorem, C&B 6.2.6)
\(T(\mathbf X)\) is sufficient for \(\theta\) if and only if there exist functions \(g(t\mid\theta)\) and \(h(\mathbf x)\) such that for all \(\mathbf x\) and all \(\theta\)
\[ f(\mathbf x\mid\theta)=g\big(T(\mathbf x)\mid\theta\big)\,h(\mathbf x). \]
\(g\) may depend on \(\theta\) but on the data only through \(T\); \(h\) must not depend on \(\theta\).
Corollary (exponential families, C&B 6.2.10)
If \(f(x\mid\boldsymbol\theta)=h(x)c(\boldsymbol\theta)\exp\big(\sum_{j=1}^kw_j(\boldsymbol\theta)t_j(x)\big)\), then for an i.i.d. sample
\(T(\mathbf X)=\big(\sum_i t_1(X_i),\dots,\sum_i t_k(X_i)\big)\) is sufficient.
Notes: a one-to-one function of a sufficient statistic is sufficient (e.g. \(\bar X\) instead of \(\sum X_i\), \((\bar X,S^2)\) instead of \((\sum X_i,\sum X_i^2)\)). The whole sample and the order statistics are always sufficient (trivial reductions).
Minimal sufficiency (brief, C&B 6.2.13)
\(T\) is minimal sufficient if it is a function of every other sufficient statistic (the greatest data reduction). Criterion: if
\(\dfrac{f(\mathbf x\mid\theta)}{f(\mathbf y\mid\theta)}\) is constant in \(\theta\) if and only if \(T(\mathbf x)=T(\mathbf y)\), then \(T\) is minimal sufficient.
For a full-rank exponential family, the natural statistic \(\sum t_j(X_i)\) is minimal sufficient.
Worked examples
Example 1 (Bernoulli, by definition). \(T=\sum X_i\sim\text{Bin}(n,p)\). For \(\mathbf x\) with \(\sum x_i=t\):
\[ P(\mathbf X=\mathbf x\mid T=t)=\frac{p^t(1-p)^{n-t}}{\binom nt p^t(1-p)^{n-t}}=\frac1{\binom nt}, \]
free of \(p\). So \(T\) is sufficient.
Example 2 (Normal, \(\sigma^2\) known).
\(f(\mathbf x\mid\mu)=(2\pi\sigma^2)^{-n/2}\exp\!\Big(-\frac{\sum x_i^2}{2\sigma^2}\Big)\cdot\exp\!\Big(\frac{\mu\sum x_i}{\sigma^2}-\frac{n\mu^2}{2\sigma^2}\Big)\).
Second factor \(=g(\sum x_i\mid\mu)\), first factor \(=h(\mathbf x)\): \(\sum X_i\) (or \(\bar X\)) is sufficient. With both unknown, \((\sum X_i,\sum X_i^2)\) is sufficient.
Example 3 (Uniform\((0,\theta)\)).
\(f(\mathbf x\mid\theta)=\prod\frac1\theta\mathbb 1\{0\lt x_i\lt\theta\}=\theta^{-n}\mathbb 1\{x_{(n)}\lt\theta\}\cdot\mathbb 1\{x_{(1)}\gt0\}\).
\(g(t\mid\theta)=\theta^{-n}\mathbb 1\{t\lt\theta\}\) with \(t=x_{(n)}\), \(h=\mathbb 1\{x_{(1)}\gt0\}\): \(X_{(n)}\) is sufficient. Trick: write the product of indicators as indicators of the min and max.
Exercises
easy
Poisson\((\lambda)\) sample. Find a sufficient statistic.
Bernoulli with \(n=2\). Show that \(X_1\) alone is not sufficient.
Show solution
\(P(\mathbf X=(1,0)\mid X_1=1)=P(X_2=0)=1-p\), which depends on \(p\). Not sufficient.
hard
Uniform\((\theta,\theta+1)\). Find a sufficient statistic. Is it one-dimensional?
Show solution
\(f=\mathbb 1\{\theta\lt x_{(1)}\}\mathbb 1\{x_{(n)}\lt\theta+1\}\): \(T=(X_{(1)},X_{(n)})\), two-dimensional although \(\theta\) is one-dimensional (it is also minimal sufficient: the likelihood ratio for two samples is constant in \(\theta\) only if they have the same min and max).
exam
\(X_1,\dots,X_n\) i.i.d. with \(f(x\mid\theta)=e^{-(x-\theta)}\), \(x\ge\theta\). (a) Find a sufficient statistic. (b) Is this an exponential family? (c) Show your statistic is minimal sufficient.
Show solution
(a) \(f(\mathbf x\mid\theta)=e^{n\theta}\mathbb 1\{x_{(1)}\ge\theta\}\cdot e^{-\sum x_i}\): \(T=X_{(1)}\). (b) No, the support depends on \(\theta\). (c) \(\frac{f(\mathbf x\mid\theta)}{f(\mathbf y\mid\theta)}=e^{-\sum x_i+\sum y_i}\frac{\mathbb 1\{x_{(1)}\ge\theta\}}{\mathbb 1\{y_{(1)}\ge\theta\}}\) is constant in \(\theta\) iff \(x_{(1)}=y_{(1)}\). So \(X_{(1)}\) is minimal sufficient.
Self-check quiz
Common mistakes
Putting a factor that contains \(\theta\) into \(h\). Everything with \(\theta\) goes into \(g\), and \(g\) may touch the data only through \(T\).
Forgetting the indicator of the support when it depends on \(\theta\) (uniform, shifted exponential): it decides the answer.
Thinking the sufficient statistic must have the same dimension as \(\theta\).
Remember
Sufficient: \(\mathbf X\mid T\) free of \(\theta\). Check with factorization \(f=g(T\mid\theta)h(\mathbf x)\).
Minimal sufficient: likelihood ratio constant in \(\theta\) iff \(T(\mathbf x)=T(\mathbf y)\).
B1.9 Point estimation: method of moments (and least squares)
Why it matters. The simplest general way to build an estimator; also the starting point for numerical MLE. Lectures 7-8 (C&B 7.1, 7.2.1). Several project proposals compare MoM with MLE.
How it is examined. "Find the method-of-moments estimator of \(\theta\)" (very common, often together with the MLE), then compute it for given data or discuss its properties.
Explanation
Definitions
A point estimator is any statistic \(W(X_1,\dots,X_n)\) used to guess \(\theta\); the value \(W(\mathbf x)\) for observed data is the estimate.
Method of moments (MoM) for \(\theta=(\theta_1,\dots,\theta_k)\): set the first \(k\) sample moments equal to the population moments and solve:
\[ m_j=\frac1n\sum_{i=1}^nX_i^j \;=\; \mu_j'(\theta_1,\dots,\theta_k)=E_\theta[X^j],\qquad j=1,\dots,k. \]
Recipe: (1) compute \(E X\) (and \(E X^2\) if two parameters) as functions of \(\theta\); (2) replace by \(\bar X\) and \(\frac1n\sum X_i^2\); (3) solve. With two parameters it is easier to match the mean and the variance: \(\bar X=\mu(\theta)\), \(\hat\sigma^2=\frac1n\sum(X_i-\bar X)^2=\sigma^2(\theta)\) (note the \(1/n\)).
MoM estimators are usually consistent (law of large numbers) and asymptotically normal (CLT + delta method), but they can be inefficient and can fall outside the parameter space.
To estimate a function \(\tau(\theta)\), plug in: \(\tau(\hat\theta)\).
Least squares (intro, lecture 7)
For a model \(Y_i=\beta_0+\beta_1x_i+\varepsilon_i\), least squares chooses \(\beta_0,\beta_1\) minimising \(\sum(y_i-\beta_0-\beta_1x_i)^2\):
\[ \hat\beta_1=\frac{\sum(x_i-\bar x)(y_i-\bar y)}{\sum(x_i-\bar x)^2},\qquad\hat\beta_0=\bar y-\hat\beta_1\bar x. \]
If the errors are i.i.d. \(N(0,\sigma^2)\), least squares = MLE. check how much regression the course covers (only listed as an intro in lecture 7).
Worked examples
Example 1 (Normal). \(\bar X=\mu\), \(\frac1n\sum X_i^2=\sigma^2+\mu^2\) give \(\hat\mu=\bar X\), \(\hat\sigma^2=\frac1n\sum X_i^2-\bar X^2=\frac1n\sum(X_i-\bar X)^2\) (biased, \(1/n\)).
Example 2 (Gamma\((\alpha,\beta)\), scale). \(EX=\alpha\beta\), \(\text{Var}X=\alpha\beta^2\). Solve: \(\hat\beta=\hat\sigma^2/\bar X\), \(\hat\alpha=\bar X^2/\hat\sigma^2\).
Data \(2.1,3.4,1.7,4.2,2.6\): \(\bar x=2.8\), \(\hat\sigma^2=0.812\), so \(\hat\alpha=7.84/0.812\approx9.66\), \(\hat\beta=0.812/2.8=0.29\).
Example 3 (Uniform\((0,\theta)\)). \(EX=\theta/2\Rightarrow\hat\theta=2\bar X\). Data \(0.5,1.2,3.9,0.8\): \(\hat\theta=3.2\), but we observed 3.9 > 3.2, so this estimate is impossible. The MLE \(X_{(n)}\) (B1.10) does not have this problem.
x <- c(2.1, 3.4, 1.7, 4.2, 2.6)
m <- mean(x) # 2.8
s2 <- mean((x - m)^2) # 0.812 (NOT var(x), which divides by n-1 = 1.015)
c(alpha = m^2 / s2, beta = s2 / m) # 9.655 0.290
Exercises
easy
Find the MoM estimator for Poisson\((\lambda)\) and for Exp(rate \(\lambda\)).
Geometric\((p)\) on \(\{1,2,\dots\}\). Data: 1, 3, 2, 1, 4, 1, 2, 2. Find the MoM estimate of \(p\) and of \(P(X=2)=p(1-p)\).
Show solution
\(EX=1/p\Rightarrow\hat p=1/\bar X=1/2=0.5\). Plug-in: \(\hat p(1-\hat p)=0.25\). (One of the project proposals on the geometric model compares this with the MLE and other estimators.)
medium
\(f(x\mid\theta)=\theta x^{\theta-1}\), \(0\lt x\lt1\). Find the MoM estimator and compute it for data 0.2, 0.5, 0.7, 0.9.
Uniform\((\theta-1,\theta+1)\)? And Uniform\((0,\theta)\): show that \(P(2\bar X\lt X_{(n)})\gt0\), so MoM can give impossible values.
Show solution
First: \(EX=\theta\Rightarrow\hat\theta=\bar X\). Second: for \(n=2\), \(2\bar X=X_1+X_2\ge X_{(2)}\) always, so take \(n=3\). \(2\bar X=\frac23(X_1+X_2+X_3)\lt X_{(3)}\) when the two smaller values are small: e.g. values \((0.1,0.1,0.9)\theta\) give \(2\bar X=0.733\theta\lt0.9\theta\). Small changes of these values keep the inequality, so the event has positive probability. Then \(2\bar X\) is smaller than an observed value, which is impossible for \(\theta\). Example 3 above shows the same for \(n=4\).
hard
Binomial\((k,p)\) with both \(k\) and \(p\) unknown. Find MoM estimators.
Show solution
\(\bar X=kp\), \(\hat\sigma^2=kp(1-p)\). Divide: \(1-p=\hat\sigma^2/\bar X\Rightarrow\hat p=1-\hat\sigma^2/\bar X\), \(\hat k=\bar X/\hat p=\dfrac{\bar X^2}{\bar X-\hat\sigma^2}\). It can be negative (if \(\hat\sigma^2\gt\bar X\)) and is not an integer: a known weakness (C&B Example 7.2.2).
exam
Fit \(y=\beta x\) (no intercept) by least squares to \((1,2.1),(2,3.9),(3,6.2),(4,7.8)\). Derive the formula first.
Plug-in for functions: \(\widehat{\tau(\theta)}=\tau(\hat\theta)\).
Least squares slope \(S_{xy}/S_{xx}\); equals MLE under normal errors.
B1.10 Maximum likelihood estimation (MLE)
Why it matters. The MLE is the central estimator of the course: it is invariant, usually consistent and asymptotically efficient, and it is the basis of the large-sample tests. Lectures 8-9 (C&B 6.3.1, 7.2.2).
How it is examined. Almost every exam has "find the MLE of \(\theta\) (and of \(\tau(\theta)\))", including a boundary case (uniform) or a case with no closed form. Expect 4-8 points.
Explanation
Definition: likelihood and MLE
For observed \(\mathbf x\), the likelihood function is \(L(\theta\mid\mathbf x)=f(\mathbf x\mid\theta)=\prod_{i=1}^nf(x_i\mid\theta)\), seen as a function of \(\theta\) with the data fixed. The maximum likelihood estimate \(\hat\theta(\mathbf x)\) is the value of \(\theta\in\Theta\) that maximises \(L(\theta\mid\mathbf x)\); \(\hat\theta(\mathbf X)\) is the MLE.
Recipe (smooth case).
Write the log-likelihood \(\ell(\theta)=\log L(\theta)=\sum\log f(x_i\mid\theta)\) (same maximiser, sums are easier).
Solve the score / likelihood equation \(\ell'(\theta)=0\).
Check it is a maximum: \(\ell''(\hat\theta)\lt0\) (or \(\ell'\) changes sign from + to −), and compare with the boundary of \(\Theta\).
Non-smooth case. If the support depends on \(\theta\), the likelihood contains indicators: draw \(L(\theta)\) as a function of \(\theta\) and find the maximum directly (do not differentiate the indicator).
Theorem (invariance of the MLE, C&B 7.2.10)
If \(\hat\theta\) is the MLE of \(\theta\), then for any function \(\tau\), the MLE of \(\tau(\theta)\) is \(\tau(\hat\theta)\).
Likelihood principle (C&B 6.3): if two samples give proportional likelihoods, \(L(\theta\mid\mathbf x)=C(\mathbf x,\mathbf y)L(\theta\mid\mathbf y)\), the conclusions about \(\theta\) should be the same. The MLE depends on the data only through a sufficient statistic.
Numerical MLE. When \(\ell'(\theta)=0\) has no closed form (Cauchy location, Beta shape, Gamma shape), use Newton-Raphson
\(\theta_{k+1}=\theta_k-\ell'(\theta_k)/\ell''(\theta_k)\), starting from a good initial value (e.g. MoM or sample median), or R's optimize / optim.
Worked examples
Example 1 (Poisson). \(\ell(\lambda)=-n\lambda+\big(\sum x_i\big)\log\lambda-\sum\log x_i!\). \(\ell'(\lambda)=-n+\sum x_i/\lambda=0\Rightarrow\hat\lambda=\bar x\); \(\ell''=-\sum x_i/\lambda^2\lt0\). Data 2, 0, 3, 1, 4: \(\hat\lambda=2\). By invariance, the MLE of \(P(X=0)=e^{-\lambda}\) is \(e^{-2}\approx0.135\).
Example 2 (Normal, both unknown). \(\ell=-\frac n2\log(2\pi\sigma^2)-\frac{1}{2\sigma^2}\sum(x_i-\mu)^2\).
\(\partial_\mu\ell=\frac{1}{\sigma^2}\sum(x_i-\mu)=0\Rightarrow\hat\mu=\bar x\). \(\partial_{\sigma^2}\ell=-\frac{n}{2\sigma^2}+\frac{1}{2\sigma^4}\sum(x_i-\mu)^2=0\Rightarrow\hat\sigma^2=\frac1n\sum(x_i-\bar x)^2\).
Data 4.2, 5.1, 3.8, 4.9, 5.5: \(\hat\mu=4.7\), \(\hat\sigma^2=0.38\) (whereas \(s^2=0.475\)), MLE of \(\sigma\) is \(\sqrt{0.38}\approx0.616\).
Example 3 (Uniform\((0,\theta)\), boundary). \(L(\theta)=\theta^{-n}\mathbb 1\{\theta\ge x_{(n)}\}\). For \(\theta\lt x_{(n)}\), \(L=0\); for \(\theta\ge x_{(n)}\), \(L=\theta^{-n}\) is decreasing. So \(\hat\theta=X_{(n)}\). The derivative is never zero: the maximum is at the edge.
\(L(\theta)=\theta^{-n}\) for \(\theta\ge x_{(n)}\), zero before: the maximum is at \(x_{(n)}\).
Example 4 (non-unique MLE). Uniform\((\theta-\frac12,\theta+\frac12)\): \(L=1\) for every \(\theta\in[x_{(n)}-\frac12,\,x_{(1)}+\frac12]\): every point of this interval is an MLE.
Example 5 (numerical, Cauchy location, R code not run here).
set.seed(2026)
x <- rcauchy(50, location = 3)
negloglik <- function(theta) -sum(dcauchy(x, location = theta, log = TRUE))
# the likelihood can be multimodal: search near the median
fit <- optimize(negloglik, interval = median(x) + c(-5, 5))
fit$minimum # MLE of the location (close to 3)
median(x) # robust competitor (project proposals!)
Exercises
easy
Find the MLE of \(\lambda\) for Exp(rate \(\lambda\)), and the MLE of the mean \(1/\lambda\).
Show solution
\(\ell=n\log\lambda-\lambda\sum x_i\); \(\ell'=n/\lambda-\sum x_i=0\Rightarrow\hat\lambda=1/\bar x\); \(\ell''=-n/\lambda^2\lt0\). Invariance: MLE of \(1/\lambda\) is \(\bar X\).
easy
Geometric\((p)\) on \(\{1,2,\dots\}\). Find the MLE of \(p\).
\(f(x\mid\theta)=\theta x^{\theta-1}\), \(0\lt x\lt1\). Find the MLE and compute it for 0.2, 0.5, 0.7, 0.9. Compare with the MoM value 1.353.
Show solution
\(\ell=n\log\theta+(\theta-1)\sum\log x_i\); \(\ell'=n/\theta+\sum\log x_i=0\Rightarrow\hat\theta=-n/\sum\log x_i\). \(\sum\log x_i=-1.609-0.693-0.357-0.105=-2.765\); \(\hat\theta=4/2.765\approx1.447\). Different from MoM (1.353): the two methods generally disagree.
medium
Uniform\((0,\theta)\): find the MLE of \(\theta\), of \(\theta^2\) and of the variance \(\theta^2/12\). Data 0.5, 1.2, 3.9, 0.8.
\(f(x\mid\theta)=e^{-(x-\theta)}\), \(x\ge\theta\). Find the MLE of \(\theta\). Then find the MLE of \(P_\theta(X\gt\theta+1)\) and of \(P_\theta(X\gt 5)\).
Show solution
\(L=e^{n\theta-\sum x_i}\mathbb 1\{\theta\le x_{(1)}\}\): increasing in \(\theta\) up to \(x_{(1)}\), then 0. \(\hat\theta=X_{(1)}\). \(P(X\gt\theta+1)=e^{-1}\) does not depend on \(\theta\), so its MLE is \(e^{-1}\). \(P(X\gt5)=e^{-(5-\theta)}\) for \(\theta\le5\) (and 1 otherwise); MLE \(e^{-(5-X_{(1)})}\) if \(X_{(1)}\le5\).
exam
Beta\((\alpha,1)\) has density \(\alpha x^{\alpha-1}\) and Beta\((\alpha,\alpha)\) does not have a closed-form MLE. (a) Write the score equation for Beta\((\alpha,\alpha)\) using the digamma function \(\psi=\Gamma'/\Gamma\). (b) Describe a Newton-Raphson iteration and a starting value.
Show solution
(a) \(\ell(\alpha)=n\log\Gamma(2\alpha)-2n\log\Gamma(\alpha)+(\alpha-1)\sum[\log x_i+\log(1-x_i)]\). \(\ell'(\alpha)=2n\psi(2\alpha)-2n\psi(\alpha)+\sum\log[x_i(1-x_i)]=0\).
(b) \(\alpha_{k+1}=\alpha_k-\ell'(\alpha_k)/\ell''(\alpha_k)\) with \(\ell''=4n\psi'(2\alpha)-2n\psi'(\alpha)\). Start at the MoM: \(\text{Var}X=\frac{1}{4(2\alpha+1)}\Rightarrow\alpha_0=\frac{1}{8\hat\sigma^2}-\frac12\). In R, simply optimize(function(a) -sum(dbeta(x, a, a, log = TRUE)), c(0.01, 100)).
Self-check quiz
Common mistakes
Setting the derivative to zero when the support depends on \(\theta\) (uniform, shifted exponential).
Not checking the second derivative / boundaries; not restricting to \(\Theta\) (e.g. \(\hat p\in[0,1]\)).
Thinking the MLE is unbiased: \(\hat\sigma^2\) and \(X_{(n)}\) are biased.
Differentiating w.r.t. \(\sigma\) and w.r.t. \(\sigma^2\) gives the same MLE (invariance), but do not mix them inside one calculation.
Remember
Maximise \(\ell(\theta)=\sum\log f(x_i\mid\theta)\); solve \(\ell'=0\); check \(\ell''\lt0\) and boundaries.
Support depends on \(\theta\): draw \(L\), the MLE is an order statistic.
Why it matters. The Bayesian approach treats \(\theta\) as random and combines prior knowledge with data; with conjugate priors everything is explicit. Lecture 9 (C&B 7.2.3).
How it is examined. "Find the posterior distribution and the Bayes estimator under squared error loss", often followed by "compare its MSE with the MLE".
Explanation
Definitions
The prior \(\pi(\theta)\) describes beliefs about \(\theta\) before the data. Given data \(\mathbf x\), the posterior is
\[ \pi(\theta\mid\mathbf x)=\frac{f(\mathbf x\mid\theta)\pi(\theta)}{m(\mathbf x)},\qquad m(\mathbf x)=\int f(\mathbf x\mid\theta)\pi(\theta)\,d\theta, \]
i.e. posterior \(\propto\) likelihood \(\times\) prior. The Bayes estimator under squared error loss is the posterior mean \(E[\theta\mid\mathbf x]\); under absolute error loss it is the posterior median.
The "\(\propto\)" trick. Drop every factor that does not contain \(\theta\), then recognise the kernel of a known density. You never need to compute \(m(\mathbf x)\).
Conjugate family
A family of priors is conjugate for a model if the posterior is in the same family.
Model
Prior
Posterior
Posterior mean
\(X_i\sim\) Bernoulli\((p)\), \(y=\sum x_i\)
Beta\((\alpha,\beta)\)
Beta\((\alpha+y,\ \beta+n-y)\)
\(\frac{\alpha+y}{\alpha+\beta+n}\)
\(X_i\sim\) Poisson\((\lambda)\)
Gamma(shape \(a\), rate \(b\))
Gamma\((a+\sum x_i,\ b+n)\)
\(\frac{a+\sum x_i}{b+n}\)
\(X_i\sim\) Exp(rate \(\lambda\))
Gamma(shape \(a\), rate \(b\))
Gamma\((a+n,\ b+\sum x_i)\)
\(\frac{a+n}{b+\sum x_i}\)
\(X_i\sim N(\theta,\sigma^2)\), \(\sigma^2\) known
Beta-binomial: \(\frac{\alpha+y}{\alpha+\beta+n}=\frac{n}{\alpha+\beta+n}\,\bar x+\frac{\alpha+\beta}{\alpha+\beta+n}\cdot\frac{\alpha}{\alpha+\beta}\): data mean and prior mean, weights \(n\) and \(\alpha+\beta\) ("prior sample size"). As \(n\to\infty\) the data dominate and the Bayes estimator approaches the MLE.
Check with the course
C&B writes Gamma\((\alpha,\beta)\) with scale \(\beta\); then the Poisson posterior is Gamma\(\big(\alpha+\sum x_i,\ \frac{\beta}{n\beta+1}\big)\) (scale), which is the same as rate \(\frac1\beta+n\). The table uses the rate form. Check the course notation.
Worked examples
Example 1 (Beta-binomial). Prior Beta(2,2), \(n=10\) trials, \(y=7\) successes.
Posterior \(\propto p^7(1-p)^3\cdot p^{1}(1-p)^{1}=p^8(1-p)^4\): Beta(9,5). Bayes estimate \(9/14\approx0.643\), between the MLE 0.7 and the prior mean 0.5: \(\frac{10}{14}0.7+\frac4{14}0.5=0.643\).
Example 2 (Normal-normal). \(\sigma^2=4\), \(n=4\), \(\bar x=3\), prior \(N(0,1)\). \(\sigma^2/n=1=\tau^2\), so posterior mean \(=\frac{1\cdot3+1\cdot0}{2}=1.5\), posterior variance \(=\frac{1\cdot1}{2}=0.5\).
Exercises
easy
Uniform prior on \(p\) (= Beta(1,1)); 3 successes in 4 Bernoulli trials. Posterior and Bayes estimate?
Show solution
Beta(4,2); mean \(4/6=2/3\) (Laplace's rule \((y+1)/(n+2)\)).
easy
Poisson data with \(n=4\), \(\sum x_i=12\); prior Gamma(shape 2, rate 1). Posterior and Bayes estimate?
Show solution
Gamma(14, 5); mean \(14/5=2.8\) (MLE 3, prior mean 2).
medium
Derive the Exponential-Gamma posterior in the table.
Show solution
\(\pi(\lambda\mid\mathbf x)\propto\lambda^ne^{-\lambda\sum x_i}\cdot\lambda^{a-1}e^{-b\lambda}=\lambda^{a+n-1}e^{-(b+\sum x_i)\lambda}\): kernel of Gamma\((a+n,b+\sum x_i)\). Posterior mean \((a+n)/(b+\sum x_i)\).
medium
Show that the Bayes estimator under squared error loss is the posterior mean: minimise \(E[(\theta-a)^2\mid\mathbf x]\) over \(a\).
Show solution
\(E[(\theta-a)^2\mid\mathbf x]=\text{Var}(\theta\mid\mathbf x)+(E[\theta\mid\mathbf x]-a)^2\), minimised at \(a=E[\theta\mid\mathbf x]\).
hard
\(X\sim\text{Bin}(n,p)\). Compare the MSE of \(\hat p=X/n\) and of \(\hat p_B=(X+1)/(n+2)\).
Show solution
\(\text{MSE}(X/n)=p(1-p)/n\). For \(\hat p_B\): bias \(=\frac{np+1}{n+2}-p=\frac{1-2p}{n+2}\), variance \(\frac{np(1-p)}{(n+2)^2}\); MSE \(=\frac{np(1-p)+(1-2p)^2}{(n+2)^2}\). \(\hat p_B\) is better for \(p\) near 1/2, worse near 0 or 1. Neither dominates.
exam
Normal-normal with general \(n\): show the posterior mean is \(w\bar x+(1-w)\mu\) with \(w=\frac{\tau^2}{\tau^2+\sigma^2/n}\). What happens as \(n\to\infty\) and as \(\tau^2\to\infty\)?
Show solution
\(\pi(\theta\mid\mathbf x)\propto\exp\big(-\frac{n(\bar x-\theta)^2}{2\sigma^2}-\frac{(\theta-\mu)^2}{2\tau^2}\big)\). Completing the square in \(\theta\): precision \(\frac{n}{\sigma^2}+\frac1{\tau^2}\), mean \(\frac{n\bar x/\sigma^2+\mu/\tau^2}{n/\sigma^2+1/\tau^2}=w\bar x+(1-w)\mu\). As \(n\to\infty\) or \(\tau^2\to\infty\) (flat prior), \(w\to1\): Bayes estimate \(\to\bar x\).
Self-check quiz
Common mistakes
Keeping the binomial coefficient or other \(\theta\)-free factors and then trying to integrate: just recognise the kernel.
Beta\((\alpha,\beta)\) kernel is \(p^{\alpha-1}(1-p)^{\beta-1}\): off-by-one errors in the exponents.
Why it matters. After finding MoM/MLE/Bayes estimators you must say which is better; MSE is the standard criterion. Lecture 10 (C&B 7.3.1).
How it is examined. "Is \(\hat\theta\) unbiased? If not, correct it", "compute the MSE of two estimators and compare". Very common exercise part.
Explanation
Definitions
Bias: \(\text{Bias}_\theta(W)=E_\theta W-\theta\). \(W\) is unbiased for \(\theta\) if \(E_\theta W=\theta\) for all \(\theta\).
Mean squared error: \(\text{MSE}_\theta(W)=E_\theta(W-\theta)^2\).
Bias-variance decomposition
\[ \text{MSE}_\theta(W)=\text{Var}_\theta(W)+\big(\text{Bias}_\theta(W)\big)^2. \]
Proof: add and subtract \(E W\): \(E(W-EW+EW-\theta)^2=\text{Var}W+(EW-\theta)^2+2(EW-\theta)\underbrace{E(W-EW)}_{=0}\).
For unbiased estimators MSE = variance. A small bias can buy a large decrease in variance (trade-off), so the best MSE estimator is often biased. Usually no estimator has the smallest MSE for every \(\theta\) (the constant \(W=\theta_0\) has MSE 0 at \(\theta_0\)), which is why we restrict to unbiased estimators:
Definition: best unbiased (UMVU) estimator
\(W^*\) is a best unbiased estimator of \(\tau(\theta)\), or uniformly minimum variance unbiased (UMVU), if \(E_\theta W^*=\tau(\theta)\) for all \(\theta\) and \(\text{Var}_\theta W^*\le\text{Var}_\theta W\) for all \(\theta\) and every other unbiased \(W\). How to find/verify it: the Cramér-Rao bound (B1.13).
Relative efficiency of two unbiased estimators: \(\text{Var}(W_2)/\text{Var}(W_1)\); a value above 1 means \(W_1\) is better.
Worked examples
Example 1 (normal variance). \(S^2\) is unbiased with \(\text{Var}S^2=\frac{2\sigma^4}{n-1}\) (B1.6), so \(\text{MSE}(S^2)=\frac{2\sigma^4}{n-1}\).
MLE \(\hat\sigma^2=\frac{n-1}{n}S^2\): bias \(-\sigma^2/n\), variance \(\frac{(n-1)^2}{n^2}\frac{2\sigma^4}{n-1}=\frac{2(n-1)\sigma^4}{n^2}\), so
\(\text{MSE}(\hat\sigma^2)=\frac{2(n-1)\sigma^4}{n^2}+\frac{\sigma^4}{n^2}=\frac{(2n-1)\sigma^4}{n^2}\lt\frac{2\sigma^4}{n-1}\). The biased MLE wins on MSE. For \(n=10\): \(0.19\sigma^4\) vs \(0.222\sigma^4\).
Example 2 (Uniform\((0,\theta)\)). \(W_1=2\bar X\): unbiased, \(\text{Var}=4\cdot\frac{\theta^2}{12n}=\frac{\theta^2}{3n}\).
\(X_{(n)}\): \(E=\frac{n}{n+1}\theta\), \(EX_{(n)}^2=\frac{n}{n+2}\theta^2\). Unbiased version \(W_2=\frac{n+1}{n}X_{(n)}\) has \(\text{Var}W_2=\frac{(n+1)^2}{n^2}\frac{n}{n+2}\theta^2-\theta^2=\frac{\theta^2}{n(n+2)}\).
For \(n=10\): \(\theta^2/30\) vs \(\theta^2/120\); \(W_2\) is 4 times more efficient, and the gap grows with \(n\).
Exercises
easy
Show that \(\bar X\) is unbiased for \(\mu\) and that \(W=X_1\) is also unbiased. Which is better?
Show solution
\(E\bar X=\mu\), \(EX_1=\mu\). \(\text{Var}\bar X=\sigma^2/n\le\sigma^2=\text{Var}X_1\): \(\bar X\) is better (efficiency ratio \(n\)).
easy
Find the MSE of \(c\bar X\) as an estimator of \(\mu\).
Exp(rate \(\lambda\)), MLE \(\hat\lambda=1/\bar X\). Using \(\sum X_i\sim\) Gamma\((n,\lambda)\) (rate) and \(E[1/\sum X_i]=\frac{\lambda}{n-1}\), find the bias and an unbiased estimator.
Uniform\((0,\theta)\): compute \(\text{MSE}(X_{(n)})\) and compare with \(\text{MSE}(\frac{n+1}nX_{(n)})\).
Show solution
\(\text{Var}X_{(n)}=\frac{n}{n+2}\theta^2-\frac{n^2}{(n+1)^2}\theta^2=\frac{n\theta^2}{(n+1)^2(n+2)}\); bias\(^2=\frac{\theta^2}{(n+1)^2}\). \(\text{MSE}=\frac{\theta^2(n+n+2)}{(n+1)^2(n+2)}=\frac{2\theta^2}{(n+1)(n+2)}\). Unbiased one: \(\frac{\theta^2}{n(n+2)}\), which is smaller for \(n\ge2\) (since \(\frac1n\lt\frac{2}{n+1}\iff n\gt1\)).
hard
Normal sample. Among estimators \(cS^2\), find the \(c\) that minimises the MSE for \(\sigma^2\).
Show solution
\(\text{MSE}(c)=c^2\frac{2\sigma^4}{n-1}+(c-1)^2\sigma^4\). Derivative: \(\frac{4c}{n-1}+2(c-1)=0\Rightarrow c=\frac{n-1}{n+1}\). So \(\frac{1}{n+1}\sum(X_i-\bar X)^2\) has the smallest MSE in this class (neither \(S^2\) nor the MLE).
exam
Bernoulli sample, \(T=\sum X_i\). (a) Is \(\bar X^2\) unbiased for \(p^2\)? (b) Find an unbiased estimator of \(p^2\).
Show solution
(a) \(E\bar X^2=\text{Var}\bar X+p^2=p^2+\frac{p(1-p)}n\): biased. (b) \(E[T(T-1)]=ET^2-ET=np(1-p)+n^2p^2-np=n(n-1)p^2\). So \(\frac{T(T-1)}{n(n-1)}\) is unbiased for \(p^2\).
Self-check quiz
Common mistakes
"Unbiased for one value of \(\theta\)" is not unbiased: it must hold for all \(\theta\).
Thinking unbiased implies better. Compare MSE.
\(E[g(W)]\neq g(E W)\): if \(W\) is unbiased for \(\theta\), \(g(W)\) is usually biased for \(g(\theta)\) (e.g. \(S\) is biased for \(\sigma\)).
Remember
MSE = Var + Bias². Unbiased: \(E_\theta W=\theta\ \forall\theta\).
Why it matters. The Cramér-Rao bound (CRB) tells you the best possible variance of an unbiased estimator; if an estimator reaches it, it is UMVU. Fisher information is also the asymptotic variance of the MLE (B1.14) and appears in Wald and score tests. Lectures 10-12 (C&B 7.3.2, Theorem 7.3.9), proved in class.
How it is examined. Exercise: "compute the Fisher information, the CRB for unbiased estimators of \(\tau(\theta)\), and check whether [estimator] attains it". Theory question: "state and prove the Cramér-Rao inequality" or "when does the CRB not apply?".
Explanation
Definition: score and Fisher information
For one observation, the score is \(s(\theta;X)=\dfrac{\partial}{\partial\theta}\log f(X\mid\theta)\). For a sample, \(S_n(\theta)=\sum_{i=1}^ns(\theta;X_i)=\ell'(\theta)\).
The Fisher information is
\[ I_n(\theta)=E_\theta\big[S_n(\theta)^2\big]=\text{Var}_\theta\big(S_n(\theta)\big). \]
For an i.i.d. sample \(I_n(\theta)=n\,I_1(\theta)\).
Lemma (mean and variance of the score)
Under regularity (we can differentiate \(\int f(x\mid\theta)dx=1\) under the integral sign):
(a) \(E_\theta[S_n(\theta)]=0\); (b) if also twice differentiable, \(I_n(\theta)=-E_\theta\!\left[\dfrac{\partial^2}{\partial\theta^2}\log f(\mathbf X\mid\theta)\right]\) (C&B Lemma 7.3.11; true for exponential families).
Proof of (a): \(E[S]=\int\frac{\partial_\theta f}{f}f\,dx=\int\partial_\theta f\,dx=\frac{d}{d\theta}\int f\,dx=\frac{d}{d\theta}1=0\).
Theorem (Cramér-Rao inequality, C&B 7.3.9 and Cor. 7.3.10)
Let \(\mathbf X\sim f(\mathbf x\mid\theta)\) and \(W(\mathbf X)\) be an estimator with \(E_\theta W=\tau(\theta)\) and \(\text{Var}_\theta W\lt\infty\), such that
\(\frac{d}{d\theta}E_\theta W=\int W(\mathbf x)\frac{\partial}{\partial\theta}f(\mathbf x\mid\theta)\,d\mathbf x\). Then
\[ \text{Var}_\theta\big(W(\mathbf X)\big)\;\ge\;\frac{\big(\tau'(\theta)\big)^2}{I_n(\theta)}=\frac{\big(\tau'(\theta)\big)^2}{n\,I_1(\theta)}\quad\text{(i.i.d.)}. \]
So \(\tau'(\theta)^2\le\text{Var}(W)\,I_n(\theta)\), i.e. \(\text{Var}(W)\ge\tau'(\theta)^2/I_n(\theta)\). For i.i.d. samples \(\text{Var}(S_n)=\sum\text{Var}(s(\theta;X_i))=nI_1(\theta)\) (independent terms, each with mean 0). \(\blacksquare\)
Attainment (C&B Cor. 7.3.15)
Equality in Cauchy-Schwarz holds iff \(S_n\) is a linear function of \(W\). So an unbiased \(W\) of \(\tau(\theta)\) attains the CRB iff
\[ S_n(\theta)=\ell'(\theta)=a(\theta)\big[W(\mathbf x)-\tau(\theta)\big]\ \text{ for some function } a(\theta). \]
Method: compute the score, try to write it as \(a(\theta)(W-\tau(\theta))\). If you can, \(W\) is the UMVU of that \(\tau(\theta)\).
Efficiency
An unbiased estimator that attains the CRB is efficient; it is then UMVU. Efficiency of an unbiased \(W\): \(e(W)=\dfrac{\text{CRB}}{\text{Var}(W)}\le1\).
When the CRB fails or is not attained
Support depends on \(\theta\) (Uniform\((0,\theta)\)): you cannot interchange derivative and integral. The formal "bound" is \(\theta^2/n\), but \(\frac{n+1}{n}X_{(n)}\) is unbiased with variance \(\frac{\theta^2}{n(n+2)}\), smaller. The CRB simply does not apply.
Bound valid but not attainable: Normal with \(\mu\) unknown, \(\tau=\sigma^2\). CRB \(=2\sigma^4/n\), but \(S^2\) has variance \(2\sigma^4/(n-1)\). The score cannot be written as \(a(\sigma^2)(W-\sigma^2)\) with \(W\) free of \(\mu\). \(S^2\) is still UMVU, but proving it needs completeness (Rao-Blackwell / Lehmann-Scheffé, C&B 7.3.3).
CRB is attained (for some \(\tau\)) only in exponential families, and only for \(\tau(\theta)=E[t(X)]\)-type parameters.
Check with the course
The lecture list mentions UMVU and "when CRB conditions fail" but lists only C&B 7.3.1-7.3.2; whether Rao-Blackwell, completeness and Lehmann-Scheffé are examined is NOT STATED. Know them as statements only.
Fisher information of common models (one observation)
none (\(\frac{n-1}{\sum X_i}\) has var \(\frac{\lambda^2}{n-2}\))
Exp(mean \(\beta\))
\(\frac1{\beta^2}\)
\(\frac{\beta^2}n\)
\(\bar X\)
Worked examples
Example 1 (Poisson, full solution). \(\log f=-\lambda+x\log\lambda-\log x!\); \(s=-1+x/\lambda\); \(\partial^2\log f=-x/\lambda^2\); \(I_1=E[X]/\lambda^2=1/\lambda\). CRB for unbiased estimators of \(\lambda\): \(\lambda/n\). \(\text{Var}\bar X=\lambda/n\): attained, so \(\bar X\) is UMVU. Check by attainment: \(S_n=\sum x_i/\lambda-n=\frac n\lambda(\bar x-\lambda)\), with \(a(\lambda)=n/\lambda\), \(W=\bar X\), \(\tau(\lambda)=\lambda\).
Example 2 (Exponential, rate). \(S_n=\frac n\lambda-\sum x_i=-n\big(\bar x-\frac1\lambda\big)\). This is \(a(\lambda)[W-\tau(\lambda)]\) with \(W=\bar X\), \(\tau(\lambda)=1/\lambda\). So \(\bar X\) attains the CRB for \(1/\lambda\) (bound \(\frac{(1/\lambda^2)^2}{n/\lambda^2}=\frac1{n\lambda^2}=\text{Var}\bar X\)), but no unbiased estimator of \(\lambda\) itself attains it.
Exercises
easy
Bernoulli\((p)\): compute \(I_1(p)\) in two ways and the CRB. Does \(\bar X\) attain it?
Show solution
\(s=\frac xp-\frac{1-x}{1-p}\). \(E s^2=p\frac1{p^2}+(1-p)\frac1{(1-p)^2}=\frac1p+\frac1{1-p}=\frac1{p(1-p)}\). Second way: \(\partial^2\log f=-\frac x{p^2}-\frac{1-x}{(1-p)^2}\), minus expectation \(=\frac1p+\frac1{1-p}\). CRB \(\frac{p(1-p)}n=\text{Var}\bar X\): attained.
easy
\(N(\mu,\sigma^2)\), \(\sigma^2\) known. Find \(I_1(\mu)\) and the CRB for unbiased estimators of \(\mu\).
Show solution
\(s=(x-\mu)/\sigma^2\), \(\partial^2=-1/\sigma^2\): \(I_1=1/\sigma^2\). CRB \(\sigma^2/n\) (attained by \(\bar X\)).
medium
Poisson: find the CRB for unbiased estimators of \(\tau(\lambda)=e^{-\lambda}=P(X=0)\).
Show solution
\(\tau'=-e^{-\lambda}\): bound \(\frac{e^{-2\lambda}}{n/\lambda}=\frac{\lambda e^{-2\lambda}}{n}\). (The score is linear in \(\bar x\), not in an estimator of \(e^{-\lambda}\), so the bound is not attained; the UMVU \((1-1/n)^{\sum X_i}\) exists by Lehmann-Scheffé.)
medium
Exp(rate \(\lambda\)): show \(\frac{n-1}{\sum X_i}\) is unbiased with variance \(\frac{\lambda^2}{n-2}\) (given \(E[1/T]=\frac\lambda{n-1}\), \(E[1/T^2]=\frac{\lambda^2}{(n-1)(n-2)}\), \(T=\sum X_i\)). Compute its efficiency.
\(f(x\mid\theta)=\theta x^{\theta-1}\), \(0\lt x\lt1\). (a) \(I_1(\theta)\). (b) Show \(W=-\frac1n\sum\log X_i\) attains the CRB for \(\tau(\theta)=1/\theta\).
Show solution
(a) \(\log f=\log\theta+(\theta-1)\log x\), \(\partial^2=-1/\theta^2\): \(I_1=1/\theta^2\). (b) \(S_n=\frac n\theta+\sum\log x_i=-n\big(W-\frac1\theta\big)\): form \(a(\theta)(W-\tau(\theta))\) with \(a=-n\). Hence \(W\) is unbiased for \(1/\theta\) and attains the bound \(\frac{(1/\theta^2)^2}{n/\theta^2}=\frac{1}{n\theta^2}\). (Check: \(-\log X\sim\) Exp(rate \(\theta\)), variance \(1/\theta^2\).)
exam
Uniform\((0,\theta)\). (a) Compute formally \(E[s^2]\) and the "bound" \(\theta^2/n\). (b) Show \(\frac{n+1}{n}X_{(n)}\) beats it. (c) Which hypothesis of the theorem fails?
Show solution
(a) \(\log f=-\log\theta\) on \((0,\theta)\), \(s=-1/\theta\), \(E s^2=1/\theta^2\), "bound" \(\theta^2/n\). Note \(E s=-1/\theta\neq0\): the lemma already fails. (b) Variance \(\frac{\theta^2}{n(n+2)}\lt\frac{\theta^2}n\). (c) \(\frac{d}{d\theta}\int_0^\theta W f\,dx\neq\int_0^\theta W\partial_\theta f\,dx\) because the limit of integration depends on \(\theta\) (Leibniz rule adds a boundary term).
Self-check quiz
Common mistakes
Forgetting the factor \(n\): \(I_n=nI_1\). The CRB for a sample is \(\tau'^2/(nI_1)\).
Forgetting \(\tau'(\theta)^2\) in the numerator when the target is \(\tau(\theta)\neq\theta\).
Applying the CRB to uniform/shift models (support depends on \(\theta\)).
Saying "not attained, so not UMVU": the UMVU may exist with variance above the bound (normal \(S^2\)).
Computing information in the scale parametrisation and the bound in the rate one (or vice versa).
Remember
Score mean 0, variance \(I_n=nI_1=-nE[\partial^2_\theta\log f]\).
Attained iff \(S_n=a(\theta)(W-\tau(\theta))\) → \(W\) efficient and UMVU.
Fails for Uniform\((0,\theta)\) (support depends on \(\theta\)).
B1.14 Convergence, laws of large numbers, CLT, Slutsky, delta method; consistency and asymptotic normality of the MLE
Why it matters. When exact distributions are not available (most real problems), we use large-sample approximations: approximate CIs, Wald/score/LR tests, and the fact that the MLE is approximately \(N(\theta,1/I_n(\theta))\). Lecture 12 (C&B 5.5) and probably lecture 13 (C&B 10.1).
How it is examined. "Show \(T_n\) is consistent", "find the asymptotic distribution of \(g(\bar X)\) (delta method)", "find the asymptotic distribution of the MLE", CLT approximations; theory: "define the types of convergence and their relations".
Check with the course
The topic of lecture 13 (29 Oct) was not visible in the brief. Because it follows the convergence lecture and precedes confidence intervals (which use C&B 10.1.3), this page assumes it covered the CLT, Slutsky, the delta method and consistency/asymptotic normality of the MLE (C&B 5.5, 10.1). This is an inference: confirm with the slides.
Explanation
Types of convergence (C&B 5.5)
In probability: \(X_n\xrightarrow{P}X\) if for every \(\varepsilon\gt0\), \(\lim_nP(|X_n-X|\ge\varepsilon)=0\).
Almost surely: \(X_n\xrightarrow{a.s.}X\) if \(P\big(\lim_nX_n=X\big)=1\).
In distribution: \(X_n\xrightarrow{d}X\) if \(F_{X_n}(x)\to F_X(x)\) at every continuity point \(x\) of \(F_X\).
In mean square: \(E(X_n-X)^2\to0\).
Relations: a.s. \(\Rightarrow\) in probability \(\Rightarrow\) in distribution; mean square \(\Rightarrow\) in probability (Chebyshev). Convergence in distribution to a constant implies convergence in probability to it.
Laws of large numbers
\(X_1,X_2,\dots\) i.i.d. with mean \(\mu\). WLLN: \(\bar X_n\xrightarrow{P}\mu\) (proof below if \(\sigma^2\lt\infty\)). SLLN: \(\bar X_n\xrightarrow{a.s.}\mu\) (needs only \(E|X|\lt\infty\)).
\(X_i\) i.i.d. with mean \(\mu\), variance \(0\lt\sigma^2\lt\infty\): \(\displaystyle\frac{\sqrt n(\bar X_n-\mu)}{\sigma}\xrightarrow{d}N(0,1)\). So \(\bar X_n\approx N(\mu,\sigma^2/n)\) for large \(n\), whatever the distribution of \(X_i\). (Proof in C&B via MGFs.)
Slutsky and continuous mapping
If \(X_n\xrightarrow{d}X\) and \(Y_n\xrightarrow{P}a\) (constant): \(Y_nX_n\xrightarrow{d}aX\) and \(X_n+Y_n\xrightarrow{d}X+a\).
If \(g\) is continuous: \(X_n\xrightarrow{P}X\Rightarrow g(X_n)\xrightarrow{P}g(X)\), same for \(\xrightarrow{d}\).
Typical use: \(S_n\xrightarrow{P}\sigma\), so \(\frac{\sqrt n(\bar X_n-\mu)}{S_n}\xrightarrow{d}N(0,1)\).
Delta method (C&B 5.5.24)
If \(\sqrt n(Y_n-\theta)\xrightarrow{d}N(0,\sigma^2)\) and \(g'(\theta)\) exists and is \(\neq0\):
\[ \sqrt n\big(g(Y_n)-g(\theta)\big)\xrightarrow{d}N\big(0,\ g'(\theta)^2\sigma^2\big). \]
Idea: first-order Taylor, \(g(Y_n)\approx g(\theta)+g'(\theta)(Y_n-\theta)\).
Consistency and asymptotic normality of estimators
Definitions
\(W_n\) is consistent for \(\theta\) if \(W_n\xrightarrow{P}\theta\) for every \(\theta\). Sufficient condition: \(\text{MSE}_\theta(W_n)\to0\), i.e. bias \(\to0\) and variance \(\to0\).
\(W_n\) is asymptotically normal if \(\sqrt n(W_n-\theta)\xrightarrow{d}N(0,v(\theta))\); \(v(\theta)\) is the asymptotic variance. It is asymptotically efficient if \(v(\theta)=1/I_1(\theta)\) (the CRB per observation).
Theorem (MLE, C&B 10.1.6 and 10.1.12)
Under regularity conditions (i.i.d., identifiable, common support not depending on \(\theta\), smooth density, \(\theta\) interior point), the MLE is consistent and
\[ \sqrt n(\hat\theta_n-\theta)\xrightarrow{d}N\Big(0,\ \frac1{I_1(\theta)}\Big),\qquad \sqrt n\big(\tau(\hat\theta_n)-\tau(\theta)\big)\xrightarrow{d}N\Big(0,\ \frac{\tau'(\theta)^2}{I_1(\theta)}\Big). \]
So the MLE is asymptotically efficient. In practice: \(\hat\theta\approx N\big(\theta,\ 1/I_n(\hat\theta)\big)\), standard error \(\text{se}(\hat\theta)=1/\sqrt{I_n(\hat\theta)}\) (or use the observed information \(-\ell''(\hat\theta)\)).
Sketch: Taylor-expand the score at \(\hat\theta\): \(0=\ell'(\hat\theta)\approx\ell'(\theta)+(\hat\theta-\theta)\ell''(\theta)\), so \(\sqrt n(\hat\theta-\theta)\approx\frac{\ell'(\theta)/\sqrt n}{-\ell''(\theta)/n}\). Numerator \(\xrightarrow{d}N(0,I_1)\) (CLT on the score), denominator \(\xrightarrow{P}I_1\) (LLN). Slutsky gives \(N(0,I_1/I_1^2)=N(0,1/I_1)\).
Counter-examples: Uniform\((0,\theta)\) (non-regular: rate \(n\), exponential limit, see B1.7) and the Cauchy sample mean (no mean, \(\bar X\) is again Cauchy for every \(n\), so no LLN).
Worked examples
Example 1 (CLT for binomial). \(X\sim\text{Bin}(100,0.5)\) is a sum of 100 Bernoulli. \(P(X\le55)\approx\Phi\big(\frac{55.5-50}{5}\big)=\Phi(1.1)\approx0.8643\) (with continuity correction 0.5). Exact value 0.8644.
Example 2 (delta method, Poisson). \(\sqrt n(\bar X-\lambda)\xrightarrow{d}N(0,\lambda)\). For \(g(\lambda)=e^{-\lambda}\): \(\sqrt n(e^{-\bar X}-e^{-\lambda})\xrightarrow{d}N(0,\lambda e^{-2\lambda})\). Note this asymptotic variance equals the CRB of B1.13: the MLE \(e^{-\bar X}\) is asymptotically efficient.
Example 3 (MLE asymptotics). For \(f=\theta x^{\theta-1}\): \(I_1=1/\theta^2\), so \(\sqrt n(\hat\theta-\theta)\xrightarrow{d}N(0,\theta^2)\) and \(\text{se}(\hat\theta)\approx\hat\theta/\sqrt n\).
# simulation check of the CLT for Exp(1) means (not run here)
set.seed(1)
means <- replicate(5000, mean(rexp(30, rate = 1)))
hist(sqrt(30) * (means - 1), breaks = 40, freq = FALSE)
curve(dnorm(x), add = TRUE) # approx N(0, 1) since sigma = 1
Exercises
easy
Show that \(\bar X\) is consistent for \(\mu\) when \(\sigma^2\lt\infty\), and that \(S^2\) is consistent for \(\sigma^2\) for normal data.
Show solution
\(\text{MSE}(\bar X)=\sigma^2/n\to0\). Normal: \(S^2\) unbiased with variance \(2\sigma^4/(n-1)\to0\). (In general \(S^2\) is consistent by the WLLN applied to \(X_i^2\) and continuous mapping.)
easy
\(n=36\) i.i.d. with \(\mu=10\), \(\sigma=3\) (distribution unknown). Approximate \(P(\bar X\gt11)\).
Exp(rate \(\lambda\)). Find the asymptotic distribution of the MLE \(\hat\lambda=1/\bar X\) in two ways: delta method and Fisher information.
Show solution
Delta: \(\sqrt n(\bar X-1/\lambda)\to N(0,1/\lambda^2)\), \(g(y)=1/y\), \(g'(1/\lambda)=-\lambda^2\): variance \(\lambda^4/\lambda^2=\lambda^2\). Fisher: \(I_1=1/\lambda^2\Rightarrow1/I_1=\lambda^2\). Both give \(\sqrt n(\hat\lambda-\lambda)\to N(0,\lambda^2)\).
exam
(a) Give an example of \(X_n\xrightarrow{d}X\) but not in probability. (b) Use Slutsky to show \(\sqrt n(\bar X-\mu)/S\xrightarrow{d}N(0,1)\) for any i.i.d. sample with finite variance.
Show solution
(a) \(X\sim N(0,1)\), \(X_n=-X\) for all \(n\): same distribution, so \(\xrightarrow{d}\), but \(P(|X_n-X|\ge\varepsilon)=P(2|X|\ge\varepsilon)\not\to0\). (b) CLT: \(\sqrt n(\bar X-\mu)/\sigma\xrightarrow{d}N(0,1)\); WLLN + continuous mapping: \(S\xrightarrow{P}\sigma\), so \(\sigma/S\xrightarrow{P}1\). Slutsky: product \(\xrightarrow{d}1\cdot N(0,1)\).
Self-check quiz
Common mistakes
Writing the delta method variance as \(g'(\theta)\sigma^2\) (must be squared).
Applying the CLT to the Cauchy (no variance) or the "MLE theorem" to the uniform (non-regular).
Saying "unbiased therefore consistent" (\(X_1\) is unbiased but not consistent) or "consistent therefore unbiased" (\(\hat\sigma^2\)).
Remember
a.s. ⇒ P ⇒ d. WLLN via Chebyshev. CLT: \(\sqrt n(\bar X-\mu)/\sigma\to N(0,1)\).
Slutsky: replace \(\sigma\) by a consistent \(S\). Delta: variance \(\times g'(\theta)^2\).
Why it matters. A point estimate without an error margin is not enough; CIs quantify uncertainty. Lectures 14-16 (C&B 9.1, 9.2.2-9.2.3, 9.3.1, 10.1.3, 10.4).
How it is examined. "Find a pivot and build a \(1-\alpha\) CI", "compute the 95% CI from these data", "find \(n\) so that the CI has length at most \(L\)", plus MC/theory questions on the correct interpretation.
Explanation
Definitions (C&B 9.1)
An interval estimator is a pair of statistics \(L(\mathbf X)\le U(\mathbf X)\); the random interval \([L(\mathbf X),U(\mathbf X)]\).
Its coverage probability is \(P_\theta\big(\theta\in[L(\mathbf X),U(\mathbf X)]\big)\), a function of \(\theta\).
The confidence coefficient is \(\inf_\theta P_\theta(\theta\in[L,U])\). A \(1-\alpha\) confidence interval has confidence coefficient \(1-\alpha\).
Interpretation (exam favourite)
The interval is random, \(\theta\) is fixed. "95% confidence" means: if we repeated the experiment many times, about 95% of the intervals would contain \(\theta\). For one computed interval, e.g. \([11.27,13.33]\), we do not say "\(P(\theta\in[11.27,13.33])=0.95\)": that probability is 0 or 1. (A Bayesian credible interval does have that interpretation.)
Pivotal quantity (C&B 9.2.2)
\(Q(\mathbf X,\theta)\) is a pivot if its distribution does not depend on \(\theta\) (nor on other unknown parameters). Method: find \(a,b\) with \(P(a\le Q\le b)=1-\alpha\), then solve \(a\le Q(\mathbf X,\theta)\le b\) for \(\theta\).
Typical pivots: location families \(\bar X-\mu\); scale families \(\bar X/\sigma\); location-scale \((\bar X-\mu)/S\).
Notation: \(z_{\alpha/2}\), \(t_{n-1,\alpha/2}\), \(\chi^2_{k,\alpha/2}\) are upper \(\alpha/2\) quantiles (area \(\alpha/2\) to the right), as in C&B. So \(z_{0.025}=1.96\), \(z_{0.05}=1.645\), and \(\chi^2_{k,\alpha/2}\) is the larger chi-squared value.
Check with the course
Some teachers (and R's qchisq) use lower-tail quantiles, e.g. \(\chi^2_{n-1;1-\alpha/2}\) for the large value. The formulas are the same intervals; just make sure the bigger quantile goes in the denominator of the lower limit.
Optimal (shortest) length
Theorem (C&B 9.3.2)
If the pivot has a unimodal pdf \(f\) and \(\int_a^bf=1-\alpha\), the interval \([a,b]\) of minimum length \(b-a\) satisfies \(f(a)=f(b)\) (and \(a\le\) mode \(\le b\)). For symmetric unimodal pivots (normal, \(t\)) this gives the symmetric "\(\pm\)" interval.
For skewed pivots (\(\chi^2\)) the equal-tail interval is not the shortest, but it is the one normally used. If the interval is \(\big[\frac{c}{b},\frac{c}{a}\big]\) (a function of the pivot), the shortest-in-\(\theta\) condition changes (C&B 9.3.1 discussion); the equal-tail choice is the standard exam answer unless asked otherwise.
Sample size for a \(z\)-interval with half-width \(E\): \(n\ge\big(z_{\alpha/2}\sigma/E\big)^2\).
Worked examples
Example 1 (t interval). \(n=25\), \(\bar x=12.3\), \(s=2.5\). \(t_{24,0.025}=2.064\), se \(=2.5/5=0.5\). 95% CI: \(12.3\pm1.032=[11.27,\ 13.33]\).
Example 2 (variance). Same data, \(s^2=6.25\). \(\chi^2_{24,0.025}=39.364\), \(\chi^2_{24,0.975}=12.401\). CI for \(\sigma^2\): \(\big[\frac{24\cdot6.25}{39.364},\frac{24\cdot6.25}{12.401}\big]=[3.81,\ 12.10]\). Not symmetric around 6.25.
Example 3 (exponential pivot). \(n=10\), \(\sum x_i=25\). Since \(\sum X_i\sim\) Gamma\((n,\text{rate }\lambda)\), \(2\lambda\sum X_i\sim\chi^2_{20}\). With \(\chi^2_{20,0.975}=9.591\), \(\chi^2_{20,0.025}=34.170\): CI for \(\lambda\) \(=[9.591/50,\ 34.170/50]=[0.192,\ 0.683]\).
Example 4 (proportion). 120 successes out of 400: \(\hat p=0.3\), se \(=\sqrt{0.21/400}=0.0229\); 95% CI \(0.3\pm0.045=[0.255,0.345]\).
x <- c(...) # your data
t.test(x, conf.level = 0.95)$conf.int
n <- 25; s2 <- 6.25
(n - 1) * s2 / qchisq(c(0.975, 0.025), df = n - 1) # 3.81 12.10
qchisq(c(0.025, 0.975), df = 20) / 50 # 0.192 0.683
Exercises
easy
\(n=16\), \(\sigma=4\) known, \(\bar x=20\). 95% CI for \(\mu\)?
Show solution
\(20\pm1.96\cdot4/4=20\pm1.96=[18.04,21.96]\).
easy
\(\sigma=10\). How large must \(n\) be for a 95% CI of half-width at most 2?
Uniform\((0,\theta)\). Show \(X_{(n)}/\theta\) is a pivot and derive the CI \([X_{(n)},X_{(n)}/\alpha^{1/n}]\). Compute it for \(n=5\), \(x_{(5)}=4.2\), \(\alpha=0.05\).
Show solution
\(P(X_{(n)}/\theta\le t)=t^n\) on \([0,1]\): free of \(\theta\). Choose \(P(\alpha^{1/n}\le X_{(n)}/\theta\le1)=1-\alpha\) (this is the shortest choice, since the pdf \(nt^{n-1}\) is increasing). Solve: \(X_{(n)}\le\theta\le X_{(n)}/\alpha^{1/n}\). \(0.05^{1/5}=0.5493\): CI \([4.2,\ 7.65]\).
medium
Poisson, \(n=50\), \(\bar x=3.2\). Approximate 95% Wald CI for \(\lambda\).
Show solution
\(I_n(\lambda)=n/\lambda\), se \(=\sqrt{\hat\lambda/n}=\sqrt{0.064}=0.253\). \(3.2\pm1.96\cdot0.253=3.2\pm0.496=[2.70,3.70]\).
hard
Show that \(2\lambda\sum X_i\sim\chi^2_{2n}\) for an Exp(rate \(\lambda\)) sample (use MGFs), and derive the interval in Example 3.
Show solution
\(M_{2\lambda X}(t)=M_X(2\lambda t)=\frac{\lambda}{\lambda-2\lambda t}=(1-2t)^{-1}\), the \(\chi^2_2\) MGF. A sum of \(n\) independent \(\chi^2_2\) is \(\chi^2_{2n}\) (MGF \((1-2t)^{-n}\)). Then \(P(\chi^2_{2n,1-\alpha/2}\le2\lambda\sum X_i\le\chi^2_{2n,\alpha/2})=1-\alpha\); divide by \(2\sum X_i\).
exam
(a) A student says: "the 95% CI is [2.70, 3.70], so there is a 95% probability that \(\lambda\) is in it". Correct the statement. (b) If you compute 20 independent 95% CIs, what is the expected number that miss the parameter, and the probability that all cover it?
Show solution
(a) \(\lambda\) is fixed; the procedure covers \(\lambda\) in 95% of repeated samples; this specific interval either contains it or not. (b) Misses \(\sim\text{Bin}(20,0.05)\): expected 1. \(P(\text{all cover})=0.95^{20}\approx0.358\).
Self-check quiz
Common mistakes
Using \(z\) instead of \(t\) when \(\sigma\) is unknown and \(n\) is small.
Putting the chi-squared quantiles the wrong way round (lower limit uses the larger quantile).
Using \(\sigma/\sqrt n\) with \(\sigma\) replaced by \(s^2\) (variance instead of sd).
Wrong interpretation ("probability that \(\theta\) is in this interval").
Remember
Pivot → probability statement → solve for \(\theta\).
Shortest interval: \(f(a)=f(b)\). Coverage is a property of the procedure.
B1.16 Hypothesis testing: errors, size, level, power function
Why it matters. This is the vocabulary of every test in the course. Lecture 16-17 (C&B 8.1, 8.3.1).
How it is examined. "Compute the size of this test", "compute and sketch the power function", "find \(c\) so that the test has level \(\alpha\)", "find \(n\) for power 0.9"; MC questions on the definitions.
Explanation
Definitions (C&B 8.1, 8.3.1)
A hypothesis is a statement about \(\theta\). We test \(H_0:\theta\in\Theta_0\) (null) against \(H_1:\theta\in\Theta_0^c\) (alternative). A hypothesis is simple if it contains one value of \(\theta\), otherwise composite.
A test specifies a rejection region \(R\) (critical region): reject \(H_0\) if \(\mathbf X\in R\), otherwise "do not reject" (accept). Usually \(R=\{\mathbf x:W(\mathbf x)\gt c\}\) for a test statistic \(W\).
Type I error: reject \(H_0\) when it is true. Type II error: accept \(H_0\) when it is false.
Power function: \(\beta(\theta)=P_\theta(\mathbf X\in R)\). For \(\theta\in\Theta_0\) it is the type I error probability; for \(\theta\in\Theta_0^c\) it is the power \(=1-P(\text{type II error})\).
The test has size \(\alpha\) if \(\sup_{\theta\in\Theta_0}\beta(\theta)=\alpha\), and level \(\alpha\) if \(\sup_{\theta\in\Theta_0}\beta(\theta)\le\alpha\).
accept \(H_0\)
reject \(H_0\)
\(H_0\) true
correct
type I error (prob. \(\le\alpha\))
\(H_1\) true
type II error
correct (power)
The ideal power function is 0 on \(\Theta_0\) and 1 on \(\Theta_0^c\); impossible with finite data. We fix the type I error (\(\alpha=0.05\), 0.01) and maximise power. Larger \(n\) gives steeper power functions.
Check with the course
C&B uses \(\beta(\theta)\) for the power function. Many other books use \(\beta\) for the type II error probability (so power \(=1-\beta\)). Read each exam question carefully.
Worked examples
Example 1 (one-sided normal test). \(X_1,\dots,X_n\sim N(\theta,\sigma^2)\), \(\sigma\) known, \(H_0:\theta\le\theta_0\) vs \(H_1:\theta\gt\theta_0\). Reject if \(Z=\frac{\bar X-\theta_0}{\sigma/\sqrt n}\gt c\). Power function:
\[ \beta(\theta)=P_\theta\Big(\frac{\bar X-\theta}{\sigma/\sqrt n}\gt c+\frac{\theta_0-\theta}{\sigma/\sqrt n}\Big)=1-\Phi\Big(c+\frac{\theta_0-\theta}{\sigma/\sqrt n}\Big). \]
It is increasing in \(\theta\), so the size is \(\beta(\theta_0)=1-\Phi(c)\); size \(\alpha\) needs \(c=z_\alpha\).
Numbers: \(\sigma=1\), \(n=25\), \(\theta_0=0\), \(\alpha=0.05\): reject if \(\bar X\gt1.645/5=0.329\). Power at \(\theta=0.5\): \(1-\Phi(1.645-2.5)=1-\Phi(-0.855)\approx0.804\).
Power function \(\beta(\theta)\) of the one-sided test: \(\le\alpha\) on \(H_0\), rising to 1 on \(H_1\).
Example 2 (sample size). Same test; we want power \(\ge0.9\) at \(\theta=0.5\): \(1-\Phi(z_\alpha-0.5\sqrt n)\ge0.9\iff z_\alpha-0.5\sqrt n\le-1.2816\iff\sqrt n\ge(1.645+1.2816)/0.5\iff n\ge34.26\). Take \(n=35\). General: \(n\ge\big((z_\alpha+z_{\beta})\sigma/\delta\big)^2\).
Exercises
easy
\(X\sim\text{Bin}(5,\theta)\), \(H_0:\theta\le\frac12\) vs \(H_1:\theta\gt\frac12\). Reject iff \(X=5\). Find \(\beta(\theta)\), the size, and the power at \(\theta=3/4\).
Show solution
\(\beta(\theta)=\theta^5\), increasing, so size \(=(1/2)^5=0.03125\). Power at 3/4: \(0.75^5\approx0.237\) (low: the test is very conservative).
easy
A drug is tested with \(H_0\): "no effect". Describe the type I and type II errors in words. Which one does \(\alpha\) control?
Show solution
Type I: concluding the drug works when it does not. Type II: missing a drug that works. \(\alpha\) bounds the type I error probability.
medium
In Example 1 (\(n=25\), \(\sigma=1\), \(c=z_{0.05}\)), compute \(\beta(0)\), \(\beta(-0.2)\), \(\beta(0.3)\).
Two-sided: \(H_0:\theta=\theta_0\) vs \(\theta\neq\theta_0\), reject if \(|Z|\gt z_{\alpha/2}\). Write \(\beta(\theta)\).
Show solution
With \(\delta=\frac{\theta-\theta_0}{\sigma/\sqrt n}\): \(\beta(\theta)=P(Z+\delta\gt z_{\alpha/2})+P(Z+\delta\lt-z_{\alpha/2})=1-\Phi(z_{\alpha/2}-\delta)+\Phi(-z_{\alpha/2}-\delta)\). Symmetric around \(\theta_0\), minimum \(\alpha\) at \(\theta_0\).
hard
\(X_1,\dots,X_5\sim U(0,\theta)\), \(H_0:\theta\le1\) vs \(H_1:\theta\gt1\), reject if \(X_{(5)}\gt c\). Find \(c\) for size 0.05 and the power at \(\theta=1.5\).
Show solution
\(\beta(\theta)=P_\theta(X_{(5)}\gt c)=1-(c/\theta)^5\) (for \(\theta\gt c\)), increasing in \(\theta\). Size: \(1-c^5=0.05\Rightarrow c=0.95^{1/5}\approx0.9898\). Power at 1.5: \(1-(0.9898/1.5)^5\approx0.875\).
exam
For the test of Example 1 with general \(n\), show that \(\beta(\theta)\to1\) for every \(\theta\gt\theta_0\) as \(n\to\infty\) (the test is consistent), and find \(n\) giving power 0.8 at \(\theta=\theta_0+0.25\sigma\) with \(\alpha=0.05\) (\(z_{0.2}=0.8416\)).
Show solution
\(\beta(\theta)=1-\Phi\big(z_\alpha-\frac{\sqrt n(\theta-\theta_0)}{\sigma}\big)\); the argument \(\to-\infty\), so \(\beta\to1\). \(\sqrt n\cdot0.25\ge1.645+0.8416=2.4866\Rightarrow n\ge98.9\), so \(n=99\).
Self-check quiz
Common mistakes
Computing the size at an arbitrary point of \(\Theta_0\): it is the supremum over \(\Theta_0\) (usually at the boundary \(\theta_0\)).
"Accept \(H_0\)" does not prove \(H_0\); it means the data are not strong evidence against it.
Mixing up the meaning of \(\beta\) (power function in C&B vs type II error elsewhere).
Remember
\(\beta(\theta)=P_\theta(\text{reject})\). Size \(=\sup_{\Theta_0}\beta\); level \(\alpha\) means size \(\le\alpha\).
Why it matters. These results tell you the best test, not just a reasonable one. Lectures 17-18 (C&B 8.3.2, 8.3.4 area); the MLR method is explicitly in the official outline.
How it is examined. "Find the most powerful level-\(\alpha\) test of \(H_0:\theta=\theta_0\) vs \(H_1:\theta=\theta_1\)", "show the family has MLR in \(T\) and find the UMP test for \(H_0:\theta\le\theta_0\)", "compute its power"; theory: "state the Neyman-Pearson lemma / Karlin-Rubin theorem".
Explanation
Definition: UMP test
A test with power function \(\beta(\theta)\) in the class \(\mathcal C\) of level-\(\alpha\) tests is uniformly most powerful (UMP) if \(\beta(\theta)\ge\beta'(\theta)\) for every \(\theta\in\Theta_0^c\) and every other test in \(\mathcal C\). For a simple \(H_1\) we say "most powerful".
Neyman-Pearson lemma (C&B 8.3.12)
Test \(H_0:\theta=\theta_0\) vs \(H_1:\theta=\theta_1\) (both simple). Consider a test with rejection region \(R\) such that, for some \(k\ge0\),
\[ \mathbf x\in R\ \text{ if } f(\mathbf x\mid\theta_1)\gt k\,f(\mathbf x\mid\theta_0),\qquad \mathbf x\in R^c\ \text{ if } f(\mathbf x\mid\theta_1)\lt k\,f(\mathbf x\mid\theta_0), \]
and \(\alpha=P_{\theta_0}(\mathbf X\in R)\). Then (sufficiency) this test is a UMP level-\(\alpha\) test; (necessity) if such a test exists with \(k\gt0\), every UMP level-\(\alpha\) test has this form (up to a set of probability 0) and has size \(\alpha\).
Proof idea (sufficiency). Let \(\phi=\mathbb 1_R\) and \(\phi'\) be any other level-\(\alpha\) test. For every \(\mathbf x\), \((\phi(\mathbf x)-\phi'(\mathbf x))(f(\mathbf x\mid\theta_1)-kf(\mathbf x\mid\theta_0))\ge0\) (check the two cases). Integrate: \(\beta(\theta_1)-\beta'(\theta_1)\ge k\big(\beta(\theta_0)-\beta'(\theta_0)\big)=k(\alpha-\beta'(\theta_0))\ge0\).
If \(T\) is sufficient, the NP test can be written in terms of \(g(t\mid\theta_1)\gt k\,g(t\mid\theta_0)\) (C&B Cor. 8.3.13). In practice: take the likelihood ratio, simplify, and see that "ratio \(\gt k\)" \(\iff\) "\(T\gt c\)" (or \(T\lt c\)); then choose \(c\) from the null distribution of \(T\).
For discrete \(T\) usually no \(c\) gives size exactly \(\alpha\): either accept a smaller size, or use a randomised test (reject with some probability at \(T=c\)).
Monotone likelihood ratio (MLR, C&B 8.3.16)
A family of pdfs/pmfs \(\{g(t\mid\theta)\}\) for a univariate statistic \(T\) has an MLR if for every \(\theta_2\gt\theta_1\), \(\dfrac{g(t\mid\theta_2)}{g(t\mid\theta_1)}\) is a monotone (non-decreasing) function of \(t\) where it is defined.
One-parameter exponential families \(g(t\mid\theta)=h(t)c(\theta)e^{w(\theta)t}\) have MLR in \(t\) if \(w(\theta)\) is non-decreasing (in \(-t\) if decreasing).
Karlin-Rubin theorem (C&B 8.3.17)
Test \(H_0:\theta\le\theta_0\) vs \(H_1:\theta\gt\theta_0\). Suppose \(T\) is sufficient for \(\theta\) and its family has a non-decreasing MLR. Then for any \(t_0\), the test that rejects \(H_0\) iff \(T\gt t_0\) is a UMP level-\(\alpha\) test with \(\alpha=P_{\theta_0}(T\gt t_0)\).
(For \(H_0:\theta\ge\theta_0\) vs \(H_1:\theta\lt\theta_0\): reject iff \(T\lt t_0\).)
Two-sided alternatives (\(H_1:\theta\neq\theta_0\)) usually have no UMP test: the best test against \(\theta\gt\theta_0\) is bad against \(\theta\lt\theta_0\). Then we use unbiased tests or the LRT (B1.18).
Worked examples
Example 1 (NP, normal). \(X_1,\dots,X_4\sim N(\theta,1)\), \(H_0:\theta=0\) vs \(H_1:\theta=1\).
\(\frac{f(\mathbf x\mid1)}{f(\mathbf x\mid0)}=\exp\big(\sum x_i-\frac n2\big)\gt k\iff\bar x\gt c\). Size 0.05: \(P_0(\bar X\gt c)=0.05\Rightarrow c=1.645/\sqrt4=0.8225\). Power: \(P_1(\bar X\gt0.8225)=1-\Phi((0.8225-1)\cdot2)=\Phi(0.355)\approx0.639\).
The rejection region does not depend on \(\theta_1\) (any \(\theta_1\gt0\) gives the same test), so it is UMP for \(H_1:\theta\gt0\), and by Karlin-Rubin also for \(H_0:\theta\le0\).
Example 2 (Karlin-Rubin, Poisson). \(f(\mathbf x\mid\lambda)\propto e^{-n\lambda}\lambda^{\sum x_i}\): exponential family with \(w(\lambda)=\log\lambda\) increasing, \(T=\sum X_i\sim\) Poisson\((n\lambda)\). UMP test for \(H_0:\lambda\le\lambda_0\) vs \(\lambda\gt\lambda_0\): reject iff \(\sum X_i\gt t_0\), with \(t_0\) from Poisson\((n\lambda_0)\).
Example 3 (direction). Exp(rate \(\lambda\)): \(\lambda^ne^{-\lambda\sum x_i}\), \(w(\lambda)=-\lambda\) is decreasing, so MLR is in \(-\sum x_i\). For \(H_1:\lambda\gt\lambda_0\), reject when \(\sum X_i\) is small. Makes sense: a high rate means short lifetimes.
Exercises
easy
Show that Bernoulli\((p)\) has MLR in \(T=\sum X_i\). State the form of the UMP test for \(H_0:p\le p_0\) vs \(H_1:p\gt p_0\).
Show solution
\(T\sim\text{Bin}(n,p)\); for \(p_2\gt p_1\): \(\frac{g(t\mid p_2)}{g(t\mid p_1)}=\big(\frac{1-p_2}{1-p_1}\big)^n\Big(\frac{p_2(1-p_1)}{p_1(1-p_2)}\Big)^t\), increasing in \(t\) since the base is \(\gt1\). UMP: reject iff \(T\gt t_0\).
medium
\(n=10\) Bernoulli, \(H_0:p\le0.5\) vs \(p\gt0.5\), \(\alpha=0.05\). \(P_{0.5}(T\ge8)=0.0547\), \(P_{0.5}(T\ge9)=0.0107\). Which non-randomised UMP test has level 0.05? Its size and its power at \(p=0.7\) (\(P_{0.7}(T\ge9)=0.149\))?
Show solution
Rejecting for \(T\ge8\) has size 0.0547 \(\gt0.05\): not level 0.05. So reject iff \(T\ge9\): size 0.0107, power at 0.7 only 0.149. (A randomised test could reach size exactly 0.05.)
medium
Exponential with mean \(\theta\) (pdf \(\frac1\theta e^{-x/\theta}\)), \(n=10\). Find the UMP level-0.05 test of \(H_0:\theta\le2\) vs \(H_1:\theta\gt2\) (\(\chi^2_{20,0.05}=31.41\), upper quantile).
Show solution
\(w(\theta)=-1/\theta\) is increasing, \(T=\sum X_i\): reject iff \(\sum X_i\gt t_0\). Under \(\theta=2\): \(2\sum X_i/\theta=\sum X_i\sim\chi^2_{20}\). So reject iff \(\sum X_i\gt31.41\) (i.e. \(\bar x\gt3.141\)).
hard
\(f(x\mid\theta)=\theta x^{\theta-1}\) on (0,1). Find the UMP level-\(\alpha\) test of \(H_0:\theta\le1\) vs \(H_1:\theta\gt1\) and express the critical value with a \(\chi^2\) quantile.
Show solution
\(f(\mathbf x\mid\theta)=\theta^n\exp((\theta-1)\sum\log x_i)\): \(w(\theta)=\theta\) increasing, MLR in \(T=\sum\log X_i\). Reject iff \(T\gt t_0\iff-2\sum\log X_i\lt c\). At \(\theta=1\), \(-\log X_i\sim\text{Exp}(1)\), so \(-2\sum\log X_i\sim\chi^2_{2n}\). Size \(\alpha\): \(c=\chi^2_{2n,1-\alpha}\) (the lower \(\alpha\) quantile).
hard
NP lemma, Poisson: \(H_0:\lambda=1\) vs \(H_1:\lambda=2\), \(n=5\). Show the MP test rejects for large \(\sum X_i\). With \(T\sim\) Poisson(5) under \(H_0\), \(P(T\ge9)=0.0681\), \(P(T\ge10)=0.0318\): pick the non-randomised level-0.05 test.
Show solution
Ratio \(=e^{-5(2-1)}2^{\sum x_i}\), increasing in \(\sum x_i\). Reject iff \(T\ge10\) (size 0.0318).
exam
\(X_1,\dots,X_n\sim N(\theta,1)\). Explain why there is no UMP level-\(\alpha\) test of \(H_0:\theta=0\) vs \(H_1:\theta\neq0\).
Show solution
For any \(\theta_1\gt0\) the NP test (MP for \(\theta_1\)) is "reject if \(\bar X\gt z_\alpha/\sqrt n\)"; by the necessity part, a UMP test would have to be this one. For \(\theta_1\lt0\) the MP test is "reject if \(\bar X\lt-z_\alpha/\sqrt n\)", a different region. No single test is MP for both sides, so no UMP test exists. The two-sided test \(|\bar X|\gt z_{\alpha/2}/\sqrt n\) is a compromise (UMP among unbiased tests).
Self-check quiz
Common mistakes
Getting the direction wrong: check whether \(w(\theta)\) increases or decreases.
Forgetting that NP is for simple vs simple; for composite one-sided hypotheses use Karlin-Rubin.
With discrete data, choosing the critical value whose size exceeds \(\alpha\).
Remember
NP: reject when \(f(\mathbf x\mid\theta_1)/f(\mathbf x\mid\theta_0)\gt k\) → most powerful.
Exponential family: MLR in \(\sum t(X_i)\) if \(w\) increasing. Two-sided: no UMP in general.
B1.18 Likelihood ratio tests, p-values, and inverting tests to get confidence intervals
Why it matters. The LRT works for any hypotheses (including two-sided and nuisance parameters); p-values are how results are reported; inverting tests gives confidence intervals. Lectures 17-18 (C&B 8.2.1, 8.3.4, 9.2.1).
How it is examined. "Derive the LRT and show it is equivalent to rejecting when \(|T|\gt c\)", "compute the p-value", "invert the test to get a CI"; MC questions on p-value interpretation.
Explanation
Likelihood ratio test (C&B 8.2.1)
For \(H_0:\theta\in\Theta_0\) vs \(H_1:\theta\in\Theta_0^c\), the LRT statistic is
\[ \lambda(\mathbf x)=\frac{\sup_{\Theta_0}L(\theta\mid\mathbf x)}{\sup_{\Theta}L(\theta\mid\mathbf x)}=\frac{L(\hat\theta_0\mid\mathbf x)}{L(\hat\theta\mid\mathbf x)}\in[0,1], \]
where \(\hat\theta\) is the unrestricted MLE and \(\hat\theta_0\) the MLE restricted to \(\Theta_0\). An LRT rejects \(H_0\) when \(\lambda(\mathbf x)\le c\), with \(0\le c\le1\) chosen to get size \(\alpha\).
Small \(\lambda\): the data are much more likely under some \(\theta\) outside \(H_0\). Steps: (1) find \(\hat\theta\) and \(\hat\theta_0\); (2) simplify \(\lambda\); (3) show \(\lambda\le c\iff\) a simple statistic is large (or far from a value); (4) use its null distribution. If \(T\) is sufficient, \(\lambda\) depends on \(\mathbf x\) only through \(T\) (C&B Thm 8.2.4).
p-value (C&B 8.3.26-8.3.27)
A p-value \(p(\mathbf X)\) is a test statistic with \(0\le p\le1\); small values are evidence against \(H_0\). It is valid if \(P_\theta(p(\mathbf X)\le\alpha)\le\alpha\) for every \(\theta\in\Theta_0\) and every \(\alpha\). If large values of \(W\) give evidence against \(H_0\):
\[ p(\mathbf x)=\sup_{\theta\in\Theta_0}P_\theta\big(W(\mathbf X)\ge W(\mathbf x)\big), \]
which is valid. Decision rule: reject at level \(\alpha\) iff \(p(\mathbf x)\le\alpha\).
The p-value is the smallest \(\alpha\) at which you would reject with these data. It is not the probability that \(H_0\) is true.
Inverting a test (C&B Thm 9.2.2)
For each \(\theta_0\), let \(A(\theta_0)\) be the acceptance region of a level-\(\alpha\) test of \(H_0:\theta=\theta_0\). Then
\[ C(\mathbf x)=\{\theta_0:\ \mathbf x\in A(\theta_0)\} \]
is a \(1-\alpha\) confidence set. Conversely, a \(1-\alpha\) confidence set gives a level-\(\alpha\) test: reject \(H_0:\theta=\theta_0\) iff \(\theta_0\notin C(\mathbf X)\).
Proof: \(P_{\theta_0}(\theta_0\in C(\mathbf X))=P_{\theta_0}(\mathbf X\in A(\theta_0))\ge1-\alpha\). Two-sided tests give two-sided intervals; one-sided tests give one-sided bounds.
Worked examples
Example 1 (normal mean, \(\sigma=1\) known). \(H_0:\theta=\theta_0\). Unrestricted MLE \(\bar x\). Using \(\sum(x_i-\theta_0)^2=\sum(x_i-\bar x)^2+n(\bar x-\theta_0)^2\):
\(\lambda(\mathbf x)=\exp\big(-\frac n2(\bar x-\theta_0)^2\big)\). \(\lambda\le c\iff|\bar x-\theta_0|\ge\sqrt{-2\log c/n}\): the two-sided \(z\)-test. Also \(-2\log\lambda=n(\bar x-\theta_0)^2\sim\chi^2_1\) exactly.
Example 2 (t-test is an LRT). \(N(\mu,\sigma^2)\), both unknown, \(H_0:\mu=\mu_0\). Unrestricted: \(\hat\sigma^2=\frac1n\sum(x_i-\bar x)^2\); restricted: \(\hat\sigma_0^2=\frac1n\sum(x_i-\mu_0)^2=\hat\sigma^2+(\bar x-\mu_0)^2\). The maximised likelihoods are \((2\pi\hat\sigma^2)^{-n/2}e^{-n/2}\) and the same with \(\hat\sigma_0^2\), so
\[ \lambda=\Big(\frac{\hat\sigma^2}{\hat\sigma_0^2}\Big)^{n/2}=\Big(1+\frac{t^2}{n-1}\Big)^{-n/2},\qquad t=\frac{\bar x-\mu_0}{s/\sqrt n}. \]
\(\lambda\) small \(\iff|t|\) large: the two-sided \(t\)-test with critical value \(t_{n-1,\alpha/2}\).
Example 3 (p-values). Observed \(z=2.1\): two-sided \(p=2(1-\Phi(2.1))=0.0357\); one-sided (\(H_1:\theta\gt\theta_0\)) \(p=0.0179\). Observed \(t=2.3\) with 15 df: two-sided \(p\approx0.036\).
Example 4 (inversion). The acceptance region of the two-sided \(z\)-test is \(|\bar x-\theta_0|\le z_{\alpha/2}\sigma/\sqrt n\). Solving for \(\theta_0\) gives \(\bar x\pm z_{\alpha/2}\sigma/\sqrt n\). The one-sided test of \(H_1:\theta\gt\theta_0\) accepts when \(\bar x\le\theta_0+z_\alpha\sigma/\sqrt n\), i.e. \(\theta_0\ge\bar x-z_\alpha\sigma/\sqrt n\): a lower confidence bound.
Exercises
easy
A one-sided \(z\)-test gives \(z=1.8\). Find the p-value. Reject at 5%? At 1%?
Show solution
\(p=1-\Phi(1.8)\approx0.0359\). Reject at 5% (\(0.036\le0.05\)), not at 1%.
easy
True or false: "p = 0.03 means there is a 3% probability that \(H_0\) is true."
Show solution
False. It is the probability, computed assuming \(H_0\), of a result at least as extreme as the one observed.
medium
Exp(rate \(\lambda\)), \(H_0:\lambda=\lambda_0\) vs \(\lambda\neq\lambda_0\). Derive \(\lambda(\mathbf x)\) and describe the rejection region in terms of \(\bar x\).
Show solution
\(L(\lambda)=\lambda^ne^{-\lambda n\bar x}\), \(\hat\lambda=1/\bar x\), \(L(\hat\lambda)=\bar x^{-n}e^{-n}\). \(\lambda(\mathbf x)=(\lambda_0\bar x)^ne^{-n(\lambda_0\bar x-1)}\). As a function of \(u=\lambda_0\bar x\), \(u^ne^{-n(u-1)}\) is maximal (=1) at \(u=1\) and decreases on both sides. So reject when \(\lambda_0\bar x\le c_1\) or \(\ge c_2\), i.e. \(\bar x\) too small or too large (constants from \(2\lambda_0\sum X_i\sim\chi^2_{2n}\)).
medium
Poisson, \(n=5\), \(\sum x_i=17\). \(H_0:\lambda\le2\) vs \(\lambda\gt2\). Compute the exact p-value (\(P(\text{Poisson}(10)\ge17)=0.0270\)) and explain why the sup over \(\Theta_0\) is attained at \(\lambda=2\).
Show solution
\(p=\sup_{\lambda\le2}P_\lambda(T\ge17)\); \(T\sim\) Poisson\((5\lambda)\) is stochastically increasing in \(\lambda\), so the sup is at \(\lambda=2\): \(P(\text{Poisson}(10)\ge17)=0.0270\). Reject at 5%.
hard
Invert the UMP test of B1.17 exercise 3 (exponential with mean \(\theta\), \(H_1:\theta\gt\theta_0\), reject iff \(2\sum X_i/\theta_0\gt\chi^2_{2n,\alpha}\)) to get a one-sided confidence bound for \(\theta\).
Show solution
Accept iff \(2\sum x_i/\theta_0\le\chi^2_{2n,\alpha}\iff\theta_0\ge\frac{2\sum x_i}{\chi^2_{2n,\alpha}}\). So \(\Big[\frac{2\sum X_i}{\chi^2_{2n,\alpha}},\infty\Big)\) is a \(1-\alpha\) lower confidence bound.
exam
Uniform\((0,\theta)\), \(H_0:\theta=\theta_0\) vs \(\theta\neq\theta_0\). (a) Find \(\lambda(\mathbf x)\). (b) Show the LRT rejects when \(X_{(n)}\gt\theta_0\) or \(X_{(n)}\le c\). (c) Find \(c\) for size \(\alpha\).
Show solution
(a) \(L(\hat\theta)=x_{(n)}^{-n}\). \(L(\theta_0)=\theta_0^{-n}\mathbb 1\{x_{(n)}\le\theta_0\}\). \(\lambda=(x_{(n)}/\theta_0)^n\) if \(x_{(n)}\le\theta_0\), and 0 otherwise. (b) \(\lambda\le k\iff x_{(n)}\gt\theta_0\) or \(x_{(n)}\le\theta_0k^{1/n}=c\). (c) Under \(H_0\), \(P(X_{(n)}\gt\theta_0)=0\), \(P(X_{(n)}\le c)=(c/\theta_0)^n=\alpha\Rightarrow c=\theta_0\alpha^{1/n}\). (Inverting gives exactly the CI \([X_{(n)},X_{(n)}\alpha^{-1/n}]\) of B1.15.)
Self-check quiz
Common mistakes
Inverting \(\lambda\): numerator is the restricted maximum (under \(H_0\)), so \(\lambda\le1\).
Rejecting for large \(\lambda\) (it is small \(\lambda\) that is evidence against \(H_0\)).
Doubling a one-sided p-value when the alternative is one-sided (or not doubling for a two-sided one).
Remember
\(\lambda=\sup_{\Theta_0}L/\sup_\Theta L\); reject when small. z-test and t-test are LRTs.
CI = set of \(\theta_0\) not rejected. Two-sided test ↔ two-sided CI.
B1.19 Large-sample tests: likelihood ratio, Wald and score tests
Why it matters. When the exact null distribution is unknown, these three tests based on the likelihood give approximate level-\(\alpha\) tests for almost any model. Lecture 19 (C&B 10.3.1-10.3.2, Buse 1982); in the official outline as "asymptotic tests based on the likelihood function".
How it is examined. "Compute the LR, Wald and score statistics for these data and decide at 5%", "explain the difference between the three tests" (Buse's picture is a likely theory question).
Explanation
Setting: i.i.d. sample from a regular model, \(H_0:\theta=\theta_0\) vs \(H_1:\theta\neq\theta_0\), MLE \(\hat\theta\), log-likelihood \(\ell\), information \(I_n(\theta)=nI_1(\theta)\).
Theorem (Wilks, C&B 10.3.1 and 10.3.3)
Under \(H_0\) and regularity conditions,
\[ -2\log\lambda(\mathbf X)=2\big[\ell(\hat\theta)-\ell(\theta_0)\big]\xrightarrow{d}\chi^2_1. \]
More generally, if \(\Theta_0\) fixes \(\nu\) of the free parameters, \(-2\log\lambda\xrightarrow{d}\chi^2_\nu\) (\(\nu\) = number of parameters in \(\Theta\) minus number in \(\Theta_0\)). Reject at level \(\approx\alpha\) if \(-2\log\lambda\gt\chi^2_{\nu,\alpha}\).
Wald test (C&B 10.3.2)
\[ Z_W=\frac{\hat\theta-\theta_0}{\text{se}(\hat\theta)},\qquad \text{se}(\hat\theta)=\frac1{\sqrt{I_n(\hat\theta)}}\ \Big(\text{or } \frac{1}{\sqrt{-\ell''(\hat\theta)}}\Big). \]
Reject if \(|Z_W|\gt z_{\alpha/2}\) (equivalently \(W=Z_W^2\gt\chi^2_{1,\alpha}\)). Needs only the unrestricted MLE. Inverting it gives the Wald CI \(\hat\theta\pm z_{\alpha/2}\text{se}(\hat\theta)\).
Score test (Rao / Lagrange multiplier)
\[ Z_S=\frac{\ell'(\theta_0)}{\sqrt{I_n(\theta_0)}}\xrightarrow{d}N(0,1)\ \text{under }H_0. \]
Reject if \(|Z_S|\gt z_{\alpha/2}\). Needs only quantities at \(\theta_0\): no need to compute the MLE.
All three are asymptotically equivalent under \(H_0\) (same \(\chi^2_1\) limit) but can differ in finite samples. One-sided versions use \(Z_W\) or \(Z_S\) with \(z_\alpha\).
Buse's (1982) geometric picture
LR uses the vertical drop \(\ell(\hat\theta)-\ell(\theta_0)\); Wald uses the horizontal distance \(\hat\theta-\theta_0\) scaled by the curvature at \(\hat\theta\); score uses the slope \(\ell'(\theta_0)\) scaled by the curvature at \(\theta_0\).
For a quadratic log-likelihood (normal mean, \(\sigma\) known) the three statistics are identical. Otherwise they differ; the LR test is invariant to reparametrisation, the Wald test is not.
Worked examples
Example 1 (Bernoulli). \(n=100\), 60 successes, \(H_0:p=0.5\). \(\hat p=0.6\), \(I_n(p)=\frac{n}{p(1-p)}\).
Normal, both parameters unknown, \(H_0:\mu=\mu_0\). Using B1.18 Example 2, write \(-2\log\lambda\) as a function of \(t\), give its asymptotic distribution and the degrees of freedom.
Show solution
\(-2\log\lambda=n\log\big(1+\frac{t^2}{n-1}\big)\). \(\Theta\) has 2 free parameters, \(\Theta_0\) has 1 (\(\sigma^2\)), so \(\nu=1\): \(\to\chi^2_1\). Indeed \(n\log(1+t^2/(n-1))\approx t^2\) and \(t^2\to\chi^2_1\).
exam
Theory: state Wilks' theorem and explain, with a sketch, the difference between the LR, Wald and score tests (Buse 1982). When would you prefer the score test?
Show solution
Wilks: under \(H_0\) and regularity, \(-2\log\lambda\to\chi^2_\nu\), \(\nu\) = difference in number of free parameters. Sketch as above (height / width / slope). Score test preferred when the MLE under the full model is hard to compute (only \(\theta_0\) needed) or lies on the boundary; Wald is convenient when the MLE and its se are already available (software output); LR is invariant to reparametrisation and often more accurate in small samples.
Self-check quiz
Common mistakes
Forgetting the factor 2 in \(-2\log\lambda\).
Comparing \(Z_W\) (normal scale) with a \(\chi^2\) critical value, or \(W=Z_W^2\) with 1.96.
Evaluating the information at the wrong point: Wald at \(\hat\theta\), score at \(\theta_0\).
Wrong degrees of freedom when there are nuisance parameters.
Equivalent asymptotically; identical for the normal mean with known variance.
B1.20 Classical one- and two-sample tests: means, proportions, variances
Why it matters. These are the tests used in practice and the most "mechanical" exam points. Lecture 20 (C&B Ch. 8, 10; "the main classical parametric tests for one and two samples" in the official outline).
How it is examined. Short exercises: "given these summary statistics, test \(H_0\) at 5%: state hypotheses, statistic, distribution under \(H_0\), critical value or p-value, conclusion". Show every step: partial credit is for steps.
Explanation
Five-step template (write it every time): (1) hypotheses \(H_0\), \(H_1\) (one- or two-sided); (2) test statistic and its distribution under \(H_0\) (with assumptions); (3) observed value; (4) critical value or p-value; (5) decision and conclusion in words.
\(\frac{\hat p_1-\hat p_2}{\sqrt{\hat p(1-\hat p)(1/n_1+1/n_2)}}\), pooled \(\hat p=\frac{y_1+y_2}{n_1+n_2}\)
\(\approx N(0,1)\)
ratio of variances (\(F\))
normal
\(S_1^2/S_2^2\)
\(F_{n_1-1,\,n_2-1}\)
Paired samples (same unit measured twice): compute differences \(D_i=X_i-Y_i\) and do a one-sample \(t\)-test on the \(D_i\) (df \(n-1\)).
For a CI for \(p_1-p_2\) use the unpooled se \(\sqrt{\hat p_1(1-\hat p_1)/n_1+\hat p_2(1-\hat p_2)/n_2}\); for the test of \(H_0:p_1=p_2\) the pooled one.
Check with the course
Whether the course prefers the pooled or the Welch test by default, and whether it pools for the two-proportion test, is NOT STATED. R's t.test uses Welch unless var.equal = TRUE; prop.test uses the pooled (score) statistic squared and a continuity correction unless correct = FALSE.
Worked examples
Example 1 (one-sample \(t\)). \(n=12\), \(\bar x=5.4\), \(s=1.2\). \(H_0:\mu=5\) vs \(\mu\neq5\). \(t=\frac{0.4}{1.2/\sqrt{12}}=1.155\), df 11, \(t_{11,0.025}=2.201\): do not reject (p \(\approx0.27\)).
Example 2 (pooled two-sample \(t\)). \(n_1=10\), \(\bar x=20.5\), \(s_1=3\); \(n_2=12\), \(\bar y=18.0\), \(s_2=2.5\). \(H_0:\mu_1=\mu_2\) vs \(\neq\).
\(s_p^2=\frac{9\cdot9+11\cdot6.25}{20}=7.4875\), \(s_p=2.736\), se \(=2.736\sqrt{\frac1{10}+\frac1{12}}=1.172\), \(t=2.5/1.172=2.134\). \(t_{20,0.025}=2.086\): reject at 5% (p \(\approx0.045\), borderline). Welch: \(t=2.097\), \(\nu\approx17.6\), p \(\approx0.051\): not rejected at 5%. The decision changes with the assumption, so always state it.
Example 3 (two proportions). 45/100 vs 30/100. \(\hat p=0.375\), se \(=\sqrt{0.375\cdot0.625\cdot0.02}=0.0685\), \(z=0.15/0.0685=2.19\), p \(=0.028\): reject at 5%.
t.test(x, mu = 5) # one-sample t
t.test(x, y, var.equal = TRUE) # pooled; omit var.equal for Welch
t.test(after, before, paired = TRUE) # paired
prop.test(c(45, 30), c(100, 100), correct = FALSE) # X-squared = 2.19^2 = 4.80
var.test(x, y) # F test
binom.test(116, 200, p = 0.5) # exact binomial test
Exercises
easy
IQ-type scores: \(\sigma=15\) known, \(n=36\), \(\bar x=103\). Test \(H_0:\mu=100\) vs \(H_1:\mu\gt100\) at 5%.
Show solution
\(z=\frac{3}{15/6}=1.2\lt1.645\): do not reject. p \(=1-\Phi(1.2)=0.115\).
easy
116 successes in 200 trials. Test \(H_0:p=0.5\) vs \(p\neq0.5\) at 5%.
Show solution
\(\hat p=0.58\), se under \(H_0\) \(=\sqrt{0.25/200}=0.0354\), \(z=0.08/0.0354=2.26\gt1.96\): reject; p \(\approx0.024\).
medium
\(n=15\) normal observations, \(s^2=6.0\). Test \(H_0:\sigma^2=4\) vs \(H_1:\sigma^2\gt4\) at 5% (\(\chi^2_{14,0.05}=23.685\)).
Show solution
\(\chi^2=\frac{14\cdot6}{4}=21\lt23.685\): do not reject (p \(\approx0.10\)).
medium
Using Example 2's data, test equality of variances at 5% with the \(F\) test (\(F_{9,11;0.975}=3.588\), \(F_{9,11;0.025}=0.256\) as lower-tail quantiles).
Show solution
\(F=9/6.25=1.44\), inside \([0.256,3.588]\): do not reject; the pooled test is reasonable.
hard
Compute a 95% CI for \(p_1-p_2\) in Example 3 (unpooled se) and check consistency with the test.
Show solution
se \(=\sqrt{\frac{0.45\cdot0.55}{100}+\frac{0.3\cdot0.7}{100}}=\sqrt{0.002475+0.0021}=0.0676\). CI \(0.15\pm1.96\cdot0.0676=[0.017,0.283]\); excludes 0, consistent with rejecting (the two se's differ slightly, so in borderline cases test and CI may disagree).
exam
Explain why analysing paired data with a two-sample test is wrong, and in which direction it usually errs.
Show solution
The two measurements on the same unit are positively correlated, so \(\text{Var}(\bar X-\bar Y)=\frac{\sigma_1^2+\sigma_2^2-2\sigma_{12}}{n}\) is smaller than the independent-samples formula. The two-sample test overestimates the se, loses power (conservative), and also uses wrong df.
Self-check quiz
Common mistakes
Using \(\hat p\) instead of \(p_0\) in the se of the one-proportion test (that is the Wald version; the classical test uses \(p_0\)).
Not stating hypotheses and the conclusion in words.
Remember
Statistic = (estimate − null value) / standard error.
\(z\) if \(\sigma\) known or proportions; \(t\) if \(\sigma\) estimated; \(\chi^2\) for one variance; \(F\) for two variances.
Paired ⇒ one-sample test on differences.
B1.21 The optional project: simulation study in Rmarkdown
Why it matters. If your project is submitted and evaluated you are exempt from the theory open questions (about 25% of the Statistical Theory test). It is also the best way to really understand one theorem.
How it is examined. Rmarkdown file + a presentation on presentation day. The evaluation criteria and the length of the talk are NOT STATED: check with Prof. Leorato.
Rules (from the course page, as given in the brief)
Work in pairs. Sign-up closed on 12 Oct; late students can only join someone who signed up alone, by contacting Prof. Leorato.
Goal: illustrate, with simulated or real data, one theoretical result from the course (or an extension).
Deadline: 8 December, only through the designated section of myAriel. Email or late submissions are not accepted. Time of day: check.
You must submit an Rmarkdown file that runs correctly (knit it on a clean session before submitting). Extra R scripts or PDF slides are only supplementary.
Presentation: it looks like your slot is Slot 4, Wednesday 16 December, 9:30-11:30check in the "Presentation day" activity (other slots were 14 Dec morning, 15 Dec morning, 15 Dec afternoon).
Explanation: the listed proposals (IDs 202601-202610) and the theory behind them
Proposal
Theory you illustrate
Key facts
Buffon's needle estimate of \(\pi\)
MoM, LLN, CLT, delta method (B1.2, B1.14)
\(p=2\ell/(\pi d)\), \(\hat\pi=2\ell n/(dC)\), \(\text{Var}(\hat\pi)\approx\pi^2(1-p)/(np)\); choose \(\ell/d\) to minimise it
Cauchy location: sample mean
when the LLN/CLT fail (B1.14)
\(\bar X\sim\) Cauchy\((\theta,1)\) for every \(n\): never converges
Cauchy location: MLE
numerical MLE, asymptotic normality, Fisher information
\(I_1=1/2\) (scale 1), \(\sqrt n(\hat\theta-\theta)\to N(0,2)\); likelihood can be multimodal
Cauchy: sample median; MLE vs median
order statistics, asymptotic relative efficiency
median: \(\sqrt n(\tilde X-\theta)\to N(0,\pi^2/4)\approx N(0,2.47)\); ARE \(=2/2.47\approx0.81\)
Estimand: what is estimated (\(\theta\), \(\pi\), \(H(\lambda)\)...).
Methods: the estimators/tests/CIs compared.
Performance measures: bias \(\frac1R\sum(\hat\theta_r-\theta)\), empirical variance, MSE, coverage of CIs, power; with Monte Carlo standard errors (e.g. se of the bias \(=\text{sd}(\hat\theta_r)/\sqrt R\)). Choose \(R\) (replications) so these are small, e.g. \(R=5000\).
Then compare the simulation with the theory: e.g. plot \(n\cdot\widehat{\text{Var}}(\hat\theta)\) against \(n\) together with the theoretical asymptotic variance, histogram of \(\sqrt n(\hat\theta-\theta)\) with the normal density on top.
Worked example: Rmarkdown skeleton (Cauchy: MLE vs median)
R is not installed in the environment where this site was written, so this code was not run. Knit it yourself and fix any typo.
---
title: "Cauchy location: MLE versus sample median"
author: "Name Surname, Name Surname"
date: "December 2026"
output:
html_document:
toc: true
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE)
set.seed(202612)
```
## 1. Aim
We compare two estimators of the location $\theta$ of a Cauchy($\theta$, 1)
model: the sample median and the MLE. Theory: both are consistent and
asymptotically normal with variances $\pi^2/(4n)$ and $2/n$.
## 2. Simulation design
```{r design}
theta <- 0 # true location
ns <- c(10, 25, 50, 100, 500)
R <- 5000 # Monte Carlo replications
```
## 3. Estimators
```{r estimators}
cauchy_mle <- function(x) {
nll <- function(t) -sum(dcauchy(x, location = t, log = TRUE))
optimize(nll, interval = median(x) + c(-3, 3))$minimum
}
```
## 4. Run
```{r run}
res <- do.call(rbind, lapply(ns, function(n) {
est <- replicate(R, {
x <- rcauchy(n, location = theta)
c(median = median(x), mle = cauchy_mle(x))
})
data.frame(n = n,
method = rownames(est),
bias = rowMeans(est) - theta,
var = apply(est, 1, var),
mse = rowMeans((est - theta)^2))
}))
res$n_var <- res$n * res$var # compare with pi^2/4 and 2
knitr::kable(res, digits = 4)
```
## 5. Comparison with theory
```{r plot}
plot(ns, res$n_var[res$method == "median"], type = "b", log = "x",
ylim = c(1.5, 3.5), xlab = "n", ylab = "n * Var")
lines(ns, res$n_var[res$method == "mle"], type = "b", col = 2)
abline(h = c(pi^2 / 4, 2), lty = 2, col = c(1, 2))
legend("topright", c("median", "MLE"), col = 1:2, lty = 1)
```
## 6. Conclusions
- ... (which estimator wins, from which n the asymptotics are accurate)
- Why the sample mean fails: $\bar X$ is again Cauchy (show a running-mean plot).
## References
Casella, G. and Berger, R. L. (2002). *Statistical Inference*, 2nd ed.
Presentation tips
About 1 slide per minute; the talk length is NOT STATED (check). Suggested order: (1) the question and the theorem in one slide; (2) the model and the estimators with formulas; (3) simulation design (the ADEMP list); (4) 2-3 plots, each with one sentence of conclusion in the title; (5) what matches the theory, what does not, and why; (6) limitations. Both partners should speak and be able to explain every formula: expect questions like "what is the Fisher information here?" or "why does the mean not converge?".
Exercises
easy
Buffon: for fixed \(n\), show that the asymptotic variance \(\pi^2(1-p)/(np)\) decreases as \(p\) increases, so you should use the longest allowed needle \(\ell=d\).
Show solution
\((1-p)/p=1/p-1\) is decreasing in \(p\), and \(p=2\ell/(\pi d)\) increases with \(\ell\). With \(\ell\le d\), the best is \(\ell=d\), \(p=2/\pi\approx0.637\), variance \(\approx\pi^2\cdot0.571/n\approx5.63/n\).
medium
Cauchy\((\theta,1)\): show that \(I_1(\theta)=1/2\). (Use \(\int\frac{4(x-\theta)^2}{\pi(1+(x-\theta)^2)^3}dx=\frac12\).)
Show solution
\(\log f=-\log\pi-\log(1+(x-\theta)^2)\), score \(\frac{2(x-\theta)}{1+(x-\theta)^2}\). \(E[s^2]=\int\frac{4u^2}{(1+u^2)^2}\frac{1}{\pi(1+u^2)}du=\frac12\). So MLE asymptotic variance \(2/n\).
medium
Exponential entropy: show that \(H(\lambda)=-E[\log f(X)]=1-\log\lambda\), give its MLE and the asymptotic variance by the delta method.
Show solution
\(-\log f=-\log\lambda+\lambda X\); expectation \(-\log\lambda+1\). MLE (invariance): \(1-\log\hat\lambda=1+\log\bar X\). \(g(\lambda)=1-\log\lambda\), \(g'=-1/\lambda\), asymptotic variance of \(\hat\lambda\) is \(\lambda^2/n\): \(\frac{1}{\lambda^2}\cdot\frac{\lambda^2}{n}=\frac1n\).
hard
Geometric: find the MLE of \(\tau(p)=P(X=2)=p(1-p)\) and its asymptotic variance (\(I_1(p)=\frac{1}{p^2(1-p)}\)). Where does the delta method break down?
Show solution
\(\hat\tau=\hat p(1-\hat p)\), \(\hat p=1/\bar X\). \(\tau'=1-2p\): asymptotic variance \(\frac{(1-2p)^2p^2(1-p)}{n}\). At \(p=1/2\), \(\tau'=0\): first-order delta method gives variance 0; you need a second-order expansion (the limit is no longer normal at rate \(\sqrt n\)). A good thing to show in the simulation.
exam
You run \(R=1000\) replications and estimate the coverage of a nominal 95% CI as 0.937. Is that evidence of under-coverage?
Show solution
Monte Carlo se \(=\sqrt{0.95\cdot0.05/1000}=0.0069\). \(z=(0.937-0.95)/0.0069=-1.89\): borderline (two-sided p \(\approx0.06\)). Increase \(R\) (e.g. 10000, se 0.0022) before concluding.
Self-check quiz
Common mistakes
Submitting an Rmd that only knits on your machine (absolute file paths, missing packages, objects created in the console). Knit in a fresh R session.
No set.seed: results not reproducible.
Reporting simulation numbers without Monte Carlo error, or without comparing them with the theory.
Missing the myAriel deadline: no email submissions.
Remember
Pairs; Rmd must run; myAriel by 8 Dec; presentation probably 16 Dec 9:30-11:30 (check).
ADEMP: aims, data, estimand, methods, performance (bias, var, MSE, coverage + MC se).
Always compare the simulation with the theorem you are illustrating.
Unit B · Statistical Theory & Mathematics · Mathematics written test: 1.5 hours, 30 points (from the course mock)
B2. Mathematics: linear algebra, graphs, optimization, dynamical systems
This module follows Prof. Naldi's Mathematics block (TA Isabella Salvaggio) and the books Lay, Lay & McDonald
(Linear Algebra and Its Applications), Sydsaeter et al. (Essential Mathematics for Economic Analysis) and
Salinelli & Tomarelli (Discrete Dynamical Models), plus the teachers' notes. The written test, as shown by the course mock, lasts
1.5 hours and is worth 30 points: multiple-choice questions (about 2-4 points each, always with
option E "none of the other answers") and open questions where you must show all your work
(partial credit for progress, no credit for a bare answer). You need at least 18/30. The number of questions per type, and whether notes, a
formula sheet or a calculator are allowed, are NOT STATED: check with the teacher.
The four official blocks are: linear algebra (B2.1-B2.9), graph theory (B2.10-B2.12), calculus and optimization (B2.13-B2.17) and
dynamical systems (B2.18-B2.19). Lecture map: 22 Sep vectors (B2.1); 24 Sep linear algebra (B2.2); 25 Sep and 1 Oct matrices and linear
transformations (B2.3, B2.5); 2 Oct linear systems and echelon form (B2.4); 8 Oct orthogonal bases and matrices (B2.6); 9-16 Oct eigenvalues
(B2.7); 22-23 Oct graph theory (B2.10-B2.12); 29 Oct quadratic forms, Laplacian and incidence matrices (B2.8, B2.10); 30 Oct real functions of
several variables, recorded videos (B2.13). The remaining topics (optimization, Lagrange multipliers, linear programming, dynamical systems,
differential equations) have no dates yet: check.
Exam-critical topics (they appear in the mock): B2.2, B2.4, B2.13 and B2.14.
B2.1 Vectors, linear combinations and span
Why it matters. Every object in this module (data points, solutions of systems, eigenvectors, gradients) is a vector. "Is this vector a combination of those vectors?" is the basic question behind systems, bases and rank.
How it is examined. Multiple-choice questions on span and dimension (the mock asks for the dimension of a span), and as the first step of open questions on bases and systems.
Explanation
A vector in \(\mathbb{R}^n\) is an ordered list of \(n\) real numbers, written as a column: \(\mathbf{u} = (u_1,\dots,u_n)^T\). We add vectors component by component and multiply by a scalar (a real number) component by component:
Geometrically, in \(\mathbb{R}^2\) or \(\mathbb{R}^3\) a vector is an arrow from the origin; addition is the parallelogram rule, and \(c\mathbf{u}\) stretches the arrow (and flips it if \(c\lt 0\)).
Definition: Real vector space
A set \(V\) with an addition and a scalar multiplication that satisfy the usual rules (commutative, associative, a zero vector \(\mathbf{0}\), opposites \(-\mathbf{u}\), distributive laws, \(1\mathbf{u}=\mathbf{u}\)). In this course the main example is \(\mathbb{R}^n\); other examples are the \(m\times n\) matrices and the polynomials of degree \(\le n\).
Definition: Linear combination
Given vectors \(\mathbf{v}_1,\dots,\mathbf{v}_p\) and scalars \(c_1,\dots,c_p\) (the weights), the vector \(\mathbf{y} = c_1\mathbf{v}_1+\dots+c_p\mathbf{v}_p\) is a linear combination of the \(\mathbf{v}_i\).
Definition: Span
\(\operatorname{Span}\{\mathbf{v}_1,\dots,\mathbf{v}_p\}\) is the set of all linear combinations of \(\mathbf{v}_1,\dots,\mathbf{v}_p\). It always contains \(\mathbf{0}\) (take all weights 0).
Definition: Subspace
A subset \(H\subseteq\mathbb{R}^n\) is a subspace if (1) \(\mathbf{0}\in H\), (2) it is closed under addition, (3) it is closed under scalar multiplication. Every span is a subspace. In \(\mathbb{R}^3\) the subspaces are: \(\{\mathbf{0}\}\), lines through the origin, planes through the origin, and \(\mathbb{R}^3\) itself.
Key fact (the bridge to systems). \(\mathbf{b}\in\operatorname{Span}\{\mathbf{v}_1,\dots,\mathbf{v}_p\}\) if and only if the linear system with augmented matrix \([\,\mathbf{v}_1\ \cdots\ \mathbf{v}_p \mid \mathbf{b}\,]\) is consistent (has at least one solution). So "is \(\mathbf{b}\) in the span?" is always answered by solving a system.
Parallelogram rule: \(\mathbf{u}+\mathbf{v}\) is the diagonal. (1 grid unit = 40 px.)
Tip: how big is a span?
One non-zero vector spans a line. Two non-parallel vectors span a plane. In \(\mathbb{R}^n\) you need at least \(n\) vectors to span all of \(\mathbb{R}^n\); so 2 vectors can never span \(\mathbb{R}^3\).
Worked examples
Example 1 (linear combination). \(\mathbf{u}=(1,2)\), \(\mathbf{v}=(3,1)\). Then \(2\mathbf{u}-\mathbf{v} = (2,4)-(3,1) = (-1,3)\).
Example 2 (is \(\mathbf{b}\) in the span?). Is \(\mathbf{b}=(5,5)\) a linear combination of \(\mathbf{u}=(1,2)\) and \(\mathbf{v}=(3,1)\)? Solve \(a\mathbf{u}+b\mathbf{v}=\mathbf{b}\):
\[ \begin{cases} a + 3b = 5\\ 2a + b = 5\end{cases} \Rightarrow \text{(second minus 2}\times\text{first)}\ -5b=-5 \Rightarrow b=1,\ a=2. \]
So \(\mathbf{b}=2\mathbf{u}+\mathbf{v}\). (In fact \(\mathbf{u},\mathbf{v}\) span all of \(\mathbb{R}^2\) because they are not parallel.)
Example 3 (in \(\mathbb{R}^3\)). \(\mathbf{v}_1=(1,0,1)\), \(\mathbf{v}_2=(0,1,1)\). Is \(\mathbf{b}=(1,2,3)\) in the span? From \(a\mathbf{v}_1+b\mathbf{v}_2=(a,b,a+b)\): \(a=1\), \(b=2\), and the third component must be \(a+b=3\) ✓. Yes. And \((1,2,4)\)? We would need \(a+b=4\) but \(a+b=3\): no, the system is inconsistent.
Example 4 (equation of a span). \(\operatorname{Span}\{(1,1,0),(0,1,1),(1,0,-1)\}\). Note \((1,0,-1)=(1,1,0)-(0,1,1)\), so the third vector adds nothing: the span is the plane spanned by the first two. A normal vector is the cross product \((1,1,0)\times(0,1,1)=(1,-1,1)\), so the plane is \(x-y+z=0\). Check: \((1,0,-1)\): \(1-0-1=0\) ✓.
Exercises
easy
Let \(\mathbf{u}=(2,-1,0)\), \(\mathbf{v}=(1,3,-2)\). Compute \(3\mathbf{u}-2\mathbf{v}\).
Is \((2,1,3)\) in \(\operatorname{Span}\{(1,0,1),(1,1,2)\}\)? If yes, give the weights.
Show solution
\(a(1,0,1)+b(1,1,2)=(a+b,\ b,\ a+2b)\). Equations: \(a+b=2\), \(b=1\), \(a+2b=3\). From the first two: \(b=1,a=1\). Third: \(1+2=3\) ✓. Yes: \((2,1,3)=1\cdot(1,0,1)+1\cdot(1,1,2)\).
medium
Find \(h\) such that \((1,h,5)\in\operatorname{Span}\{(1,1,2),(0,1,1)\}\).
Show solution
\(a(1,1,2)+b(0,1,1)=(a,\ a+b,\ 2a+b)\). So \(a=1\); \(2a+b=5\Rightarrow b=3\); then \(h=a+b=4\). Answer: \(h=4\) (only this value).
medium
Describe geometrically \(\operatorname{Span}\{(1,2,3),(2,4,6)\}\) and \(\operatorname{Span}\{(1,-1),(2,1)\}\).
Show solution
\((2,4,6)=2(1,2,3)\): parallel vectors, so the first span is the line through the origin with direction \((1,2,3)\). The vectors \((1,-1)\) and \((2,1)\) are not multiples of each other, so their span is the whole plane \(\mathbb{R}^2\).
hard
Show that \(H=\{(x,y,z): x-y+z=0\}\) is a subspace, and that \(H=\{(x,y,z): x-y+z=1\}\) is not.
Show solution
First set: \(\mathbf{0}\) satisfies \(0-0+0=0\). If \(\mathbf{p},\mathbf{q}\in H\), then \((p_1+q_1)-(p_2+q_2)+(p_3+q_3) = 0+0=0\), so \(\mathbf{p}+\mathbf{q}\in H\); and \(cp_1-cp_2+cp_3=c\cdot 0=0\). All three conditions hold. (It is \(\operatorname{Span}\{(1,1,0),(0,1,1)\}\), Example 4.) Second set: \(\mathbf{0}\) gives \(0\ne 1\), so \(\mathbf{0}\notin H\): not a subspace (it is a plane that misses the origin).
exam
(MC style) Let \(W=\operatorname{Span}\{(1,1,0),(0,1,1),(1,0,-1)\}\). Which is true? A) \(W=\mathbb{R}^3\); B) \(W\) is a line; C) \((1,0,1)\in W\); D) \((2,1,-1)\in W\); E) none of the other answers.
Show solution
From Example 4, \(W\) is the plane \(x-y+z=0\). A and B false. C: \(1-0+1=2\ne0\), false. D: \(2-1-1=0\) ✓, true. Answer D. (Indeed \((2,1,-1)=2(1,1,0)-(0,1,1)\).)
Self-check quiz
Common mistakes
Checking only some equations: when testing \(\mathbf{b}\in\operatorname{Span}\), every component must hold with the same weights.
Thinking that 3 vectors in \(\mathbb{R}^3\) always span \(\mathbb{R}^3\): if one is a combination of the others, the span is smaller.
Forgetting that a subspace must contain \(\mathbf{0}\) (planes or lines not through the origin are not subspaces).
Remember
Linear combination: \(c_1\mathbf{v}_1+\dots+c_p\mathbf{v}_p\). Span = all of them; always a subspace.
\(\mathbf{b}\in\operatorname{Span}\{\mathbf{v}_i\}\iff [\mathbf{v}_1\cdots\mathbf{v}_p\mid\mathbf{b}]\) is consistent.
In \(\mathbb{R}^3\): span is \(\{\mathbf 0\}\), a line, a plane or \(\mathbb{R}^3\).
B2.2 Linear independence, basis and dimension
Why it matters. A basis is the smallest set of vectors that describes a whole space; the dimension counts "degrees of freedom". It is the language of rank, of SVD and of every "how many solutions?" question.
How it is examined. Exam-critical. The mock asks: "for which values of the parameter do these vectors form a basis of \(\mathbb{R}^3\)?" and "what is the dimension of this span?" (multiple choice with option E).
Explanation
Definition: Linear (in)dependence
Vectors \(\mathbf{v}_1,\dots,\mathbf{v}_p\) are linearly independent if the only solution of \(c_1\mathbf{v}_1+\dots+c_p\mathbf{v}_p=\mathbf{0}\) is \(c_1=\dots=c_p=0\). Otherwise they are linearly dependent: some non-trivial combination gives \(\mathbf{0}\), i.e. at least one vector is a combination of the others.
Quick rules:
Two vectors are dependent iff one is a multiple of the other.
Any set containing \(\mathbf{0}\) is dependent.
More than \(n\) vectors in \(\mathbb{R}^n\) are always dependent.
Put the vectors as columns of a matrix \(A\): they are independent iff \(A\mathbf{x}=\mathbf{0}\) has only the trivial solution iff every column has a pivot iff \(\operatorname{rank}A = p\).
Definition: Basis and dimension
A basis of a subspace \(H\) is a set of vectors that (1) is linearly independent and (2) spans \(H\). All bases of \(H\) have the same number of vectors: this number is the dimension \(\dim H\). The standard basis of \(\mathbb{R}^n\) is \(\mathbf{e}_1=(1,0,\dots,0),\dots,\mathbf{e}_n\); so \(\dim\mathbb{R}^n=n\).
Theorem (basis test in \(\mathbb{R}^n\))
For exactly \(n\) vectors in \(\mathbb{R}^n\), with \(A=[\mathbf{v}_1\cdots\mathbf{v}_n]\) (square), the following are equivalent: they are a basis of \(\mathbb{R}^n\) \(\iff\) they are independent \(\iff\) they span \(\mathbb{R}^n\) \(\iff\) \(\det A\neq 0\) \(\iff\) \(\operatorname{rank}A=n\) \(\iff\) \(A\) is invertible.
Theorem (dimension of a span)
\(\dim\operatorname{Span}\{\mathbf{v}_1,\dots,\mathbf{v}_p\} = \operatorname{rank}[\mathbf{v}_1\cdots\mathbf{v}_p]\) = number of pivots after row reduction. The pivot columns of the original matrix form a basis of the span.
Definition: Coordinates
If \(\mathcal{B}=\{\mathbf{b}_1,\dots,\mathbf{b}_n\}\) is a basis, every \(\mathbf{x}\) can be written in exactly one way as \(\mathbf{x}=c_1\mathbf{b}_1+\dots+c_n\mathbf{b}_n\). The weights \((c_1,\dots,c_n)\) are the coordinates \([\mathbf{x}]_{\mathcal B}\).
Tip: the parameter recipe (exam)
Three vectors in \(\mathbb{R}^3\) depending on \(k\): (1) write them as columns of \(A(k)\); (2) compute \(\det A(k)\) and factor it; (3) basis \(\iff\det\neq0\); (4) for each root \(k_0\), substitute and row-reduce to find the rank, i.e. the dimension of the span, and if asked an explicit dependence relation.
Worked examples
Example 1 (parameter, mock style). For which \(k\) are \(\mathbf{v}_1=(1,k,1)\), \(\mathbf{v}_2=(k,1,1)\), \(\mathbf{v}_3=(0,0,1)\) a basis of \(\mathbb{R}^3\)?
\[ \det\begin{pmatrix}1 & k & 0\\ k & 1 & 0\\ 1 & 1 & 1\end{pmatrix} = 1\cdot\det\begin{pmatrix}1 & k\\ k & 1\end{pmatrix} = 1-k^2 \quad(\text{expand along the 3rd column}). \]
So they are a basis iff \(1-k^2\neq0\), i.e. \(k\neq 1\) and \(k\neq -1\). For \(k=1\): \(\mathbf{v}_1=\mathbf{v}_2=(1,1,1)\), dependent; the span has dimension 2. For \(k=-1\): \(\mathbf{v}_1=(1,-1,1)\), \(\mathbf{v}_2=(-1,1,1)\) and \(\mathbf{v}_1+\mathbf{v}_2=(0,0,2)=2\mathbf{v}_3\): dependent, dimension 2.
Example 2 (dimension of a span of 4 vectors). \(\mathbf{w}_1=(1,0,1),\ \mathbf{w}_2=(2,1,3),\ \mathbf{w}_3=(0,1,1),\ \mathbf{w}_4=(1,1,2)\). Row-reduce the matrix with these columns:
Two pivots (columns 1 and 2) so \(\dim=2\), basis \(\{\mathbf{w}_1,\mathbf{w}_2\}\). Relations (from the reduced echelon form): \(\mathbf{w}_3=-2\mathbf{w}_1+\mathbf{w}_2\), \(\mathbf{w}_4=-\mathbf{w}_1+\mathbf{w}_2\).
Example 3 (coordinates). \(\mathcal{B}=\{(1,1,0),(1,0,1),(0,1,1)\}\), \(\mathbf{x}=(3,1,2)\). Solve \(c_1+c_2=3,\ c_1+c_3=1,\ c_2+c_3=2\): adding all, \(2(c_1+c_2+c_3)=6\), so the sum is 3, and \(c_3=0,\ c_2=2,\ c_1=1\). \([\mathbf{x}]_{\mathcal B}=(1,2,0)\).
Exercises
easy
Are \((1,2,3)\), \((2,4,6)\), \((0,1,0)\) independent?
Show solution
No: \(2(1,2,3)-(2,4,6)+0(0,1,0)=\mathbf 0\) is a non-trivial relation. (Any set with two parallel vectors is dependent.)
easy
Can 4 vectors in \(\mathbb{R}^3\) be independent? Can 2 vectors span \(\mathbb{R}^3\)?
Show solution
No and no. More than \(n\) vectors in \(\mathbb{R}^n\) are dependent (at most 3 pivots for 4 columns). Fewer than \(n\) vectors cannot span \(\mathbb{R}^n\) (at most 2 pivots for 3 rows, so some row has no pivot).
medium
For which \(k\) are \((1,1,0)\), \((1,0,1)\), \((0,1,k)\) a basis of \(\mathbb{R}^3\)?
Let \(\mathbf{v}_1=(k,1,1)\), \(\mathbf{v}_2=(1,k,1)\), \(\mathbf{v}_3=(1,1,k)\). Find, for every \(k\), \(\dim\operatorname{Span}\{\mathbf v_1,\mathbf v_2,\mathbf v_3\}\).
Show solution
\(\det = k^3-3k+2=(k-1)^2(k+2)\). (Check: \(k^3 - 3k + 2\) at \(k=1\) is 0 and at \(k=-2\) is \(-8+6+2=0\).)
\(k\neq1,-2\): det \(\neq0\), dimension 3 (basis of \(\mathbb{R}^3\)).
\(k=1\): all three vectors equal \((1,1,1)\): dimension 1.
\(k=-2\): \(\mathbf v_1+\mathbf v_2+\mathbf v_3=(0,0,0)\), and \(\mathbf v_1,\mathbf v_2\) are not parallel: dimension 2.
exam
(MC) Let \(\mathbf{v}_1=(1,k,1)\), \(\mathbf{v}_2=(k,1,1)\), \(\mathbf{v}_3=(0,0,1)\). A) they are a basis of \(\mathbb{R}^3\) for every \(k\); B) they are a basis iff \(k\neq1\); C) for \(k=-1\) the span has dimension 1; D) for \(k=1\) the span has dimension 2; E) none of the other answers.
Show solution
Example 1: basis iff \(k\neq\pm1\), so A and B are false. For \(k=-1\) the dimension is 2, so C is false. For \(k=1\), \(\mathbf v_1=\mathbf v_2=(1,1,1)\) and \(\mathbf v_3=(0,0,1)\) is not parallel to it: dimension 2. D.
Self-check quiz
Common mistakes
Saying "the vectors are independent" because no two are parallel: three vectors can be pairwise non-parallel and still dependent (they lie in one plane).
Taking pivot columns of the echelon matrix as the basis of the span: use the corresponding columns of the original matrix.
Forgetting the special parameter values: after "\(\det\neq0\iff k\neq\dots\)", the exam usually asks what happens at those values (dimension 1 or 2?).
Sign errors in the 3×3 determinant: expand along a row/column with zeros.
Remember
Independent \(\iff\) \(A\mathbf x=\mathbf 0\) only trivial \(\iff\) pivot in every column.
\(\dim\operatorname{Span}=\operatorname{rank}\) = number of pivots.
B2.3 Matrix algebra and linear transformations
Why it matters. A matrix is a compact way to store a linear map \(\mathbf{x}\mapsto A\mathbf{x}\). Data sets, graphs (adjacency matrices), Markov/PageRank matrices and Hessians are all matrices, and the product rule is used everywhere.
How it is examined. Short MC computations (a product, a transpose, "which matrix represents this map?"), and as a tool inside bigger exercises.
Explanation
An \(m\times n\) matrix \(A=(a_{ij})\) has \(m\) rows and \(n\) columns; \(a_{ij}\) is in row \(i\), column \(j\).
Sum and scalar multiple: entry by entry (same size needed).
Product: if \(A\) is \(m\times n\) and \(B\) is \(n\times p\), then \(AB\) is \(m\times p\) with \((AB)_{ij}=\sum_{k=1}^n a_{ik}b_{kj}\) = (row \(i\) of \(A\)) · (column \(j\) of \(B\)). The inner sizes must match.
Matrix-vector product: \(A\mathbf{x}=x_1\mathbf{a}_1+\dots+x_n\mathbf{a}_n\), a linear combination of the columns of \(A\).
Transpose: \((A^T)_{ij}=a_{ji}\) (rows become columns). \((AB)^T=B^TA^T\). \(A\) is symmetric if \(A^T=A\).
Identity \(I_n\): ones on the diagonal; \(AI=IA=A\).
Watch out: the product is not commutative
In general \(AB\neq BA\) (one of them may not even be defined). Also \(AB=0\) does not imply \(A=0\) or \(B=0\). Rules that DO hold: \(A(BC)=(AB)C\), \(A(B+C)=AB+AC\).
Definition: Linear transformation
\(T:\mathbb{R}^n\to\mathbb{R}^m\) is linear if \(T(\mathbf u+\mathbf v)=T(\mathbf u)+T(\mathbf v)\) and \(T(c\mathbf u)=cT(\mathbf u)\) for all vectors and scalars. Consequence: \(T(\mathbf 0)=\mathbf 0\).
Theorem (standard matrix)
Every linear \(T:\mathbb{R}^n\to\mathbb{R}^m\) is \(T(\mathbf x)=A\mathbf x\) with the \(m\times n\) matrix \(A=[\,T(\mathbf e_1)\ \cdots\ T(\mathbf e_n)\,]\): the columns are the images of the standard basis vectors. Composition corresponds to the product: \((S\circ T)(\mathbf x)=S(T(\mathbf x))=BA\mathbf x\) if \(S\) has matrix \(B\) and \(T\) has matrix \(A\) (note the order).
Example 3 (standard matrix). \(T(x,y)=(x+2y,\ 3x,\ x-y)\). \(T(\mathbf e_1)=(1,3,1)\), \(T(\mathbf e_2)=(2,0,-1)\), so \(A=\begin{pmatrix}1 & 2\\ 3 & 0\\ 1 & -1\end{pmatrix}\) and \(T(1,1)=A(1,1)^T=(3,3,0)\).
Example 4 (composition). \(R\) = rotation by 90°, \(F\) = reflection in the \(x\)-axis. "First reflect, then rotate" is \(RF=\begin{pmatrix}0 & -1\\ 1 & 0\end{pmatrix}\begin{pmatrix}1 & 0\\ 0 & -1\end{pmatrix}=\begin{pmatrix}0 & 1\\ 1 & 0\end{pmatrix}\) (reflection in the line \(y=x\)), but \(FR=\begin{pmatrix}0 & -1\\ -1 & 0\end{pmatrix}\) (reflection in \(y=-x\)). Different!
Exercises
easy
\(A\) is 3×2, \(B\) is 2×4, \(C\) is 4×3. Which of \(AB,\ BA,\ ABC,\ CA,\ B^TA^T\) are defined, and what size?
Write \(A\mathbf x\) with \(A=\begin{pmatrix}2 & -1\\ 0 & 3\end{pmatrix}\), \(\mathbf x=(4,1)\) as a combination of columns.
Show solution
\(4(2,0)+1(-1,3)=(8,0)+(-1,3)=(7,3)\).
medium
Find the matrix of the rotation by 90° and compute the image of \((2,1)\). Then find the matrix of "rotate by 90° twice" and interpret it.
Show solution
\(R=\begin{pmatrix}0 & -1\\ 1 & 0\end{pmatrix}\), \(R(2,1)^T=(-1,2)\). \(R^2=\begin{pmatrix}-1 & 0\\ 0 & -1\end{pmatrix}=-I\): rotation by 180°, which sends \(\mathbf x\) to \(-\mathbf x\).
medium
Is \(T(x,y)=(x+1,\ y)\) linear? Is \(T(x,y)=(xy,\ x)\) linear?
Show solution
First: \(T(0,0)=(1,0)\neq\mathbf 0\), not linear (a translation). Second: \(T(2(1,1))=T(2,2)=(4,2)\) but \(2T(1,1)=(2,2)\): not linear.
hard
For \(A=\begin{pmatrix}1 & 2 & 3\\ 2 & 4 & 6\end{pmatrix}\) find a basis of \(\operatorname{Nul}A\) and of \(\operatorname{Col}A\), and check rank-nullity.
(MC) Let \(A\) and \(B\) be \(n\times n\). Which is always true? A) \(AB=BA\); B) \((AB)^T=A^TB^T\); C) \((A+B)^2=A^2+2AB+B^2\); D) \(A(B+I)=AB+A\); E) none of the other answers.
Show solution
D: distributivity, \(A(B+I)=AB+AI=AB+A\). A false in general; B false (correct is \(B^TA^T\)); C false: \((A+B)^2=A^2+AB+BA+B^2\). D.
Self-check quiz
Common mistakes
Multiplying entry by entry instead of row-times-column.
Writing \((AB)^T=A^TB^T\) or \((AB)^{-1}=A^{-1}B^{-1}\): the order reverses.
Putting \(T(\mathbf e_j)\) as rows instead of columns of the standard matrix.
Composition order: "first \(T\), then \(S\)" is \(BA\), not \(AB\).
Remember
\((m\times n)(n\times p)=m\times p\); \(AB\neq BA\) in general.
\(A\mathbf x\) = combination of the columns; standard matrix columns = \(T(\mathbf e_j)\).
rank + nullity = number of columns.
B2.4 Linear systems: row reduction, echelon form, rank and consistency
Why it matters. Almost every linear-algebra question becomes "solve a linear system": span, independence, inverses, eigenvectors, least squares, Lagrange conditions.
How it is examined. Exam-critical. The mock has an open question on the consistency of a system in echelon form that depends on a parameter: you must say for which values there are no solutions, exactly one, or infinitely many, and show your work.
Explanation
A system of \(m\) linear equations in \(n\) unknowns is \(A\mathbf x=\mathbf b\); its augmented matrix is \([A\mid\mathbf b]\). Three elementary row operations do not change the solution set: (1) swap two rows; (2) multiply a row by a non-zero number; (3) add a multiple of one row to another.
Definition: Echelon form, pivots
A matrix is in (row) echelon form if all zero rows are at the bottom, and the first non-zero entry (the pivot) of each row is to the right of the pivot of the row above. It is in reduced echelon form if, in addition, every pivot is 1 and is the only non-zero entry in its column. A pivot column contains a pivot; the variables of non-pivot columns are free variables.
Definition: Rank
\(\operatorname{rank}A\) = number of pivots in an echelon form of \(A\) = maximum number of independent columns (= of independent rows).
Theorem (Rouché–Capelli)
The system \(A\mathbf x=\mathbf b\) with \(n\) unknowns is consistent iff \(\operatorname{rank}A=\operatorname{rank}[A\mid\mathbf b]\). In that case, with \(r\) this common rank:
\(r=n\): exactly one solution;
\(r\lt n\): infinitely many solutions, with \(n-r\) free parameters ("\(\infty^{n-r}\) solutions").
In echelon form, "inconsistent" means a row \([0\ \cdots\ 0\mid c]\) with \(c\neq0\), i.e. the equation \(0=c\).
Tip: square systems
If \(A\) is \(n\times n\): \(\det A\neq0\Rightarrow\) exactly one solution for every \(\mathbf b\). If \(\det A=0\), the system has either no solutions or infinitely many: you must check \(\operatorname{rank}[A\mid\mathbf b]\). A homogeneous system \(A\mathbf x=\mathbf 0\) is always consistent (\(\mathbf x=\mathbf 0\)).
Check with the course
The name "Rouché–Capelli" is used in Italian courses; Lay states the same result as "existence and uniqueness theorem" (consistent iff the last column of the augmented matrix is not a pivot column). Use whichever name your teacher uses.
Worked examples
Example 1 (Gaussian elimination). Solve \(x+2y-z=6,\ 2x+5y+z=11,\ x+3y+z=6\).
It is already in echelon form. The only entry that can vanish on the diagonal is \(k^2-1=(k-1)(k+1)\).
\(k\neq\pm1\): three pivots, \(\operatorname{rank}A=\operatorname{rank}[A|\mathbf b]=3=n\): one solution. From row 3, \(z=\frac{k+1}{(k-1)(k+1)}=\frac1{k-1}\); then \(y=2-kz\), \(x=1-2y+z\). (E.g. \(k=2\): \(z=1,\ y=0,\ x=2\).)
\(k=1\): row 3 becomes \([0\ 0\ 0\mid 2]\), i.e. \(0=2\): \(\operatorname{rank}A=2\neq\operatorname{rank}[A|\mathbf b]=3\): no solution.
Example 3 (general solution in parametric form). \(x+y-z=2,\ 2x+y+z=3\). \(R_2-2R_1\): \(-y+3z=-1\). Free \(z=t\): \(y=1+3t\), \(x=2-y+z=1-2t\). So \(\mathbf x=(1,1,0)+t(-2,3,1)\): a particular solution plus the null space of \(A\) (a line in \(\mathbb{R}^3\)).
Exercises
easy
Is \(\left(\begin{array}{ccc|c}1 & 3 & 0 & 2\\ 0 & 0 & 1 & 5\\ 0 & 0 & 0 & 0\end{array}\right)\) in echelon form? Reduced? Give rank, free variables and solutions.
Show solution
Yes, and reduced (pivots are 1 and alone in their columns 1 and 3). Rank 2, \(n=3\), so one free variable \(x_2=t\). Solutions \(x_1=2-3t,\ x_2=t,\ x_3=5\).
easy
A system has 4 equations and 3 unknowns with \(\operatorname{rank}A=3\) and \(\operatorname{rank}[A|\mathbf b]=4\). How many solutions?
Show solution
None: the ranks differ (Rouché–Capelli). There is a row \(0=c\neq0\).
medium
Discuss \(kx+y=1,\ x+ky=1\) for \(k\in\mathbb{R}\).
Show solution
\(\det\begin{pmatrix}k & 1\\ 1 & k\end{pmatrix}=k^2-1\). If \(k\neq\pm1\): unique solution; subtracting the equations, \((k-1)(x-y)=0\Rightarrow x=y\), then \((k+1)x=1\): \(x=y=\frac1{k+1}\). If \(k=1\): both equations are \(x+y=1\): infinitely many, \((1-t,t)\). If \(k=-1\): \(-x+y=1\) and \(x-y=1\); adding gives \(0=2\): no solution.
medium
Solve by row reduction: \(x+2y+3z=1,\ 2x+4y+7z=3,\ 3x+6y+10z=4\).
(Open, 5 points) For the system with augmented matrix \(\left(\begin{array}{ccc|c}1 & -1 & 2 & 3\\ 0 & k & 1 & 1\\ 0 & 0 & k-2 & k-2\end{array}\right)\), find all \(k\) for which the system has no solution / exactly one / infinitely many, and solve it for \(k=2\).
Show solution
Pivots can disappear when \(k=0\) (row 2) or \(k=2\) (row 3).
\(k\neq0,2\): pivots in columns 1,2,3; rank 3 = 3: one solution: \(z=1\), \(y=(1-z)/k=0\), \(x=3+y-2z=1\): \((1,0,1)\) for every such \(k\).
No value of \(k\) gives no solution. Marking idea (assumption): 1 pt identifying critical values, 1 pt generic case with rank argument, 1 pt each special case, 1 pt solution for \(k=2\).
Self-check quiz
Common mistakes
Dividing by an expression containing the parameter (like \(k-1\)) without first treating the case where it is 0.
Concluding "infinitely many solutions" just because \(\det A=0\): you must also check the augmented matrix (it may be inconsistent).
Forgetting that in a parameter problem a pivot can also vanish in a middle row (case \(k=0\) above).
Giving a bare answer in an open question: write the ranks and the theorem you use.
Remember
Row-reduce \([A|\mathbf b]\) to echelon form; count pivots.
Consistent \(\iff\operatorname{rank}A=\operatorname{rank}[A|\mathbf b]\); unique iff this rank \(=n\); else \(\infty^{n-r}\).
Parameter: find values that kill a pivot, then study each case separately.
B2.5 Determinants and the inverse matrix
Why it matters. The determinant is the fastest test for "is this square matrix invertible / are these \(n\) vectors a basis / does this system have a unique solution?". It also gives eigenvalues (B2.7) and the second-order test for optimization (B2.14).
How it is examined. Inside parameter questions (basis, systems), and as MC on determinant properties (\(\det(2A)\), \(\det(AB)\), \(\det A^{-1}\)).
Explanation
2×2: \(\det\begin{pmatrix}a & b\\ c & d\end{pmatrix}=ad-bc\). Geometric meaning: the (signed) area of the parallelogram spanned by the columns; in 3D, the signed volume.
3×3 (Laplace / cofactor expansion along row \(i\)): \(\det A=\sum_j (-1)^{i+j}a_{ij}\det A_{ij}\), where \(A_{ij}\) is \(A\) with row \(i\) and column \(j\) deleted. Signs follow the chessboard \(\begin{pmatrix}+ & - & +\\ - & + & -\\ + & - & +\end{pmatrix}\). You may expand along any row or column: choose one with many zeros. (For 3×3 only, the rule of Sarrus also works.)
Properties of the determinant (\(A,B\) are \(n\times n\))
Triangular matrix: \(\det\) = product of the diagonal entries.
Row operations: swapping two rows changes the sign; multiplying a row by \(c\) multiplies \(\det\) by \(c\); adding a multiple of a row to another row does not change \(\det\).
\(A\) (square) is invertible if there is \(A^{-1}\) with \(AA^{-1}=A^{-1}A=I\). Then \(A\mathbf x=\mathbf b\) has the unique solution \(\mathbf x=A^{-1}\mathbf b\). Rules: \((AB)^{-1}=B^{-1}A^{-1}\), \((A^T)^{-1}=(A^{-1})^T\).
How to compute \(A^{-1}\). 2×2: \(\begin{pmatrix}a & b\\ c & d\end{pmatrix}^{-1}=\frac1{ad-bc}\begin{pmatrix}d & -b\\ -c & a\end{pmatrix}\) (swap the diagonal, change sign off the diagonal). General: Gauss–Jordan: row-reduce \([A\mid I]\) to \([I\mid A^{-1}]\); if a zero row appears on the left, \(A\) is not invertible. (Alternative: \(A^{-1}=\frac{1}{\det A}\operatorname{adj}A\), the transposed matrix of cofactors.)
Check one entry of \(CC^{-1}\): row 1 of \(C\) times column 1 of \(C^{-1}\) = \(\frac12(1+0+1)=1\) ✓. Also \(\det C=-2\) (the pivots \(1\cdot1\cdot(-2)\)).
Example 4 (properties). \(A\) is 3×3 with \(\det A=5\). Then \(\det(2A)=2^3\cdot5=40\), \(\det A^T=5\), \(\det A^{-1}=\frac15\), \(\det(A^2)=25\).
Find the inverse of \(A=\begin{pmatrix}1 & 2 & 0\\ 3 & 1 & 1\\ 0 & 2 & 1\end{pmatrix}\) and check it.
Show solution
\(\det A=1(1-2)-2(3-0)+0=-7\). Cofactors \(C_{ij}\): \(C_{11}=-1,\ C_{12}=-3,\ C_{13}=6,\ C_{21}=-2,\ C_{22}=1,\ C_{23}=-2,\ C_{31}=2,\ C_{32}=-1,\ C_{33}=-5\). The adjugate is the transpose of the cofactor matrix, so
(MC) Let \(A=\begin{pmatrix}2 & 1 & 3\\ 0 & -1 & 4\\ 1 & 2 & 0\end{pmatrix}\). Then \(\det(2A^T)\) equals: A) \(-18\); B) \(-72\); C) \(72\); D) \(-9\); E) none of the other answers.
Show solution
\(\det A=-9\) (Example 1), \(\det A^T=-9\), \(\det(2A^T)=2^3(-9)=-72\). B. (A is the classic mistake \(2\det A\).)
Self-check quiz
Common mistakes
\(\det(cA)=c\det A\): wrong, it is \(c^n\det A\).
\(\det(A+B)=\det A+\det B\): false in general.
Forgetting the chessboard signs in the cofactor expansion, or forgetting to transpose the cofactor matrix for the inverse.
Scaling a row during row reduction and forgetting it changes the determinant.
Remember
\(ad-bc\); cofactor expansion along a row/column with zeros; triangular = product of diagonal.
\(A^{-1}\): 2×2 formula, or Gauss–Jordan \([A|I]\to[I|A^{-1}]\).
B2.6 Inner product, norm, orthogonality, Gram–Schmidt and projections
Why it matters. Lengths, angles and "closest point" problems: orthogonal projection is the geometry of least squares (B2.17); orthogonal matrices appear in diagonalization of symmetric matrices (B2.8) and in the SVD (B2.9).
How it is examined. Short MC computations (norm, angle, orthogonality with a parameter, properties of orthogonal matrices) and short exercises (projection, Gram–Schmidt).
Explanation
Definition: Inner (dot) product, norm, distance
For \(\mathbf u,\mathbf v\in\mathbb{R}^n\): \(\mathbf u\cdot\mathbf v=\mathbf u^T\mathbf v=u_1v_1+\dots+u_nv_n\). Norm (length): \(\|\mathbf u\|=\sqrt{\mathbf u\cdot\mathbf u}\). Distance: \(\operatorname{dist}(\mathbf u,\mathbf v)=\|\mathbf u-\mathbf v\|\). A unit vector has norm 1; \(\mathbf u/\|\mathbf u\|\) is the normalization of \(\mathbf u\ne\mathbf0\).
Properties: symmetric, linear in each argument, \(\mathbf u\cdot\mathbf u\ge0\) with equality only for \(\mathbf 0\). Angle: \(\cos\theta=\dfrac{\mathbf u\cdot\mathbf v}{\|\mathbf u\|\|\mathbf v\|}\).
\(\mathbf u\perp\mathbf v\) iff \(\mathbf u\cdot\mathbf v=0\). A set is orthogonal if every pair is orthogonal, and orthonormal if, in addition, every vector has norm 1. An orthogonal set of non-zero vectors is automatically linearly independent.
Theorem (coordinates in an orthogonal basis)
If \(\{\mathbf u_1,\dots,\mathbf u_p\}\) is an orthogonal basis of \(W\) and \(\mathbf y\in W\), then \(\mathbf y=c_1\mathbf u_1+\dots+c_p\mathbf u_p\) with \(c_j=\dfrac{\mathbf y\cdot\mathbf u_j}{\mathbf u_j\cdot\mathbf u_j}\). No system to solve!
Definition: Orthogonal projection
Onto a line spanned by \(\mathbf u\): \(\operatorname{proj}_{\mathbf u}\mathbf y=\dfrac{\mathbf y\cdot\mathbf u}{\mathbf u\cdot\mathbf u}\mathbf u\). Onto a subspace \(W\) with orthogonal basis \(\mathbf u_1,\dots,\mathbf u_p\): \(\hat{\mathbf y}=\sum_j\dfrac{\mathbf y\cdot\mathbf u_j}{\mathbf u_j\cdot\mathbf u_j}\mathbf u_j\). The residual \(\mathbf z=\mathbf y-\hat{\mathbf y}\) is orthogonal to \(W\), and \(\hat{\mathbf y}\) is the closest point of \(W\) to \(\mathbf y\) (best approximation theorem). If \(U\) has orthonormal columns, \(\hat{\mathbf y}=UU^T\mathbf y\).
Definition: Orthogonal matrix
A square \(Q\) with orthonormal columns: \(Q^TQ=I\), so \(Q^{-1}=Q^T\). It preserves lengths and angles (\(\|Q\mathbf x\|=\|\mathbf x\|\)), and \(\det Q=\pm1\). Examples: rotations, reflections, permutation matrices.
Gram–Schmidt process
From a basis \(\mathbf x_1,\dots,\mathbf x_p\) of \(W\) build an orthogonal basis: \(\mathbf v_1=\mathbf x_1\), \(\mathbf v_2=\mathbf x_2-\dfrac{\mathbf x_2\cdot\mathbf v_1}{\mathbf v_1\cdot\mathbf v_1}\mathbf v_1\), \(\mathbf v_3=\mathbf x_3-\dfrac{\mathbf x_3\cdot\mathbf v_1}{\mathbf v_1\cdot\mathbf v_1}\mathbf v_1-\dfrac{\mathbf x_3\cdot\mathbf v_2}{\mathbf v_2\cdot\mathbf v_2}\mathbf v_2\), ... (subtract the projections onto the previous \(\mathbf v\)'s). Normalize at the end for an orthonormal basis.
Worked examples
Example 1. \(\mathbf u=(1,2,2)\), \(\mathbf v=(2,0,-1)\): \(\mathbf u\cdot\mathbf v=2+0-2=0\), so they are orthogonal. \(\|\mathbf u\|=\sqrt{1+4+4}=3\), \(\|\mathbf v\|=\sqrt5\). Angle between \((3,4)\) and \((4,3)\): \(\cos\theta=\frac{24}{5\cdot5}=0.96\), \(\theta\approx16.26^\circ\).
Example 2 (projection on a line). \(\mathbf y=(3,1)\), \(\mathbf u=(1,2)\): \(\frac{\mathbf y\cdot\mathbf u}{\mathbf u\cdot\mathbf u}=\frac55=1\), \(\hat{\mathbf y}=(1,2)\), residual \(\mathbf z=(2,-1)\), and \(\mathbf z\cdot\mathbf u=2-2=0\) ✓. Distance from \(\mathbf y\) to the line: \(\|\mathbf z\|=\sqrt5\).
Example 4 (projection on a plane). \(W=\operatorname{Span}\{\mathbf u_1=(1,1,0),\mathbf u_2=(-1,1,1)\}\) (orthogonal: \(-1+1+0=0\)), \(\mathbf y=(2,3,4)\). \(\hat{\mathbf y}=\frac52\mathbf u_1+\frac53\mathbf u_2=(\tfrac56,\tfrac{25}6,\tfrac53)\). Residual \(\mathbf z=(\tfrac76,-\tfrac76,\tfrac73)\): \(\mathbf z\cdot\mathbf u_1=0\), \(\mathbf z\cdot\mathbf u_2=-\tfrac76-\tfrac76+\tfrac73=0\) ✓.
Exercises
easy
Normalize \((2,-1,2)\).
Show solution
\(\|\cdot\|=\sqrt{4+1+4}=3\); unit vector \((\tfrac23,-\tfrac13,\tfrac23)\).
easy
For which \(k\) is \((1,k,2)\perp(3,1,-1)\)?
Show solution
\(3+k-2=0\Rightarrow k=-1\).
medium
Write \(\mathbf y=(2,3,4)\) in the orthogonal basis \(\{(1,1,0),(-1,1,1),(1,-1,2)\}\).
Show solution
Check orthogonality: \((1,1,0)\cdot(1,-1,2)=0\), \((-1,1,1)\cdot(1,-1,2)=-1-1+2=0\), and the first pair from Example 4. \(c_1=\frac{5}{2}\), \(c_2=\frac{-2+3+4}{3}=\frac53\), \(c_3=\frac{2-3+8}{6}=\frac76\). Check: \(\frac52(1,1,0)+\frac53(-1,1,1)+\frac76(1,-1,2)=(2,3,4)\) ✓.
medium
Is \(Q=\frac{1}{\sqrt2}\begin{pmatrix}1 & 1\\ 1 & -1\end{pmatrix}\) orthogonal? Find \(Q^{-1}\) and \(\det Q\).
Show solution
Columns have norm \(\sqrt{(1+1)/2}=1\) and dot product \(\frac12(1-1)=0\): orthogonal. \(Q^{-1}=Q^T=Q\) (it is symmetric). \(\det Q=\frac12(-1-1)=-1\) (a reflection).
hard
Apply Gram–Schmidt to \((1,1,0),(1,0,1),(0,1,1)\) and give an orthonormal basis of \(\mathbb{R}^3\).
(MC) The distance from \(\mathbf y=(2,3,4)\) to the plane \(W=\operatorname{Span}\{(1,1,0),(-1,1,1)\}\) is: A) \(\frac76\); B) \(\frac{7\sqrt6}{6}\); C) \(\sqrt{29}\); D) \(\frac{49}{6}\); E) none of the other answers.
Show solution
Distance \(=\|\mathbf z\|\) with \(\mathbf z=\frac76(1,-1,2)\) (Example 4): \(\frac76\sqrt6=\frac{7\sqrt6}6\approx2.86\). B. (D is \(\|\mathbf z\|^2\).)
Self-check quiz
Common mistakes
Using the projection formula \(\sum\frac{\mathbf y\cdot\mathbf u_j}{\mathbf u_j\cdot\mathbf u_j}\mathbf u_j\) with a basis that is NOT orthogonal (first run Gram–Schmidt).
Dividing by \(\|\mathbf u\|\) instead of \(\|\mathbf u\|^2=\mathbf u\cdot\mathbf u\) in the projection.
Calling a matrix with orthogonal but not unit columns "orthogonal matrix": its columns must be orthonormal.
B2.7 Eigenvalues, eigenvectors and diagonalization
Why it matters. Eigenvectors are the directions a matrix only stretches. They give powers \(A^k\) (long-run behaviour of Markov chains and PageRank), the type of a quadratic form (B2.8), the SVD (B2.9), the Laplacian of a graph (B2.10) and the stability of dynamical systems (B2.18-19).
How it is examined. Homework 3 covered eigenvalues and eigenvectors. Expect MC on eigenvalues of a given matrix, trace/determinant facts, diagonalizability, and exercises computing eigenvectors.
Explanation
Definition: Eigenvalue and eigenvector
For a square \(A\), a scalar \(\lambda\) is an eigenvalue if there is a non-zero vector \(\mathbf v\) with \(A\mathbf v=\lambda\mathbf v\); \(\mathbf v\) is an eigenvector for \(\lambda\). The eigenspace of \(\lambda\) is \(\operatorname{Nul}(A-\lambda I)\) (all eigenvectors plus \(\mathbf 0\)).
How to find them. \(A\mathbf v=\lambda\mathbf v\iff(A-\lambda I)\mathbf v=\mathbf 0\) has a non-zero solution \(\iff\det(A-\lambda I)=0\). This is the characteristic equation; \(p(\lambda)=\det(A-\lambda I)\) is a polynomial of degree \(n\). Steps: (1) solve \(p(\lambda)=0\); (2) for each root, solve \((A-\lambda I)\mathbf v=\mathbf 0\).
In general \(\lambda_1+\dots+\lambda_n=\operatorname{tr}A\) (sum of diagonal) and \(\lambda_1\cdots\lambda_n=\det A\) (with multiplicity, complex ones included).
Triangular matrix: the eigenvalues are the diagonal entries.
\(A\) invertible \(\iff\) 0 is not an eigenvalue. Eigenvalues of \(A^k\): \(\lambda^k\); of \(A^{-1}\): \(1/\lambda\) (same eigenvectors).
Eigenvectors of distinct eigenvalues are linearly independent.
Definition: Diagonalizable
\(A\) is diagonalizable if \(A=PDP^{-1}\) with \(D\) diagonal. Then the columns of \(P\) are \(n\) independent eigenvectors and the diagonal of \(D\) holds the matching eigenvalues (same order). Consequence: \(A^k=PD^kP^{-1}\).
Theorem (diagonalization)
\(A\) (\(n\times n\)) is diagonalizable \(\iff\) it has \(n\) linearly independent eigenvectors \(\iff\) for every eigenvalue, geometric multiplicity (\(\dim\) eigenspace) = algebraic multiplicity (multiplicity as a root). Sufficient: \(n\) distinct eigenvalues. Real symmetric matrices are always diagonalizable (B2.8).
Example 2 (not diagonalizable). \(J=\begin{pmatrix}1 & 1\\ 0 & 1\end{pmatrix}\): \(\lambda=1\) twice (algebraic multiplicity 2), but \(J-I=\begin{pmatrix}0 & 1\\ 0 & 0\end{pmatrix}\) has a 1-dimensional null space \(\operatorname{Span}\{(1,0)\}\). Only one independent eigenvector: not diagonalizable.
Example 3 (no real eigenvalues). Rotation by 90°, \(\begin{pmatrix}0 & -1\\ 1 & 0\end{pmatrix}\): \(p(\lambda)=\lambda^2+1\), eigenvalues \(\pm i\). Makes sense: a rotation keeps no direction fixed.
Example 4 (Markov chain, long run). \(M=\begin{pmatrix}0.8 & 0.3\\ 0.2 & 0.7\end{pmatrix}\) (columns sum to 1). \(\operatorname{tr}=1.5\), \(\det=0.5\): \(\lambda^2-1.5\lambda+0.5=(\lambda-1)(\lambda-0.5)\). For \(\lambda=1\): \(-0.2v_1+0.3v_2=0\Rightarrow\mathbf v=(3,2)\), normalized to sum 1: \(\mathbf q=(0.6,0.4)\). Since \(0.5^k\to0\), \(M^k\mathbf x_0\to\mathbf q\) for every probability vector \(\mathbf x_0\): the steady state. PageRank (B2.12) is exactly this idea.
Exercises
easy
Is \((1,1)\) an eigenvector of \(\begin{pmatrix}2 & 1\\ 1 & 2\end{pmatrix}\)? And \((1,2)\)?
Show solution
\(A(1,1)=(3,3)=3(1,1)\): yes, \(\lambda=3\). \(A(1,2)=(4,5)\), not a multiple of \((1,2)\): no.
easy
Eigenvalues of \(\begin{pmatrix}2 & 0 & 0\\ 1 & 3 & 0\\ 0 & 4 & 5\end{pmatrix}\)? Is it diagonalizable?
Show solution
Triangular: 2, 3, 5. Three distinct eigenvalues in a 3×3 matrix \(\Rightarrow\) diagonalizable.
medium
Find eigenvalues and eigenvectors of \(\begin{pmatrix}5 & -2\\ 1 & 2\end{pmatrix}\) and diagonalize it.
Find the eigenvalues of \(\begin{pmatrix}3 & 1 & 1\\ 1 & 3 & 1\\ 1 & 1 & 3\end{pmatrix}\) and the dimension of each eigenspace. (Hint: \(A=2I+\mathbf 1\mathbf 1^T\).)
Show solution
\(A-2I\) is the all-ones matrix, rank 1, so \(\lambda=2\) has a 2-dimensional eigenspace (\(x_1+x_2+x_3=0\), basis \((-1,1,0),(-1,0,1)\)). Trace \(=9=2+2+\lambda_3\Rightarrow\lambda_3=5\), eigenvector \((1,1,1)\). Eigenvalues 2, 2, 5; diagonalizable.
hard
For which \(k\) does \(\begin{pmatrix}1 & k\\ 1 & 1\end{pmatrix}\) have two distinct real eigenvalues? For \(k=0\), is it diagonalizable?
Show solution
\(p(\lambda)=(1-\lambda)^2-k\), roots \(\lambda=1\pm\sqrt k\). Two distinct real iff \(k\gt0\) (\(k\lt0\): complex; \(k=0\): double root). For \(k=0\): \(\begin{pmatrix}1 & 0\\ 1 & 1\end{pmatrix}\), \(A-I=\begin{pmatrix}0 & 0\\ 1 & 0\end{pmatrix}\), eigenspace \(\operatorname{Span}\{(0,1)\}\) of dimension 1 \(\lt2\): not diagonalizable.
exam
(MC) A 2×2 real matrix has trace 5 and determinant 6. Then: A) its eigenvalues are \(-2,-3\); B) it is not invertible; C) its eigenvalues are 2 and 3, so it is diagonalizable; D) \(A^2\) has eigenvalues 4 and 6; E) none of the other answers.
Show solution
\(\lambda^2-5\lambda+6=(\lambda-2)(\lambda-3)\): eigenvalues 2, 3, distinct, so diagonalizable. C. (D: \(A^2\) has 4 and 9; B: \(\det=6\neq0\).)
Self-check quiz
Common mistakes
Accepting \(\mathbf v=\mathbf 0\) as an eigenvector (it never is; but \(\lambda=0\) can be an eigenvalue).
Row-reducing \(A\) to find eigenvalues: row operations change eigenvalues. Use \(\det(A-\lambda I)\).
Thinking a repeated eigenvalue means "not diagonalizable": check the dimension of the eigenspace (Exercise 4 is diagonalizable).
Putting eigenvectors in \(P\) in a different order from the eigenvalues in \(D\).
Remember
\(\det(A-\lambda I)=0\) for \(\lambda\); \(\operatorname{Nul}(A-\lambda I)\) for \(\mathbf v\).
Sum = trace, product = det; triangular: read the diagonal.
B2.8 Symmetric matrices, quadratic forms and definiteness
Why it matters. The sign of a quadratic form decides whether a critical point is a minimum, a maximum or a saddle (the Hessian test, B2.14), whether a loss is convex (B2.17), and it explains why the graph Laplacian has non-negative eigenvalues. Lecture of 29 Oct.
How it is examined. MC: "the quadratic form ... is: positive definite / indefinite / ..." possibly with a parameter; and inside the second-order test of open optimization questions.
Explanation
Spectral theorem (real symmetric matrices)
If \(A=A^T\) then: (1) all eigenvalues are real; (2) eigenvectors of different eigenvalues are orthogonal; (3) \(A\) is orthogonally diagonalizable: \(A=QDQ^T\) with \(Q\) orthogonal (orthonormal eigenvectors as columns) and \(D\) diagonal.
Definition: Quadratic form
A function \(Q(\mathbf x)=\mathbf x^TA\mathbf x\) with \(A\) symmetric. In two variables: \(Q(x,y)=ax^2+2bxy+cy^2\) with \(A=\begin{pmatrix}a & b\\ b & c\end{pmatrix}\): the coefficient of \(xy\) is split in half between the two off-diagonal entries. In three variables, the coefficient of \(x_ix_j\) (\(i\neq j\)) is \(2a_{ij}\).
Definition: Definiteness
\(Q\) (or \(A\)) is positive definite if \(Q(\mathbf x)\gt0\) for all \(\mathbf x\neq\mathbf 0\); positive semidefinite if \(Q(\mathbf x)\ge0\) for all \(\mathbf x\); negative (semi)definite analogously with \(\lt0\) (\(\le0\)); indefinite if it takes both positive and negative values.
Two tests
Eigenvalue test. PD \(\iff\) all \(\lambda_i\gt0\); PSD \(\iff\) all \(\lambda_i\ge0\); ND \(\iff\) all \(\lambda_i\lt0\); NSD \(\iff\) all \(\le0\); indefinite \(\iff\) some \(\lambda\gt0\) and some \(\lambda\lt0\). (Reason: in the eigenvector coordinates \(\mathbf y=Q^T\mathbf x\), \(Q(\mathbf x)=\lambda_1y_1^2+\dots+\lambda_ny_n^2\): the principal axes form.)
Leading principal minors (Sylvester). Let \(D_k\) = determinant of the top-left \(k\times k\) submatrix. PD \(\iff D_1\gt0, D_2\gt0,\dots,D_n\gt0\). ND \(\iff D_1\lt0, D_2\gt0, D_3\lt0,\dots\) (signs alternate, starting negative). If \(\det A\neq0\) and neither pattern holds, \(A\) is indefinite.
2×2 shortcut: \(\det A\lt0\Rightarrow\) indefinite; \(\det A\gt0\) and \(a\gt0\Rightarrow\) PD; \(\det A\gt0\) and \(a\lt0\Rightarrow\) ND; \(\det A=0\Rightarrow\) semidefinite (sign of the trace).
Careful with semidefinite
The leading-minor test does NOT extend to semidefinite by just writing \(\ge\): for PSD you need all principal minors \(\ge0\) (not only the leading ones), or use eigenvalues. E.g. \(\begin{pmatrix}0 & 0\\ 0 & -1\end{pmatrix}\) has \(D_1=0, D_2=0\) but is NSD, not PSD.
Graph link. For the Laplacian \(L\) of a graph (B2.10), \(\mathbf x^TL\mathbf x=\sum_{\text{edges }\{i,j\}}(x_i-x_j)^2\ge0\): \(L\) is positive semidefinite, so its eigenvalues are \(\ge0\).
Worked examples
Example 1. \(Q=3x^2+2xy+3y^2\): \(A=\begin{pmatrix}3 & 1\\ 1 & 3\end{pmatrix}\). \(D_1=3\gt0\), \(D_2=9-1=8\gt0\): positive definite. Eigenvalues \(4\) (eigenvector \((1,1)\)) and \(2\) (eigenvector \((1,-1)\)); with \(u=\frac{x+y}{\sqrt2}\), \(v=\frac{x-y}{\sqrt2}\): \(Q=4u^2+2v^2\). Level curves \(Q=c\) are ellipses with axes along \((1,1)\) and \((1,-1)\).
For which \(k\) is \(Q=x^2+2kxy+4y^2\) positive definite? What is it for \(k=2\)?
Show solution
\(A=\begin{pmatrix}1 & k\\ k & 4\end{pmatrix}\): \(D_1=1\gt0\), \(D_2=4-k^2\gt0\iff-2\lt k\lt2\). For \(k=2\): \(Q=x^2+4xy+4y^2=(x+2y)^2\ge0\), zero on the line \(x=-2y\): positive semidefinite (not definite). For \(|k|\gt2\): indefinite.
medium
Classify \(A=\begin{pmatrix}1 & 2 & 0\\ 2 & 1 & 0\\ 0 & 0 & 3\end{pmatrix}\) in two ways.
Show solution
Minors: \(D_1=1\), \(D_2=-3\), \(D_3=-9\). \(\det\neq0\) and the pattern is neither PD nor ND: indefinite. Eigenvalues: block \(\begin{pmatrix}1 & 2\\ 2 & 1\end{pmatrix}\) gives 3, \(-1\); plus 3. Signs \(+,-,+\): indefinite ✓.
hard
Find the principal axes of \(Q=5x^2+4xy+2y^2\) and write \(Q\) without the cross term.
(MC) The quadratic form \(Q(x,y,z)=x^2+y^2+z^2+2xy\) is: A) positive definite; B) negative semidefinite; C) positive semidefinite but not positive definite; D) indefinite; E) none of the other answers.
Show solution
\(Q=(x+y)^2+z^2\ge0\), and \(Q(1,-1,0)=0\) with a non-zero vector. So PSD but not PD: C. (Matrix \(\begin{pmatrix}1 & 1 & 0\\ 1 & 1 & 0\\ 0 & 0 & 1\end{pmatrix}\), eigenvalues 2, 0, 1.)
Self-check quiz
Common mistakes
Putting the full coefficient of \(xy\) in both off-diagonal entries (\(4xy\) gives entries 2 and 2, not 4 and 4).
Concluding PSD from "leading minors \(\ge0\)".
Forgetting that \(D_2\lt0\) in 2×2 already means indefinite, whatever \(D_1\) is.
Remember
Symmetric \(\Rightarrow\) real eigenvalues, \(A=QDQ^T\) with orthonormal eigenvectors.
\(Q(\mathbf x)=\mathbf x^TA\mathbf x\); sign of \(Q\) = signs of the eigenvalues.
PD: all \(D_k\gt0\); ND: \(D_1\lt0,D_2\gt0,D_3\lt0,\dots\); 2×2: \(\det\lt0\Rightarrow\) indefinite.
B2.9 Singular value decomposition (case study: low-rank approximation)
Why it matters. The SVD works for any matrix (rectangular, not symmetric). It reveals the rank, the most important directions of a data matrix (PCA), and gives the best low-rank approximation, used to compress images and to remove noise. It is the official linear-algebra case study.
How it is examined. Not stated precisely. Likely: compute singular values of a small matrix via \(A^TA\), know the facts (rank = number of non-zero \(\sigma\), best rank-\(k\) approximation and its error), interpret a compression example. check with Benfenati's SVD notes.
Explanation
Definition: Singular values
For any \(m\times n\) matrix \(A\), the matrix \(A^TA\) is \(n\times n\), symmetric and positive semidefinite (\(\mathbf x^TA^TA\mathbf x=\|A\mathbf x\|^2\ge0\)), so its eigenvalues are \(\lambda_1\ge\dots\ge\lambda_n\ge0\). The singular values of \(A\) are \(\sigma_i=\sqrt{\lambda_i}\), in decreasing order. Meaning: \(\sigma_i=\|A\mathbf v_i\|\), where \(\mathbf v_i\) is the unit eigenvector of \(A^TA\).
Theorem (SVD)
Every \(m\times n\) matrix of rank \(r\) can be written \(A=U\Sigma V^T\), with \(U\) (\(m\times m\)) and \(V\) (\(n\times n\)) orthogonal and \(\Sigma\) (\(m\times n\)) "diagonal" with \(\sigma_1\ge\dots\ge\sigma_r\gt0\) followed by zeros. Columns of \(V\): right singular vectors (orthonormal eigenvectors of \(A^TA\)); columns of \(U\): left singular vectors (eigenvectors of \(AA^T\)), with \(\mathbf u_i=\frac1{\sigma_i}A\mathbf v_i\) for \(i\le r\). Equivalent "outer-product" form:
Geometric picture: \(V^T\) rotates, \(\Sigma\) stretches along the axes, \(U\) rotates again. The unit circle is mapped to an ellipse with semi-axes \(\sigma_1,\sigma_2\).
Facts
\(\operatorname{rank}A\) = number of non-zero singular values.
\(\max_{\|\mathbf x\|=1}\|A\mathbf x\|=\sigma_1\) (the spectral norm), reached at \(\mathbf x=\mathbf v_1\).
Theorem (Eckart–Young: best low-rank approximation)
Keep the first \(k\) terms: \(A_k=\sum_{i=1}^k\sigma_i\mathbf u_i\mathbf v_i^T\). Among all matrices of rank \(\le k\), \(A_k\) is the closest to \(A\), with errors \(\|A-A_k\|_2=\sigma_{k+1}\) and \(\|A-A_k\|_F=\sqrt{\sigma_{k+1}^2+\dots+\sigma_r^2}\). Storage: \(k(m+n+1)\) numbers instead of \(mn\).
Worked examples
Example 1 (full SVD by hand). \(A=\begin{pmatrix}1 & 1\\ 0 & 1\\ 1 & 0\end{pmatrix}\). \(A^TA=\begin{pmatrix}2 & 1\\ 1 & 2\end{pmatrix}\), eigenvalues 3 and 1, so \(\sigma_1=\sqrt3\), \(\sigma_2=1\), rank 2.
\(\mathbf u_3\) completes an orthonormal basis of \(\mathbb{R}^3\): \(\frac1{\sqrt3}(1,-1,-1)\) (orthogonal to \(\mathbf u_1,\mathbf u_2\)).
Best rank-1 approximation: \(A_1=\sigma_1\mathbf u_1\mathbf v_1^T=\sqrt3\cdot\frac{1}{\sqrt6\sqrt2}(2,1,1)^T(1,1)=\begin{pmatrix}1 & 1\\ 0.5 & 0.5\\ 0.5 & 0.5\end{pmatrix}\), with error \(\|A-A_1\|_F=\sigma_2=1\). (Check: \(A-A_1\) has entries \(0,0,-0.5,0.5,0.5,-0.5\), squares sum to 1 ✓.)
Example 2 (singular values are not eigenvalues). \(A=\begin{pmatrix}3 & 0\\ 4 & 5\end{pmatrix}\): eigenvalues 3 and 5 (triangular), but \(A^TA=\begin{pmatrix}25 & 20\\ 20 & 25\end{pmatrix}\) has eigenvalues 45 and 5, so \(\sigma_1=3\sqrt5\approx6.71\), \(\sigma_2=\sqrt5\approx2.24\). Check: \(\sigma_1\sigma_2=15=|\det A|\) ✓.
Example 3 (case study: compression in Python). A 6×5 "image" (smooth pattern + small noise):
import numpy as np
i, j = np.meshgrid(np.arange(6), np.arange(5), indexing="ij")
A = np.sin(i / 2) + np.cos(j / 3) + 0.05 * ((i * j) % 3)
U, s, Vt = np.linalg.svd(A, full_matrices=False)
print("singular values:", s.round(3))
for k in (1, 2, 3):
Ak = U[:, :k] @ np.diag(s[:k]) @ Vt[:k, :]
err = np.linalg.norm(A - Ak) # Frobenius norm
print(k, "rel. error:", round(err / np.linalg.norm(A), 4),
"check:", round(np.sqrt((s[k:]**2).sum()), 4) == round(err, 4),
"storage:", k * (6 + 5 + 1), "of", 6 * 5)
One singular value dominates, so rank 1 already approximates the matrix with a relative (Frobenius) error of about 5%, using 12 numbers instead of 30. The check confirms Eckart–Young (error = \(\sqrt{\sum_{i\gt k}\sigma_i^2}\)). For such a tiny matrix, \(k=3\) already needs more storage than the matrix itself; for a real 1000×1000 image, \(k=50\) needs about 10% of the storage.
Exercises
easy
Singular values of \(\begin{pmatrix}2 & 0\\ 0 & -3\end{pmatrix}\)?
Show solution
\(A^TA=\operatorname{diag}(4,9)\): \(\sigma_1=3\), \(\sigma_2=2\) (absolute values, sorted decreasingly; the eigenvalues are 2 and \(-3\)).
easy
A 100×80 matrix has singular values \(\sigma_1,\dots,\sigma_{30}\gt0\) and the rest 0. What is its rank? How many numbers to store \(A_{10}\)?
Find the singular values of \(\begin{pmatrix}1 & 2\\ 2 & 4\end{pmatrix}\) and its rank.
Show solution
\(A^TA=\begin{pmatrix}5 & 10\\ 10 & 20\end{pmatrix}\), trace 25, det 0: eigenvalues 25, 0. \(\sigma_1=5\), \(\sigma_2=0\); rank 1. Indeed \(A=(1,2)^T(1,2)\) and \(A=5\,\mathbf u_1\mathbf v_1^T\) with \(\mathbf u_1=\mathbf v_1=\frac1{\sqrt5}(1,2)\).
medium
For \(A=\begin{pmatrix}3 & 0\\ 4 & 5\end{pmatrix}\) (Example 2) find \(\mathbf v_1,\mathbf u_1\) and the maximum of \(\|A\mathbf x\|\) over unit vectors.
Show solution
Eigenvector of \(A^TA\) for 45: \((1,1)\), so \(\mathbf v_1=\frac1{\sqrt2}(1,1)\). \(A\mathbf v_1=\frac1{\sqrt2}(3,9)\), \(\mathbf u_1=\frac{A\mathbf v_1}{3\sqrt5}=\frac1{\sqrt{10}}(1,3)\). Max \(\|A\mathbf x\|=\sigma_1=3\sqrt5\), at \(\mathbf x=\mathbf v_1\).
hard
Compute the SVD of \(A=\begin{pmatrix}3 & 2 & 2\\ 2 & 3 & -2\end{pmatrix}\) (use \(AA^T\), it is smaller).
(MC) \(A\) is 4×3 with singular values 6, 2, 0. Which is true? A) \(\operatorname{rank}A=3\); B) the best rank-1 approximation \(A_1\) satisfies \(\|A-A_1\|_F=2\); C) \(A^TA\) has eigenvalues 6, 2, 0; D) \(\|A\|_F=8\); E) none of the other answers.
Show solution
Rank 2 (A false). \(\|A-A_1\|_F=\sqrt{2^2+0^2}=2\): B. C false (36, 4, 0). D false: \(\|A\|_F=\sqrt{36+4}=\sqrt{40}\).
Self-check quiz
Common mistakes
Taking the eigenvalues of \(A\) (or of \(A^TA\)) as singular values: take square roots of the eigenvalues of \(A^TA\).
Choosing \(\mathbf u_i\) independently of \(\mathbf v_i\) (signs!): use \(\mathbf u_i=A\mathbf v_i/\sigma_i\).
Forgetting to normalize \(\mathbf v_i\) before computing \(\mathbf u_i\).
B2.10 Graphs: definitions and representations (adjacency, incidence, Laplacian)
Why it matters. Networks (web pages, social networks, roads) are graphs. Storing them as matrices lets us use linear algebra: walks via powers of \(A\), connectivity via the Laplacian, ranking via PageRank.
How it is examined. Homework 3 included graphs; lectures of 22-23 and 29 Oct (incidence and Laplacian). Expect MC on writing \(A\), \(D\), \(L\) for a small drawn graph, degrees, number of edges, and the meaning of entries of \(A^k\) or of the zero eigenvalues of \(L\).
Explanation
Definition: Graph
A graph \(G=(V,E)\) is a set of nodes (vertices) \(V\) and edges \(E\) between pairs of nodes. Undirected: an edge \(\{i,j\}\) has no direction. Directed (digraph): an edge \((i,j)\) goes from \(i\) to \(j\). Weighted: each edge carries a number \(w_{ij}\) (distance, cost, strength). We consider simple graphs: no loops, no repeated edges, unless stated. \(n=|V|\), \(m=|E|\).
Definition: Degree
Undirected: \(\deg(i)\) = number of edges at \(i\) (neighbours). Directed: out-degree = edges leaving \(i\), in-degree = edges entering \(i\).
Handshake lemma
Undirected: \(\sum_i\deg(i)=2m\) (each edge is counted at both ends). So the number of odd-degree nodes is even. Directed: \(\sum\) out-degrees \(=\sum\) in-degrees \(=m\).
Representations
Representation
What it stores
Memory
Good for
Adjacency matrix \(A\) (\(n\times n\))
\(a_{ij}=1\) if edge \(i\to j\) (or \(\{i,j\}\)), else 0; weight \(w_{ij}\) if weighted. Undirected \(\Rightarrow A\) symmetric.
\(O(n^2)\)
"is there an edge \(i\)-\(j\)?" in \(O(1)\); linear algebra
Adjacency list
for each node, the list of its neighbours
\(O(n+m)\)
sparse graphs, BFS/DFS
Sparse (coordinate / edge list, CSR)
only the non-zero entries: triples \((i,j,w_{ij})\)
\(O(m)\)
huge sparse matrices (web graph)
Incidence matrix \(B\) (\(n\times m\))
rows = nodes, columns = edges; oriented version: \(+1\) at the tail, \(-1\) at the head of each edge
\(O(nm)\)
theory: \(L=BB^T\)
Definition: Degree matrix and Laplacian
\(D=\operatorname{diag}(\deg(1),\dots,\deg(n))\); Laplacian \(L=D-A\) (undirected). Each row of \(L\) sums to 0, so \(L\mathbf 1=\mathbf 0\): 0 is always an eigenvalue. With any orientation of the edges, \(L=BB^T\), hence \(\mathbf x^TL\mathbf x=\|B^T\mathbf x\|^2=\sum_{\{i,j\}\in E}(x_i-x_j)^2\ge0\): \(L\) is positive semidefinite.
Theorems
Walks: \((A^k)_{ij}\) = number of walks of length \(k\) from \(i\) to \(j\). In particular \((A^2)_{ii}=\deg(i)\) and \(\operatorname{tr}(A^3)=6\times\) (number of triangles).
Laplacian and connectivity: the multiplicity of the eigenvalue 0 of \(L\) equals the number of connected components. So \(G\) is connected iff the second smallest eigenvalue \(\lambda_2(L)\gt0\).
\(\operatorname{tr}L=\sum\deg=2m\).
Check with the course
Some texts orient the incidence matrix with \(-1\) at the tail and \(+1\) at the head, or use the unoriented version (all \(+1\)); for the unoriented one \(BB^T=D+A\) (the "signless Laplacian"). Check which convention your teacher used on 29 Oct.
Sparse (coordinate) form of \(A\): the 10 pairs (1,2),(1,3),(2,1),(2,3),(3,1),(3,2),(3,4),(4,3),(4,5),(5,4) instead of 25 entries.
Example 2 (walks). \((A^2)_{14}=1\): the walk 1-3-4. \((A^2)_{33}=3=\deg(3)\). \(\operatorname{tr}(A^3)=6\), so \(G_1\) has \(6/6=1\) triangle (1-2-3) ✓.
Example 3 (components). \(L\) of \(G_1\) has eigenvalue 0 with multiplicity 1 (connected). For the graph with edges 1-2, 2-3, 4-5 (two pieces), the eigenvalues of \(L\) are \(0,0,1,2,3\): two zeros, two components.
Exercises
easy
Can an undirected simple graph have degree sequence \((3,2,2,1,1)\)? And \((3,3,2,2,2)\): how many edges?
Show solution
First: sum 9 is odd, impossible (handshake). Second: sum 12, so \(m=6\) (and such a graph exists, e.g. a 5-cycle plus one chord).
easy
Directed graph: \(1\to2\), \(1\to3\), \(2\to3\), \(3\to1\). Write the adjacency matrix and in/out-degrees.
Show solution
\(A=\begin{pmatrix}0 & 1 & 1\\ 0 & 0 & 1\\ 1 & 0 & 0\end{pmatrix}\) (row = from, column = to). Out-degrees (row sums) 2, 1, 1; in-degrees (column sums) 1, 1, 2. Both sum to \(m=4\). Not symmetric.
medium
Write \(L\) for the path 1-2-3 and check that its eigenvalues are 0, 1, 3. What eigenvector goes with 0?
In \(G_1\), how many walks of length 3 go from node 1 to node 4? List them. How many closed walks of length 3 start at node 1?
Show solution
\((A^3)_{14}=1\): the only one is 1-2-3-4. Reason: a walk 1-a-b-4 needs b a neighbour of 4 (b = 3 or 5) and a a common neighbour of 1 and b; for b = 3 only a = 2 works, for b = 5 there is none. \((A^3)_{11}=2\): 1-2-3-1 and 1-3-2-1 (the triangle in both directions).
exam
(MC) The Laplacian of an undirected graph with 6 nodes has eigenvalues \(0,0,0,2,3,3\). Then: A) the graph is connected; B) the graph has 3 connected components and 4 edges; C) the graph has 3 connected components and 8 edges; D) the graph has 2 connected components; E) none of the other answers.
Show solution
Three zero eigenvalues: 3 components. \(\operatorname{tr}L=0+0+0+2+3+3=8=2m\Rightarrow m=4\). B. (E.g. a triangle (eigenvalues 0,3,3), one edge (0,2), and an isolated node (0).)
Self-check quiz
Common mistakes
Forgetting that for an undirected graph each edge appears twice in \(A\) (and in the sparse list), so \(m\) = (number of ones)/2.
Confusing rows and columns for directed graphs: here row = "from"; PageRank's transition matrix uses the transpose (column = "from").
Writing \(L=A-D\) (wrong sign): diagonal of \(L\) is the positive degrees.
Remember
\(\sum\deg=2m\). Adjacency matrix \(O(n^2)\), list \(O(n+m)\).
\(L=D-A=BB^T\), PSD, rows sum to 0; # zero eigenvalues = # components.
\((A^k)_{ij}\) = number of walks of length \(k\).
B2.11 Paths, distance, connected components, diameter; BFS and DFS
Why it matters. "How far apart are two people in a network?", "Is the network in one piece?", "What is the largest separation?": answered by breadth-first and depth-first search, the two basic graph algorithms.
How it is examined. The syllabus asks for "intuition and complexity". Expect MC on distances/diameter of a small drawn graph, the BFS visiting order, and the complexity \(O(n+m)\) vs \(O(n^2)\).
Explanation
Definitions
Walk: a sequence of nodes where consecutive nodes are joined by an edge. Path: a walk with no repeated nodes. Cycle: a closed path (first = last node, length \(\ge3\)). Length = number of edges.
Distance \(d(i,j)\): length of a shortest path from \(i\) to \(j\) (\(\infty\) if none). Weighted graphs: minimal total weight (computed by Dijkstra's algorithm, not BFS).
Connected: there is a path between every pair of nodes. A connected component is a maximal connected piece. (Directed: strongly connected = a directed path in both directions between every pair.)
Eccentricity of \(i\): \(\max_j d(i,j)\). Diameter: \(\max_{i,j}d(i,j)\) = largest eccentricity (\(\infty\) for a disconnected graph). Radius: smallest eccentricity.
Breadth-first search (BFS)
Start at \(s\), visit all neighbours (distance 1), then all their unvisited neighbours (distance 2), and so on, "in layers". It uses a queue (first in, first out). Because nodes are discovered layer by layer, BFS computes the distances \(d(s,\cdot)\) in an unweighted graph and a shortest-path tree.
Depth-first search (DFS)
Start at \(s\), go to an unvisited neighbour, and keep going deeper; when stuck, backtrack to the last node with unvisited neighbours. It uses a stack (last in, first out) or recursion. DFS does not give shortest distances, but finds components, cycles and orderings.
Complexity
With an adjacency list, BFS and DFS visit each node once and look at each edge once (twice if undirected): time \(O(n+m)\). With an adjacency matrix, finding the neighbours of a node costs \(O(n)\), so the time is \(O(n^2)\). For sparse graphs (\(m\ll n^2\)) lists are much better. Memory for the visited set and queue/stack: \(O(n)\).
Tip: connected components
Run BFS (or DFS) from any node: everything reached is one component. Restart from an unvisited node to find the next one. Total time still \(O(n+m)\).
Check with the course
The order in which neighbours are visited is a convention (here: increasing label). Exam answers about "the BFS order" depend on it, so read the convention in the question.
Example 1 (BFS by hand from node 1, neighbours in increasing order).
Step
Dequeue
New nodes found (distance)
Queue after
0
-
1 (0)
[1]
1
1
2 (1), 4 (1)
[2, 4]
2
2
3 (2), 5 (2)
[4, 3, 5]
3
4
- (5 already seen)
[3, 5]
4
3
-
[5]
5
5
6 (3)
[6]
6
6
-
[]
BFS order 1, 2, 4, 3, 5, 6; distances from 1: \(d=0,1,2,1,2,3\) for nodes 1..6. Nodes 7, 8 are not reached: another component.
Example 2 (DFS from node 1). 1 → 2 (first neighbour) → 3 (first unvisited of 2) → stuck, back to 2 → 5 → 4 (first unvisited of 5) → stuck, back to 5 → 6. Order: 1, 2, 3, 5, 4, 6.
Example 3 (diameter). Eccentricities in the component \(\{1,\dots,6\}\) (from BFS at each node): 3, 2, 3, 3, 2, 3. Diameter 3 (e.g. \(d(3,4)=3\), \(d(1,6)=3\)), radius 2 (centres 2 and 5). The whole graph \(H\) is disconnected, so its diameter is \(\infty\).
Example 4 (the same in Python).
from collections import deque
G = {1: [2, 4], 2: [1, 3, 5], 3: [2], 4: [1, 5],
5: [2, 4, 6], 6: [5], 7: [8], 8: [7]}
def bfs(G, s):
dist = {s: 0}
queue = deque([s])
order = []
while queue:
u = queue.popleft() # FIFO: oldest first
order.append(u)
for v in G[u]:
if v not in dist: # first time we see v
dist[v] = dist[u] + 1
queue.append(v)
return order, dist
def dfs(G, u, seen=None):
if seen is None:
seen = []
seen.append(u)
for v in G[u]:
if v not in seen:
dfs(G, v, seen) # go deep first
return seen
print(bfs(G, 1))
print(dfs(G, 1))
# connected components: repeat BFS from unvisited nodes
left, comps = set(G), []
while left:
comp = set(bfs(G, min(left))[1])
comps.append(sorted(comp))
left -= comp
print(comps)
Which data structure does BFS use, and which does DFS use?
Show solution
BFS: a queue (FIFO). DFS: a stack (LIFO), often implicit through recursion.
medium
Run BFS on \(H\) from node 6 (increasing order). Give the visiting order and all distances.
Show solution
6 → 5 (1) → from 5: 2 (2), 4 (2) → from 2: 1 (3), 3 (3) → from 4: nothing new. Order 6, 5, 2, 4, 1, 3. Distances: \(d(6,5)=1\), \(d(6,2)=d(6,4)=2\), \(d(6,1)=d(6,3)=3\).
medium
A graph has \(n=10^6\) nodes and \(m=5\cdot10^6\) edges. Compare BFS cost with adjacency list vs adjacency matrix, and the memory of the two representations.
Show solution
List: time \(\approx n+m=6\cdot10^6\) steps; memory \(O(n+m)\). Matrix: time \(\approx n^2=10^{12}\) steps; memory \(10^{12}\) entries (about 1 TB even with 1 byte each). The list is the only practical choice for sparse graphs.
hard
Explain why BFS gives the correct distance in an unweighted graph but not in a weighted graph. Give a small counter-example.
Show solution
BFS discovers nodes in order of number of edges from \(s\), so the first time a node is reached is via a path with the fewest edges = the distance when all edges have length 1. With weights, fewer edges may cost more: triangle \(s\)-\(a\) weight 10, \(s\)-\(b\) weight 1, \(b\)-\(a\) weight 1. BFS reaches \(a\) directly (1 edge, cost 10), but the shortest path \(s\)-\(b\)-\(a\) costs 2. Use Dijkstra.
exam
(MC) For the undirected graph with edges 1-2, 2-3, 3-4, 4-1, 3-5 (5 nodes): A) the diameter is 3; B) BFS from 1 (increasing order) visits 1, 2, 3, 4, 5; C) \(d(1,5)=2\); D) the graph has 2 components; E) none of the other answers.
Show solution
Neighbours: 1: [2,4]; 2: [1,3]; 3: [2,4,5]; 4: [1,3]; 5: [3]. BFS from 1: 1, 2, 4, then 3 (from 2), then 5 (from 3): order 1, 2, 4, 3, 5 (B false). \(d(1,5)=3\) (1-2-3-5), C false. Eccentricities: node 1: 3; node 5: 3 (to 1); others ≤ 2. Diameter 3: A. Connected, D false.
Self-check quiz
Common mistakes
Marking a node visited when it is dequeued instead of when it is discovered: the node may then enter the queue twice.
Using DFS to compute shortest distances (it does not give them).
Saying the diameter of a disconnected graph is the largest finite distance: by definition it is \(\infty\) (or "not defined"); check the teacher's convention.
Remember
Distance = fewest edges; diameter = max distance; components via repeated BFS/DFS.
Both \(O(n+m)\) with lists, \(O(n^2)\) with a matrix.
B2.12 Case study: PageRank
Why it matters. PageRank (Google, 1998) ranks web pages by the links between them. It combines graphs (B2.10), Markov chains and eigenvectors (B2.7): the ranking is the eigenvector for eigenvalue 1 of a special matrix. It is the official graph-theory case study.
How it is examined. Likely MC or short exercises: build the transition matrix of a small directed graph, compute the PageRank vector (by solving a small system or by 1-2 steps of power iteration), explain the damping factor and dangling nodes.
Explanation
Random surfer model. A surfer is on page \(j\). With probability \(d\) (the damping factor, typically \(0.85\)) she clicks one of the out-links of \(j\), chosen uniformly; with probability \(1-d\) she gets bored and teleports to a random page (each with probability \(1/n\)). The PageRank \(r_i\) of page \(i\) is the long-run fraction of time she spends on \(i\).
Definition: Transition (link) matrix
\(M\) is \(n\times n\) with \(M_{ij}=\frac{1}{\text{outdeg}(j)}\) if \(j\to i\), else 0. Column \(j\) describes where you go from \(j\); every column sums to 1 (column-stochastic). Note: \(M=(\text{row-normalized }A)^T\), where \(A\) is the adjacency matrix with row = from.
Equivalently (using \(\sum r_i=1\)): \(\mathbf r=d\,M\mathbf r+\frac{1-d}{n}\mathbf 1\), i.e. \((I-dM)\mathbf r=\frac{1-d}{n}\mathbf 1\): a linear system.
Why it works (Perron–Frobenius)
For \(0\lt d\lt1\), \(G\) has all entries positive and columns summing to 1. Then 1 is an eigenvalue, it is the largest in absolute value, its eigenvector can be chosen positive and is unique once normalized to sum 1. All other eigenvalues satisfy \(|\lambda|\le d\), so power iteration \(\mathbf r^{(k+1)}=G\mathbf r^{(k)}\), starting from \(\mathbf r^{(0)}=(\frac1n,\dots,\frac1n)\), converges, with error shrinking roughly like \(d^k\).
Why damping? Without it (\(d=1\)) the surfer can get trapped: in a cycle the iteration may oscillate forever; in a group of pages with no exit the rank of all other pages goes to 0; and the stationary vector may not be unique if the graph is not strongly connected. Teleportation makes \(G\) positive and fixes all of this.
Dangling nodes (pages with no out-links) give a zero column in \(M\) (not stochastic). Standard fix: replace that column by \(\frac1n\mathbf 1\) (from a dead end, jump anywhere).
Check with the course
Some notes use the row-vector convention \(\boldsymbol\pi=\boldsymbol\pi P\) with a row-stochastic \(P=M^T\); the numbers are the same. Some write \(\alpha\) or \(p\) instead of \(d\), and some call \(1-d\) the teleport probability. Check which symbol and which matrix orientation the slides use.
Worked examples
Web of 3 pages: \(1\to2,\ 1\to3,\ 2\to3,\ 3\to1\).
Example 1 (transition matrix and PageRank without damping). Out-degrees 2, 1, 1.
Damping breaks the tie: page 3 (two in-links) is now first. Values checked with numpy (100 iterations) and sympy (exact solve).
Example 3 (a page nobody links to). Add page 4 with links \(4\to1\), \(4\to3\). No page links to 4, so its rank is only the teleport part: \(r_4=\frac{1-d}{n}=\frac{0.15}{4}=0.0375\) (checked). Without damping, \(r_4=0\).
Exercises
easy
Why does every column of \(M\) sum to 1? What goes wrong for a page with no out-links?
Show solution
Column \(j\) lists the probabilities of moving from \(j\) to each page: \(\text{outdeg}(j)\) entries equal to \(1/\text{outdeg}(j)\), total 1. A dangling page has a zero column (probability "leaks"); fix it with the column \(\frac1n\mathbf 1\).
easy
For the 3-cycle \(1\to2\to3\to1\), find the PageRank for any \(d\). Does power iteration without damping converge from \(\mathbf r^{(0)}=(1,0,0)\)?
Show solution
By symmetry \(\mathbf r=(\frac13,\frac13,\frac13)\) for every \(d\). Without damping from \((1,0,0)\): \((0,1,0),(0,0,1),(1,0,0),\dots\) oscillates forever (eigenvalues of \(M\) are the cube roots of 1, all with \(|\lambda|=1\)). With \(d\lt1\) it converges.
medium
Graph: \(1\to2,\ 1\to3,\ 2\to1,\ 3\to1,\ 3\to2\). Find \(M\) and the PageRank without damping.
For the graph of Example 1 with \(d=\frac12\), find the exact PageRank by solving \(\mathbf r=\frac12M\mathbf r+\frac16\mathbf 1\).
Show solution
Equations: \(r_1=\frac12r_3+\frac16\); \(r_2=\frac14r_1+\frac16\); \(r_3=\frac14r_1+\frac12r_2+\frac16\). Substitute \(r_2\): \(r_3=\frac14r_1+\frac18r_1+\frac1{12}+\frac16=\frac38r_1+\frac14\). Then \(r_1=\frac3{16}r_1+\frac18+\frac16\Rightarrow\frac{13}{16}r_1=\frac7{24}\Rightarrow r_1=\frac{14}{39}\). \(r_2=\frac{14}{156}+\frac16=\frac{10}{39}\), \(r_3=\frac{15}{39}\). Sum \(=1\) ✓. \(\mathbf r=\frac1{39}(14,10,15)\).
hard
Show that \(G=dM+\frac{1-d}n\mathbf 1\mathbf 1^T\) is column-stochastic and that \(G\mathbf r=dM\mathbf r+\frac{1-d}n\mathbf 1\) whenever \(\sum r_i=1\). Why is the rank of a page with no in-links exactly \(\frac{1-d}n\)?
Show solution
Column sums: \(\mathbf 1^TG=d\,\mathbf 1^TM+\frac{1-d}n(\mathbf 1^T\mathbf 1)\mathbf 1^T=d\,\mathbf 1^T+(1-d)\mathbf 1^T=\mathbf 1^T\). And \(\mathbf 1\mathbf 1^T\mathbf r=\mathbf 1(\sum r_i)=\mathbf 1\). For page \(i\) with no in-links, row \(i\) of \(M\) is zero, so \(r_i=d\cdot0+\frac{1-d}n\).
exam
(MC) Graph \(1\to2\), \(2\to1\), \(3\to1\), damping \(d=0.8\), \(n=3\). The PageRank of page 3 is: A) 0; B) \(\frac13\); C) \(\frac1{15}\); D) \(\frac{4}{15}\); E) none of the other answers.
Show solution
Page 3 has no in-links: \(r_3=\frac{1-d}n=\frac{0.2}3=\frac1{15}\). C. (Full vector: \((\frac{13}{27},\frac{61}{135},\frac1{15})\), checked with sympy.)
Self-check quiz
Common mistakes
Building \(M\) with rows = from (that is the adjacency matrix normalized; PageRank in the column convention needs its transpose).
Dividing by the in-degree instead of the out-degree of the source page.
Forgetting to normalize \(\mathbf r\) to sum 1 (an eigenvector is defined only up to a factor).
Thinking more in-links always means higher rank: a link from an important page with few out-links is worth more.
Remember
\(M_{ij}=1/\text{outdeg}(j)\) if \(j\to i\); columns sum to 1.
Power iteration converges (Perron–Frobenius); no in-links \(\Rightarrow r_i=\frac{1-d}n\).
B2.13 Functions of several variables: domain, continuity, partial derivatives, gradient, differentiability, tangent plane, Hessian
Why it matters. Losses, likelihoods and utilities depend on many variables. The gradient tells how to move to increase (or decrease) them; the Hessian tells the curvature. Everything in optimization (B2.14-B2.17) is built on these objects. Lecture of 30 Oct (recorded videos).
How it is examined. Exam-critical. The mock asks about differentiability, partial derivatives and the gradient of \(f(x,y)=e^{1+x^2+y^2}\) (multiple choice with option E). Also: domains, tangent planes, Hessians as part of optimization questions.
Explanation
A function \(f:D\subseteq\mathbb{R}^n\to\mathbb{R}\) assigns a number to each point \(\mathbf x=(x_1,\dots,x_n)\) of its domain \(D\). In two variables its graph \(z=f(x,y)\) is a surface in \(\mathbb{R}^3\).
Definition: Natural domain, level sets
The natural domain is the largest set where the formula makes sense: denominators \(\neq0\), arguments of \(\sqrt{\ }\) \(\ge0\), arguments of \(\ln\) \(\gt0\). The level set (level curve in 2D) of value \(c\) is \(\{\mathbf x: f(\mathbf x)=c\}\): like contour lines on a map.
Definition: Continuity
\(f\) is continuous at \(\mathbf a\) if \(f(\mathbf x)\to f(\mathbf a)\) as \(\mathbf x\to\mathbf a\) along every path. Polynomials, \(e^{(\cdot)}\), \(\sin\), \(\cos\), and sums, products, quotients (where the denominator \(\neq0\)) and compositions of continuous functions are continuous. To show a limit does NOT exist, find two paths with different limits.
Definition: Partial derivatives and gradient
\(\dfrac{\partial f}{\partial x}(a,b)=\lim_{h\to0}\dfrac{f(a+h,b)-f(a,b)}{h}\): differentiate in \(x\) treating the other variables as constants. The gradient is the vector of all partial derivatives: \(\nabla f=\left(\frac{\partial f}{\partial x_1},\dots,\frac{\partial f}{\partial x_n}\right)\).
Definition: Differentiability
\(f\) is differentiable at \(\mathbf a\) if it is well approximated by a linear function near \(\mathbf a\):
Differentiable \(\Rightarrow\) continuous, and all partial (and directional) derivatives exist.
Sufficient condition (the one to use in the exam): if all partial derivatives exist and are continuous near \(\mathbf a\) (\(f\in C^1\)), then \(f\) is differentiable at \(\mathbf a\).
The converse is false: partial derivatives can exist at a point where \(f\) is not even continuous (Example 4).
Directional derivative along a unit vector \(\mathbf u\): \(D_{\mathbf u}f(\mathbf a)=\nabla f(\mathbf a)\cdot\mathbf u\) (if differentiable). It is maximal for \(\mathbf u=\nabla f/\|\nabla f\|\): the gradient points in the direction of steepest ascent, its length is the maximal slope, and it is perpendicular to the level curves.
Tangent plane / linearization
If \(f\) is differentiable at \((a,b)\), the tangent plane to the graph is
\[ z=f(a,b)+f_x(a,b)(x-a)+f_y(a,b)(y-b). \]
The right side \(L(x,y)\) is the linearization: \(f(x,y)\approx L(x,y)\) near \((a,b)\).
Hessian matrix
Second partial derivatives: \(f_{xx},f_{xy},f_{yx},f_{yy}\). Schwarz (Clairaut) theorem: if they are continuous (\(f\in C^2\)) then \(f_{xy}=f_{yx}\), so the Hessian \(H_f=\begin{pmatrix}f_{xx} & f_{xy}\\ f_{yx} & f_{yy}\end{pmatrix}\) is symmetric. Second-order approximation: \(f(\mathbf a+\mathbf h)\approx f(\mathbf a)+\nabla f(\mathbf a)\cdot\mathbf h+\frac12\mathbf h^TH_f(\mathbf a)\mathbf h\): the Hessian is a quadratic form (B2.8).
Worked examples
Example 1 (the mock function). \(f(x,y)=e^{1+x^2+y^2}\). Domain \(\mathbb{R}^2\). By the chain rule:
Both partials are continuous on \(\mathbb{R}^2\) (products/compositions of continuous functions), so \(f\) is differentiable everywhere. \(\nabla f(0,0)=(0,0)\); \(\nabla f(1,0)=(2e^2,0)\). Tangent plane at \((1,0)\): \(z=e^2+2e^2(x-1)\). Hessian: \(H_f=e^{1+x^2+y^2}\begin{pmatrix}2+4x^2 & 4xy\\ 4xy & 2+4y^2\end{pmatrix}\); at \((0,0)\) it is \(2e\,I\), positive definite, so \((0,0)\) is a minimum (\(f(0,0)=e\), the global minimum since \(1+x^2+y^2\ge1\)). Level curves \(f=c\) (\(c\gt e\)) are circles \(x^2+y^2=\ln c-1\), and \(\nabla f\) points radially outward, perpendicular to them ✓.
Example 2 (domain and tangent plane). \(f(x,y)=\sqrt{9-x^2-y^2}\): domain the closed disk \(x^2+y^2\le9\) (upper hemisphere of radius 3). At \((1,2)\): \(f=2\), \(f_x=\frac{-x}{\sqrt{9-x^2-y^2}}=-\frac12\), \(f_y=\frac{-y}{\sqrt{\cdots}}=-1\). Tangent plane \(z=2-\frac12(x-1)-(y-2)\). (On the boundary circle \(f\) is not differentiable: the partials blow up.)
Example 3 (directional derivative and linearization). \(f=x^2y\), at \((1,2)\), direction \((3,4)\) (unit: \(\mathbf u=(0.6,0.8)\)): \(\nabla f=(2xy,x^2)=(4,1)\), \(D_{\mathbf u}f=2.4+0.8=3.2\). Linearization of \(g=xe^y\) at \((1,0)\): \(g=1\), \(\nabla g=(e^y,xe^y)=(1,1)\), \(L=1+(x-1)+y\). \(g(1.1,-0.1)\approx L=1.0\); true value \(0.9953\).
Example 4 (partials exist, not continuous). \(f(x,y)=\frac{xy}{x^2+y^2}\) for \((x,y)\neq(0,0)\), \(f(0,0)=0\). On the axes \(f=0\), so \(f_x(0,0)=f_y(0,0)=0\). But along \(y=mx\): \(f=\frac{m}{1+m^2}\) (e.g. \(\frac25\) for \(m=2\)), not tending to 0. So \(f\) is not continuous at the origin, hence not differentiable there, even though both partial derivatives exist.
Exercises
easy
Find and sketch the domain of \(f(x,y)=\ln(x-y^2)\) and of \(g(x,y)=\frac{1}{x+y}\).
Show solution
\(f\): \(x\gt y^2\), the open region to the right of (inside) the parabola \(x=y^2\). \(g\): all of \(\mathbb{R}^2\) except the line \(y=-x\).
easy
Compute \(\nabla g\) and the Hessian of \(g(x,y)=x^2y+3y\).
For \(f(x,y)=x^2y-y^3\) at \((2,1)\): in which direction does \(f\) increase fastest, and what is that rate? Find a direction in which the rate is 0.
Show solution
\(\nabla f=(2xy,\ x^2-3y^2)=(4,1)\) at \((2,1)\). Fastest increase along \(\frac{(4,1)}{\sqrt{17}}\), rate \(\|\nabla f\|=\sqrt{17}\). Rate 0 along a unit vector perpendicular to \(\nabla f\): \(\pm\frac{(1,-4)}{\sqrt{17}}\) (tangent to the level curve).
exam
(MC, mock style) Let \(f(x,y)=e^{1+x^2+y^2}\). Which is true? A) \(f\) is not differentiable at \((0,0)\); B) \(\nabla f(1,0)=(2e^2,\ 0)\); C) \(\frac{\partial f}{\partial x}=e^{1+x^2+y^2}\); D) \(\nabla f(1,1)=(2e^3,\ 0)\); E) none of the other answers.
Show solution
\(f_x=2xe^{1+x^2+y^2}\) (C forgets the chain rule). \(\nabla f(1,0)=(2e^2,0)\) ✓: B. A false: \(f\in C^1\) so differentiable everywhere. D false: \(\nabla f(1,1)=(2e^3,2e^3)\).
Self-check quiz
Common mistakes
Forgetting the chain rule factor: \(\frac{\partial}{\partial x}e^{g}=g_x e^{g}\).
Concluding "differentiable" just because the partial derivatives exist: you need continuous partials (or the definition).
Testing a limit only along straight lines and concluding it exists: lines can agree while a curve (e.g. \(y=x^2\)) disagrees. Lines are enough only to disprove.
Writing the tangent plane without the point: \(z=f(a,b)+f_x(a,b)(x-a)+\dots\), all evaluated at \((a,b)\).
\(C^1\Rightarrow\) differentiable \(\Rightarrow\) continuous; partials alone are not enough.
Tangent plane \(z=f+f_x(x-a)+f_y(y-b)\). Hessian symmetric if \(C^2\).
B2.14 Unconstrained optimization; global extrema on closed bounded sets
Why it matters. Finding the best value of a function (minimum loss, maximum profit) is the core of data science. In two variables the method is: find critical points, classify them with the Hessian, and on a closed region also check the boundary.
How it is examined. Exam-critical. The mock has an open question: max and min of \(f(x,y)=(y-2)x^2-y^2\) on a closed rectangle. Show every step (critical points, boundary pieces, corners, comparison table) to get partial credit.
Explanation
Definitions
\(\mathbf a\) is a local maximum of \(f\) if \(f(\mathbf x)\le f(\mathbf a)\) for all \(\mathbf x\) near \(\mathbf a\); global if this holds for all \(\mathbf x\) in the domain. (Same with \(\ge\) for minima.) A critical (stationary) point is a point where \(\nabla f=\mathbf 0\). A saddle point is a critical point that is neither a local max nor a local min.
First-order condition (FOC)
If \(f\) is differentiable and has a local extremum at an interior point \(\mathbf a\), then \(\nabla f(\mathbf a)=\mathbf 0\). (Necessary, not sufficient.)
Second-order test (two variables)
Let \(\nabla f(\mathbf a)=\mathbf 0\), \(f\in C^2\), and \(\Delta=\det H_f(\mathbf a)=f_{xx}f_{yy}-f_{xy}^2\) at \(\mathbf a\).
\(\Delta\gt0\) and \(f_{xx}\gt0\): local minimum (\(H\) positive definite).
\(\Delta\gt0\) and \(f_{xx}\lt0\): local maximum (\(H\) negative definite).
\(\Delta\lt0\): saddle point (\(H\) indefinite).
\(\Delta=0\): the test is inconclusive (study \(f\) directly).
In \(n\) variables: \(H\) PD \(\Rightarrow\) local min, ND \(\Rightarrow\) local max, indefinite \(\Rightarrow\) saddle (use B2.8).
Extreme value theorem (Weierstrass)
A continuous function on a closed and bounded (compact) set \(K\) attains a global maximum and a global minimum on \(K\). They are at interior critical points, at boundary points, or at points where \(f\) is not differentiable.
Recipe for a closed rectangle \([a,b]\times[c,d]\)
Interior: solve \(\nabla f=\mathbf 0\); keep only points strictly inside. (Classifying them is optional for the global question, but often asked.)
Each of the 4 sides: substitute (e.g. \(x=a\)) to get a one-variable function, find its critical points inside the side.
The 4 corners.
Table of all candidate values: the largest is the global max, the smallest the global min.
\(f=x^4+y^2\) and \(g=x^3+y^2\) both have \(\Delta=0\) at \((0,0)\). Classify the origin for each.
Show solution
Both have \(\nabla=\mathbf 0\) and \(H(0,0)=\begin{pmatrix}0 & 0\\ 0 & 2\end{pmatrix}\): test inconclusive. \(f\ge0=f(0,0)\): global min. \(g(x,0)=x^3\) takes positive and negative values near 0: neither max nor min (saddle-like). Same Hessian, different answers.
Find the global max and min of \(f=x^2+2y^2-x\) on the closed disk \(x^2+y^2\le1\).
Show solution
Interior: \(f_x=2x-1=0\), \(f_y=4y=0\): \((\frac12,0)\), inside, \(f=-\frac14\). Boundary: \(y^2=1-x^2\), \(x\in[-1,1]\): \(h(x)=x^2+2(1-x^2)-x=2-x-x^2\). \(h'=-1-2x=0\Rightarrow x=-\frac12\): \(h=2+\frac12-\frac14=\frac94\), at \((-\frac12,\pm\frac{\sqrt3}2)\). Endpoints \(x=1\): \(h=0\) at \((1,0)\); \(x=-1\): \(h=2\) at \((-1,0)\). Compare \(-\frac14,\frac94,0,2\): global max \(\frac94\) at \((-\frac12,\pm\frac{\sqrt3}2)\), global min \(-\frac14\) at \((\frac12,0)\). (Grid check: max 2.2499, min \(-0.25\).)
exam
(Open, 6 points) Find the global max and min of \(f(x,y)=(y-2)x^2-y^2\) on \(Q=[-3,3]\times[0,4]\). Show all work.
Show solution
Interior: critical points of \(f\) are \((0,0)\), \((2,2)\), \((-2,2)\) (Example 3). \((0,0)\) lies on the side \(y=0\), not inside. \((\pm2,2)\) are inside: \(H=\begin{pmatrix}0 & \pm4\\ \pm4 & -2\end{pmatrix}\), \(\Delta=-16\lt0\): saddles, \(f=-4\).
Side \(y=0\): \(f=-2x^2\), \(x\in[-3,3]\): critical \(x=0\), \(f(0,0)=0\). Side \(y=4\): \(f=2x^2-16\): critical \(x=0\), \(f(0,4)=-16\). Sides \(x=\pm3\): \(g(y)=9(y-2)-y^2=-y^2+9y-18\), \(g'=9-2y=0\Rightarrow y=4.5\notin[0,4]\): no interior critical point; \(g\) increasing on \([0,4]\).
Compare: \(0,-4,-16,-18,2\). Global max \(2\) at \((\pm3,4)\); global min \(-18\) at \((\pm3,0)\). (Grid check agrees.)
Marking (assumption): 1 pt gradient and interior critical points; 1 pt classification/inside check; 2 pts the four sides; 1 pt corners; 1 pt correct comparison and conclusion. Bare answer: 0.
Self-check quiz
Common mistakes
Keeping critical points that lie outside the region (or on the boundary, counted as interior).
Forgetting the corners or the endpoints of each side when reducing to one variable.
From \(2x(y-2)=0\) keeping only \(x=0\): a product is zero if either factor is zero.
Concluding "global max" from a local test on an unbounded domain.
Saying "\(\Delta=0\) means saddle": it means the test gives no information.
Remember
FOC \(\nabla f=\mathbf 0\); \(\Delta=f_{xx}f_{yy}-f_{xy}^2\): \(\gt0\) min/max by sign of \(f_{xx}\); \(\lt0\) saddle; \(=0\) inconclusive.
Closed + bounded + continuous \(\Rightarrow\) global max and min exist (Weierstrass).
Rectangle: interior points + 4 sides + 4 corners, then a table.
B2.15 Constrained optimization with equality constraints: Lagrange multipliers
Why it matters. Real problems have constraints: a budget, a fixed total, a unit-norm vector. Lagrange multipliers turn "optimize \(f\) on the curve \(g=c\)" into a system of equations, and the multiplier measures how much the optimum improves if the constraint is relaxed.
How it is examined. Listed in the syllabus; it is also a tool for the boundary of a disk or ellipse in global-extremum questions. Expect an open exercise (set up the Lagrangian, solve the system, compare values) or MC on the candidate points. check the mocks for the exact style.
Explanation
Problem: maximize or minimize \(f(x,y)\) subject to \(g(x,y)=c\). Picture: walk along the constraint curve. At the best point, the level curve of \(f\) just touches the constraint curve (is tangent to it); otherwise you could still move along the curve and increase \(f\). Tangent curves have parallel normals, so \(\nabla f\) is parallel to \(\nabla g\).
Theorem (Lagrange)
Let \(f,g\in C^1\). If \((x^*,y^*)\) is a local extremum of \(f\) on \(\{g=c\}\) and \(\nabla g(x^*,y^*)\neq\mathbf 0\), then there is a number \(\lambda\) (the Lagrange multiplier) with
Equivalently, \((x^*,y^*,\lambda)\) is a critical point of the Lagrangian \(\mathcal L(x,y,\lambda)=f(x,y)-\lambda\,(g(x,y)-c)\): \(\mathcal L_x=0,\ \mathcal L_y=0,\ \mathcal L_\lambda=0\). With \(n\) variables and \(m\) constraints: one multiplier per constraint, \(\nabla f=\sum_j\lambda_j\nabla g_j\).
How to decide max or min
If the constraint set is closed and bounded (circle, ellipse, segment), Weierstrass guarantees a global max and min: evaluate \(f\) at all candidates and compare. This is the usual exam route.
Otherwise argue directly (e.g. \(f\to\infty\) along the constraint), or use the bordered Hessian (two variables, one constraint): \(\bar D=\det\begin{pmatrix}0 & g_x & g_y\\ g_x & \mathcal L_{xx} & \mathcal L_{xy}\\ g_y & \mathcal L_{xy} & \mathcal L_{yy}\end{pmatrix}\) at the point: \(\bar D\gt0\Rightarrow\) local max, \(\bar D\lt0\Rightarrow\) local min.
Meaning of \(\lambda\) (shadow price)
If \(f^*(c)\) is the optimal value as a function of the constraint level, then \(\dfrac{df^*}{dc}=\lambda\). So increasing \(c\) by a small \(\Delta c\) changes the optimum by about \(\lambda\,\Delta c\).
Check with the course
Some books write \(\mathcal L=f+\lambda(g-c)\); then \(\lambda\) has the opposite sign but the candidate points are the same. Sydsaeter uses \(f-\lambda(g-c)\), as here.
Worked examples
Example 1. Maximize \(f=xy\) subject to \(x+y=10\). \(\mathcal L=xy-\lambda(x+y-10)\).
It is a max: on the line, \(f=x(10-x)\) is a downward parabola (or: \(\bar D=\det\begin{pmatrix}0 & 1 & 1\\ 1 & 0 & 1\\ 1 & 1 & 0\end{pmatrix}=2\gt0\)). Shadow price: with \(x+y=11\) the max is \(5.5^2=30.25\approx25+\lambda\cdot1=30\) ✓.
Example 2 (compact constraint). Extremes of \(f=x+y\) on the circle \(x^2+y^2=2\). \(\nabla f=(1,1)=\lambda(2x,2y)\Rightarrow x=y=\frac1{2\lambda}\). Constraint: \(2x^2=2\Rightarrow x=\pm1\). Candidates \((1,1)\): \(f=2\); \((-1,-1)\): \(f=-2\). The circle is closed and bounded: max 2, min \(-2\).
Example 3. Extremes of \(f=x^2+y^2\) on the ellipse \(\frac{x^2}4+y^2=1\). \(2x=\lambda\frac{x}{2}\), \(2y=\lambda\,2y\). First: \(x=0\) or \(\lambda=4\). Second: \(y=0\) or \(\lambda=1\).
\(\lambda=4\) and \(\lambda=1\) together: impossible.
Max 4 at \((\pm2,0)\) (farthest points), min 1 at \((0,\pm1)\) (closest points).
Example 4 (three variables). Maximize \(xyz\) with \(x+y+z=3\), \(x,y,z\gt0\). \(yz=\lambda,\ xz=\lambda,\ xy=\lambda\). From \(yz=xz\) and \(z\gt0\): \(x=y\); similarly \(y=z\). So \(x=y=z=1\), \(f=1\), \(\lambda=1\).
Exercises
easy
Minimize \(x^2+y^2\) subject to \(x+2y=5\). Interpret geometrically.
Show solution
\(2x=\lambda\), \(2y=2\lambda\Rightarrow y=2x\). \(x+4x=5\Rightarrow x=1,\ y=2\), \(\lambda=2\), \(f=5\). It is the point of the line closest to the origin (distance \(\sqrt5\)); min because \(f\to\infty\) along the line.
medium
Find max and min of \(f=xy\) on the circle \(x^2+y^2=8\).
Show solution
\(y=2\lambda x\), \(x=2\lambda y\Rightarrow y=4\lambda^2y\). If \(y=0\) then \(x=0\), not on the circle; so \(\lambda^2=\frac14\), \(\lambda=\pm\frac12\). \(\lambda=\frac12\): \(y=x\), \(2x^2=8\), \((2,2),(-2,-2)\), \(f=4\). \(\lambda=-\frac12\): \(y=-x\): \((2,-2),(-2,2)\), \(f=-4\). Max 4, min \(-4\).
(Cobb–Douglas) Maximize \(U=\sqrt{xy}\) with budget \(2x+y=8\), \(x,y\gt0\). Give \(\lambda\) and interpret it.
Show solution
\(U_x=\frac{y}{2\sqrt{xy}}=2\lambda\), \(U_y=\frac{x}{2\sqrt{xy}}=\lambda\). Dividing: \(\frac yx=2\Rightarrow y=2x\). Budget: \(4x=8\Rightarrow x=2,\ y=4\), \(U=\sqrt8=2\sqrt2\). \(\lambda=\frac{x}{2\sqrt{xy}}=\frac2{2\sqrt8}=\frac{\sqrt2}4\approx0.354\): one more unit of budget raises the maximal utility by about 0.354. (Max: \(U\) is 0 at the ends of the budget segment.)
exam
(Open, 5 points) Find the global max and min of \(f(x,y)=x^2+y^2-2x\) on the closed disk \(x^2+y^2\le4\), using Lagrange multipliers on the boundary.
Boundary \(g=x^2+y^2=4\): \(2x-2=2\lambda x\), \(2y=2\lambda y\). Second: \(y=0\) or \(\lambda=1\). If \(\lambda=1\): first gives \(-2=0\), impossible. So \(y=0\), \(x=\pm2\): \(f(2,0)=0\), \(f(-2,0)=8\).
Compare: \(-1,0,8\). Global max 8 at \((-2,0)\), global min \(-1\) at \((1,0)\). (Check: \(f=(x-1)^2+y^2-1\): squared distance from \((1,0)\) minus 1; the farthest disk point from \((1,0)\) is \((-2,0)\) ✓.)
Marking (assumption): 1 interior point; 1 Lagrange system; 1 case analysis; 1 values; 1 conclusion with Weierstrass justification.
Self-check quiz
Common mistakes
Dividing an equation by \(x\) or \(y\) without treating \(x=0\) / \(y=0\) separately (you lose candidates, as in Example 3).
Forgetting to use the constraint itself as the third equation.
Calling every Lagrange point a maximum: compare values (compact case) or argue.
On a disk, using Lagrange only: interior critical points must be checked too.
Geometric: level curve of \(f\) tangent to the constraint.
Compact constraint: compare \(f\) at all candidates. \(\lambda=df^*/dc\).
B2.16 Inequality constraints (KKT intuition) and linear programming
Why it matters. Most real constraints are inequalities: a budget you need not spend completely, capacities, non-negativity. Linear programming (linear objective, linear inequality constraints) is the most used optimization model in industry and logistics.
How it is examined. Listed in the syllabus ("inequality constraints; linear programming"). Likely: a two-variable LP solved graphically (feasible region, vertices, optimal value), or a small problem with one inequality constraint solved by "inactive vs active" cases. check the depth with the teacher's notes.
Explanation
One inequality constraint: two cases
Problem: maximize \(f(x,y)\) subject to \(g(x,y)\le c\). At the optimum, either
the constraint is inactive (slack): \(g\lt c\), the point is interior, so \(\nabla f=\mathbf 0\) (unconstrained FOC); or
the constraint is active (binding): \(g=c\), and the point is a Lagrange point \(\nabla f=\lambda\nabla g\), where now the sign matters: \(\lambda\ge0\) (for a max with \(g\le c\), \(\nabla f\) must point "outwards", towards larger \(g\); otherwise moving inside would increase \(f\)).
Kuhn–Tucker (KKT) conditions, max \(f\) s.t. \(g_j\le c_j\)
With \(\mathcal L=f-\sum_j\lambda_j(g_j-c_j)\), at a solution (under a regularity condition):
Complementary slackness says: for each constraint, either it is active, or its multiplier is 0. If \(f\) is concave and the \(g_j\) convex, these conditions are also sufficient. For a minimum, apply them to \(-f\).
Check with the course
The syllabus says only "inequality constraints". Whether the full KKT theorem, or only the two-case reasoning, is required is NOT STATED: check. Sign conventions for \(\lambda\) differ between books (here: Sydsaeter, max with \(\le\) constraints, \(\lambda\ge0\)).
Linear programming
Definition: Linear program
Optimize a linear objective \(z=c_1x_1+\dots+c_nx_n\) subject to linear inequalities \(A\mathbf x\le\mathbf b\) and usually \(\mathbf x\ge\mathbf 0\). The feasible region (all points satisfying the constraints) is a convex polygon (polyhedron); its corners are the vertices.
Fundamental theorem of LP
If an LP has an optimal solution, then at least one optimum is at a vertex of the feasible region. Possible outcomes: a unique optimal vertex; a whole optimal edge (objective parallel to a side); unbounded (the objective improves forever in an unbounded region); infeasible (empty region). The simplex method moves from vertex to vertex; in two variables we use the graphical method.
Graphical method (2 variables)
Draw each constraint as a line and shade the allowed side (test the origin).
Find the vertices: intersect pairs of boundary lines; keep only feasible intersections.
Evaluate the objective at every vertex (or slide the level line \(c_1x+c_2y=z\) in the improving direction until it last touches the region).
If the region is unbounded, check whether the objective can grow without limit.
Inactive case: \(\nabla f=\mathbf 0\Rightarrow(2,2)\), but \(2+2=4\gt2\): not feasible. Reject.
Active case: \(x+y=2\) and \(\nabla(-f)=\lambda\nabla g\): \(-2(x-2)=\lambda,\ -2(y-2)=\lambda\Rightarrow x=y=1\), \(\lambda=2\ge0\) ✓.
Minimum at \((1,1)\), \(f=2\): the point of the half-plane closest to \((2,2)\). If instead the constraint were \(x+y\le6\), the inactive case gives \((2,2)\), feasible, \(\lambda=0\), min 0: the constraint does not matter.
Example 2 (LP, graphical). Maximize \(z=2x+3y\) s.t. \(x+y\le4\), \(x+3y\le6\), \(x,y\ge0\).
Feasible region (shaded) and the optimal level line \(2x+3y=9\) touching it at the vertex \((3,1)\). 1 unit = 40 px.
Vertices: \((0,0)\); \((4,0)\) (\(x+y=4\) with \(y=0\); check \(4\le6\) ✓); \((0,2)\) (\(x+3y=6\) with \(x=0\); check \(2\le4\) ✓); \((3,1)\) (intersection: subtract, \(2y=2\)). (\((6,0)\) and \((0,4)\) are intersections too, but infeasible.)
Vertex
(0,0)
(4,0)
(3,1)
(0,2)
\(z=2x+3y\)
0
8
9
6
Maximum \(z=9\) at \((3,1)\), confirmed with SciPy:
In Example 2, change the objective to \(z=3x+2y\). Where is the maximum?
Show solution
Values at \((0,0),(4,0),(3,1),(0,2)\): \(0,12,11,4\). Max 12 at \((4,0)\).
medium
Minimize \(z=2x+y\) s.t. \(x+y\ge3\), \(x+2y\ge4\), \(x,y\ge0\). Is the maximum finite?
Show solution
Unbounded region (above both lines). Vertices: \((0,3)\) (\(x+y=3\), \(x=0\); check \(6\ge4\) ✓), \((2,1)\) (intersection: subtract, \(y=1\)), \((4,0)\) (\(x+2y=4\), \(y=0\); check \(4\ge3\) ✓). Values \(3,5,8\): min 3 at \((0,3)\). The maximum is \(+\infty\) (take \(x\) large): unbounded.
medium
Maximize \(f=-(x^2+y^2)\) (i.e. minimize \(x^2+y^2\)) s.t. \(x+y\ge2\), using the two cases.
Show solution
Write as \(g=-x-y\le-2\). Inactive: \(\nabla f=\mathbf 0\Rightarrow(0,0)\), but \(0+0\ge2\) fails. Active: \(x+y=2\), \(\nabla f=(-2x,-2y)=\lambda(-1,-1)\Rightarrow x=y=\frac\lambda2\), so \(x=y=1\), \(\lambda=2\ge0\) ✓. Minimum of \(x^2+y^2\) is 2 at \((1,1)\).
Vertices (walk around the region): \((0,0)\); \((4,0)\) (\(6x+4y=24\), \(y=0\)); \((3,1.5)\) (\(6x+4y=24\) and \(x+2y=6\): from the second \(x=6-2y\), \(36-12y+4y=24\Rightarrow y=1.5,\ x=3\)); \((2,2)\) (\(x+2y=6\), \(y=2\)); \((1,2)\) (\(-x+y=1\), \(y=2\)); \((0,1)\). Values: \(0,20,21,18,13,4\). Max 21 at \((3,1.5)\) (checked with linprog).
exam
(MC) For the LP: maximize \(x+y\) s.t. \(x+2y\le8\), \(3x+y\le9\), \(x,y\ge0\), the optimal value is: A) 4; B) 3; C) 5; D) unbounded; E) none of the other answers.
Show solution
Vertices \((0,0),(3,0),(0,4)\) and the intersection: \(x=8-2y\Rightarrow24-6y+y=9\Rightarrow y=3,\ x=2\). Values \(0,3,4,5\): max 5 at \((2,3)\). C.
Self-check quiz
Common mistakes
Evaluating the objective at intersections of lines that are not feasible.
Forgetting the non-negativity constraints when listing vertices.
In the active case, ignoring the sign condition on \(\lambda\) (a wrong sign means that point is not a solution of the inequality problem).
Declaring an optimum on an unbounded region without checking the direction of improvement.
Remember
Inequality constraint: case inactive (\(\nabla f=\mathbf 0\), \(\lambda=0\)) or active (Lagrange, \(\lambda\ge0\)); \(\lambda(g-c)=0\).
LP optimum (if it exists) is at a vertex: list vertices, evaluate, compare.
B2.17 Case study: minimizing a loss function (least squares and gradient descent)
Why it matters. Training a model = minimizing a loss. Least squares regression is the simplest case and has a closed-form answer; gradient descent is the general-purpose method used for everything else (logistic regression, neural networks). This is the official calculus case study, and it links gradients (B2.13), Hessians and definiteness (B2.8, B2.14), and projections (B2.6).
How it is examined. Not stated precisely. Likely: write the loss and its gradient, derive/solve the normal equations for a tiny data set, do 1-2 gradient-descent steps by hand, or reason about the step size. check.
Explanation
Set-up. Data points \((x_i,y_i)\), \(i=1,\dots,m\). Model \(y\approx w_0+w_1x\). In matrix form \(\mathbf y\approx X\mathbf w\) with the design matrix \(X\) (a column of 1s, then the \(x_i\)) and \(\mathbf w=(w_0,w_1)\). The residuals are \(\mathbf e=\mathbf y-X\mathbf w\).
(Some texts use the mean \(\frac1m\sum\) or a factor \(\frac12\): same minimizer, gradient rescaled.)
Theorem (normal equations)
\(H_L=2X^TX\) is positive semidefinite (\(\mathbf v^TX^TX\mathbf v=\|X\mathbf v\|^2\ge0\)), so \(L\) is convex: every critical point is a global minimum. The FOC \(\nabla L=\mathbf 0\) gives the normal equations
\[ X^TX\,\hat{\mathbf w}=X^T\mathbf y. \]
If the columns of \(X\) are independent, \(X^TX\) is invertible (positive definite) and \(\hat{\mathbf w}=(X^TX)^{-1}X^T\mathbf y\) is the unique minimizer. Geometry: \(X\hat{\mathbf w}\) is the orthogonal projection of \(\mathbf y\) onto \(\operatorname{Col}X\); the residual is orthogonal to every column (\(X^T\mathbf e=\mathbf 0\)).
Gradient descent
To minimize a differentiable \(f\), start at \(\mathbf w^{(0)}\) and repeat
with a step size (learning rate) \(\eta\gt0\): move a little in the direction of steepest descent \(-\nabla f\). Stop when \(\|\nabla f\|\) is small.
Step size for a quadratic
For \(f(\mathbf w)=\frac12\mathbf w^TA\mathbf w-\mathbf b^T\mathbf w\) with \(A\) positive definite (Hessian \(A\)), gradient descent converges to the minimizer for every start iff \(0\lt\eta\lt\dfrac2{\lambda_{\max}(A)}\). Too small: slow. Too large: oscillates and diverges. The speed depends on \(\lambda_{\max}/\lambda_{\min}\) (the condition number): elongated level sets mean zig-zagging.
Worked examples
Example 1 (least squares by hand). Points \((0,1),(1,3),(2,4),(3,4)\).
Normal equations \(4w_0+6w_1=12\), \(6w_0+14w_1=23\). From the first, \(w_0=3-1.5w_1\); then \(18-9w_1+14w_1=23\Rightarrow w_1=1\), \(w_0=1.5\). Line \(\hat y=1.5+x\). Residuals \((-0.5,\ 0.5,\ 0.5,\ -0.5)\), minimal loss \(L=1\). Check \(X^T\mathbf e=(0,\ 0+0.5+1-1.5)=(0,0)\) ✓.
Example 2 (gradient descent by hand). \(f(x,y)=x^2+3y^2\), \(\nabla f=(2x,6y)\), \(\eta=0.1\), start \((1,1)\). Update: \(x\leftarrow x-0.2x=0.8x\), \(y\leftarrow y-0.6y=0.4y\).
\((1,1)\to(0.8,\ 0.4)\to(0.64,\ 0.16)\to(0.512,\ 0.064)\to\cdots\to(0,0)\). Hessian \(\operatorname{diag}(2,6)\), so convergence needs \(\eta\lt\frac26=\frac13\). With \(\eta=0.4\): \(y\leftarrow(1-2.4)y=-1.4y\), which blows up.
Example 3 (both methods in Python, data of Example 1).
import numpy as np
X = np.array([[1, 0], [1, 1], [1, 2], [1, 3]], float) # column of 1s, then x
y = np.array([1, 3, 4, 4], float)
# 1) closed form: normal equations X^T X w = X^T y
w_exact = np.linalg.solve(X.T @ X, X.T @ y)
print("normal equations:", w_exact)
# 2) gradient descent on L(w) = ||Xw - y||^2, grad = 2 X^T (Xw - y)
w, eta = np.zeros(2), 0.02
for k in range(1, 2001):
w = w - eta * 2 * X.T @ (X @ w - y)
if k in (1, 10, 100, 2000):
print(k, w.round(4), "loss", round(float(np.sum((X @ w - y) ** 2)), 4))
print("eigenvalues of Hessian 2X^TX:", np.linalg.eigvalsh(2 * X.T @ X).round(2))
normal equations: [1.5 1. ]
1 [0.48 0.92] loss 6.2304
10 [0.9804 1.2433] loss 1.3916
100 [1.4935 1.003 ] loss 1.0001
2000 [1.5 1. ] loss 1.0
eigenvalues of Hessian 2X^TX: [ 2.38 33.62]
Gradient descent reaches the same \(\hat{\mathbf w}=(1.5,1)\). Here \(\eta=0.02\lt2/33.62\approx0.059\); try \(\eta=0.07\) and it diverges.
Exercises
easy
Write the loss \(L(w_0,w_1)\) and the design matrix for the points \((0,1),(1,2),(2,4)\).
Solve the least squares problem of Exercise 1 and give the minimal loss.
Show solution
\(X^TX=\begin{pmatrix}3 & 3\\ 3 & 5\end{pmatrix}\), \(X^T\mathbf y=(7,10)\). \(3w_0+3w_1=7\), \(3w_0+5w_1=10\Rightarrow2w_1=3\), \(w_1=\frac32\); then \(3w_0=7-3\cdot\frac32=\frac52\), so \(w_0=\frac56\). Fitted values \(\frac56,\frac73,\frac{23}6\); residuals \(\frac16,-\frac13,\frac16\); loss \(\frac1{36}+\frac19+\frac1{36}=\frac16\).
medium
Do two gradient descent steps for \(f(x,y)=(x-1)^2+2y^2\) from \((0,1)\) with \(\eta=0.25\). Which \(\eta\) guarantee convergence?
Show solution
\(\nabla f=(2(x-1),4y)\). Step 1: \(\nabla f(0,1)=(-2,4)\), new point \((0,1)-0.25(-2,4)=(0.5,0)\). Step 2: \(\nabla f(0.5,0)=(-1,0)\), new point \((0.75,0)\). Hessian \(\operatorname{diag}(2,4)\): converges iff \(0\lt\eta\lt\frac24=0.5\).
hard
Show that \(\nabla\|X\mathbf w-\mathbf y\|^2=2X^T(X\mathbf w-\mathbf y)\) and explain why \(X^TX\) is invertible when the columns of \(X\) are independent.
Show solution
Expand: \(L=\mathbf w^TX^TX\mathbf w-2\mathbf y^TX\mathbf w+\mathbf y^T\mathbf y\). Using \(\nabla(\mathbf w^TA\mathbf w)=2A\mathbf w\) for symmetric \(A\) and \(\nabla(\mathbf c^T\mathbf w)=\mathbf c\): \(\nabla L=2X^TX\mathbf w-2X^T\mathbf y\). If \(X^TX\mathbf v=\mathbf 0\) then \(\mathbf v^TX^TX\mathbf v=\|X\mathbf v\|^2=0\Rightarrow X\mathbf v=\mathbf 0\Rightarrow\mathbf v=\mathbf 0\) (independent columns). So \(\operatorname{Nul}(X^TX)=\{\mathbf 0\}\): invertible.
exam
(MC) Gradient descent with constant step \(\eta\) is applied to \(f(x,y)=2x^2+5y^2\). It converges from every starting point iff: A) \(\eta\lt1\); B) \(\eta\lt0.2\); C) \(\eta\lt0.5\); D) \(\eta\lt0.4\); E) none of the other answers.
Show solution
Hessian \(\operatorname{diag}(4,10)\), \(\lambda_{\max}=10\): need \(0\lt\eta\lt\frac2{10}=0.2\). (Directly: \(y\leftarrow(1-10\eta)y\) needs \(|1-10\eta|\lt1\).) B.
Self-check quiz
Common mistakes
Forgetting the column of 1s (intercept) in the design matrix.
Moving in the direction \(+\nabla f\) (that is ascent).
Believing a smaller loss after one step proves convergence: check the step-size condition.
Computing \((X^TX)^{-1}\) when \(X\) has dependent columns (it does not exist; the minimizer is not unique).
Why it matters. Many processes evolve in steps: a population each year, a price each period, an algorithm each iteration (gradient descent and PageRank are discrete dynamical systems). The questions are always: where does it settle (fixed points) and does it settle (stability)? The official case study is an ecological model (the course uses the Ricker model).
How it is examined. The course materials include exercises with solutions on dynamical systems. Expect: find fixed points, classify their stability with \(|f'(x^*)|\), draw/read a cobweb, analyse the logistic or Ricker map for given parameters (MC or short open questions).
Explanation
Definitions
A (first-order, autonomous) discrete dynamical system is \(x_{t+1}=f(x_t)\), \(t=0,1,2,\dots\), with initial value \(x_0\). The sequence \(x_0,x_1=f(x_0),x_2=f(f(x_0)),\dots\) is the orbit. A fixed point (equilibrium) is \(x^*\) with \(f(x^*)=x^*\): if you start there, you stay there. A 2-cycle is a pair \(p\neq q\) with \(f(p)=q\), \(f(q)=p\).
Stability
\(x^*\) is locally asymptotically stable (attracting) if orbits starting close enough to \(x^*\) converge to \(x^*\); unstable (repelling) if orbits starting arbitrarily close move away.
Theorem (derivative test)
Let \(f\) be \(C^1\) and \(f(x^*)=x^*\).
\(|f'(x^*)|\lt1\Rightarrow x^*\) is locally asymptotically stable. If \(0\lt f'(x^*)\lt1\) the convergence is monotone; if \(-1\lt f'(x^*)\lt0\) it oscillates (alternating sides); if \(f'(x^*)=0\) it is very fast ("superstable").
\(|f'(x^*)|\gt1\Rightarrow x^*\) is unstable.
\(|f'(x^*)|=1\): the test is inconclusive.
A 2-cycle \(\{p,q\}\) is stable if \(|f'(p)\,f'(q)|\lt1\) (it is a fixed point of \(f\circ f\)).
Why? Near \(x^*\), \(f(x)\approx x^*+f'(x^*)(x-x^*)\), so the error \(e_t=x_t-x^*\) satisfies \(e_{t+1}\approx f'(x^*)\,e_t\): it is multiplied by \(f'(x^*)\) each step.
Linear (affine) case: solved exactly
\(x_{t+1}=ax_t+b\). If \(a\neq1\): \(x^*=\frac{b}{1-a}\) and \(x_t=x^*+a^t(x_0-x^*)\). Stable iff \(|a|\lt1\). (If \(a=1\): \(x_t=x_0+tb\).) In \(\mathbb{R}^n\), \(\mathbf x_{t+1}=A\mathbf x_t\) converges to \(\mathbf 0\) for every start iff all eigenvalues satisfy \(|\lambda|\lt1\) (B2.7).
Cobweb diagram. Draw \(y=f(x)\) and the diagonal \(y=x\); fixed points are where they cross. From \((x_0,0)\) go vertically to the curve (height \(x_1=f(x_0)\)), horizontally to the diagonal (now at \(x=x_1\)), vertically to the curve again, and so on. A staircase means monotone convergence/divergence, a spiral means oscillation.
Cobweb for the logistic map with \(r=2.8\), \(x_0=0.1\): the orbit 0.1, 0.252, 0.528, 0.698, 0.590, ... spirals into \(x^*=1-\frac1{2.8}\approx0.643\) because \(f'(x^*)=2-r=-0.8\in(-1,0)\).
Two population models
Logistic map
\(x_{t+1}=r\,x_t(1-x_t)\), \(0\lt r\le4\), \(x_t\in[0,1]\) (population as a fraction of capacity). Fixed points: \(x^*=0\) and \(x^*=1-\frac1r\) (positive for \(r\gt1\)). \(f'(x)=r(1-2x)\), so \(f'(0)=r\) and \(f'(1-\frac1r)=2-r\).
\(0\lt r\lt1\): 0 stable, population dies out.
\(1\lt r\lt3\): 0 unstable, \(1-\frac1r\) stable (monotone for \(r\lt2\), oscillating for \(2\lt r\lt3\)).
\(r\gt3\): \(1-\frac1r\) unstable; a stable 2-cycle appears (up to \(r=1+\sqrt6\approx3.449\)), then 4-cycles, ... (period doubling), and chaos around \(r\approx3.57\).
\(0\lt r\lt2\): \(K\) stable (monotone for \(r\lt1\), oscillating for \(1\lt r\lt2\)).
\(r\gt2\): \(K\) unstable; 2-cycle, then period doubling and chaos, as for the logistic map.
Check with the course
The Ricker model is also written \(x_{t+1}=a\,x_te^{-bx_t}\) (then \(a=e^r\), \(b=r/K\), positive fixed point \(\frac{\ln a}{b}\), stable iff \(0\lt\ln a\lt2\)). Use the form of the course notes (Salinelli–Tomarelli / DynSys notes).
Example 2 (logistic). \(r=2.5\): \(x^*=0.6\), \(f'(0.6)=2-2.5=-0.5\): stable, oscillating; from \(x_0=0.2\): \(0.4,\ 0.6,\ 0.6,\dots\) (lands exactly). \(r=3.2\): \(x^*=0.6875\), \(f'=-1.2\): unstable. The orbit from 0.2 approaches the 2-cycle \(\{0.5130,\ 0.7995\}\) (exactly \(\frac{21\mp\sqrt{21}}{32}\)), with multiplier \(f'(p)f'(q)=0.16\): stable cycle. (Simulated: after 60 steps the orbit alternates 0.7995, 0.5130.)
Example 3 (Ricker, \(K=1\)). \(r=1.5\): \(f'(1)=-0.5\): the orbit converges to 1 with oscillations. \(r=2.5\): \(f'(1)=-1.5\): unstable; simulation settles on the 2-cycle \(\{0.2896,\ 1.7104\}\): the population alternates boom and bust.
Exercises
easy
Find the fixed points of \(x_{t+1}=x_t^2\) and classify them. What happens from \(x_0=0.5\) and from \(x_0=2\)?
Show solution
\(x^2=x\Rightarrow x=0,1\). \(f'(x)=2x\): \(f'(0)=0\) stable (superstable), \(f'(1)=2\) unstable. From 0.5: 0.25, 0.0625, ... \(\to0\). From 2: 4, 16, ... \(\to\infty\).
easy
Solve \(x_{t+1}=-0.5x_t+6\) with \(x_0=0\). Describe the behaviour.
\(\sqrt{x+2}=x\Rightarrow x^2-x-2=0\Rightarrow x=2\) or \(x=-1\); \(x=-1\) is rejected (\(\sqrt{\cdot}\ge0\), and \(\sqrt{1}=1\neq-1\)). \(f'(x)=\frac1{2\sqrt{x+2}}\), \(f'(2)=\frac14\in(0,1)\): stable, monotone convergence to 2.
medium
For the logistic map, find the values of \(r\) for which the positive fixed point is stable, and say whether convergence is monotone or oscillating for \(r=1.5\) and \(r=2.8\).
Show solution
Need \(|2-r|\lt1\iff1\lt r\lt3\). \(r=1.5\): \(f'=0.5\), monotone, \(x^*=\frac13\). \(r=2.8\): \(f'=-0.8\), oscillating, \(x^*\approx0.643\) (the cobweb above).
hard
For the Ricker model \(x_{t+1}=x_te^{r(1-x_t/K)}\) prove that \(f'(K)=1-r\) and deduce the stability range. Then find the period-2 condition for the logistic map: show that the 2-cycle points satisfy \(r^2x^2-r(r+1)x+(r+1)=0\).
Logistic: \(f(f(x))=x\) is degree 4 and contains the fixed points \(0\) and \(1-\frac1r\). Factoring them out (verified with sympy): \(f(f(x))-x=-x\,(rx-r+1)\,(r^2x^2-r(r+1)x+(r+1))\). The last factor gives the 2-cycle; it has real roots iff \(r^2(r+1)^2-4r^2(r+1)\gt0\iff(r+1)(r-3)\gt0\iff r\gt3\).
exam
(MC) For \(x_{t+1}=\frac{2x_t}{1+x_t}\), \(x_t\ge0\): A) the only fixed point is 1; B) 0 is stable and 1 is unstable; C) 0 is unstable and 1 is stable; D) both fixed points are stable; E) none of the other answers.
Show solution
\(\frac{2x}{1+x}=x\Rightarrow x(2-1-x)=0\Rightarrow x=0,1\). \(f'(x)=\frac{2}{(1+x)^2}\): \(f'(0)=2\) (unstable), \(f'(1)=\frac12\) (stable). C. (This is the Beverton–Holt model.)
Self-check quiz
Common mistakes
Using the continuous-time rule (\(f'\lt0\) stable) for a discrete map: for maps the test is \(|f'(x^*)|\lt1\).
Forgetting \(x^*=0\) when solving \(f(x)=x\) (dividing by \(x\)).
Evaluating \(f'\) at \(x_0\) instead of at the fixed point.
In the cobweb, going from the curve vertically instead of horizontally to the diagonal.
Remember
Fixed point: \(f(x^*)=x^*\). Stable if \(|f'(x^*)|\lt1\), unstable if \(\gt1\); sign of \(f'\): monotone vs oscillating.
Affine: \(x_t=x^*+a^t(x_0-x^*)\).
Logistic: \(x^*=1-\frac1r\), \(f'=2-r\), stable for \(1\lt r\lt3\). Ricker: \(x^*=K\), \(f'=1-r\), stable for \(0\lt r\lt2\).
B2.19 Continuous dynamical systems and 2D linear differential equations
Why it matters. When time is continuous, change is described by derivatives: \(x'(t)=f(x)\). Populations, epidemics, chemical reactions and gradient flows are all of this type. For two interacting quantities, the eigenvalues of a 2×2 matrix decide the whole qualitative picture.
How it is examined. The course materials include exercises with solutions on 2D differential equations. Expect: find equilibria, classify them (node / saddle / spiral / center, stable or unstable) from the eigenvalues or from trace and determinant, solve \(\mathbf x'=A\mathbf x\) with an initial condition, and read a phase portrait.
Explanation
One dimension
\(x'=ax\) has the solution \(x(t)=x_0e^{at}\): it decays to 0 if \(a\lt0\), grows if \(a\gt0\). For a general autonomous equation \(x'=f(x)\): an equilibrium is \(x^*\) with \(f(x^*)=0\) (constant solution).
Stability in 1D (compare with B2.18!)
\(f'(x^*)\lt0\Rightarrow x^*\) asymptotically stable; \(f'(x^*)\gt0\Rightarrow\) unstable; \(f'(x^*)=0\): inconclusive. The phase line: mark equilibria on the \(x\)-axis and the sign of \(f\) between them (arrow right if \(f\gt0\), left if \(f\lt0\)).
Logistic equation \(x'=rx(1-\frac xK)\), \(r,K\gt0\): equilibria 0 and \(K\); \(f'(x)=r(1-\frac{2x}K)\): \(f'(0)=r\gt0\) unstable, \(f'(K)=-r\lt0\) stable. Every positive population tends to the carrying capacity \(K\) (no oscillations or chaos in continuous 1D, unlike the logistic map).
Two dimensions: \(\mathbf x'=A\mathbf x\)
Here \(\mathbf x(t)=(x(t),y(t))\) and \(A\) is a constant 2×2 matrix. The origin is an equilibrium (the only one if \(\det A\neq0\)).
Solution by eigenvectors
If \(A\mathbf v=\lambda\mathbf v\), then \(\mathbf x(t)=e^{\lambda t}\mathbf v\) is a solution (check: \(\mathbf x'=\lambda e^{\lambda t}\mathbf v=A\mathbf x\)). With two real distinct eigenvalues, the general solution is
with \(c_1,c_2\) fixed by \(\mathbf x(0)=c_1\mathbf v_1+c_2\mathbf v_2\). Complex eigenvalues \(\alpha\pm i\beta\) give solutions \(e^{\alpha t}(\cos\beta t,\ \sin\beta t\) combinations): rotation with amplitude \(e^{\alpha t}\).
Eigenvalues
Type of the origin
Stability
real, both \(\lt0\)
node (sink)
asymptotically stable
real, both \(\gt0\)
node (source)
unstable
real, opposite signs
saddle
unstable
\(\alpha\pm i\beta\), \(\alpha\lt0\)
spiral (focus)
asymptotically stable
\(\alpha\pm i\beta\), \(\alpha\gt0\)
spiral (focus)
unstable
\(\pm i\beta\) (\(\alpha=0\))
center (closed orbits)
stable, not asymptotically
Trace–determinant shortcut
With \(\tau=\operatorname{tr}A\), \(\Delta=\det A\): eigenvalues solve \(\lambda^2-\tau\lambda+\Delta=0\). \(\Delta\lt0\): saddle. \(\Delta\gt0\) and \(\tau^2-4\Delta\ge0\): node; \(\tau^2-4\Delta\lt0\): spiral (\(\tau=0\): center). For \(\Delta\gt0\): stable iff \(\tau\lt0\). Rule: asymptotically stable \(\iff\tau\lt0\) and \(\Delta\gt0\) \(\iff\) all eigenvalues have negative real part.
Nonlinear systems: linearization
For \(x'=F(x,y)\), \(y'=G(x,y)\): find equilibria (\(F=G=0\)), compute the Jacobian \(J=\begin{pmatrix}F_x & F_y\\ G_x & G_y\end{pmatrix}\) at each, and classify with the table. Valid when no eigenvalue has zero real part (a "center" of the linearization is inconclusive for the nonlinear system).
Check with the course
Names vary: "focus" = "spiral", "sink/source" = "stable/unstable node"; some notes also distinguish degenerate (repeated eigenvalue) and star nodes. Follow the DynSys notes' vocabulary.
Worked examples
Left: \(A=\begin{pmatrix}1 & 2\\ 2 & 1\end{pmatrix}\), trajectories come in along \((1,-1)\) and leave along \((1,1)\) (dashed eigenvector lines). Right: \(A=\begin{pmatrix}-1 & -2\\ 2 & -1\end{pmatrix}\), trajectories spiral counter-clockwise into the origin. Drawn from the exact solutions.
Example 1 (saddle, with initial condition). \(x'=x+2y,\ y'=2x+y\), \(\mathbf x(0)=(3,1)\). \(A=\begin{pmatrix}1 & 2\\ 2 & 1\end{pmatrix}\): \(\tau=2\), \(\Delta=-3\lt0\): saddle. Eigenvalues 3 (\(\mathbf v_1=(1,1)\)) and \(-1\) (\(\mathbf v_2=(1,-1)\)). \((3,1)=c_1(1,1)+c_2(1,-1)\Rightarrow c_1=2,\ c_2=1\):
Find and classify all equilibria of \(x'=x(3-x-y)\), \(y'=y(2-y)\) (competition-type model).
Show solution
Equilibria: \(y=0\) or \(y=2\); \(x=0\) or \(x=3-y\). So \((0,0),(3,0),(0,2),(1,2)\). Jacobian \(J=\begin{pmatrix}3-2x-y & -x\\ 0 & 2-2y\end{pmatrix}\) (triangular: eigenvalues on the diagonal).
\((0,0)\): \(3,2\): unstable node.
\((3,0)\): \(-3,2\): saddle.
\((0,2)\): \(1,-2\): saddle.
\((1,2)\): \(-1,-2\): stable node; populations with \(x_0,y_0\gt0\) tend to \((1,2)\).
(All checked with sympy.)
exam
(MC) For \(\mathbf x'=A\mathbf x\) with \(\operatorname{tr}A=-2\) and \(\det A=5\), the origin is: A) a saddle; B) an unstable spiral; C) a stable node; D) a stable spiral; E) none of the other answers.
Show solution
\(\Delta=5\gt0\), \(\tau^2-4\Delta=4-20\lt0\): spiral; \(\tau\lt0\): stable. D (\(\lambda=-1\pm2i\)).
Self-check quiz
Common mistakes
Mixing the discrete and continuous criteria: continuous needs \(\operatorname{Re}\lambda\lt0\); discrete needs \(|\lambda|\lt1\).
Calling a saddle "semi-stable": it is unstable (almost all trajectories leave).
Forgetting to use the initial condition to find \(c_1,c_2\).
Concluding "center" for a nonlinear system from a linearization with \(\pm i\beta\) (inconclusive).
Remember
1D: equilibrium \(f(x^*)=0\); stable if \(f'(x^*)\lt0\).
One timed mock per module. Multiple-choice questions are graded automatically; for open questions you compare your work with the model solution and score yourself strictly (exam rule: no credit for unsupported answers). The timer submits automatically when it reaches zero.
About durations and points
Official durations: R 30 min, Python 30 min, Data Management 60 min, Mathematics 90 min (from the course mock). The Statistical Theory duration is not statedcheck: its mock assumes 120 min, change it in the box before starting. Point weights inside the Coding modules are assumptions.
Quick reference
Cheat sheets
Formulas, operators and syntax for revision. Notation may differ slightly from the teachers' slides; when in doubt, follow the slides.
Data Management: SQL & MongoDB
Relational vocabulary
Term
Meaning
Relation / tuple / attribute
table / row / column
Degree / cardinality
# columns / # rows
Schema / instance
structure (stable) / current rows
Superkey
no two rows agree on it
Key (candidate)
minimal superkey
Primary key
chosen key, UNIQUE + NOT NULL
Foreign key
value must exist as a key in the parent (or be NULL)
DDL
CREATE TABLE rating (
movie INTEGER REFERENCES movie(id)
ON DELETE CASCADE ON UPDATE CASCADE,
source VARCHAR(20),
score NUMERIC(3,1) NOT NULL
CHECK (score BETWEEN 0 AND 10),
votes INTEGER DEFAULT 0,
PRIMARY KEY (movie, source)
);
CREATE DOMAIN d AS NUMERIC(3,1) CHECK (VALUE >= 0);
ALTER TABLE movie ADD COLUMN plot TEXT;
ALTER TABLE movie ADD CONSTRAINT c CHECK (length > 0);
DROP TABLE genre; -- schema + data
DML & transactions
INSERT INTO movie (id, title) VALUES (9, 'Dune'), (10, 'X');
UPDATE movie SET length = length + 10 WHERE id IN (SELECT ...);
DELETE FROM rating WHERE score IS NULL; -- no WHERE = all rows
BEGIN; ...; COMMIT; -- or ROLLBACK;
FK policies on parent delete/update: NO ACTION (default, reject), RESTRICT, CASCADE, SET NULL, SET DEFAULT. Child insert with missing parent: always rejected.
ACID: Atomicity (all or nothing), Consistency (legal → legal), Isolation (as if serial), Durability (commit survives crash).
SELECT skeleton (logical order)
SELECT [DISTINCT] cols, agg(...) AS a -- 5
FROM t1 x JOIN t2 y ON y.fk = x.id -- 1
WHERE row_condition -- 2
GROUP BY cols -- 3
HAVING agg_condition -- 4
ORDER BY a DESC NULLS LAST -- 6
LIMIT 10; -- 7
BETWEEN includes ends · LIKE '%a_' (% many, _ one; case-sensitive in PG, ILIKE) · IN (..) · IS [NOT] NULL · AND before OR.
NULL rules
x = NULL → unknown; WHERE keeps only TRUE.
NOT unknown = unknown; TRUE OR unknown = TRUE; FALSE AND unknown = FALSE.
Aggregates ignore NULL, except count(*).
CHECK passes on unknown.
NOT IN (.., NULL) → never TRUE. Use NOT EXISTS.
COALESCE(x, 0) replaces NULL.
Joins
FROM movie m, rating r WHERE m.id = r.movie -- implicit
FROM movie m JOIN rating r ON m.id = r.movie -- inner
FROM movie m LEFT JOIN rating r
ON m.id = r.movie AND r.source = 'IMDb' -- filter on right table in ON!
-- anti-join
FROM person p LEFT JOIN crew c ON c.person = p.id
WHERE c.person IS NULL
-- self-join: crew c1 JOIN crew c2 ON c1.movie = c2.movie
Cartesian product: \(n \times m\) rows. LEFT keeps all left, RIGHT all right, FULL both.
Subqueries & set operators
WHERE length > (SELECT avg(length) FROM movie) -- scalar
WHERE id IN / NOT IN (SELECT movie FROM genre ...)
WHERE EXISTS (SELECT * FROM rating r WHERE r.movie = m.id) -- correlated
WHERE length >= ALL (SELECT length FROM movie) -- ALL: true if empty
WHERE year > ANY (SELECT ...) -- = ANY ≡ IN
-- "for all": NOT EXISTS ( ... NOT EXISTS ( ... ) )
q1 UNION q2 q1 UNION ALL q2 q1 INTERSECT q2 q1 EXCEPT q2
-- same # columns, by position, ORDER BY once at the end
Python
conn = psycopg2.connect(dbname="imdb", user=..., password=...)
cur = conn.cursor()
cur.execute("SELECT * FROM movie WHERE year >= %s", (2010,))
rows = cur.fetchall(); conn.commit(); conn.close()
engine = create_engine("postgresql+psycopg2://u:p@host:5432/imdb")
df = pd.read_sql(text("... WHERE y = :y"), engine, params={"y": 1})
df.to_sql("t", engine, if_exists="replace", index=False)
NoSQL theory
Family
Example
Key-value (opaque value)
Redis, DynamoDB
Document (JSON/BSON)
MongoDB, CouchDB
Column-family
Cassandra, HBase
Graph
Neo4j
CAP: with a partition choose C or A. BASE: Basically Available, Soft state, Eventual consistency. Mongo: db → collection → document (_id) → field; embed vs reference.
m <- matrix(1:6, nrow = 2) # by COLUMN
# [,1] [,2] [,3]
# [1,] 1 3 5
# [2,] 2 4 6
m[2, ]; m[, 2] # vectors (drop)
m[, 2, drop = FALSE] # 2x1 matrix
m * m; m %*% t(m) # elementwise | product
apply(m, 1, sum) # 9 12
array(1:24, dim = c(2, 3, 4))
Lists
l <- list(a = 1, b = "x", c = 1:3)
l["c"] # list
l[["c"]] # 1 2 3
l$c[2] # 2
l$zzz # NULL (no error)
l$a <- NULL # removes a
unlist(l); str(l)
Data frames & factors
df[df$age > 30, c("name", "age")]
df$age; df[["age"]]; df[, "age"] # vector
df["age"] # data frame
df[order(-df$age), ]
subset(df, age > 30, select = name)
aggregate(age ~ city, df, mean)
f <- factor(c("b", "a")); levels(f) # "a" "b"
as.numeric(as.character(f_num)) # not as.numeric(f)
Control flow
if (x > 0) "pos" else "neg" # 1 value only
ifelse(v > 0, "pos", "neg") # vectorised
& | (vectors) && || (scalars)
switch(k, a = 1, b = , c = 2, 99) # fall-through, default
any(v); all(v); which(v)
split(values, groups) # named list
Loops & apply family
Call
Returns
apply(m, 1|2, f)
vector / matrix (results as columns)
lapply(x, f)
list
sapply(x, f)
vector, matrix or list
vapply(x, f, numeric(1))
typed vector
tapply(v, g, f)
one value per group
mapply(f, x, y)
parallel map
for (i in seq_along(x)) {...}; next; break
res <- numeric(n) # pre-allocate
Functions & scope
f <- function(x, n = 2, ...) {
if (!is.numeric(x)) stop("numeric!")
x^n # last value returned
}
# lazy: unused args never evaluated
# defaults evaluated at first use
# lexical scoping: lookup where f was DEFINED
cnt <<- cnt + 1 # modify enclosing/global
make <- function(p) function(x) x^p # closure
Import / export
read.csv("f.csv", na.strings = c("", "NA"))
read.csv2("f.csv") # sep ";" dec ","
read.csv("https://...") # from web
write.csv(df, "o.csv", row.names = FALSE)
saveRDS(x, "x.rds"); x <- readRDS("x.rds")
save(a, b, file = "f.RData"); load("f.RData")
quantmod::getSymbols("AAPL") # creates AAPL
Base graphics
plot(x, y, type = "l", col = "red", pch = 19,
lty = 2, main = "", xlab = "", ylab = "")
points(); lines(); abline(h = 0, v = 1)
legend("topright", legend = ..., col = ...)
old <- par(mfrow = c(2, 2)); par(old)
hist(x, freq = FALSE); lines(density(x))
boxplot(y ~ g, data = df); barplot(table(f))
png("p.png"); plot(x); dev.off()
dplyr & tibbles
df |>
filter(a > 1, b == "x") |>
select(a, b) |>
mutate(c = a * 2) |>
group_by(b) |>
summarise(n = n(), m = mean(c, na.rm = TRUE)) |>
arrange(desc(m))
count(df, b); left_join(x, y, by = "id")
# tibble: tb[, 1] is a tibble; no partial $;
# only length-1 recycling
s = "PYTHON"
s[1:4] # 'YTH' (stop excluded)
s[-3:] # 'HON'
s[::-1] # 'NOHTYP'
s[::2] # 'PTO'
s[4:1] # '' (no error)
s[10] # IndexError
"-".join(["a", "b"]) # 'a-b'
"a b c".split() # ['a', 'b', 'c']
Lists, aliasing, copies
b = a # alias (same object)
b = a[:] # shallow copy (also a.copy())
copy.deepcopy(a)
a = a + [4] # NEW list, rebinds a
a += [4] # in place (aliases see it)
L.append([3,4]) # one element
L.extend([3,4]) # two elements
L.sort() # returns None
sorted(L) # new list
[[0]*3]*3 # 3 refs to SAME row!
[[0]*3 for _ in range(3)] # OK
(5,) # 1-element tuple
Dicts and sets
d.get(k, 0) # no KeyError
d.items() / d.keys() / d.values()
k in d # tests KEYS
{1: "a", True: "b"} # {1: 'b'}
{k: v for k, v in d.items() if v}
set() # empty set ({} is a dict)
a | b a & b a - b a ^ b
Counter(words).most_common(2)
defaultdict(list)
Functions and scope
def f(x, acc=None): # never acc=[]
if acc is None: acc = []
def g(a, *args, k=1, **kw): ...
# args: tuple, kw: dict, k keyword-only
# no return -> None
# LEGB; assignment makes a name local
global x / nonlocal x
fs = [lambda: i for i in range(3)]
# all return 2 (late binding);
# fix: lambda i=i: i
sorted(xs, key=lambda p: (-p[1], p[0]))
Classes
class Dog(Animal):
species = "dog" # class attr
def __init__(self, name):
super().__init__()
self.name = name # instance attr
def __str__(self): return ... # print
def __repr__(self): return ... # in lists
def __len__(self): return ... # len, bool
def __eq__(self, o): return ... # ==
def __del__(self): ... # last ref gone
# obj.m(x) == Class.m(obj, x)
# self.n += 1 creates an INSTANCE attr
# MRO: Class.__mro__ ; super() = next in MRO
Exceptions, files, modules
try: ...
except (KeyError, ValueError) as e: ...
else: ... # no exception
finally: ... # always (even after return)
raise ValueError("msg")
with open(p, "w", encoding="utf-8") as f:
f.write("x\n") # "a" append, "r" read
if __name__ == "__main__": ... # not on import
# int("a") ValueError "a"+1 TypeError
# [][0] IndexError {}["k"] KeyError
NumPy
a = np.array([1, 2, 3]); a.dtype, a.shape
np.arange(0, 1, .25); np.linspace(0, 1, 5)
a.reshape(3, 1); a.T; a.reshape(-1)
x.sum(axis=0) # per COLUMN
x.sum(axis=1) # per ROW
a[1:] # VIEW (shares memory)
a[a > 1] # copy; a[[0, 2]] copy
(a > 1) & (a < 5) # not and/or
np.where(c, "y", "n")
x * y # element-wise; x @ y matrix
# broadcasting: compare shapes from the
# right, sizes equal or 1: (3,1)+(4,)->(3,4)
np.isnan(v) # nan != nan
pandas: select and clean
df = pd.read_csv(p, sep=",", decimal=".")
df.head(); df.info(); df.describe()
df["c"] # Series df[["c"]] # DataFrame
df.loc[1:3, "c"] # labels, end INCLUDED
df.iloc[1:3, 0] # positions, end excluded
df[(df.a > 1) & (df.b == "x")]
df[df.b.isin(["x", "y"])]; df.query("a > 1")
df.loc[mask, "c"] = 0 # not df[mask]["c"]=0
df.isna().sum(); df.dropna(); df.fillna(v)
pd.to_numeric(s, errors="coerce")
s.str.strip().str.lower(); s.dt.year
a + b # aligns on index -> NaN
df.to_csv(p, index=False)
pandas: group, join, reshape
df.groupby("g")["v"].mean()
df.groupby("g").agg(n=("v", "size"),
tot=("v", "sum"))
df.groupby("g")["v"].transform("mean") # same len
df.groupby(["a", "b"], as_index=False).sum()
pd.merge(l, r, on="k", how="left")
# inner | left | right | outer (SQL joins)
pd.concat([d1, d2], ignore_index=True)
s.map({"a": 1}) # missing -> NaN
df.apply(f, axis=1) # per row (slow)
df.pivot_table(index="r", columns="c",
values="v", aggfunc="sum")
df.melt(id_vars="id") # wide -> long
s.value_counts(normalize=True)
# count() skips NaN; size() counts rows
MLE: solve \(\ell'(\theta)=0\), check \(\ell''\lt0\); support depends on \(\theta\) → order statistic. Invariance \(\widehat{\tau(\theta)}=\tau(\hat\theta)\).
CRB: \(\text{Var}W\ge\frac{\tau'(\theta)^2}{nI_1(\theta)}\). Attained iff \(\ell'(\theta)=a(\theta)[W-\tau(\theta)]\). Fails if support depends on \(\theta\).
\(I_1\): Bern \(\frac1{p(1-p)}\); Pois \(\frac1\lambda\); \(N\) mean \(\frac1{\sigma^2}\); \(N\) var \(\frac1{2\sigma^4}\); Exp rate \(\frac1{\lambda^2}\); Geom \(\frac1{p^2(1-p)}\).
Asymptotics
a.s. ⇒ P ⇒ d. WLLN \(\bar X\xrightarrow{P}\mu\). CLT \(\sqrt n(\bar X-\mu)/\sigma\xrightarrow{d}N(0,1)\).
NP: reject if \(f(\mathbf x\mid\theta_1)/f(\mathbf x\mid\theta_0)\gt k\). Karlin–Rubin: MLR in sufficient \(T\) ⇒ reject iff \(T\gt t_0\) is UMP for \(\theta\le\theta_0\) vs \(\theta\gt\theta_0\).
LRT \(\lambda=\frac{\sup_{\Theta_0}L}{\sup_\Theta L}\), reject if small. p-value \(=\sup_{\Theta_0}P_\theta(W\ge w_{obs})\). CI = non-rejected \(\theta_0\).
Large sample: \(-2\log\lambda\to\chi^2_\nu\); Wald \((\hat\theta-\theta_0)^2I_n(\hat\theta)\); score \(\ell'(\theta_0)^2/I_n(\theta_0)\); \(\chi^2_{1,0.05}=3.841\).