ΣExam Prep Hub

UniMi · Data Science LM · a.a. 2026/27

Exam Prep Hub

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.

ModuleTimeContent
R framework30 minClosed-form and code exercises on R scripts (lectures and labs)
Python30 minQuizzes on code fragments + open questions on data analysis (or the project)
Data Management60 minQuizzes 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

PartWeight
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.

DateCourseWhat
Mon 2 NovRR lesson 16:30–18:30, G12 (swapped with the Python lab)
Tue 3 – Wed 4 NovDMMongoDB aggregation pipeline
Thu 5 NovPython MathPython lab 16:30–18:30, G09 (data manipulation). Mathematics 10:30–12:30 and 14:30–16:30
Fri 6 NovStatStatistical Theory 10:30–12:30 and 14:30–16:30 (constructing CIs)
9, 16, 23 NovPythonTheory 14:30–16:30, labs 16:30–18:30 (scientific computing, visualisation, Streamlit)
12 – 26 NovStatHypothesis testing block: NP lemma, UMP, Karlin–Rubin, p-values, large-sample tests, classical tests
November checkPythonProject registration form opens "for a couple of weeks"; invite the instructor to your GitHub repo
Mon 30 Nov, 23:59 CETPythonPython project deadline (last GitHub commit counts; no extensions)
1, 2 or 3 DecPythonProject evaluation: 5–10 min presentation + questions on code and course topics (slot assigned after registration)
27 Nov, 3–4 DecStatMake-up classes if necessary
8 DecStatStatistical Theory project deadline: Rmarkdown that runs, only via myAriel
Wed 16 Dec, 9:30–11:30 checkStatProject presentation (looks like you booked Slot 4)
December checkCodingWritten exam: R (30 min), Python (30 min unless project), Data Management (60 min)
Not stated checkStat/MathWritten tests for Statistical Theory and Mathematics; register online
January 2027 →PythonOrdinary 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.

Week 1 · 31 Oct – 8 Novcatch-up

  • A1 Relational model, keys, DDL/DML, SELECT, joins
  • A2 R lessons 1–3: types, structures, control flow
  • B1 Catch-up to Cramér–Rao, convergence, CIs
  • B2 Linear algebra review + functions of several variables
  • Project: choose proposal, repo, README skeleton

Week 2 · 9 – 15 Novcore

  • A1 Aggregates, subqueries, set ops; MongoDB find + aggregation
  • A2 R lessons 4–5: apply family, functions, I/O, base plots
  • B1 CIs (pivotal, approximate) and testing basics
  • B2 Unconstrained optimisation, Lagrange multipliers
  • Project: data I/O and pandas cleaning (atomic commits)

Week 3 · 16 – 22 Novcore

  • A2 tidyverse, ggplot2, random numbers
  • A3 NumPy, pandas, scikit-learn (also for project questions)
  • B1 Neyman–Pearson, UMP, MLR, Karlin–Rubin, p-values
  • B2 Inequality constraints, LP, dynamical systems
  • Project: numpy/scipy analysis + plots

Week 4 · 23 – 30 Novdeadline

  • Project: notebook/Streamlit, README, final commit by 30 Nov
  • B1 LR/Wald/score tests, classical tests; start the Rmd project
  • A1 Full DM review + DM mock
  • A2 Review + R mock
  • B2 SVD, graphs, PageRank review

Week 5 · 1 – 7 Decmocks

  • Project evaluation (1–3 Dec): rehearse the 5–10 min talk
  • B1 project: finish and upload the Rmd before 8 Dec
  • Mocks: DM, R (and Python if written); fix weak spots
  • Maths mock (90 min) + review errors
  • Statistics mock + formula sheet

Week 6+ · from 8 Decexams check

  • Coding written exam prep (dates check): cheat sheets + second mock round
  • B1 presentation rehearsal (16 Dec? check)
  • Statistics: past-exam style exercises, theory questions
  • Maths: open questions with full working, MC with option E

How to use this site

1 · Learn

Read a topic, run the code yourself (R, Python, SQL, MongoDB), and try each exercise before opening its solution.

2 · Check

Answer the self-check quiz, then press Mark as done. Progress is saved in this browser only.

3 · Simulate

Take the timed mocks under exam conditions. Use the cheat sheets and glossary for revision. Press / to search.

Unit A · Coding for DS & DM · exam module: 60 min

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:

PropertyWhat it meansJS analogy
Large and persistentData larger than RAM, stored on disk, survives crashesNot a const data = [] that dies on reload
SharedMany users and programs read/write at the same time; the DBMS controls concurrencyLike many browser tabs writing to one backend safely
ReliableTransactions and recovery after failures (ACID, see A1.7)No "half-saved" state
Private / secureUsers get permissions (GRANT/REVOKE)Auth middleware, but inside the DB
EfficientIndexes and a query optimizer decide how to run your queryYou write what you want (declarative), like CSS selectors, not loops
Data independencePrograms do not depend on how data is physically storedUsing 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).

movie id, title, year, length, budget crew movie→movie, person→ person, role, character person id, name, birth_year, sex rating movie→movie, source, score, votes genre movie→movie, genre
Primary keys: movie(id), person(id), crew(movie, person, role), rating(movie, source), genre(movie, genre).

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
SELECT * FROM rating;
movie  source      score  votes
-----  ----------  -----  -------
1      IMDb        8.8    2400000
1      Metacritic  7.4    70
2      IMDb        8.7    2000000
2      Metacritic  7.3    35
3      IMDb        8.7    2100000
4      IMDb        8.9    2200000
4      Metacritic  9.5    30
5      IMDb        9      2900000
5      Metacritic  8.4    39
6      IMDb        8.4    1300000
7      IMDb        8.5    950000
7      Metacritic  9.6    50
SELECT * FROM genre;
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

  1. 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).

  2. 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.

  3. 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.

  4. 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.
  • DDL = structure (CREATE/ALTER/DROP), DML = data changes (INSERT/UPDATE/DELETE), DQL = SELECT.
  • Our tables: movie, person, crew, rating, genre.

A1.2 The relational model: schema vs instance

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 wordSQL / everyday wordExample
RelationTablemovie
AttributeColumntitle
TupleRow(1, 'Inception', 2010, 148, 160000000)
DomainType (set of allowed values)INTEGER, VARCHAR(200)
DegreeNumber of columnsmovie has degree 5
CardinalityNumber of rowsmovie 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:

titlegenres
InceptionSci-Fi, Action
InceptionSci-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

  1. easy

    Give the degree and the cardinality of person and of genre in our instance (see A1.1).

    Show solution

    person: degree 4 (id, name, birth_year, sex), cardinality 12. genre: degree 2 (movie, genre), cardinality 16.

  2. easy

    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).

  3. 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.)

  4. 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).

  5. 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).

Definition: Superkey, key (candidate key), primary key

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);
Error: UNIQUE constraint failed: crew.movie, crew.person, crew.role

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);
Error: UNIQUE constraint failed: award.name, award.year

PostgreSQL vs SQLite

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

  1. 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.

  2. 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, 'IMDb', 8.0, 10);
    Error: UNIQUE constraint failed: rating.movie, rating.source
    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
  3. 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.)

  4. 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.

  5. 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.

Explanation

-- template
CREATE TABLE table_name (
  column_name  data_type  [DEFAULT value]  [column constraints],
  ...,
  [table constraints]          -- e.g. PRIMARY KEY (a, b), UNIQUE (c), CHECK (...), FOREIGN KEY ...
);
Common PostgreSQL data types (domains)
TypeMeaningExample
INTEGER / SMALLINT / BIGINTwhole numbers (4 / 2 / 8 bytes)year INTEGER
NUMERIC(p, s) (= DECIMAL)exact number, p digits in total, s after the pointscore NUMERIC(3,1) holds 0.0 … 99.9
REAL / DOUBLE PRECISIONapproximate floating pointratio REAL
CHAR(n)fixed-length text, padded with spacessex CHAR(1)
VARCHAR(n)variable-length text up to n characterstitle VARCHAR(200)
TEXTtext of any length (PostgreSQL)plot TEXT
BOOLEANtrue / false (/ NULL)is_adult BOOLEAN DEFAULT false
DATE, TIME, TIMESTAMPdates 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

  1. 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?

    Show solution

    (a) VARCHAR(200) or TEXT; (b) CHAR(2); (c) NUMERIC(3,1) (exact; 10.0 needs 3 digits); (d) DATE; (e) BOOLEAN.

  2. medium

    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.

  3. 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.

  4. 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;
movie  source      score  votes
-----  ----------  -----  ------
7      IMDb        8.5    951000
7      Metacritic  9.6    50

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';
movie  source      score
-----  ----------  -----
4      Metacritic  9.5
5      Metacritic  8.4
7      Metacritic  9.6

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

  1. 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.

  2. 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.

  3. 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.

  4. 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).

  5. 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.

In our schema: crew.movie → movie.id, crew.person → person.id, rating.movie → movie.id, genre.movie → movie.id.

-- 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):
PolicyWhen 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).
RESTRICTAlso rejected, checked immediately (cannot be deferred). In practice the same as NO ACTION for exam purposes.
CASCADEThe change propagates: delete → the child rows are deleted too; update → the child foreign key values are updated to the new key.
SET NULLThe child foreign key columns are set to NULL.
SET DEFAULTThe 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;
crew_left  rating_left  genre_left
---------  -----------  ----------
0          0            0

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

  1. 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).

  2. 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.

  3. 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
  4. 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)
    );
  5. 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)
AnomalyExample on our data
Lost updateT1 reads votes = 100, T2 reads votes = 100, T1 writes 101, T2 writes 101. One vote is lost (should be 102).
Dirty readT1 updates a score, T2 reads the new score, then T1 rolls back: T2 used a value that never officially existed.
Non-repeatable readT1 reads Inception's score twice; between the reads T2 commits a change, so T1 sees two different values.
PhantomT1 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

  1. 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.

    Show solution

    (a) Durability. (b) Atomicity. (c) Consistency. (d) Isolation.

  2. easy

    What is the content of rating for movie 3 after this script (PostgreSQL)?

    BEGIN;
    UPDATE rating SET score = 9.9 WHERE movie = 3;
    DELETE FROM rating WHERE movie = 3;
    ROLLBACK;
    SELECT * FROM rating WHERE movie = 3;
    Show solution

    Unchanged: ROLLBACK undoes both statements.

    movie  source  score  votes
    -----  ------  -----  -------
    3      IMDb    8.7    2100000
  3. medium

    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).

  4. 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.

  5. 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).

OperatorMeaningExample
= <> != < <= > >=comparison (<> is the standard "not equal")year <> 2010
AND, OR, NOTlogic; 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 includedyear BETWEEN 1999 AND 2008
LIKE pattern% = any sequence (also empty), _ = exactly one charactertitle LIKE 'The %'
IN (v1, v2, ...)equal to one of the valuesbirth_year IN (1970, 1974)
IS NULL, IS NOT NULLtest for NULL (the only correct way)budget IS NULL
DISTINCTremove duplicate rows from the resultSELECT DISTINCT role FROM crew
ASrename a column (or table) in the resultlength / 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;
title         budget
------------  --------
Pulp Fiction  8000000
Memento       9000000
Parasite      11400000

PostgreSQL vs SQLite

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

  1. 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
  2. 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).

  3. 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.

  4. 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;
    title         budget_musd
    ------------  -----------
    Interstellar  165.0
    Inception     160.0
    Parasite      11.4
    Memento       9.0
    Pulp Fiction  8.0

    (PostgreSQL prints 165.0, 11.4 etc. the same way here because the type is numeric.)

  5. 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

  1. Which columns do I output? Which tables hold them?
  2. Which tables do I need for the conditions?
  3. Connect all those tables through the foreign keys (draw the path: movie ← crew → person).
  4. 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

  1. 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
  2. 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
  3. 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.

  4. 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.

  5. 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.
  • Join = product + condition (FK = PK). JOIN ... ON = INNER JOIN: unmatched rows vanish.
  • 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.
A LEFT JOIN BA RIGHT JOIN BA FULL JOIN B ABABAB
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;
title     movie_title  oscar_year
--------  -----------  ----------
Parasite  Parasite     2020
Tenet
          Oppenheimer  2024

(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

  1. 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
  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.

  3. 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.

  4. 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;
    title         metacritic
    ------------  ----------
    Inception     7.4
    Interstellar  0
    Parasite      9.6
    Tenet         0

    Here the condition on m.year is on the left table, so WHERE is correct.

  5. 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

FunctionResultNULLs
count(*)number of rowscounts every row
count(col)number of non-NULL valuesignored
count(DISTINCT col)number of different non-NULL valuesignored
sum(col), avg(col)sum, meanignored (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;
avg_ignoring_null  sum_over_all_rows
-----------------  -----------------
85914285.7142857   75175000

GROUP BY one column:

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;
genre   avg_len
------  ----------------
Action  150.0
Crime   152.0
Drama   150.5
Sci-Fi  155.666666666667

count(DISTINCT ...):

SELECT count(DISTINCT person) AS n_people_in_crew, count(person) AS n_rows FROM crew;
n_people_in_crew  n_rows
----------------  ------
11                19

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

  1. 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
  2. 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;
    decade  n  avg_len
    ------  -  -------
    1990    2  145.0
    2000    2  132.5
    2010    3  149.7
    2020    1  150.0

    Integer division: 2014 / 10 * 10 = 2010. (PostgreSQL also allows GROUP BY decade, using the alias.)

  3. 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.

  4. 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;
    genre     avg_imdb
    --------  --------
    Crime     8.95
    Action    8.83
    Sci-Fi    8.73
    Drama     8.7
    Thriller  8.45

    Mystery has only Memento, so HAVING removes it.

  5. exam

    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:

FormMeaning
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 tSubquery 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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

OperatorResultLike (sets)
q1 UNION q2rows in q1 or q2, duplicates removed\(A \cup B\)
q1 UNION ALL q2all rows of both, duplicates kept (faster)concatenation
q1 INTERSECT q2rows in both\(A \cap B\)
q1 EXCEPT q2rows 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

  1. 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
  2. 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.

  3. 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).

  4. 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;
    label
    -----------------
    Meryl Streep
    Pulp Fiction
    Quentin Tarantino
  5. exam

    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

  1. 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).

  2. 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:).

  3. 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.

  4. 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,).
  • Mixing placeholder styles: %s (psycopg2), ? (sqlite3), :name (SQLAlchemy text).
  • Loading a whole table into pandas and filtering in Python when a WHERE/GROUP BY in SQL would do it.

Remember

  • Driver (psycopg2) → connection → cursor → execute(sql, params) → fetch → commit → close.
  • Parameters, never string formatting.
  • pd.read_sql(sql, engine_or_conn, params=...) → DataFrame; df.to_sql(name, engine) → table.
  • Streamlit: Python script + widgets + query + st.dataframe.

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
ModelData looks likeGood forExamples
Key-valuea huge hash map: key → opaque value (the DB does not look inside)caching, sessions, shopping carts; very fast get/put by keyRedis, Amazon DynamoDB, Riak
Documentkey → a structured document (JSON/BSON) with nested objects and arrays; the DB can query inside itcatalogues, content, user profiles, our movies with embedded cast and ratingsMongoDB, CouchDB
Column-family (wide-column)rows with a key and many columns grouped in families; each row can have different columns; stored by column familyhuge write-heavy data, time series, analytics at scaleApache Cassandra, HBase, Google Bigtable
Graphnodes and edges, both with propertiesrelationships: 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)
Structuretables, fixed schema defined first (schema-on-write)collections of documents; flexible schema, documents in one collection can differ (often called "schemaless")
Valuesatomic values only (1NF)nested objects and arrays allowed
Relationshipsnormalised tables + foreign keys + joinsusually embedding (denormalised); references possible ($lookup exists but is used less)
Query languageSQL (standard)product-specific API (MongoDB query language, aggregation pipeline)
Scalingtraditionally vertical (bigger server)horizontal: sharding (split data across servers) + replication (copies)
ConsistencyACID transactionsatomic per document; often eventual consistency across replicas (BASE). MongoDB also offers multi-document transactions since version 4.0.
Integrityconstraints 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.

The document model (MongoDB)
  • MongoDB server → databases → collections (≈ tables) → documents (≈ rows) → fields (≈ columns).
  • 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):

db.movies.findOne({ _id: 1 })
{
  _id: 1,
  title: 'Inception',
  year: 2010,
  length: 148,
  budget: 160000000,
  genres: [ 'Sci-Fi', 'Action' ],
  director: 'Christopher Nolan',
  cast: [ { name: 'Leonardo DiCaprio', character: 'Cobb' } ],
  ratings: [
    { source: 'IMDb', score: 8.8, votes: 2400000 },
    { source: 'Metacritic', score: 7.4, votes: 70 }
  ]
}

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.

db.movies.findOne({ title: "Tenet" })
{
  _id: 8,
  title: 'Tenet',
  year: '2020',
  length: 150,
  budget: null,
  genres: [ 'Action', 'Sci-Fi' ],
  director: 'Christopher Nolan',
  cast: []
}

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

  1. 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.

    Show solution

    (a) key-value; (b) graph; (c) document; (d) column-family (wide-column, e.g. Cassandra).

  2. easy

    Map the terms: table, row, column, primary key, join → MongoDB.

    Show solution

    table → collection; row → document; column → field; primary key → _id; join → embedding (or references + $lookup).

  3. medium

    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.

    // template
    {
      _id: 10,
      name: "Carrie-Anne Moss",
      birth_year: 1967,
      sex: "F",
      credits: [
        { movie_id: 2, title: "The Matrix", role: "actor", character: "Trinity" },
        { movie_id: 6, title: "Memento",    role: "actor", character: "Natalie" }
      ]
    }

    The title is duplicated from the movie document (denormalisation) to avoid a second query when listing credits.

  4. 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.

  5. 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).
  • NoSQL: flexible schema, nesting/denormalisation, horizontal scaling (sharding, replication).
  • 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.
OperatorMeaningExampleSQL
$eqequal (usually implicit){ year: { $eq: 2010 } } = { year: 2010 }=
$nenot equal (also matches docs without the field){ director: { $ne: "Christopher Nolan" } }<>
$gt $gte $lt $lte>, ≥, <, ≤{ year: { $gte: 1995, $lte: 2005 } }BETWEEN
$in, $ninvalue in / not in a list ($nin also matches docs without the field){ genres: { $in: ["Crime", "Mystery"] } }IN, NOT IN
$andall conditions true{ $and: [ {...}, {...} ] }AND
$orat least one true{ $or: [ { year: { $lt: 1995 } }, { length: { $gte: 160 } } ] }OR
$notnegates an operator expression of one field (also matches docs without the field){ year: { $not: { $gte: 2000 } } }NOT
$existsfield present (true) / absent (false){ ratings: { $exists: false } }≈ IS NULL
$typefield has a BSON type{ year: { $type: "string" } }-
$sizearray has exactly n elements{ cast: { $size: 2 } }-
$elemMatchone array element satisfies all the conditions{ ratings: { $elemMatch: { source: "Metacritic", score: { $gte: 9 } } } }≈ EXISTS on a child table

Arrays and embedded documents

  • { 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.

JS analogy

{ ratings: { $elemMatch: { source: "Metacritic", score: { $gte: 9 } } } } ≈ m.ratings.some(r => r.source === "Metacritic" && r.score >= 9), while the dot-notation version ≈ m.ratings.some(r => r.source === "Metacritic") && m.ratings.some(r => r.score >= 9).

Worked examples

Collection movies (8 documents, see A1.15). Outputs are real mongosh outputs.

Comparison + projection without _id:

db.movies.find({ year: { $gte: 2010 } }, { title: 1, year: 1, _id: 0 })
[
  { title: 'Inception', year: 2010 },
  { title: 'Interstellar', year: 2014 },
  { title: 'Parasite', year: 2019 }
]

Tenet (2020) is missing: its year is the string "2020", and comparison operators only match values of the same type class (numbers with numbers).

Implicit AND (and _id is returned by default):

db.movies.find({ director: "Christopher Nolan", length: { $gt: 150 } }, { title: 1, length: 1 })
[
  { _id: 3, title: 'Interstellar', length: 169 },
  { _id: 5, title: 'The Dark Knight', length: 152 }
]

Value in an array, $in, $nin:

db.movies.find({ genres: "Crime" }, { title: 1, _id: 0 })
[ { title: 'Pulp Fiction' }, { title: 'The Dark Knight' } ]
db.movies.find({ genres: { $in: ["Mystery", "Crime"] } }, { title: 1, genres: 1, _id: 0 })
[
  { title: 'Pulp Fiction', genres: [ 'Crime', 'Drama' ] },
  { title: 'The Dark Knight', genres: [ 'Action', 'Crime' ] },
  { title: 'Memento', genres: [ 'Thriller', 'Mystery' ] }
]
db.movies.find({ genres: { $nin: ["Action", "Sci-Fi"] } }, { title: 1, _id: 0 })
[
  { title: 'Pulp Fiction' },
  { title: 'Memento' },
  { title: 'Parasite' }
]

$or, $and, $ne, $eq:

db.movies.find(
  { $or: [ { year: { $lt: 1995 } }, { length: { $gte: 160 } } ] },
  { title: 1, year: 1, length: 1, _id: 0 }
)
[
  { title: 'Interstellar', year: 2014, length: 169 },
  { title: 'Pulp Fiction', year: 1994, length: 154 }
]
db.movies.find({ $and: [ { genres: "Sci-Fi" }, { genres: "Drama" } ] }, { title: 1, _id: 0 })
[ { title: 'Interstellar' } ]

(Here $and is needed: { genres: "Sci-Fi", genres: "Drama" } is a JS object with a duplicated key, and the second one silently wins.)

db.movies.find({ director: { $ne: "Christopher Nolan" } }, { title: 1, director: 1, _id: 0 })
[
  { title: 'The Matrix', director: 'Lana Wachowski' },
  { title: 'Pulp Fiction', director: 'Quentin Tarantino' },
  { title: 'Parasite', director: 'Bong Joon-ho' }
]
db.movies.find({ director: { $eq: "Bong Joon-ho" } }, { title: 1, _id: 0 })
[ { title: 'Parasite' } ]

$not vs the opposite operator: $not also returns documents where the condition cannot be true (here Tenet, whose year is a string):

db.movies.find({ year: { $not: { $gte: 2000 } } }, { title: 1, year: 1, _id: 0 })
[
  { title: 'The Matrix', year: 1999 },
  { title: 'Pulp Fiction', year: 1994 },
  { title: 'Tenet', year: '2020' }
]
db.movies.find({ year: { $lt: 2000 } }, { title: 1, year: 1, _id: 0 })
[
  { title: 'The Matrix', year: 1999 },
  { title: 'Pulp Fiction', year: 1994 }
]

$exists, null, $type:

db.movies.find({ ratings: { $exists: false } }, { title: 1, _id: 0 })
[ { title: 'Tenet' } ]
db.movies.find({ budget: null }, { title: 1, budget: 1, _id: 0 })
[ { title: 'Tenet', budget: null } ]
db.movies.find({ year: { $type: "string" } }, { title: 1, year: 1, _id: 0 })
[ { title: 'Tenet', year: '2020' } ]

Dot notation vs $elemMatch (the key difference). Request: movies with a Metacritic score of at least 9.

db.movies.find({ "ratings.source": "Metacritic", "ratings.score": { $gte: 9 } }, { title: 1, _id: 0 })
[
  { title: 'Pulp Fiction' },
  { title: 'The Dark Knight' },
  { title: 'Parasite' }
]

Wrong: The Dark Knight matches because it has a Metacritic rating (8.4) and a score ≥ 9 (its IMDb 9.0): two different elements.

db.movies.find({ ratings: { $elemMatch: { source: "Metacritic", score: { $gte: 9 } } } }, { title: 1, _id: 0 })
[ { title: 'Pulp Fiction' }, { title: 'Parasite' } ]

The same issue with a range on one array field:

db.movies.find({ "ratings.score": { $gt: 8.5, $lt: 8.8 } }, { title: 1, _id: 0 })
[
  { title: 'Inception' },
  { title: 'The Matrix' },
  { title: 'Interstellar' },
  { title: 'The Dark Knight' },
  { title: 'Parasite' }
]
db.movies.find({ ratings: { $elemMatch: { score: { $gt: 8.5, $lt: 8.8 } } } }, { title: 1, _id: 0 })
[ { title: 'The Matrix' }, { title: 'Interstellar' } ]

Dot notation in the projection, $size, exact array:

db.movies.find({ "cast.name": "Carrie-Anne Moss" }, { title: 1, "cast.character": 1, _id: 0 })
[
  {
    title: 'The Matrix',
    cast: [ { character: 'Neo' }, { character: 'Trinity' } ]
  },
  { title: 'Memento', cast: [ { character: 'Natalie' } ] }
]
db.movies.find({ cast: { $size: 2 } }, { title: 1, _id: 0 })
[ { title: 'The Matrix' }, { title: 'Pulp Fiction' } ]
db.movies.find({ genres: ["Sci-Fi", "Action"] }, { title: 1, _id: 0 })
[ { title: 'Inception' }, { title: 'The Matrix' } ]

(Tenet has ["Action", "Sci-Fi"]: different order, so no match.)

sort, limit, count:

db.movies.find({}, { title: 1, year: 1, _id: 0 }).sort({ year: -1 }).limit(3)
[
  { title: 'Tenet', year: '2020' },
  { title: 'Parasite', year: 2019 },
  { title: 'Interstellar', year: 2014 }
]

Tenet comes first: when types are mixed, MongoDB sorts by type first (numbers before strings), so the string "2020" is "greater" than every number.

db.movies.countDocuments({ director: "Christopher Nolan" })
5

Projection error (mixing inclusion and exclusion):

db.movies.find({ title: "Memento" }, { title: 1, ratings: 0 })
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

  1. easy

    Titles and years of movies released from 1995 to 2005 (included), oldest first, without _id.

    Show solution
    db.movies.find({ year: { $gte: 1995, $lte: 2005 } }, { _id: 0, title: 1, year: 1 }).sort({ year: 1 })
    [
      { title: 'The Matrix', year: 1999 },
      { title: 'Memento', year: 2000 }
    ]
  2. easy

    Thriller movies not directed by Christopher Nolan (titles only).

    Show solution
    db.movies.find({ genres: "Thriller", director: { $ne: "Christopher Nolan" } }, { _id: 0, title: 1 })
    [ { title: 'Parasite' } ]
  3. medium

    Movies with an IMDb rating that has more than 2 000 000 votes, sorted by title.

    Show solution
    db.movies.find(
      { ratings: { $elemMatch: { source: "IMDb", votes: { $gt: 2000000 } } } },
      { _id: 0, title: 1 }
    ).sort({ title: 1 })
    [
      { title: 'Inception' },
      { title: 'Interstellar' },
      { title: 'Pulp Fiction' },
      { title: 'The Dark Knight' }
    ]

    The Matrix has exactly 2 000 000, so it is excluded ($gt, not $gte).

  4. medium

    Movies whose budget is unknown or below 10 million: title and budget.

    Show solution
    db.movies.find(
      { $or: [ { budget: null }, { budget: { $lt: 10000000 } } ] },
      { _id: 0, title: 1, budget: 1 }
    )
    [
      { title: 'Pulp Fiction', budget: 8000000 },
      { title: 'Memento', budget: 9000000 },
      { title: 'Tenet', budget: null }
    ]
  5. hard

    Movies that have no Metacritic rating. Does { "ratings.source": { $ne: "Metacritic" } } work? Does it include Tenet?

    Show solution
    db.movies.find({ "ratings.source": { $ne: "Metacritic" } }, { _id: 0, title: 1 })
    [ { title: 'Interstellar' }, { title: 'Memento' }, { title: 'Tenet' } ]

    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"] } }.)

  6. exam

    The two longest Nolan movies whose year is stored as a number: title and year.

    Show solution
    db.movies.find(
      { director: "Christopher Nolan", year: { $type: "number" } },
      { _id: 0, title: 1, year: 1 }
    ).sort({ length: -1 }).limit(2)
    [
      { 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).

// template
db.movies.aggregate([
  { $match:   { ...filter like find... } },
  { $unwind:  "$arrayField" },
  { $group:   { _id: "$groupField", total: { $sum: 1 }, avg: { $avg: "$x" }, list: { $push: "$y" } } },
  { $match:   { total: { $gte: 2 } } },          // filter groups = HAVING
  { $project: { _id: 0, group: "$_id", total: 1 } },
  { $sort:    { total: -1 } }
])
StageWhat it doesSQL analogue
$matchkeeps documents that satisfy a filter (same syntax as find)WHERE (before $group), HAVING (after $group)
$projectchooses fields (1/0) and/or computes new ones (name: expression); fields not mentioned are dropped (except _id)SELECT list
$set (= $addFields)adds or replaces fields, keeps all otherscomputed column
$groupone output document per distinct _id value, with accumulators. _id: null = one group for allGROUP BY + aggregates
$unwindone output document per element of an array (the array field becomes a single element). Documents with a missing, null or empty array are droppedlike joining with a child table
$sort{ f: 1 } ascending, -1 descendingORDER BY
$limit, $countkeep n documents; count documents into one fieldLIMIT, count(*)
Expression / accumulatorMeaning
"$field", "$a.b"field path: the value of a field (with $!). In a filter ($match) you write field names without $.
{ $sum: 1 }, { $sum: "$x" }in $group: count documents / add up x
{ $avg: "$x" }in $group: mean of x (ignores non-numbers and missing). In $project/$set on an array: mean of the array
{ $push: "$x" }in $group: array of all x values in the group
{ $size: "$arr" }length of an array (error if the field is not an array, e.g. missing)
{ $type: "$x" }BSON type name as a string: "int", "double", "string", "array", "null", "missing", ...
{ $cond: [ if, then, else ] } or { $cond: { if: ..., then: ..., else: ... } }if-then-else (like JS c ? a : b)
{ $ifNull: [ expr, replacement ] }expr unless it is null or missing (like JS expr ?? replacement)
{ $gte: [a, b] }, { $eq: [a, b] }, ...comparison expressions (array form) used inside $cond/$project. Different from the query form { f: { $gte: v } }

How to build a pipeline in the exam

  1. $match early to drop documents you do not need (like WHERE).
  2. $unwind the array if you must group or filter by its elements.
  3. $match again on the unwound elements if needed (e.g. only IMDb ratings).
  4. $group with _id = the "per ..." field and the accumulators asked.
  5. $match on the accumulated fields (= HAVING), then $project / $sort.

Worked examples

$match + $project + $sort (a find could do this too):

db.movies.aggregate([
  { $match: { director: "Christopher Nolan" } },
  { $project: { _id: 0, title: 1, length: 1 } },
  { $sort: { length: -1 } }
])
[
  { title: 'Interstellar', length: 169 },
  { title: 'The Dark Knight', length: 152 },
  { title: 'Tenet', length: 150 },
  { title: 'Inception', length: 148 },
  { title: 'Memento', length: 113 }
]

$group: movies and average length per director (SQL: GROUP BY director).

db.movies.aggregate([
  { $group: { _id: "$director", n_movies: { $sum: 1 }, avg_length: { $avg: "$length" } } },
  { $sort: { n_movies: -1, _id: 1 } }
])
[
  { _id: 'Christopher Nolan', n_movies: 5, avg_length: 146.4 },
  { _id: 'Bong Joon-ho', n_movies: 1, avg_length: 132 },
  { _id: 'Lana Wachowski', n_movies: 1, avg_length: 136 },
  { _id: 'Quentin Tarantino', n_movies: 1, avg_length: 154 }
]

$unwind + $group + $push + $match after group (HAVING): genres with at least 3 movies and their titles.

db.movies.aggregate([
  { $unwind: "$genres" },
  { $group: { _id: "$genres", n: { $sum: 1 }, titles: { $push: "$title" } } },
  { $match: { n: { $gte: 3 } } },
  { $sort: { n: -1, _id: 1 } }
])
[
  {
    _id: 'Action',
    n: 4,
    titles: [ 'Inception', 'The Matrix', 'The Dark Knight', 'Tenet' ]
  },
  {
    _id: 'Sci-Fi',
    n: 4,
    titles: [ 'Inception', 'The Matrix', 'Interstellar', 'Tenet' ]
  },
  {
    _id: 'Drama',
    n: 3,
    titles: [ 'Interstellar', 'Pulp Fiction', 'Parasite' ]
  }
]

To see what $unwind produces: Inception becomes two documents, one per genre.

db.movies.aggregate([
  { $match: { _id: 1 } },
  { $unwind: "$genres" },
  { $project: { title: 1, genres: 1 } }
])
[
  { _id: 1, title: 'Inception', genres: 'Sci-Fi' },
  { _id: 1, title: 'Inception', genres: 'Action' }
]

$unwind on an array of sub-documents: average score and total votes per rating source.

db.movies.aggregate([
  { $unwind: "$ratings" },
  { $group: { _id: "$ratings.source", avg_score: { $avg: "$ratings.score" }, total_votes: { $sum: "$ratings.votes" } } },
  { $sort: { _id: 1 } }
])
[
  { _id: 'IMDb', avg_score: 8.714285714285714, total_votes: 13850000 },
  { _id: 'Metacritic', avg_score: 8.440000000000001, total_votes: 224 }
]

$size and $ifNull: number of cast members and of ratings. $size fails on Tenet's missing ratings, so replace missing with an empty array first:

db.movies.aggregate([
  { $project: { _id: 0, title: 1, n_cast: { $size: "$cast" },
                n_ratings: { $size: { $ifNull: ["$ratings", []] } } } }
])
[
  { title: 'Inception', n_cast: 1, n_ratings: 2 },
  { title: 'The Matrix', n_cast: 2, n_ratings: 2 },
  { title: 'Interstellar', n_cast: 1, n_ratings: 1 },
  { title: 'Pulp Fiction', n_cast: 2, n_ratings: 2 },
  { title: 'The Dark Knight', n_cast: 1, n_ratings: 2 },
  { title: 'Memento', n_cast: 1, n_ratings: 1 },
  { title: 'Parasite', n_cast: 1, n_ratings: 2 },
  { title: 'Tenet', n_cast: 0, n_ratings: 0 }
]
db.movies.aggregate([ { $project: { title: 1, n: { $size: "$ratings" } } } ])   // without $ifNull
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

$type: inspect the types (find the dirty data):

db.movies.aggregate([
  { $match: { title: { $in: ["Tenet", "Memento"] } } },
  { $project: { _id: 0, title: 1, year_type: { $type: "$year" },
                budget_type: { $type: "$budget" }, ratings_type: { $type: "$ratings" } } }
])
[
  {
    title: 'Memento',
    year_type: 'int',
    budget_type: 'int',
    ratings_type: 'array'
  },
  {
    title: 'Tenet',
    year_type: 'string',
    budget_type: 'null',
    ratings_type: 'missing'
  }
]

$set + $cond: label each movie (array form of $cond):

db.movies.aggregate([
  { $set: { kind: { $cond: [ { $gte: ["$length", 150] }, "long", "normal" ] } } },
  { $project: { _id: 0, title: 1, length: 1, kind: 1 } }
])
[
  { title: 'Inception', length: 148, kind: 'normal' },
  { title: 'The Matrix', length: 136, kind: 'normal' },
  { title: 'Interstellar', length: 169, kind: 'long' },
  { title: 'Pulp Fiction', length: 154, kind: 'long' },
  { title: 'The Dark Knight', length: 152, kind: 'long' },
  { title: 'Memento', length: 113, kind: 'normal' },
  { title: 'Parasite', length: 132, kind: 'normal' },
  { title: 'Tenet', length: 150, kind: 'long' }
]

$ifNull for display:

db.movies.aggregate([
  { $match: { title: { $in: ["Tenet", "Parasite"] } } },
  { $project: { _id: 0, title: 1, budget: { $ifNull: ["$budget", "unknown"] } } }
])
[
  { title: 'Parasite', budget: 11400000 },
  { title: 'Tenet', budget: 'unknown' }
]

$avg on an array inside $set (no $unwind needed): average score of each movie.

db.movies.aggregate([
  { $match: { ratings: { $exists: true } } },
  { $set: { avg_score: { $avg: "$ratings.score" } } },
  { $project: { _id: 0, title: 1, avg_score: 1 } },
  { $sort: { avg_score: -1, title: 1 } }
])
[
  { title: 'Pulp Fiction', avg_score: 9.2 },
  { title: 'Parasite', avg_score: 9.05 },
  { title: 'Interstellar', avg_score: 8.7 },
  { title: 'The Dark Knight', avg_score: 8.7 },
  { title: 'Memento', avg_score: 8.4 },
  { title: 'Inception', avg_score: 8.100000000000001 },
  { title: 'The Matrix', avg_score: 8 }
]

(8.100000000000001 is ordinary floating-point rounding, as in JS (8.8 + 7.4) / 2.)

Conditional counting with _id: null (one group for the whole collection): total movies, how many are long, average length.

db.movies.aggregate([
  { $group: { _id: null,
              n: { $sum: 1 },
              n_long: { $sum: { $cond: { if: { $gte: ["$length", 150] }, then: 1, else: 0 } } },
              avg_length: { $avg: "$length" } } }
])
[ { _id: null, n: 8, n_long: 4, avg_length: 144.25 } ]

$unwind drops documents with an empty array: Tenet has cast: [], so after unwinding the cast it disappears:

db.movies.aggregate([
  { $unwind: "$cast" },
  { $match: { title: "Tenet" } }
])
(no rows)

(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

  1. easy

    Number of movies per director, most first (ties by name).

    Show solution
    db.movies.aggregate([
      { $group: { _id: "$director", n: { $sum: 1 } } },
      { $sort: { n: -1, _id: 1 } }
    ])
    [
      { _id: 'Christopher Nolan', n: 5 },
      { _id: 'Bong Joon-ho', n: 1 },
      { _id: 'Lana Wachowski', n: 1 },
      { _id: 'Quentin Tarantino', n: 1 }
    ]
  2. medium

    Average IMDb score per director with the list of their IMDb-rated titles, best average first.

    Show solution
    db.movies.aggregate([
      { $unwind: "$ratings" },
      { $match: { "ratings.source": "IMDb" } },
      { $group: { _id: "$director", avg_imdb: { $avg: "$ratings.score" }, titles: { $push: "$title" } } },
      { $sort: { avg_imdb: -1 } }
    ])
    [
      {
        _id: 'Quentin Tarantino',
        avg_imdb: 8.9,
        titles: [ 'Pulp Fiction' ]
      },
      {
        _id: 'Christopher Nolan',
        avg_imdb: 8.725,
        titles: [ 'Inception', 'Interstellar', 'The Dark Knight', 'Memento' ]
      },
      { _id: 'Lana Wachowski', avg_imdb: 8.7, titles: [ 'The Matrix' ] },
      { _id: 'Bong Joon-ho', avg_imdb: 8.5, titles: [ 'Parasite' ] }
    ]

    The second $match works on single rating sub-documents because of $unwind (no $elemMatch needed).

  3. medium

    Genres whose movies have an average length above 145: output fields genre, avg_len, n (no _id), longest first.

    Show solution
    db.movies.aggregate([
      { $unwind: "$genres" },
      { $group: { _id: "$genres", avg_len: { $avg: "$length" }, n: { $sum: 1 } } },
      { $match: { avg_len: { $gt: 145 } } },
      { $sort: { avg_len: -1 } },
      { $project: { _id: 0, genre: "$_id", avg_len: 1, n: 1 } }
    ])
    [
      { avg_len: 153, n: 2, genre: 'Crime' },
      { avg_len: 151.66666666666666, n: 3, genre: 'Drama' },
      { avg_len: 150.75, n: 4, genre: 'Sci-Fi' },
      { avg_len: 146.5, n: 4, genre: 'Action' }
    ]

    genre: "$_id" renames _id. Field order in the output follows the original document, not the projection.

  4. hard

    Actors (cast names) who appear in more than one movie, with the movie titles.

    Show solution
    db.movies.aggregate([
      { $unwind: "$cast" },
      { $group: { _id: "$cast.name", n_movies: { $sum: 1 }, movies: { $push: "$title" } } },
      { $match: { n_movies: { $gt: 1 } } }
    ])
    [
      {
        _id: 'Carrie-Anne Moss',
        n_movies: 2,
        movies: [ 'The Matrix', 'Memento' ]
      }
    ]
  5. exam

    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).

    Show solution
    db.movies.aggregate([
      { $set: { avg_score: { $avg: "$ratings.score" } } },
      { $set: { label: { $cond: [ { $eq: [ { $type: "$avg_score" }, "null" ] },
                                  "unrated",
                                  { $cond: [ { $gte: ["$avg_score", 8.7] }, "top", "good" ] } ] } } },
      { $project: { _id: 0, title: 1, avg_score: 1, label: 1 } },
      { $sort: { title: 1 } }
    ])
    [
      { title: 'Inception', avg_score: 8.100000000000001, label: 'good' },
      { title: 'Interstellar', avg_score: 8.7, label: 'top' },
      { title: 'Memento', avg_score: 8.4, label: 'good' },
      { title: 'Parasite', avg_score: 9.05, label: 'top' },
      { title: 'Pulp Fiction', avg_score: 9.2, label: 'top' },
      { title: 'Tenet', avg_score: null, label: 'unrated' },
      { title: 'The Dark Knight', avg_score: 8.7, label: 'top' },
      { title: 'The Matrix', avg_score: 8, label: 'good' }
    ]

    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" ] }.

  6. exam

    For each genre: number of IMDb-rated movies, average IMDb score and titles; keep only genres with average ≥ 8.7; best first.

    Show solution
    db.movies.aggregate([
      { $unwind: "$ratings" },
      { $match: { "ratings.source": "IMDb" } },
      { $unwind: "$genres" },
      { $group: { _id: "$genres", n: { $sum: 1 }, avg_imdb: { $avg: "$ratings.score" }, titles: { $push: "$title" } } },
      { $match: { avg_imdb: { $gte: 8.7 } } },
      { $sort: { avg_imdb: -1 } }
    ])
    [
      {
        _id: 'Crime',
        n: 2,
        avg_imdb: 8.95,
        titles: [ 'Pulp Fiction', 'The Dark Knight' ]
      },
      {
        _id: 'Action',
        n: 3,
        avg_imdb: 8.833333333333334,
        titles: [ 'Inception', 'The Matrix', 'The Dark Knight' ]
      },
      {
        _id: 'Sci-Fi',
        n: 3,
        avg_imdb: 8.733333333333333,
        titles: [ 'Inception', 'The Matrix', 'Interstellar' ]
      },
      {
        _id: 'Drama',
        n: 3,
        avg_imdb: 8.700000000000001,
        titles: [ 'Interstellar', 'Pulp Fiction', 'Parasite' ]
      }
    ]

    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 } }.

Remember

  • Pipeline = array of stages; order matters.
  • $match (WHERE/HAVING), $project (SELECT), $set (add fields), $group (GROUP BY: _id + $sum/$avg/$push), $unwind (one doc per array element), $sort.
  • Field values: "$field". Count: { $sum: 1 }.
  • $size (array length), $type (type name), $cond (if/then/else), $ifNull (default for null/missing).

Unit A · Coding for DS & DM · exam module: 30 min

A2. R framework (prof.ssa Chiara Masci)

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
CommandWhat 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.

CommandMeaning
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, ...).

StepCodeHow oftenJS analogy
Install (download to your library folder)install.packages("dplyr") (name in quotes)once per computernpm install
Load (attach to the search path)library(dplyr)every session / scriptimport
Use one function without attachingdplyr::filter(df, x > 1)any timeimport { 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.
  • installed.packages(), update.packages(), remove.packages("pkg"), search() (attached packages), .libPaths() (where packages are installed), help(package = "dplyr").

Worked examples

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

  1. 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).

  2. 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.

  3. 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.

  4. 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()ExamplesJS analogy
"logical""logical"TRUE, FALSE, NAboolean
"integer""integer"5L, 1:3, length(x)(none: JS has one number type)
"double""numeric"5, 3.14, 1e-3, Infnumber
"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 warning NAs 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).
  • ^ (or **) is power. integer + integer stays integer, integer + double becomes double.
  • Floating point: 0.1 + 0.2 == 0.3 is FALSE; use isTRUE(all.equal(0.1 + 0.2, 0.3)).
  • round(2.5) is 2 and round(0.5) is 0 (rounding to even, IEC 60559 standard); round(3.14159, 2) is 3.14. Also floor, ceiling, trunc, signif.
Strings

paste("a", "b") gives "a b" (default sep = " "); paste0("a", "b") gives "ab"; paste(c("x", "y"), collapse = "+") gives "x+y". Also nchar(), toupper(), substr(s, start, stop), sprintf("%.2f", pi), grepl(), sub()/gsub(), strsplit() (returns a list). length("hello") is 1 (one element), nchar("hello") is 5.

Worked examples

typeof(5); class(5); typeof(5L); typeof(1:3)
typeof(TRUE + 1L)
typeof(5L / 1L)
c(1, "a", TRUE)
c(1, TRUE, FALSE)
c(1.5, 2L)
sum(c(TRUE, FALSE, TRUE, TRUE))
as.numeric("3.5"); as.integer("3.5")
as.numeric("abc")
[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

Exercises

  1. easy

    Give typeof() of: 3, 3L, "3", 3 > 2, c(1L, 2.5), c(TRUE, 2L).

    Show solution

    "double", "integer", "character", "logical", "double" (integer coerced up to double), "integer" (logical coerced up to integer).

  2. medium

    What does each line print?

    c(FALSE, 2, "b")
    as.logical(c("yes", "T", "false", "0"))
    as.logical(c(0, 2, -1))
    TRUE + TRUE * 3
    Show solution
    [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\).

  3. medium

    Predict: 5 %/% 2, 5 %% 2, -7 %% 3, 7.5 %/% 2, typeof(4L %/% 2L), typeof(4L / 2L).

    Show solution

    2, 1, 2 (because \(-7 = 3 \cdot (-3) + 2\)), 3, "integer", "double".

  4. exam

    What does this print? Explain each step.

    x <- c("10", "9", "100")
    max(x)
    max(as.numeric(x))
    sort(x)
    x == 10
    Show solution
    [1] "9"
    [1] 100
    [1] "10"  "100" "9"
    [1]  TRUE FALSE FALSE

    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).
  • Coercion: logical < integer < double < complex < character.
  • as.*() converts; failure gives NA + warning.
  • 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
CodeResult
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!)
KindExample (x <- c(10, 20, 30, 40))Result
positive integersx[2], x[c(1, 3)], x[c(1, 1)]20; 10 30; 10 10
negative = dropx[-1], x[-c(1, 2)], x[-length(x)]20 30 40; 30 40; 10 20 30
logicalx[x > 15], x[c(TRUE, FALSE)]20 30 40; 10 30 (recycled)
namesv <- c(a = 1, b = 2); v["b"]named b 2
out of rangex[6]NA (no error!)
zerox[0]numeric(0) (empty)
emptyx[]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

  1. 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
  2. medium

    Let n <- 4. What is 1:n-1? 1:(n-1)? -1:2? 2^(0:3)? seq(10, 1, by = -4)?

    Show solution
    [1] 0 1 2 3
    [1] 1 2 3
    [1] -1  0  1  2
    [1] 1 2 4 8
    [1] 10  6  2
  3. medium

    What does this print?

    y <- c(2, NA, 7, 1)
    sum(y); sum(y, na.rm = TRUE); sum(is.na(y))
    y[y > 1]
    y[which(y > 1)]
    Show solution
    [1] NA
    [1] 10
    [1] 1
    [1]  2 NA  7
    [1] 2 7

    The logical index y > 1 is TRUE NA TRUE FALSE; an NA index returns NA. which() keeps only the TRUE positions (1 and 3).

  4. 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.

  5. 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.

    Show solution
    sum(temps > 15, na.rm = TRUE)            # (a) 3
    which(is.na(temps))                       # (b) 2 6
    round(mean(temps, na.rm = TRUE), 1)       # (c) 19.1

    (c): \((12.5 + 18.0 + 25.3 + 9.8 + 30.1)/5 = 95.7/5 = 19.14\), rounded 19.1. Summing a logical vector counts the TRUEs.

Self-check quiz

Common mistakes

  • 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)
     [,1] [,2] [,3]
[1,]    1    3    5
[2,]    2    4    6
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    4    5    6
[1] 2 3
[1] 2
[1] 3
[1] 6
Indexing: m[row, col]
  • 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.

Math
CodeMeaning
m * m, m + 1, m / 2element-wise (with recycling, column-major)
A %*% Bmatrix product (needs ncol(A) == nrow(B))
t(A), solve(A), solve(A, b), det(A), diag(n), diag(A)transpose, inverse, solve \(Ax = b\), determinant, identity, diagonal
rowSums, colSums, rowMeans, colMeansfast 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
[1] 2 4 6
[1] 3 4
     [,1]
[1,]    3
[2,]    4
[1] 4 5 6
     [,1] [,2]
[1,]   35   44
[2,]   44   56
     [,1] [,2] [,3]
[1,]   10   30   50
[2,]  200  400  600

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\).

m2 <- matrix(1:6, nrow = 2,
             dimnames = list(c("r1", "r2"), c("A", "B", "C")))
m2
m2["r2", "B"]
m2[, "C"]
rbind(a = 1:2, b = 3:4)
a <- array(1:24, dim = c(2, 3, 4))
a[2, 3, 4]; a[1, 2, 3]; dim(a[, , 1])
   A B C
r1 1 3 5
r2 2 4 6
[1] 4
r1 r2
 5  6
  [,1] [,2]
a    1    2
b    3    4
[1] 24
[1] 15
[1] 2 3

Exercises

  1. easy

    Print matrix(1:6, ncol = 2). What are dim() and the element in row 3, column 1?

    Show solution
         [,1] [,2]
    [1,]    1    4
    [2,]    2    5
    [3,]    3    6

    dim is 3 2; element [3,1] is 3. With 2 columns and 6 values, R computes 3 rows and fills column by column.

  2. medium

    m <- matrix(1:6, nrow = 2). What are class(m[1, ]), dim(m[1, , drop = FALSE]), m[-1, ], m[4], sum(m[, c(1, 3)])?

    Show solution

    "integer" (a plain vector because of drop = TRUE); 1 3; [1] 2 4 6 (the 2nd row, dropped to a vector); 4; \(1+2+5+6 = 14\).

  3. medium

    What does this print?

    A <- matrix(c(2, 0, 1, 3), nrow = 2)
    A
    A * A
    A %*% A
    A %*% c(1, 1)
    Show solution
         [,1] [,2]
    [1,]    2    1
    [2,]    0    3
         [,1] [,2]
    [1,]    4    1
    [2,]    0    9
         [,1] [,2]
    [1,]    4    5
    [2,]    0    9
         [,1]
    [1,]    3
    [2,]    3

    \(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.

  4. 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.

    Show solution
    m <- matrix(1:9, nrow = 3, byrow = TRUE)
    m[m > 6] <- 0
    colSums(m)
    [1] 5 7 9

    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).

CodeReturnsclass()
l[3] or l["c"]a list of length 1 containing 1:3"list"
l[[3]], l[["c"]], l$cthe 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$zzzNULL (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).
  • Combine: c(l1, l2), append(l, list(z = 0)). Nested lists: list(p = list(q = 1)), accessed as x$p$q.
  • 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"
student <- list(name = "Ann", grades = c(28, 30, 27),
                address = list(city = "Milano", cap = "20133"))
mean(student$grades)
student$address$city
student[["grades"]][student[["grades"]] > 27]
p <- student
p$name <- "Bob"
student$name           # unchanged (copy-on-modify)
[1] 28.33333
[1] "Milano"
[1] 28 30
[1] "Ann"

Exercises

  1. easy

    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.

  2. 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.

  3. 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.

  4. 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.

df <- data.frame(name = c("Ann", "Bob", "Cy", "Dan"),
                 age  = c(28, 35, 42, 19),
                 city = c("MI", "RM", "MI", "TO"))
df
str(df)
dim(df); nrow(df); ncol(df); length(df); names(df)
  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
CodeResult
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".

f <- factor(c("low", "high", "med", "low"))
f
levels(f); as.integer(f); nlevels(f)
f2 <- factor(c("low", "high", "med", "low"), levels = c("low", "med", "high"))
as.integer(f2)
table(f2)
[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 codes 1 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

  1. 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).

  2. 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.

    Show solution
    mi <- df[df$city == "MI", ]
    mi[order(-mi$age), "name"]          # "Cy"  "Ann"
    df$decade <- (df$age %/% 10) * 10   # 20 30 40 10
  3. medium

    What is printed?

    sizes <- factor(c("M", "S", "L", "M"), levels = c("S", "M", "L"))
    as.integer(sizes)
    levels(sizes)[2]
    sizes[2] <- "XL"
    sizes
    Show solution
    [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.

  4. 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).

  5. 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".

    Show solution
    sum(is.na(diab$glucose))                                         # (a)
    mean(diab$glucose[diab$age > 50], na.rm = TRUE)                   # (b)
    tapply(diab$glucose, diab$outcome, mean, na.rm = TRUE)            # (c)
    # or: aggregate(glucose ~ outcome, data = diab, FUN = mean)  (formula drops NA rows)
    diab$outcome <- factor(diab$outcome, levels = c(0, 1),
                           labels = c("neg", "pos"))                 # (d)

    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.
  • Create and use: e <- new.env(), assign("x", 1, envir = e), e$x <- 1, get("x", envir = e), exists("x", envir = e), ls(e), rm("x", envir = e), environment(f), environment() (current), parent.frame() (the caller).
  • 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
[1] 1
[1] 2
count <- 0
inc_local  <- function() count <- count + 1    # creates a local 'count'
inc_global <- function() count <<- count + 1
inc_local(); count
inc_global(); inc_global(); count
[1] 0
[1] 2

(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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

    Show solution
    make_account <- function(balance = 0) {
      list(deposit = function(x) { balance <<- balance + x; invisible(balance) },
           get     = function() balance)
    }
    a1 <- make_account(100); a2 <- make_account()
    a1$deposit(50)
    a1$get(); a2$get()
    [1] 150
    [1] 0

    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 one TRUE/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
OperatorWorks onUse
&, |, !vectors, element-wisefiltering: x[x > 1 & x < 5]
&&, ||single values only, short-circuitinside if(): if (!is.null(x) && x > 0)
xor(a, b)vectorsexclusive or
any(), all()a logical vector → one valueif (any(is.na(x)))
isTRUE(x), isFALSE(x)anythingTRUE only for a single non-NA TRUE
which()logical vectorpositions 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 number n, 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")
[1] "non-pos" "non-pos" "pos"     NA        "pos"
[1] -2  0  3  0  7
[1] FALSE FALSE  TRUE    NA FALSE
[1] TRUE
[1] NA
[1] TRUE
[1] 3 5
[1] "two"
[1] "other"

all(x > -5) is NA because the only non-TRUE element is NA: R cannot know if it would be TRUE. any(x > 5) is TRUE because 7 > 5 already decides it.

split(1:6, c("a", "b"))                          # grouping vector recycled
g <- c("x", "y", "x", "y", "x")
v <- c(10, 20, 30, 40, 50)
s <- split(v, g)
s
sapply(s, sum)
$a
[1] 1 3 5

$b
[1] 2 4 6

$x
[1] 10 30 50

$y
[1] 20 40

 x  y
90 60 

Exercises

  1. easy

    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
  2. 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".

  3. 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)
    Show solution
    [1] "weekend"
    [1] "weekday"
    [1] "weekend"
    [1] TRUE

    sat has an empty value, so it falls through to sun. An if without else whose condition is FALSE returns NULL.

  4. 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".

  5. 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?

    Show solution
    bmi_class <- function(w, h) {
      bmi <- w / h^2
      ifelse(bmi < 18.5, "under", ifelse(bmi < 25, "normal", "over"))
    }
    bmi_class(c(50, 70, 90), c(1.80, 1.75, 1.70))
    [1] "under"  "normal" "over"  

    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
FunctionInputOutputJS idea
apply(X, MARGIN, FUN)matrix/array (or data frame, converted to matrix); MARGIN = 1 rows, 2 columns, c(1, 2) cellsvector, or matrix if FUN returns length > 1 (results become columns)rows.map(f)
lapply(X, FUN)vector or listalways a list of the same lengtharr.map(f)
sapply(X, FUN)vector or listsimplified: vector if every result has length 1; matrix if all have the same length > 1; otherwise a listarr.map(f)
vapply(X, FUN, FUN.VALUE)vector or listlike sapply but with a declared result type (safer)typed map
tapply(X, INDEX, FUN)a vector + a grouping vectorone result per group (named)groupBy + reduce
mapply(FUN, x, y, ...) / Map()several vectors in parallelsimplified like sapply / listzip + map
Reduce(f, x), Filter(f, x)vector or lista single accumulated value / the elements where f is TRUEreduce, 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)
[1]  9 12
[1] 2 4 6
     [,1] [,2]
[1,]    2    4
[2,]    6    8
[3,]   10   12
lapply(1:2, function(x) x * 10)
sapply(1:3, function(x) x^2)
sapply(1:3, function(x) c(x, x^2))
sapply(c(a = 1, b = 2), function(x) x * 2)
sapply(c("x", "yy", "zzz"), nchar)
sapply(1:3, seq_len)
[[1]]
[1] 10

[[2]]
[1] 20

[1] 1 4 9
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    1    4    9
a b
2 4
  x  yy zzz
  1   2   3
[[1]]
[1] 1

[[2]]
[1] 1 2

[[3]]
[1] 1 2 3

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).

sales  <- c(10, 20, 30, 40, 50)
region <- c("N", "S", "N", "S", "N")
tapply(sales, region, sum)
tapply(sales, region, length)
mapply(function(a, b) a * b, 1:3, 4:6)
mapply(rep, 1:3, 3:1)
Reduce(`+`, 1:4, accumulate = TRUE)
Filter(function(v) v > 2, c(5, 1, 3))
 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

  1. 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.

  2. 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).

  3. 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).

  4. 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).

  5. 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.
  • apply: matrix rows (1) / columns (2). lapply → list. sapply → simplified.
  • 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)
[1] 9
[1] 27
[1] 8
[1]  1  4  9 16
[1] "no y"
[1] 3
[1] 10
[1] 20

h(1) is 20, not 2: the default y = x * 2 is evaluated only when y is used, and by then the local x is 10.

my_sum <- function(...) sum(...)
count_args <- function(...) length(list(...))
my_sum(1, 2, 3); count_args("a", 1:10, NULL)
describe <- function(x) list(mean = mean(x), sd = sd(x))
r <- describe(c(2, 4, 6)); r$mean; r$sd
set_y <- function(x) { y <- x + 1 }
set_y(1)          # prints nothing (invisible)
(set_y(1))
make_power <- function(p) function(x) x^p
square <- make_power(2); cube <- make_power(3)
square(4); cube(2)
fact <- function(n) if (n <= 1) 1 else n * fact(n - 1)
fact(5)
[1] 6
[1] 3
[1] 4
[1] 2
[1] 2
[1] 16
[1] 8
[1] 120

Exercises

  1. easy

    Write celsius_to_f(c) (F = C·9/5 + 32). What does celsius_to_f(c(0, 100, -40)) return?

    Show solution
    celsius_to_f <- function(c) c * 9 / 5 + 32
    celsius_to_f(c(0, 100, -40))
    [1]  32 212 -40

    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.)

  2. 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.

  3. 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"
  4. 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.

  5. 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
FunctionDefaultsUse 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 defaultgeneric text tables, sep = "\t" for TSV
readr::read_csv(file)tidyverse; returns a tibble, faster, prints column typestidyverse workflow
readxl::read_excel("f.xlsx", sheet = 1)package readxlExcel 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.
  • write.table(df, "out.txt", sep = "\t", quote = FALSE); readr::write_csv(df, "out.csv") (never writes row names).
R binary formats
FormatWriteReadNote
.rds (one object)saveRDS(obj, "f.rds")x <- readRDS("f.rds")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

  1. easy

    Write the line to read sales.csv, where values are separated by semicolons and decimals use a comma (12,50).

    Show solution
    sales <- read.csv2("sales.csv")
    # equivalent: read.csv("sales.csv", sep = ";", dec = ",")
  2. medium

    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.

  3. 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.

  4. 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.

    Show solution
    birds <- read.csv("https://example.org/birds.csv", na.strings = c("", "NA"))
    birds <- birds[!is.na(birds$SPECIES), ]
    write.csv(birds, "birds_clean.csv", row.names = FALSE)
    saveRDS(birds, "birds_clean.rds")

Self-check quiz

Common mistakes

  • 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.

plot() and its main arguments
ArgumentMeaning
type"p" points (default), "l" lines, "b" both, "o" overplotted, "h" vertical bars, "s" steps, "n" nothing (empty frame)
main, xlab, ylab, subtitle and axis labels
xlim = c(0, 10), ylimaxis ranges
colcolour: name ("red"), number, rgb(), hex "#1f77b4"
pchpoint symbol (0-25; 16 = filled circle, 19 = solid circle)
cexsize multiplier of points/text
lty, lwdline 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
CodeShows
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 = TRUEdensity 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

  1. 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.

  2. 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.

    hist(mtcars$mpg, freq = FALSE)
    lines(density(mtcars$mpg))
  3. medium

    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.

  4. 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
Behaviourdata.frametibble
Printingall rows (up to a limit), no typesfirst 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$colNULL + warning Unknown or uninitialised column
Recycling at creationrecycles if lengths are multiplesonly length-1 values are recycled
Refer to a column just creatednoyes: tibble(x = 1:3, y = x * 2)
Row namesyesno (use a column)
Non-syntactic nameschanged (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
VerbDoesSQL / JS analogy
filter(df, cond1, cond2)keep rows (commas = AND); rows where the condition is NA are droppedWHERE / .filter
select(df, a, b), select(-c), starts_with("x"), a:ckeep/drop/reorder columnsSELECT a, b
mutate(df, new = expr)add or modify columnscomputed column / .map
arrange(df, a, desc(b))sort rowsORDER 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 groupGROUP 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_joinjoinsLEFT JOIN
if_else(cond, a, b), case_when(c1 ~ v1, c2 ~ v2, .default = v3)vectorised conditionals inside mutateternary / CASE WHEN

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.

Worked examples

library(dplyr)
emp <- tibble(name   = c("Ann", "Bob", "Cy", "Dan", "Eve"),
              dept   = c("IT", "HR", "IT", "HR", "IT"),
              salary = c(3000, 2500, 3500, 2800, 4000))
emp |> filter(dept == "IT", salary > 3200) |> select(name, salary)
# A tibble: 2 × 2
  name  salary
  <chr>  <dbl>
1 Cy      3500
2 Eve     4000
emp |>
  group_by(dept) |>
  summarise(n = n(), avg = mean(salary))
# A tibble: 2 × 3
  dept      n   avg
  <chr> <int> <dbl>
1 HR        2  2650
2 IT        3  3500
emp |>
  mutate(bonus = salary * 0.1) |>
  arrange(desc(salary)) |>
  slice(1:2)
# A tibble: 2 × 4
  name  dept  salary bonus
  <chr> <chr>  <dbl> <dbl>
1 Eve   IT      4000   400
2 Cy    IT      3500   350
emp[, "salary"]                 # still a tibble (5 x 1)
as.data.frame(emp)[, "salary"]  # a plain vector
emp |> mutate(level = case_when(salary >= 3500 ~ "high",
                                 salary >= 2800 ~ "mid",
                                 .default = "low")) |> pull(level)
# A tibble: 5 × 1
  salary
   <dbl>
1   3000
2   2500
3   3500
4   2800
5   4000
[1] 3000 2500 3500 2800 4000
[1] "mid"  "low"  "high" "mid"  "high"

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

  1. 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).

  2. 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.

  3. medium

    What does this return (number of rows and the values)?

    t1 <- tibble(x = c(1, NA, 3, 4), g = c("a", "a", "b", "b"))
    t1 |> filter(x > 1) |> nrow()
    t1 |> group_by(g) |> summarise(s = sum(x)) |> pull(s)
    t1 |> group_by(g) |> summarise(s = sum(x, na.rm = TRUE)) |> pull(s)
    Show solution
    [1] 2
    [1] NA  7
    [1] 1 7

    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.

  4. 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.

  5. 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.

    Show solution
    diab |>
      filter(!is.na(glucose)) |>
      mutate(age_group = case_when(age < 40  ~ "<40",
                                   age <= 60 ~ "40-60",
                                   TRUE      ~ ">60")) |>
      group_by(age_group, outcome) |>
      summarise(n = n(), mean_bmi = mean(bmi, na.rm = TRUE), .groups = "drop") |>
      arrange(desc(mean_bmi))

    Marking idea: correct verb order (filter → mutate → group_by → summarise → arrange), case_when conditions checked top to bottom, n(), desc().

Self-check quiz

Common mistakes

  • Writing df$col inside dplyr verbs (works but defeats the purpose and breaks with groups).
  • Using = instead of == in filter(): error We detected a named input.
  • Forgetting to assign the result: pipelines do not modify df in place (df <- df |> ...).
  • Using + instead of a pipe between dplyr steps (the + is for ggplot2 layers).
  • Expecting tb[, 1] to be a vector.

Remember

  • Tibble: no drop, no partial matching, strict recycling, prints types and 10 rows.
  • x |> f(y) = f(x, y); %>% is the magrittr version.
  • filter (rows), select (columns), mutate (new columns), arrange (sort), group_by + summarise (aggregate), count.
  • 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 outside aes(), 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
GeomNeedsPlot
geom_point()x, yscatterplot (geom_jitter() adds random noise)
geom_line()x, yline (connects in x order), e.g. time series
geom_smooth(method = "lm")x, ytrend line with confidence band (default method: loess/gam)
geom_histogram(bins = 30) or binwidth = 5xhistogram (counts); aes(y = after_stat(density)) for density scale
geom_density()xkernel density
geom_boxplot()x (categorical), yboxplots by group
geom_bar()x onlybar chart that counts rows (stat = "count")
geom_col()x and ybars with heights given by y (already summarised)
geom_hline(yintercept = 0), geom_vline(), geom_abline(), geom_text(aes(label = v))reference lines, labels

Aesthetics given in ggplot(aes(...)) apply to all layers; aesthetics inside one geom apply only to that layer; a geom can also use different data.

Facets, scales, themes, saving
  • facet_wrap(~ var): one panel per value of var; facet_grid(rows ~ cols): a grid by two variables.
  • labs(title, subtitle, x, y, colour, caption); xlim(), scale_x_log10(), scale_colour_manual(values = c(...)), scale_fill_brewer(); coord_flip().
  • Themes: theme_minimal(), theme_bw(), theme_classic(); theme(legend.position = "bottom").
  • 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)).

Worked examples

library(ggplot2)
library(patchwork)
p1 <- ggplot(mtcars, aes(x = wt, y = mpg, colour = factor(cyl))) +
  geom_point(size = 2) +
  geom_smooth(method = "lm", se = FALSE) +
  labs(title = "Fuel use vs weight", x = "Weight (1000 lb)", y = "Miles per gallon",
       colour = "Cylinders")
p2 <- ggplot(mtcars, aes(x = factor(cyl), y = mpg)) +
  geom_boxplot(fill = "lightblue") +
  labs(x = "Cylinders", y = "mpg")
p3 <- ggplot(mtcars, aes(x = mpg)) +
  geom_histogram(binwidth = 2, colour = "white") +
  facet_wrap(~ am)
(p1 | p2) / p3 + plot_annotation(title = "mtcars overview", tag_levels = "A")

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

  1. 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?

    Show solution

    (a) geom_histogram or geom_density; (b) geom_bar; (c) geom_col; (d) geom_boxplot (or geom_violin); (e) geom_line.

  2. medium

    Find the errors.

    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")
  3. 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.

  4. 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.

    Show solution
    library(ggplot2); library(patchwork)
    p1 <- ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, colour = Species)) +
      geom_point()
    p2 <- ggplot(iris, aes(x = Sepal.Width)) +
      geom_histogram(bins = 15) +
      facet_wrap(~ Species)
    p <- p1 / p2
    ggsave("iris.png", p, width = 8, height = 8)

Self-check quiz

Common mistakes

  • Constants inside aes(); variables outside aes() (geom_point(colour = cyl) fails: object not found).
  • + at the start of a line, or |> / %>% between layers.
  • geom_bar(aes(x, y)) with precomputed heights: use geom_col (or geom_bar(stat = "identity")).
  • Mapping a numeric code (cyl, month) to colour without factor().
  • Trying to combine base plots with patchwork.

Remember

  • ggplot(data, aes(...)) + geom_*() + labs() + theme_*().
  • Variables in aes(), constants outside.
  • 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.

DistributionRandom drawsParameters (defaults)
Normalrnorm(n, mean = 0, sd = 1)note: sd, not variance
Uniformrunif(n, min = 0, max = 1)
Binomialrbinom(n, size, prob)size = number of trials; rbinom(n, 1, p) = Bernoulli
Poissonrpois(n, lambda)
Exponentialrexp(n, rate = 1)mean = 1/rate
Student t, chi-squaredrt(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.

Worked examples

dnorm(0)
pnorm(1.96)
qnorm(0.975)
pnorm(1) - pnorm(-1)
pnorm(2, lower.tail = FALSE)
dbinom(2, size = 4, prob = 0.5)
pbinom(1, size = 3, prob = 0.5)
punif(0.3); qunif(0.25, min = 0, max = 4)
ppois(0, lambda = 2)
pexp(1, rate = 1)
[1] 0.3989423
[1] 0.9750021
[1] 1.959964
[1] 0.6826895
[1] 0.02275013
[1] 0.375
[1] 0.5
[1] 0.3
[1] 1
[1] 0.1353353
[1] 0.6321206

Checks: \(\binom{4}{2}/2^4 = 6/16 = 0.375\); \(P(X \le 1) = (1+3)/8 = 0.5\); \(P(X=0)=e^{-2}\approx 0.1353\); \(1-e^{-1}\approx 0.6321\).

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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

    Show solution
    set.seed(2024)
    B <- 10000
    d1 <- sample(1:6, B, replace = TRUE)
    d2 <- sample(1:6, B, replace = TRUE)
    mean(d1 + d2 >= 10)       # simulated, close to 0.1667
    6 / 36                    # exact: (4,6),(5,5),(6,4),(5,6),(6,5),(6,6)

    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()).

Explanation

Structure of a source package
File / folderContent
DESCRIPTIONmetadata: Package name, Title, Version, Authors, Description, License, Imports/Depends (dependencies). Like package.json.
NAMESPACEwhich 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
ToolWhat 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 breakpointsclick 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

  1. 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?

    Show solution

    (a) DESCRIPTION (Imports field); (b) NAMESPACE; (c) the R/ folder; (d) man/.

  2. medium

    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.

  3. 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.

  4. 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.

Remember

  • Package = DESCRIPTION + NAMESPACE + R/ + man/ (+ tests/, data/).
  • devtools: create_package → document → load_all → check → install; roxygen #' @export.
  • traceback() after an error; browser(), debug(), debugonce() to step through.
  • stop/warning/message; tryCatch(expr, error = , warning = , finally = ).

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
CallTests
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.99one-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
chisq.test(table(a, b))independence of two categorical variables
cor.test(x, y), wilcox.test(x, y), shapiro.test(x)correlation, non-parametric, normality

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.

res$p.value
res$conf.int[1:2]
mean(x) + c(-1, 1) * qt(0.975, df = 4) * sd(x) / sqrt(5)
t.test(x, mu = 5, alternative = "greater")$p.value
a <- c(12, 15, 11, 14, 13, 16); b <- c(10, 11, 9, 12, 10, 13)
round(t.test(a, b)$p.value, 4)                 # Welch two-sample
t.test(a, b, paired = TRUE)$statistic
round(prop.test(45, 100)$p.value, 4)
[1] 0.2301996
[1] 4.903676 5.296324
[1] 4.903676 5.296324
[1] 0.1150998
[1] 0.0217
t
8
[1] 0.3681

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 optionEffect
echo: falserun the code, show results, hide the code
eval: falseshow the code, do not run it
include: falserun the code, show nothing (neither code nor results)
message: false, warning: falsehide messages / warnings (e.g. from library())
fig-width, fig-capfigure 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

  1. 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)
  2. 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.

  3. 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.

  4. 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\).

    Show solution

    \(\bar y = 81/6 = 13.5\); deviations \(-1.5, 1.5, -2.5, 0.5, -0.5, 2.5\), squares sum \(2.25+2.25+6.25+0.25+0.25+6.25 = 17.5\); \(s^2 = 17.5/5 = 3.5\), \(s = 1.8708\); \(s/\sqrt6 = 0.7638\); margin \(2.571 \times 0.7638 = 1.963\). CI \(\approx (11.54, 15.46)\).

    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").

TypeExampleNote (vs JS)
int7, -3, 10**20Unlimited size (no Number.MAX_SAFE_INTEGER).
float3.0, 1e-364-bit like JS numbers; 0.1 + 0.2 != 0.3 too.
str'hi', "hi", """multi"""Immutable. No separate char type.
boolTrue, FalseCapitalised. A subclass of int: True == 1.
NoneTypeNoneLike 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.
  • ** power; 2 ** 3 ** 2 is 2 ** 9 = 512 (right-associative).
  • 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).

Worked examples

print(7 / 2, 7 // 2, 7 % 2)
print(-7 // 2, -7 % 2)
print(7 // 2.0, 2 ** 3 ** 2)
print(type(6 / 2), type(6 // 2))
3.5 3 1
-4 1
3.0 512
<class 'float'> <class 'int'>
print(True + True, True * 10, int(3.99), int("42") + 1)
print(round(2.5), round(3.5), round(2.675, 2))
print(0.1 + 0.2 == 0.3, abs(0.1 + 0.2 - 0.3) < 1e-9)
2 10 3 43
2 4 2.67
False True

round uses "banker's rounding" (ties go to the even number), and 2.675 is stored as 2.67499999..., so it rounds to 2.67.

print("a", "b", "c", sep="-", end="!\n")
print("x", end="")
print("y")
name, score = "Ada", 91.456
print(f"{name} scored {score:.1f} ({score:8.2f})")
print(f"{name=}")
a-b-c!
xy
Ada scored 91.5 (   91.46)
name='Ada'
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

  1. 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).

  2. 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?

    Show solution
    c = float(input("Celsius: "))
    print(f"{c * 9 / 5 + 32:.1f} F")

    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'.

  3. 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).

  4. 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)
[0, 1, 2, 3, 4] [2, 5, 8] [5, 3, 1] []
1 a; 2 b; 3 c; 
Ann 31
Bob 25
while, break, continue, and the loop else

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

  1. 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).

  2. 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
  3. 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 ==.

  4. 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, ... excluding stop. 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.

PYTHON 012345 -6-5-4-3-2-1
Positive indices on top, negative below. s[1:4] = "YTH".

Worked examples

s = "PYTHON"
print(s[0], s[-1], s[1:4], s[:2], s[3:], s[-3:])
print(s[::2], s[1::2], s[::-1], s[-2:0:-1])
print(repr(s[4:1]), repr(s[10:]), s[:100])
print(s[10])
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

  1. 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
  2. medium

    Write a function is_palindrome(text) that ignores case and spaces: "Never odd or even" is a palindrome.

    Show solution
    def is_palindrome(text):
        clean = "".join(text.lower().split())
        return clean == clean[::-1]
    
    print(is_palindrome("Never odd or even"), is_palindrome("Python"))
    True False
  3. medium

    Predict the output:

    w = "abcdefgh"
    print(w[1:7:2], w[-1:-4:-1], w[7:2:-2], len(w[2:-2]))
    Show solution
    bdf hgf hfd 4

    Indices 1, 3, 5 → bdf; -1, -2, -3 → hgf; 7, 5, 3 → hfd; positions 2..5 → 4 characters.

  4. exam

    Predict the output:

    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.

OperationEffectReturns
L.append(x)adds x as ONE element at the endNone
L.extend(it) / L += itadds each element of itNone
L.insert(i, x)inserts before position iNone
L.pop() / L.pop(i)removes last / at ithe removed element
L.remove(x)removes the first x (ValueError if absent)None
L.sort()sorts in placeNone
sorted(L)does not change La new list
L.reverse() / L[::-1]in place / new listNone / list
L + M, L * 2new listlist

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)
[1, 2, 3, 4] [1, 2, 3, 4] [1, 2, 3, 99] True False
L = [1, 2]
L.append([3, 4])
print(L, len(L))
L = [1, 2]
L.extend([3, 4])
print(L, len(L))
L += "ab"              # += extends with any iterable
print(L)
print([3, 1, 2].sort(), sorted([3, 1, 2]), sorted("bca"), sorted([3, 1, 2], reverse=True))
[1, 2, [3, 4]] 3
[1, 2, 3, 4] 4
[1, 2, 3, 4, 'a', 'b']
None [1, 2, 3] ['a', 'b', 'c'] [3, 2, 1]

The famous nested-list trap: * repeats the reference to the inner list.

grid = [[0] * 3] * 3
grid[0][0] = 1
print(grid)
good = [[0] * 3 for _ in range(3)]
good[0][0] = 1
print(good)
[[1, 0, 0], [1, 0, 0], [1, 0, 0]]
[[1, 0, 0], [0, 0, 0], [0, 0, 0]]

Shallow vs deep copy:

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
(1, [2, 3, 4], 'x') <class 'int'> <class 'tuple'> 0
10 [20, 30, 40] 1 2 3
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment
List comprehensions

[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:])
[4, 16, 36]
[(0, 1), (0, 2), (1, 0), (1, 2)]
['odd', 'even', 'odd']
21 6 1 3 False [5, 6]

Exercises

  1. easy

    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.

  2. 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.

  3. 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.

  4. 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.

  5. exam

    Predict the output:

    row = [0, 0]
    table = [row, row]
    table[0][1] = 5
    row = [7, 7]
    print(table)
    Show solution
    [[0, 5], [0, 5]]

    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.

OperationMeaning
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 dtests 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 | othermerge (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"])
{'ann': 32, 'bob': 25, 'cy': 40}
['ann', 'bob', 'cy'] [32, 25, 40]
ann 32 | bob 25 | cy 40 | 
None 0 True False
Traceback (most recent call last):
  ...
KeyError: 'zoe'
d = {"a": 1, "b": 2, "a": 3}        # duplicate key: last value wins
print(d, len(d))
e = {1: "int", 1.0: "float", True: "bool"}
print(e)
print({(1, 2): "ok"})
print({[1, 2]: "no"})
{'a': 3, 'b': 2} 2
{1: 'bool'}
{(1, 2): 'ok'}
Traceback (most recent call last):
  ...
TypeError: unhashable type: 'list'

{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))
{'the': 2, 'cat': 1, 'and': 2, 'horse': 1, 'a': 1, 'bat': 1}
[('the', 2), ('and', 2)]
{3: ['the', 'cat', 'and', 'the', 'and', 'bat'], 5: ['horse'], 1: ['a']}
Comprehensions
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))
{'apple': 1.0, 'kiwi': 0.5}
{0.5: 'apple', 0.25: 'kiwi', 2.0: 'mango'}
{4, 5}
['kiwi', 'apple', 'mango'] mango
Sets
a = {1, 2, 3, 4}
b = {3, 4, 5}
print(a | b, a & b, a - b, a ^ b)
print(sorted(set("banana")), len(set([1, 1, 2, 2, 3])), type({}))
a.add(10); a.discard(99); a.remove(1)
print(sorted(a), {1, 2} <= {1, 2, 3})
{1, 2, 3, 4, 5} {3, 4} {1, 2} {1, 2, 5}
['a', 'b', 'n'] 3 <class 'dict'>
[2, 3, 4, 10] True

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

  1. 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).

  2. 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)
    {'ann': [28, 30], 'bob': [24, 18], 'cy': [27]}
    {'ann': 29.0, 'bob': 21.0, 'cy': 27.0}

    This is exactly a "group by student, then mean": in pandas it is df.groupby("student")["grade"].mean() (A3.13).

  3. 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".

  4. 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).
def report(title, *args, sep=", ", **kwargs):
    print(title, args, sep, kwargs)

report("A")
report("B", 1, 2, 3, x=9, y="z")
report(*["C", 4], sep=";", **{"k": 1})
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.

def add_item(item, bucket=[]):
    bucket.append(item)
    return bucket

print(add_item(1))
print(add_item(2))
print(add_item(3, []))
print(add_item(4))

def add_item_ok(item, bucket=None):
    if bucket is None:
        bucket = []
    bucket.append(item)
    return bucket

print(add_item_ok(1), add_item_ok(2))
[1]
[1, 2]
[3]
[1, 2, 4]
[1] [2]
Scope: the LEGB rule

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])
[('ann', 31), ('cy', 31), ('bob', 25)]
[1, 4, 9] [1, 3, 5]
<class 'map'>
42
[2, 2, 2]
[0, 1, 2]

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

  1. 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.

  2. 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
  3. 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))
    Show solution
    (1, 2, (), 4, {})
    (1, 5, (6, 7), 8, {'z': 9})

    d comes after *c, so it is keyword-only.

  4. exam

    Predict the output:

    def counter(d={}):
        d["n"] = d.get("n", 0) + 1
        return d["n"]
    print(counter(), counter(), counter({}), counter())
    Show solution
    1 2 1 3

    The default dict is shared: 1, 2, then a fresh dict gives 1, then the shared one again gives 3.

  5. 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.

A3.7 OOP: classes, objects, attributes, magic methods

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. Assigning obj.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))
(1, 2) [Point(1, 2)] (1, 2) Point(1, 2)
False False
True

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 name x; 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))
Movie(title='Alien', year=1979, rating=8.5) 1970 True

Worked examples

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

  1. 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.

  2. 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__.

    Show solution
    class BankAccount:
        """A simple bank account."""
        def __init__(self, owner, balance=0):
            self.owner = owner
            self.balance = balance
        def deposit(self, amount):
            self.balance += amount
        def withdraw(self, amount):
            if amount > self.balance:
                raise ValueError("insufficient funds")
            self.balance -= amount
        def __str__(self):
            return f"{self.owner}: {self.balance:.2f} EUR"
    
    acc = BankAccount("Ann")
    acc.deposit(100); acc.withdraw(30)
    print(acc)
    try:
        acc.withdraw(500)
    except ValueError as e:
        print("error:", e)
    Ann: 70.00 EUR
    error: insufficient funds
  3. medium

    Predict the output:

    class A:
        items = []
        n = 0
        def add(self, x):
            self.items.append(x)
            self.n += 1
    a1, a2 = A(), A()
    a1.add(1); a2.add(2)
    print(A.items, A.n, a1.n, a2.n)
    Show solution
    [1, 2] 0 1 1

    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.

  4. 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's speak 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

  1. 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).

  2. 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.

    Show solution
    class Employee:
        def __init__(self, name, salary):
            self.name, self.salary = name, salary
        def annual(self):
            return 12 * self.salary
    
    class Manager(Employee):
        def __init__(self, name, salary, bonus):
            super().__init__(name, salary)
            self.bonus = bonus
        def annual(self):
            return super().annual() + self.bonus
    
    print(Employee("A", 2000).annual(), Manager("B", 3000, 5000).annual())
    24000 41000
  3. medium

    Predict the output:

    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.

  4. 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
ExceptionTypical cause
ZeroDivisionError1 / 0, 1 // 0, 1 % 0
TypeErrorwrong type: "a" + 1, calling with wrong number of arguments
ValueErrorright type, bad value: int("abc")
IndexError / KeyErrorlist index out of range / missing dict key
AttributeError[1].push(2) (no such attribute/method)
NameErrorundefined variable
FileNotFoundErroropening 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}))
{'city': 'Milan', 'temp': '14.5'} <class 'str'>
19.0 {"a": null, "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

  1. easy

    Name the exception: int("3.5"), [1, 2][2], {"a": 1}["b"], None + 1.

    Show solution

    ValueError, IndexError, KeyError, TypeError.

  2. medium

    Predict the output:

    def f():
        try:
            return "try"
        finally:
            print("finally")
    print(f())
    Show solution
    finally
    try

    finally runs before the function actually returns.

  3. 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
  4. 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.
  • Module = .py file; if __name__ == "__main__": guards script-only code.

A3.10 Shell, Git and GitHub (labs)

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.

CommandWhat it does
git init / git clone URLcreate a repo / copy a remote repo
git status, git diffwhat changed (unstaged); git diff --staged for staged
git add file / git add -pstage a file / stage chosen hunks (for atomic commits)
git commit -m "msg"record a snapshot
git log --onelinehistory
git branch x, git switch xcreate / move to a branch
git remote add origin URL, git push -u origin mainconnect to GitHub and push
git restore file, git rm --cached filediscard 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:

git add .
git commit -m "Add project skeleton"
git log --oneline
abc1234 Add project skeleton
echo "print(2)" >> src/main.py
git status --short
 M src/main.py
Good commits (criterion 1)
  • 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

  1. 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"
  2. 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).

  3. 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.

  4. 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.

A3.11 NumPy: arrays, dtype, broadcasting, views, axis

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))
int64 (4,) (2, 3) 2 6
[2 4 6 8] [2 4 6 8] [ 1  4  9 16] [False False  True  True]
[1, 2, 1, 2] [2 4]
float64 <U21 [1 2]
[0.   0.25 0.5  0.75] [0.   0.25 0.5  0.75 1.  ] [[0. 0.]
 [0. 0.]] [1 1 1]

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.

m = np.arange(12).reshape(3, 4)
print(m)
print(m[1, 2], m[-1], m[:, 1], m[0:2, 1:3].shape)
v = m[0, :2]          # view
v[:] = -1
print(m[0])
c = m[m > 8]          # boolean mask -> copy (1-D)
c[0] = 1000
print(c, m.max())
lst = [1, 2, 3]
s = lst[:2]; s[0] = 99
print(lst)             # list slice = copy
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
6 [ 8  9 10 11] [1 5 9] (2, 2)
[-1 -1  2  3]
[1000   10   11] 11
[1, 2, 3]
Boolean indexing

Combine conditions with &, |, ~ and parentheses (not and/or, which raise an error on arrays).

t = np.array([12.5, 18.0, 25.3, 30.1, 8.4])
print(t[(t > 10) & (t < 26)], (t > 20).sum(), (t > 20).mean())
print(np.where(t > 20, "hot", "ok"))
t[t < 10] = 10
print(t)
print(t > 10 and t < 26)
[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.

b = np.array([[1, 2, 3], [4, 5, 6]])
print(b.sum(), b.sum(axis=0), b.sum(axis=1), b.mean(axis=0).shape)
print(b.max(axis=1), b.argmax(axis=1), b.T.shape, b.reshape(-1))
21 [5 7 9] [ 6 15] (3,)
[3 6] [2 2] (3, 2) [1 2 3 4 5 6]
Broadcasting

Definition: Broadcasting rules

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.

b = np.array([[1, 2, 3], [4, 5, 6]])         # shape (2, 3)
print(b + 10)                                  # scalar
print(b + np.array([10, 20, 30]))              # (3,) -> (1, 3) -> (2, 3)
print(b + np.array([[100], [200]]))            # (2, 1) -> (2, 3)
col = np.array([1, 2, 3]).reshape(3, 1)
row = np.array([10, 20])
print((col * row).shape)                       # (3,1) and (2,) -> (3, 2)
print(b + np.array([1, 2]))
[[11 12 13]
 [14 15 16]]
[[11 22 33]
 [14 25 36]]
[[101 102 103]
 [204 205 206]]
(3, 2)
Traceback (most recent call last):
  ...
ValueError: operands could not be broadcast together with shapes (2,3) (2,) 
Vectorisation and useful functions
rng = np.random.default_rng(42)            # reproducible random numbers
x = rng.normal(loc=20, scale=5, size=1000)
print(round(x.mean(), 2), round(x.std(), 2), x.shape)
z = (x - x.mean()) / x.std()               # standardise: no loop needed
print(round(z.mean(), 6) == 0, round(z.std(), 6))
print(np.percentile([1, 2, 3, 4, 5], 50), np.median([3, 1, 2, 10]), np.unique([3, 1, 3, 2]))
print(np.cumsum([1, 2, 3]), np.diff([1, 4, 9, 16]), np.round(np.sqrt([2, 9]), 3))
print(np.nan == np.nan, np.isnan(np.array([1, np.nan])), np.nanmean([1, np.nan, 3]))
19.86 4.94 (1000,)
True 1.0
3.0 2.5 [1 2 3]
[1 3 6] [3 5 7] [1.414 3.   ]
False [False  True] 2.0

Worked examples

Distances from points to a centre without loops (broadcasting (5, 2) - (2,)), then the closest point:

pts = np.array([[0, 0], [3, 4], [1, 1], [6, 8], [2, 2]])
centre = np.array([1, 1])
d = np.sqrt(((pts - centre) ** 2).sum(axis=1))
print(np.round(d, 3), d.argmin(), pts[d < 3])
[1.414 3.606 0.    8.602 1.414] 2 [[0 0]
 [1 1]
 [2 2]]

Exercises

  1. easy

    What are the shapes of np.zeros((3, 4)).sum(axis=0), np.arange(6).reshape(2, -1) and np.ones((2, 3)) + np.ones((4, 1, 3))?

    Show solution
    print(np.zeros((3, 4)).sum(axis=0).shape, np.arange(6).reshape(2, -1).shape,
          (np.ones((2, 3)) + np.ones((4, 1, 3))).shape)
    (4,) (2, 3) (4, 2, 3)

    Right-aligned: (2,3) vs (4,1,3) → (1,2,3) vs (4,1,3) → (4,2,3).

  2. 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.

    Show solution
    scores = np.array([[18, 25, 30], [15, 20, 28], [27, 17, 19]])
    print(scores.mean(axis=1).round(2), scores.max(axis=0), (scores >= 18).sum(axis=0))
    [24.33 21.   21.  ] [27 25 30] [2 2 3]
  3. medium

    Normalise each column of a matrix to the range [0, 1] (min-max scaling) with broadcasting.

    Show solution
    X = np.array([[1., 10.], [2., 30.], [3., 20.]])
    mn, mx = X.min(axis=0), X.max(axis=0)      # shape (2,)
    print((X - mn) / (mx - mn))
    [[0.  0. ]
     [0.5 1. ]
     [1.  0.5]]
  4. exam

    Predict the output:

    a = np.arange(6)
    b = a[1:4]
    b *= 10
    c = a[[0, 5]]
    c += 1
    print(a, a.dtype == b.dtype, c)
    Show solution
    [ 0 10 20 30  4  5] True [1 6]

    b is a view: a changes. c (fancy indexing) is a copy: a unchanged by c += 1.

  5. exam

    Predict the output:

    x = np.array([[1, 2], [3, 4]])
    print(x * x)
    print(x @ x)
    print(x.sum(axis=0) / x.sum())
    Show solution
    [[ 1  4]
     [ 9 16]]
    [[ 7 10]
     [15 22]]
    [0.4 0.6]

    * is element-wise; @ is the matrix product.

Self-check quiz

Common mistakes

  • Using and/or with arrays, or forgetting parentheses: a > 1 & a < 5 is parsed as a > (1 & a) < 5.
  • Mixing up axis=0 (per column) and axis=1 (per row).
  • Modifying a slice and being surprised the original changed (views).
  • Assigning floats into an int array (silently truncated).
  • Comparing with np.nan == np.nan (always False): use np.isnan.
  • Thinking * is matrix multiplication (it is @ or np.dot).

Remember

  • One dtype per array; operations are element-wise and vectorised.
  • Slices are views; masks and fancy indexing are copies.
  • Broadcasting: compare shapes from the right; equal or 1.
  • The axis you pass disappears: axis=0 → per column.

A3.12 pandas basics: Series, DataFrame, selection, missing data, CSV

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']
df = pd.DataFrame({
    "title": ["Alien", "Up", "Heat", "Coco", "Rocky"],
    "year": [1979, 2009, 1995, 2017, 1976],
    "genre": ["SciFi", "Anim", "Crime", "Anim", "Drama"],
    "rating": [8.5, 8.3, np.nan, 8.4, 8.1],
})
print(df)
print(df.shape, list(df.columns))
print(df.dtypes)
   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().

Selecting: [], loc, iloc
SyntaxSelectsNotes
df["year"]one column → Seriesdf.year also works for simple names
df[["title", "year"]]several columns → DataFramedouble brackets
df[mask]rows where the boolean Series is True
df.loc[rows, cols]by labelslices include the end label
df.iloc[rows, cols]by positionslices exclude the end (like Python)
print(df.loc[1, "title"], df.iloc[1, 0])
print(df.loc[1:3, ["title", "year"]])      # labels 1, 2, 3 (end INCLUDED)
print(df.iloc[1:3, 0:2])                   # positions 1, 2 (end excluded)
Up Up
  title  year
1    Up  2009
2  Heat  1995
3  Coco  2017
  title  year
1    Up  2009
2  Heat  1995
d2 = df.set_index("title")
print(d2.loc["Heat":"Rocky", "year"].tolist(), d2.iloc[-1]["genre"])
print(d2.loc[d2["year"] > 2000, "rating"])
[1995, 2017, 1976] Drama
title
Up      8.3
Coco    8.4
Name: rating, dtype: float64
Filtering, new columns, sorting
recent_anim = df[(df["year"] >= 2000) & (df["genre"] == "Anim")]
print(recent_anim["title"].tolist())
print(df[df["genre"].isin(["Crime", "Drama"])]["title"].tolist())
print(df.query("year < 1990 and rating > 8.2")["title"].tolist())
df["age"] = 2026 - df["year"]                         # vectorised new column
df["classic"] = np.where(df["year"] < 1990, "yes", "no")
print(df.sort_values("rating", ascending=False)[["title", "rating", "age", "classic"]])
['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.

Missing data
m = pd.DataFrame({"city": ["Milan", "Rome", None, "Turin"],
                  "temp": [14.0, np.nan, 11.0, np.nan]})
print(m.isna().sum())
print(m["temp"].mean(), m["temp"].sum(), m["temp"].count(), len(m))
print(m.dropna())
print(m.fillna({"city": "unknown", "temp": m["temp"].mean()}))
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

Useful read_csv options: sep=";", decimal="," (Italian files!), parse_dates=["date"], na_values=["?", "-"], encoding="latin-1", dtype={...}. Also pd.read_json, pd.read_excel (needs openpyxl), pd.read_sql.

Worked examples

A tiny cleaning pipeline (strings to numbers, duplicates, bad values):

raw = pd.DataFrame({"name": [" ann", "Bob ", "bob", "Cy"],
                    "age": ["31", "x", "25", "40"],
                    "score": ["7,5", "8,0", "8,0", "6,5"]})
clean = raw.copy()
clean["name"] = clean["name"].str.strip().str.title()
clean["age"] = pd.to_numeric(clean["age"], errors="coerce")       # "x" -> NaN
clean["score"] = clean["score"].str.replace(",", ".").astype(float)
clean = clean.drop_duplicates(subset="name", keep="last")
print(clean)
print(clean.dtypes)
  name   age  score
0  Ann  31.0    7.5
2  Bob  25.0    8.0
3   Cy  40.0    6.5
name         str
age      float64
score    float64
dtype: object

Exercises

  1. easy

    With the df of movies above, select the titles of movies with rating above 8.3, as a list.

    Show solution
    print(df.loc[df["rating"] > 8.3, "title"].tolist())
    ['Alien', 'Coco']

    The NaN rating of Heat compares as False, so it is excluded.

  2. medium

    Predict the output:

    t = pd.DataFrame({"v": [5, 6, 7, 8]}, index=[10, 20, 30, 40])
    print(t.loc[20:30, "v"].tolist(), t.iloc[1:3, 0].tolist(), t.iloc[-1, 0], t.loc[40, "v"])
    Show solution
    [6, 7] [6, 7] 8 8

    loc[20:30] includes 30; iloc[1:3] is positions 1 and 2. Here both give the same rows.

  3. 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.)

    Show solution
    filled = df["rating"].fillna(df.groupby("genre")["rating"].transform("median"))
    filled = filled.fillna(df["rating"].median())
    print(filled.round(2).tolist())
    [8.5, 8.3, 8.35, 8.4, 8.1]

    "Crime" has no other rating, so the overall median (8.35) is used.

  4. 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.

  5. 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.
  • isna/dropna/fillna; aggregations skip NaN.

A3.13 pandas wrangling: groupby, merge, apply, pivot/melt

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).

import pandas as pd
import numpy as np
sales = pd.DataFrame({
    "city":  ["Milan", "Rome", "Milan", "Rome", "Turin", "Milan"],
    "month": ["Jan", "Jan", "Feb", "Feb", "Jan", "Jan"],
    "units": [10, 7, 12, 3, 5, 8],
    "price": [2.0, 2.5, 2.0, 3.0, 1.5, 2.2],
})
sales["revenue"] = sales["units"] * sales["price"]
print(sales.groupby("city")["units"].sum())
print(sales.groupby("city").agg(n=("units", "size"), total=("revenue", "sum"),
                                avg_price=("price", "mean")).round(2))
print(sales.groupby(["city", "month"], as_index=False)["units"].sum())
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.

sales["city_avg_units"] = sales.groupby("city")["units"].transform("mean")
sales["share"] = (sales["units"] / sales.groupby("city")["units"].transform("sum")).round(2)
print(sales[["city", "units", "city_avg_units", "share"]])
    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).

movies = pd.DataFrame({"mid": [1, 2, 3], "title": ["Alien", "Up", "Heat"]})
ratings = pd.DataFrame({"mid": [1, 1, 2, 4], "stars": [5, 4, 3, 2]})
print(pd.merge(movies, ratings, on="mid"))
print(pd.merge(movies, ratings, on="mid", how="left"))
print(len(pd.merge(movies, ratings, on="mid", how="outer")))
print(pd.concat([movies, movies.head(1)], ignore_index=True).shape)
   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.

movies["code"] = movies["title"].str.upper().str[:3]
movies["len"] = movies["title"].map(len)
movies["kind"] = movies["mid"].map({1: "scifi", 2: "anim"})          # missing key -> NaN
movies["label"] = movies.apply(lambda r: f'{r["title"]}#{r["mid"]}', axis=1)
print(movies)
dates = pd.to_datetime(pd.Series(["2026-11-30", "2026-12-01"]))
print(dates.dt.day_name().tolist(), (dates.iloc[1] - dates.iloc[0]).days)
   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.

wide = sales.pivot_table(index="city", columns="month", values="units",
                         aggfunc="sum", fill_value=0)
print(wide)
long = wide.reset_index().melt(id_vars="city", var_name="month", value_name="units")
print(long.sort_values(["city", "month"]).reset_index(drop=True))
month  Feb  Jan
city           
Milan   12   18
Rome     3    7
Turin    0    5
    city month  units
0  Milan   Feb     12
1  Milan   Jan     18
2   Rome   Feb      3
3   Rome   Jan      7
4  Turin   Feb      0
5  Turin   Jan      5

Worked examples

Top film per genre and genres with at least 2 films (like SQL HAVING COUNT(*) >= 2):

films = pd.DataFrame({"title": ["A", "B", "C", "D", "E"],
                      "genre": ["x", "y", "x", "x", "y"],
                      "score": [7.1, 8.0, 9.2, 6.5, 8.0]})
best = films.loc[films.groupby("genre")["score"].idxmax()]
print(best)
counts = films.groupby("genre").size()
print(counts[counts >= 3].index.tolist())
print(films.groupby("genre").filter(lambda g: g["score"].mean() > 7.6)["title"].tolist())
  title genre  score
2     C     x    9.2
1     B     y    8.0
['x']
['B', 'E']

Exercises

  1. easy

    Using sales, compute the total revenue per month, sorted from highest to lowest.

    Show solution
    print(sales.groupby("month")["revenue"].sum().sort_values(ascending=False))
    month
    Jan    62.6
    Feb    33.0
    Name: revenue, dtype: float64
  2. medium

    Predict the number of rows of each merge:

    L = pd.DataFrame({"k": [1, 1, 2, 3]})
    R = pd.DataFrame({"k": [1, 1, 1, 4]})
    print(len(L.merge(R, on="k")), len(L.merge(R, on="k", how="left")),
          len(L.merge(R, on="k", how="outer")))
    Show solution
    6 8 9

    Inner: key 1 appears 2 × 3 = 6 times. Left: 6 + one row for 2 + one for 3 = 8. Outer: 8 + one row for 4 = 9.

  3. medium

    Add a column to films with each film's score minus the mean score of its genre.

    Show solution
    films["vs_genre"] = (films["score"] - films.groupby("genre")["score"].transform("mean")).round(2)
    print(films)
      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
  4. 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.

  5. 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.
  • merge(how="inner|left|right|outer") = SQL joins; concat stacks.
  • 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
  1. Structure: shape, dtypes, head(), info(). Are the types right (dates, numbers)?
  2. Quality: missing values isna().sum(), duplicates duplicated().sum(), impossible values (negative ages).
  3. Univariate: describe() for numeric columns (count, mean, std, min, quartiles, max), value_counts() for categorical ones; histograms and box plots.
  4. Bivariate: corr() and scatter plots (numeric–numeric), groupby + bar/box plots (category–numeric), pd.crosstab (category–category).
  5. Conclusions: state findings with numbers, and the limits (missing data, correlation is not causation).
import pandas as pd
import numpy as np
rng = np.random.default_rng(0)
n = 200
df = pd.DataFrame({
    "hours": rng.uniform(0, 10, n).round(1),
    "group": rng.choice(["A", "B", "C"], n, p=[0.5, 0.3, 0.2]),
})
df["score"] = (50 + 4 * df["hours"] + rng.normal(0, 6, n)).round(1)
df.loc[3, "score"] = 250            # an obvious data-entry error
print(df.describe().round(2))
print(df["group"].value_counts())
print(df["group"].value_counts(normalize=True).round(2).to_dict())
        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.

q1, q3 = df["score"].quantile([0.25, 0.75])
iqr = q3 - q1
mask = (df["score"] < q1 - 1.5 * iqr) | (df["score"] > q3 + 1.5 * iqr)
print(df[mask])
clean = df[~mask]
print(round(clean["hours"].corr(clean["score"]), 3), round(df["hours"].corr(df["score"]), 3))
print(clean.groupby("group")["score"].agg(["mean", "median", "std"]).round(1))
   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
QuestionPlotmatplotlib / seaborn
Distribution of one numeric variablehistogram, box plotax.hist, sns.histplot, sns.boxplot
Relationship of two numeric variablesscatter (+ trend line)ax.scatter, sns.scatterplot, sns.regplot
Evolution over timeline plotax.plot, sns.lineplot
Compare a quantity across categoriesbar chart (or box plot for distributions)ax.bar, sns.barplot
Many pairwise correlationsheatmapsns.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())
group score

Worked examples

Category–category relation with crosstab:

clean = clean.assign(passed=clean["score"] >= 70)
print(pd.crosstab(clean["group"], clean["passed"], normalize="index").round(2))
passed  False  True 
group               
A        0.41   0.59
B        0.48   0.52
C        0.45   0.55

Exercises

  1. 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).

  2. 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).

  3. 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
  4. 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.

    Marking idea (assumption): loading/cleaning 1, appropriate summaries 1, appropriate plots 1, correlation/quantification 1, pitfalls 2.

Self-check quiz

Common mistakes

  • Reporting only the mean for skewed data (use median and quantiles too).
  • Plots without axis labels/units, or with too many categories in a pie chart.
  • Concluding causation from correlation.
  • Removing outliers silently: always say how many and why.
  • Calling df.corr() on text columns (use numeric_only=True).

Remember

  • EDA: structure → quality → univariate → bivariate → conclusions.
  • describe, value_counts, corr, crosstab, IQR rule.
  • fig, ax = plt.subplots(); title, labels, legend; seaborn for DataFrames.
  • Pick the plot from the question: distribution, relation, time, comparison.

A3.15 scikit-learn: classification, clustering, performance metrics

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
(150, 4) {0: 50, 1: 50, 2: 50}
(105, 4) (45, 4) [2 1 2 1 2 2 1 1]
0.978

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
ModelIdeaKey hyperparameterNote
k-nearest neighbours (KNeighborsClassifier)predict the majority class among the k closest training pointsn_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_depthno 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.

\[ \text{accuracy} = \frac{TP+TN}{TP+TN+FP+FN},\quad \text{precision} = \frac{TP}{TP+FP},\quad \text{recall} = \frac{TP}{TP+FN},\quad F_1 = \frac{2\,\text{precision}\cdot\text{recall}}{\text{precision}+\text{recall}} \]

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.

from sklearn.metrics import (accuracy_score, precision_score, recall_score,
                             f1_score, confusion_matrix, classification_report)
y_true = [1, 1, 1, 1, 0, 0, 0, 0, 0, 0]
y_hat  = [1, 1, 1, 0, 1, 1, 0, 0, 0, 0]
print(confusion_matrix(y_true, y_hat))            # [[TN FP] [FN TP]]
print(accuracy_score(y_true, y_hat), precision_score(y_true, y_hat),
      recall_score(y_true, y_hat), round(f1_score(y_true, y_hat), 4))
[[4 2]
 [1 3]]
0.7 0.6 0.75 0.6667

By hand: TP = 3, FN = 1, FP = 2, TN = 4. Accuracy = 7/10 = 0.7; precision = 3/5 = 0.6; recall = 3/4 = 0.75; \(F_1 = 2(0.6)(0.75)/(1.35) = 0.6667\).

Tip: why accuracy can lie

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.

print(classification_report(y_test, models["logistic"].predict(X_test), digits=3))
              precision    recall  f1-score   support

           0      1.000     1.000     1.000        15
           1      0.824     0.933     0.875        15
           2      0.923     0.800     0.857        15

    accuracy                          0.911        45
   macro avg      0.916     0.911     0.911        45
weighted avg      0.916     0.911     0.911        45

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"]))
2 222.4 0.582
3 139.8 0.46
4 114.1 0.387
5 90.8 0.346
species   0   1   2
cluster            
0         0  39  14
1        50   0   0
2         0  11  36

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))
|--- petal width (cm) <= 0.70
|   |--- class: 0
|--- petal width (cm) >  0.70
|   |--- petal width (cm) <= 1.55
|   |   |--- class: 1
|   |--- petal width (cm) >  1.55
|   |   |--- class: 2

[1] [[0.017 0.757 0.225]]

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

  1. easy

    A spam filter checks 100 emails: 20 are spam. It flags 18 emails, of which 15 are spam. Compute precision, recall, accuracy, F1.

    Show solution

    TP = 15, FP = 3, FN = 5, TN = 77. Precision = 15/18 ≈ 0.833; recall = 15/20 = 0.75; accuracy = 92/100 = 0.92; F1 = 2·0.833·0.75/(0.833+0.75) ≈ 0.789.

    y_true = [1] * 20 + [0] * 80
    y_hat = [1] * 15 + [0] * 5 + [1] * 3 + [0] * 77
    print(round(precision_score(y_true, y_hat), 3), recall_score(y_true, y_hat),
          accuracy_score(y_true, y_hat), round(f1_score(y_true, y_hat), 3))
    0.833 0.75 0.92 0.789
  2. medium

    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.

  3. 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.

  4. 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.

  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
  1. A Streamlit app is a normal Python script (app.py) run with streamlit run app.py; it opens in the browser.
  2. 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.
  3. Widgets return their current value: year = st.slider("Year", 1950, 2020, 2000).
  4. Expensive work (loading a CSV, calling an API) must be cached with @st.cache_data, otherwise it repeats at every rerun.
  5. 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))
FunctionShows
st.title, st.header, st.markdown, st.writetext (st.write renders almost anything, including DataFrames)
st.dataframe, st.table, st.metrictables and KPI numbers
st.selectbox, st.multiselect, st.slider, st.checkbox, st.button, st.file_uploaderwidgets (return values)
st.line_chart, st.bar_chart, st.pyplot(fig), st.plotly_chartcharts
st.sidebar.*, st.columns, st.tabslayout
st.error, st.warning, st.stop()messages, e.g. after a failed data load

Tip: notebook or Streamlit?

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

  1. 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).

  2. 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.

  3. 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.

  4. 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
RuleDetail
WhoIndividual (no groups). You may propose your own idea by email to prof. Montanelli and Dr. Picascia.
Deadline30 Nov 2026, 23:59 CET, based on the last GitHub commit. Firm; later updates are ignored.
SubmissionGitHub repository (may be private) + invite the instructor as a collaborator.
RegistrationOnline form open "for a couple of weeks in November"; exact dates check (NOT STATED; announced during the course).
EvaluationDec 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.
FormatJupyter notebook or Streamlit app (optional but appreciated). It must import and use the project code. Example of a complete project: vgrank.
GradeReplaces the Python module result. If not satisfied, reject immediately and take the written Python exam in an ordinary session from January 2027.
IncompatibilityIn December you cannot take both the project and the written Python exam. You can still take R and Data Management written in December.
FAQPick 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)
WhenGoal
Oct 30 - Nov 2Choose a proposal; explore the data in a scratch notebook; create the repo with README skeleton, .gitignore, requirements.txt; invite the instructor.
Nov 3 - 9Data loading module with clean error handling (criterion 3); download script; first cleaning functions.
Nov 10 - 16Data manipulation with pandas (4) and numerical/statistical work with NumPy/SciPy (5). Register when the form opens (check dates).
Nov 17 - 23Plots (6); the notebook or Streamlit app that tells the story (7).
Nov 24 - 28Docstrings, PEP8 (run Black/Pylint), README final, fresh-clone test, rehearse the presentation.
Nov 29 - 30Only small fixes. Push and verify on github.com well before 23:59 CET.
The 7 criteria as a self-review checklist
  1. GitHub usage
    • [ ] README explains: what the project is, data sources (with links/licences), libraries, setup and run instructions.
    • [ ] .gitignore excludes data, virtual env, caches, checkpoints, secrets.
    • [ ] Many small commits over several weeks, each with a descriptive message.
  2. Project organisation
    • [ ] Code split into modules (src/<package>/), not one giant notebook.
    • [ ] Every function/class has a docstring (purpose, parameters, return value).
    • [ ] PEP8 style, clear names (load_ratings, not f2), no dead code.
    • [ ] requirements.txt with versions.
  3. Input/Output
    • [ ] Loads at least one external source (CSV, JSON, Excel, database or API).
    • [ ] Loading errors handled cleanly (missing file, network error, bad format) with a clear message.
    • [ ] Results saved or displayed (tables, plots).
  4. Data manipulation: [ ] cleaning, transforming, reshaping, merging with pandas; every cleaning decision justified.
  5. Scientific computing: [ ] meaningful NumPy/SciPy computation (vectorised statistics, trends, distances, tests...), not just df.mean().
  6. Visualisation: [ ] clear, informative plots: titles, labelled axes with units, legends, sensible scales.
  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)
ProposalDataWatch out forIdeas that hit the criteria
1. Powering the PlanetOur World in Data energy and CO2 CSVs (GitHub); optionally World Bank APIRows for aggregates (World, continents, income groups) mixed with countries: filter them; totals vs per-capita; many missing early yearsEnergy mix over time (stacked area), GDP vs CO2 "decoupling", growth rates and trends with NumPy/SciPy, comparing per-capita figures
2. Weather ReportOpen-Meteo Historical Weather API (free, from 1940) + its geocoding for coordinatesAPI limits: download once, cache files in data/ (ignored); handle network errors; long ranges may need several requestsTrend per decade (linear regression with SciPy), anomalies vs a baseline period, extremes (hot days per year), seasonality plots, city comparison
3. Around the Globe QuizWorld countries dataset; World Bank APIIt is a program, not only an analysis: still cover pandas, NumPy and plots; reproducible randomness (seed); four distinct answersClasses (Question, Quiz) with docstrings; difficulty of distractors from numeric similarity; difficulty-weighted score; plots of score statistics
4. Page Turnersgoodbooks-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, BoxFastF1 library (lap timing)Enable the FastF1 cache folder and ignore it; lap times are timedeltas; pit-stop / safety-car laps distort averagesStint and tyre degradation analysis (slopes with NumPy), teammate comparison, lap-time distributions
6. Douze PointsEurovision voting history since 1956Scoring rules changed over the years (normalise points); countries appear/disappear or change name; finals vs semi-finalsVoting matrices (NumPy), "friendship" beyond expectation, neighbour bias, heatmaps, time evolution
7. Rate My RecipeFood.com (180,000+ recipes, 700,000+ reviews)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 ratingA ranking that accounts for the number of ratings (justify and explain the formula yourself), comparison with naive ranking, nutrition/ingredient analysis
8. Island HopperOurAirports datasetFilter 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)
  1. Question (30 s): what you wanted to find out and why.
  2. Data (1 min): source, size, how it is loaded (show the loading function and its error handling).
  3. Preparation (1-2 min): the main cleaning/reshaping decisions and their effect (e.g. "removed 3% of rows with missing temperature").
  4. Analysis and results (3 min): 2-3 key plots, each with one sentence of conclusion; the NumPy/SciPy computation behind them.
  5. Code tour (1-2 min): repository structure, one module, how the notebook/app imports it, commit history.
  6. 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

  1. 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).

  2. 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.
  3. 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.

  4. 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.
  • Seven criteria: GitHub, organisation, I/O, manipulation, scientific computing, visualisation, presentation.
  • Notebook/app imports your package; data ignored and scripted.
  • Reject the grade immediately if unhappy → written Python from January 2027.

Unit B · Statistical Theory & Mathematics · written test, duration check

B1. Statistical Theory: probability, estimation, intervals, tests

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)\).

Everything else follows from the axioms:

  • \(P(A^c)=1-P(A)\), \(P(\emptyset)=0\), \(P(A)\le 1\).
  • If \(A\subseteq B\) then \(P(A)\le P(B)\).
  • Inclusion-exclusion: \(P(A\cup B)=P(A)+P(B)-P(A\cap B)\).
  • Bonferroni: \(P(A\cap B)\ge P(A)+P(B)-1\). Boole: \(P(\bigcup A_i)\le\sum 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 replacementwith 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

  1. 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)\)).

  2. easy

    Three fair coins are tossed. Find the probability of at least one head.

    Show solution

    Complement: \(1-P(\text{no head})=1-(1/2)^3=7/8\).

  3. medium

    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\).

  4. 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.

  5. 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.

  6. 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.
  • Independence: \(P(A\cap B)=P(A)P(B)\). Counting table: \(n^r\), \(n!/(n-r)!\), \(\binom nr\), \(\binom{n+r-1}{r}\).

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)\)).

X d needle, length ℓ
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

  1. easy

    \(f(x)=cx\) for \(x=1,2,3,4\). Find \(c\), \(P(X\ge3)\) and \(F(2.5)\).

    Show solution

    \(c(1+2+3+4)=1\Rightarrow c=1/10\). \(P(X\ge3)=(3+4)/10=0.7\). \(F(2.5)=P(X\le2)=0.3\).

  2. easy

    \(X\sim\text{Exp}(\lambda)\) with pdf \(\lambda e^{-\lambda x}\), \(x\gt0\). Find the CDF and the median.

    Show solution

    \(F(x)=\int_0^x\lambda e^{-\lambda t}dt=1-e^{-\lambda x}\). Median \(m\): \(1-e^{-\lambda m}=1/2\Rightarrow m=\ln2/\lambda\).

  3. medium

    Is \(F(x)=\dfrac{1}{1+e^{-x}}\) a CDF? If yes find the pdf.

    Show solution

    Limits 0 and 1, increasing (derivative positive), continuous: yes (logistic distribution). \(f(x)=\dfrac{e^{-x}}{(1+e^{-x})^2}\).

  4. medium

    \(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\).

  5. hard

    Buffon: \(\ell=1\), \(d=2\). (a) Find \(P(\text{cross})\). (b) In 1000 drops you see 310 crossings. Estimate \(\pi\).

    Show solution

    (a) \(2\cdot1/(\pi\cdot2)=1/\pi\approx0.3183\). (b) \(\hat\pi=2\ell n/(dC)=2000/620\approx3.226\).

  6. exam

    \(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.

Remember

  • CDF: limits 0/1, non-decreasing, right-continuous. pdf \(=F'\); pmf \(=\) jump sizes.
  • \(f_Y(y)=f_X(g^{-1}(y))\,|dg^{-1}(y)/dy|\) for monotone \(g\); otherwise use the CDF method.
  • Buffon: \(P(\text{cross})=2\ell/(\pi d)\), \(\hat\pi=2\ell n/(dC)\).

B1.3 Expectation, variance, moments and the MGF

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

  1. 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\).

  2. easy

    \(f(x)=2x\) on \((0,1)\). Find \(EX\) and \(\text{Var}X\).

    Show solution

    \(EX=\int_0^1 2x^2dx=2/3\); \(EX^2=\int_0^12x^3dx=1/2\); \(\text{Var}=1/2-4/9=1/18\).

  3. medium

    Poisson\((\lambda)\): \(f(x)=e^{-\lambda}\lambda^x/x!\). Show \(M(t)=\exp(\lambda(e^t-1))\) and deduce mean and variance.

    Show solution

    \(M(t)=e^{-\lambda}\sum_x(\lambda e^t)^x/x!=e^{-\lambda}e^{\lambda e^t}\). \(M'(t)=\lambda e^tM(t)\Rightarrow EX=\lambda\). \(M''(t)=(\lambda e^t+\lambda^2e^{2t})M(t)\Rightarrow EX^2=\lambda+\lambda^2\), \(\text{Var}=\lambda\).

  4. medium

    \(X_1,\dots,X_n\) independent, \(X_i\sim\text{Poisson}(\lambda_i)\). Use MGFs to find the distribution of \(\sum X_i\).

    Show solution

    \(\prod_i e^{\lambda_i(e^t-1)}=e^{(\sum\lambda_i)(e^t-1)}\): Poisson\((\sum\lambda_i)\) by uniqueness.

  5. hard

    Which distribution has MGF \(M(t)=\frac13+\frac23e^t\)? And \(M(t)=e^{2t+8t^2}\)?

    Show solution

    Bernoulli\((2/3)\). Normal MGF is \(e^{\mu t+\sigma^2t^2/2}\): \(\mu=2\), \(\sigma^2/2=8\Rightarrow\sigma^2=16\): \(N(2,16)\).

  6. exam

    \(X\) has mean 10 and standard deviation 2. (a) Bound \(P(|X-10|\ge5)\) with Chebyshev. (b) If in fact \(X\sim N(10,4)\), what is the exact value?

    Show solution

    (a) \(\le 4/25=0.16\). (b) \(P(|Z|\ge2.5)=2(1-\Phi(2.5))\approx0.0124\). Chebyshev is valid for every distribution, so it is often loose.

Self-check quiz

Common mistakes

  • \(E[g(X)]\neq g(E[X])\) in general (e.g. \(E[1/X]\neq1/E[X]\)). Equality holds for linear \(g\).
  • \(\text{Var}(X-Y)=\text{Var}X+\text{Var}Y\) (plus covariance terms), not a difference.
  • Using the MGF outside the interval where it exists (Exp: only \(t\lt\lambda\)).

Remember

  • \(\text{Var}X=EX^2-(EX)^2\); \(\text{Var}(aX+b)=a^2\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.

Explanation

Table of common families
Familypmf / pdfSupportMeanVarianceMGF
Bernoulli\((p)\)\(p^x(1-p)^{1-x}\)\(\{0,1\}\)\(p\)\(p(1-p)\)\(1-p+pe^t\)
Binomial\((n,p)\)\(\binom nx p^x(1-p)^{n-x}\)\(0,\dots,n\)\(np\)\(np(1-p)\)\((1-p+pe^t)^n\)
Poisson\((\lambda)\)\(e^{-\lambda}\lambda^x/x!\)\(0,1,2,\dots\)\(\lambda\)\(\lambda\)\(e^{\lambda(e^t-1)}\)
Geometric\((p)\)\(p(1-p)^{x-1}\)\(1,2,\dots\)\(1/p\)\((1-p)/p^2\)\(\frac{pe^t}{1-(1-p)e^t}\)
Uniform\((a,b)\)\(\frac1{b-a}\)\((a,b)\)\(\frac{a+b}2\)\(\frac{(b-a)^2}{12}\)\(\frac{e^{bt}-e^{at}}{(b-a)t}\)
Exponential (rate \(\lambda\))\(\lambda e^{-\lambda x}\)\(x\gt0\)\(1/\lambda\)\(1/\lambda^2\)\(\frac{\lambda}{\lambda-t}\)
Gamma\((\alpha,\beta)\), scale \(\beta\)\(\frac{x^{\alpha-1}e^{-x/\beta}}{\Gamma(\alpha)\beta^\alpha}\)\(x\gt0\)\(\alpha\beta\)\(\alpha\beta^2\)\((1-\beta t)^{-\alpha}\)
Normal\((\mu,\sigma^2)\)\(\frac1{\sqrt{2\pi}\sigma}e^{-(x-\mu)^2/(2\sigma^2)}\)\(\mathbb R\)\(\mu\)\(\sigma^2\)\(e^{\mu t+\sigma^2t^2/2}\)
Beta\((\alpha,\beta)\)\(\frac{x^{\alpha-1}(1-x)^{\beta-1}}{B(\alpha,\beta)}\)\((0,1)\)\(\frac{\alpha}{\alpha+\beta}\)\(\frac{\alpha\beta}{(\alpha+\beta)^2(\alpha+\beta+1)}\)(no simple form)
Cauchy\((\theta,\sigma)\)\(\frac{1}{\pi\sigma}\frac{1}{1+((x-\theta)/\sigma)^2}\)\(\mathbb R\)does not existdoes not existdoes not exist

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\).

Example 2 (Binomial, \(n\) known). \(\binom nx(1-p)^n\exp\!\big(x\log\frac{p}{1-p}\big)\): natural parameter = log-odds, \(t(x)=x\).

Example 3 (Normal, both unknown). \(f=\frac{1}{\sqrt{2\pi}\sigma}e^{-\mu^2/(2\sigma^2)}\exp\!\Big(-\frac{1}{2\sigma^2}x^2+\frac{\mu}{\sigma^2}x\Big)\): \(k=2\), \(t_1=x^2\), \(t_2=x\), \(w_1=-1/(2\sigma^2)\), \(w_2=\mu/\sigma^2\).

Example 4 (memoryless). Exp\((\lambda)\): \(P(X\gt s+t\mid X\gt s)=e^{-\lambda(s+t)}/e^{-\lambda s}=e^{-\lambda t}\).

Exercises

  1. easy

    \(X\sim\text{Bin}(10,0.3)\). Find \(P(X=2)\), \(EX\), \(\text{Var}X\).

    Show solution

    \(\binom{10}{2}0.3^2\,0.7^8=45\cdot0.09\cdot0.05765\approx0.2335\). \(EX=3\), \(\text{Var}X=2.1\).

  2. easy

    \(X\sim\text{Poisson}(2)\). Find \(P(X\le1)\).

    Show solution

    \(e^{-2}(1+2)=3e^{-2}\approx0.4060\).

  3. medium

    Lifetimes are Exp with rate \(\lambda=0.5\). Given that a bulb has worked 3 hours, what is the probability it works more than 5 hours in total?

    Show solution

    Memoryless: \(P(X\gt5\mid X\gt3)=P(X\gt2)=e^{-0.5\cdot2}=e^{-1}\approx0.368\).

  4. medium

    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.

  5. 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.

  6. 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]\).

    Show solution

    \(f=\theta\exp((\theta-1)\log x)\mathbb 1_{(0,1)}\). Take \(h=\mathbb 1_{(0,1)}(x)/x\), \(c=\theta\), \(w=\theta\), \(t=\log x\) (since \(x^{\theta-1}=e^{\theta\log x}/x\)). Theorem: \(E[1\cdot\log X]=-\frac{d}{d\theta}\log\theta=-1/\theta\). Check: \(-\log X\sim\text{Exp}(\text{rate }\theta)\), mean \(1/\theta\).

Self-check quiz

Common mistakes

  • Mixing rate and scale parametrisations of exponential/gamma (mean \(1/\lambda\) vs \(\beta\)).
  • Calling Uniform\((0,\theta)\) an exponential family: the support depends on \(\theta\).
  • Forgetting that the Cauchy has no mean, so \(\bar X\) is useless for it (project topic!).

Remember

  • Learn the table: mean, variance, MGF of the 9 families.
  • Exponential family: \(h(x)c(\theta)\exp(\sum w_i(\theta)t_i(x))\), support free of \(\theta\).
  • Memoryless: exponential (continuous), geometric (discrete).

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.

\(\text{Var}(aX+bY)=a^2\text{Var}X+b^2\text{Var}Y+2ab\,\text{Cov}(X,Y)\). \(\text{Cov}(aX+b,cY+d)=ac\,\text{Cov}(X,Y)\).

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).

Exercises

  1. easy

    Joint pmf: \(P(0,0)=0.1\), \(P(0,1)=0.3\), \(P(1,0)=0.2\), \(P(1,1)=0.4\). Find marginals, check independence, compute \(\text{Cov}(X,Y)\).

    Show solution

    \(P(X=1)=0.6\), \(P(Y=1)=0.7\). \(P(0,0)=0.1\neq0.4\cdot0.3=0.12\): not independent. \(E[XY]=P(1,1)=0.4\); \(\text{Cov}=0.4-0.6\cdot0.7=-0.02\).

  2. easy

    \(\text{Var}X=4\), \(\text{Var}Y=1\), \(\text{Cov}(X,Y)=0.5\). Find \(\text{Var}(X-2Y)\) and \(\rho\).

    Show solution

    \(4+4\cdot1-2\cdot2\cdot0.5=6\). \(\rho=0.5/(2\cdot1)=0.25\).

  3. medium

    \(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)\)).

  4. medium

    In Example 2 compute \(\text{Var}X\) with the total-variance formula.

    Show solution

    \(E[\text{Var}(X\mid Y)]=E[Yp(1-p)]=\lambda p(1-p)\); \(\text{Var}(E[X\mid Y])=\text{Var}(pY)=p^2\lambda\). Sum: \(\lambda p(1-p)+\lambda p^2=\lambda p\) (consistent with Poisson\((\lambda p)\)).

  5. hard

    \(X\sim U(-1,1)\), \(Y=X^2\). Show \(\text{Cov}(X,Y)=0\) but \(X,Y\) are dependent.

    Show solution

    \(EX=0\), \(E[XY]=E[X^3]=0\) (odd function, symmetric density), so \(\text{Cov}=0\). Dependent: \(P(Y\gt\frac14\mid |X|\gt\frac12)=1\neq P(Y\gt\frac14)=\frac12\).

  6. exam

    \(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)\).

Remember

  • Marginal = sum/integrate out; conditional = joint / marginal.
  • Independent iff joint factorises on a product support.
  • \(\text{Cov}=E[XY]-EXEY\); \(\rho=\text{Cov}/(\sigma_X\sigma_Y)\); \(E X=E[E(X\mid Y)]\).

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\))

\(E\bar X=\mu\), \(\text{Var}\bar X=\sigma^2/n\), \(E S^2=\sigma^2\). Useful identity: \(\sum(X_i-\bar X)^2=\sum X_i^2-n\bar X^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\).

# quantiles in R (lower tail by default)
qchisq(0.95, df = 9)   # 16.919
qt(0.975, df = 8)      # 2.306
qf(0.95, 10, 10)       # 2.978
pnorm(1, lower.tail = FALSE)  # 0.1587

The values in the comments were checked with scipy (R itself was not run here).

Exercises

  1. easy

    \(X_i\) i.i.d. Bernoulli\((p)\). Find \(E\bar X\) and \(\text{Var}\bar X\).

    Show solution

    \(p\) and \(p(1-p)/n\).

  2. easy

    \(n=25\) from \(N(\mu,100)\). Find \(P(|\bar X-\mu|\lt4)\).

    Show solution

    \(\sigma/\sqrt n=2\): \(P(|Z|\lt2)\approx0.9545\).

  3. medium

    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.

  4. 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}\).

    Show solution

    \((X_1+X_2)/\sqrt2\sim N(0,1)\), \(V=X_3^2+X_4^2\sim\chi^2_2\), independent. \(T=\frac{(X_1+X_2)/\sqrt2}{\sqrt{V/2}}\sim t_2\). \(X_1^2/X_2^2=\frac{\chi^2_1/1}{\chi^2_1/1}\sim F_{1,1}\).

  5. hard

    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\).

  6. 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.
  • Normal sample: \(\bar X\sim N(\mu,\sigma^2/n)\), \((n-1)S^2/\sigma^2\sim\chi^2_{n-1}\), independent, \(\frac{\bar X-\mu}{S/\sqrt n}\sim t_{n-1}\).
  • \(t=Z/\sqrt{V/p}\), \(F=(U/p)/(V/q)\).

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\).

x <- c(3, 1, 4, 1, 5)
Fn <- ecdf(x)
Fn(c(0.5, 1, 3.5, 5))   # 0.0 0.4 0.6 1.0
plot(Fn)                 # step plot
sort(x); median(x)       # 1 1 3 4 5 ; 3

Exercises

  1. easy

    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\).

  2. 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\).

  3. 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}\).

  4. 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.)

  5. 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)\).

  6. 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)\).
  • \(nF_n(x)\sim\text{Bin}(n,F(x))\): unbiased, variance \(F(1-F)/n\); Glivenko-Cantelli.

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

  1. easy

    Poisson\((\lambda)\) sample. Find a sufficient statistic.

    Show solution

    \(f(\mathbf x\mid\lambda)=e^{-n\lambda}\lambda^{\sum x_i}\cdot\frac1{\prod x_i!}\): \(g=e^{-n\lambda}\lambda^t\), \(h=1/\prod x_i!\). \(T=\sum X_i\).

  2. easy

    Exponential (rate \(\lambda\)) sample. Find a sufficient statistic.

    Show solution

    \(\lambda^ne^{-\lambda\sum x_i}\) (times \(\mathbb 1\{x_{(1)}\gt0\}\)): \(T=\sum X_i\).

  3. medium

    \(f(x\mid\theta)=\theta x^{\theta-1}\), \(0\lt x\lt1\). Find a sufficient statistic. Also for Gamma\((\alpha,\beta)\) with both unknown.

    Show solution

    \(\theta^n\big(\prod x_i\big)^{\theta-1}\): \(T=\prod X_i\) (equivalently \(\sum\log X_i\)). Gamma: \(\frac{1}{\Gamma(\alpha)^n\beta^{n\alpha}}\big(\prod x_i\big)^{\alpha-1}e^{-\sum x_i/\beta}\): \(T=(\prod X_i,\sum X_i)\).

  4. medium

    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.

  5. 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).

  6. 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)\).
  • Exponential family: \(\sum t_j(X_i)\). Uniform\((0,\theta)\): \(X_{(n)}\). Shift models: \(X_{(1)}\).
  • 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

  1. easy

    Find the MoM estimator for Poisson\((\lambda)\) and for Exp(rate \(\lambda\)).

    Show solution

    Poisson: \(EX=\lambda\Rightarrow\hat\lambda=\bar X\). Exp: \(EX=1/\lambda\Rightarrow\hat\lambda=1/\bar X\).

  2. easy

    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.)

  3. 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.

    Show solution

    \(EX=\int_0^1\theta x^\theta dx=\frac{\theta}{\theta+1}\). \(\bar X=\frac{\theta}{\theta+1}\Rightarrow\hat\theta=\frac{\bar X}{1-\bar X}\). \(\bar x=0.575\): \(\hat\theta=0.575/0.425\approx1.353\).

  4. medium

    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\).

  5. 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).

  6. 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.

    Show solution

    Minimise \(\sum(y_i-\beta x_i)^2\): derivative \(-2\sum x_i(y_i-\beta x_i)=0\Rightarrow\hat\beta=\frac{\sum x_iy_i}{\sum x_i^2}=\frac{2.1+7.8+18.6+31.2}{30}=\frac{59.7}{30}=1.99\).

Self-check quiz

Common mistakes

  • Using \(S^2\) (divide by \(n-1\)) in MoM: the second sample moment gives the \(1/n\) version. (Some teachers accept both; check.)
  • Forgetting that MoM may give values outside the parameter space; mention it when discussing properties.
  • Matching moments that do not depend on \(\theta\) (e.g. \(E X=0\) for a symmetric scale family; then use \(EX^2\)).

Remember

  • MoM: solve \(\bar X=E_\theta X\) (and \(\frac1n\sum X_i^2=E_\theta X^2\)).
  • 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).

  1. Write the log-likelihood \(\ell(\theta)=\log L(\theta)=\sum\log f(x_i\mid\theta)\) (same maximiser, sums are easier).
  2. Solve the score / likelihood equation \(\ell'(\theta)=0\).
  3. 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.

x(n) θ L(θ)
\(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

  1. 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\).

  2. easy

    Geometric\((p)\) on \(\{1,2,\dots\}\). Find the MLE of \(p\).

    Show solution

    \(\ell=n\log p+(\sum x_i-n)\log(1-p)\); \(\ell'=\frac np-\frac{\sum x_i-n}{1-p}=0\Rightarrow\hat p=\frac{n}{\sum x_i}=1/\bar x\) (same as MoM here).

  3. medium

    \(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.

  4. 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.

    Show solution

    \(\hat\theta=x_{(4)}=3.9\). Invariance: \(\widehat{\theta^2}=15.21\), \(\widehat{\theta^2/12}=1.2675\).

  5. hard

    \(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\).

  6. 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.
  • Invariance: \(\widehat{\tau(\theta)}=\tau(\hat\theta)\). Numerical: Newton-Raphson / optimize.

B1.11 Bayes estimation and conjugate priors

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.

ModelPriorPosteriorPosterior 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\(N(\mu,\tau^2)\)\(N\!\Big(\frac{\tau^2\bar x+(\sigma^2/n)\mu}{\tau^2+\sigma^2/n},\ \frac{\tau^2\sigma^2/n}{\tau^2+\sigma^2/n}\Big)\)weighted average

Tip: posterior mean = weighted average

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

  1. 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)\)).

  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).

  3. 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)\).

  4. 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]\).

  5. 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.

  6. 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.
  • Mixing rate and scale in the Gamma.

Remember

  • Posterior \(\propto\) likelihood \(\times\) prior; Bayes estimator (squared loss) = posterior mean.
  • Beta-Bernoulli, Gamma-Poisson, Gamma-Exponential, Normal-Normal.
  • Posterior mean = weighted average of data estimate and prior mean.

B1.12 Evaluating estimators: bias, MSE, bias-variance trade-off

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

  1. 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\)).

  2. easy

    Find the MSE of \(c\bar X\) as an estimator of \(\mu\).

    Show solution

    Bias \((c-1)\mu\), variance \(c^2\sigma^2/n\): \(\text{MSE}=c^2\sigma^2/n+(c-1)^2\mu^2\).

  3. medium

    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.

    Show solution

    \(E\hat\lambda=nE[1/\sum X_i]=\frac{n}{n-1}\lambda\): bias \(\frac{\lambda}{n-1}\gt0\). Unbiased: \(\frac{n-1}{n}\hat\lambda=\frac{n-1}{\sum X_i}\).

  4. medium

    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\)).

  5. 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).

  6. 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\).
  • \(\text{MSE}(S^2)=\frac{2\sigma^4}{n-1}\gt\text{MSE}(\hat\sigma^2)=\frac{(2n-1)\sigma^4}{n^2}\).
  • Uniform: \(\frac{n+1}nX_{(n)}\) (var \(\frac{\theta^2}{n(n+2)}\)) beats \(2\bar X\) (var \(\frac{\theta^2}{3n}\)).

B1.13 Score, Fisher information, Cramér-Rao bound, efficiency, UMVU

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.)}. \]

Proof
  1. Unbiasedness: \(\tau(\theta)=\int W(\mathbf x)f(\mathbf x\mid\theta)\,d\mathbf x\). Differentiate (allowed by assumption): \(\tau'(\theta)=\int W\,\partial_\theta f\,d\mathbf x=\int W\,\frac{\partial_\theta f}{f}\,f\,d\mathbf x=E_\theta[W\,S_n(\theta)]\).
  2. Since \(E[S_n]=0\) (lemma, take \(W\equiv1\)), \(\text{Cov}_\theta(W,S_n)=E[WS_n]-E[W]E[S_n]=\tau'(\theta)\).
  3. Cauchy-Schwarz (covariance inequality): \(\big(\text{Cov}(W,S_n)\big)^2\le\text{Var}(W)\,\text{Var}(S_n)\).
  4. 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)
Model\(I_1(\theta)\)CRB for unbiased estimators of \(\theta\)Attained by
Bernoulli\((p)\)\(\frac1{p(1-p)}\)\(\frac{p(1-p)}n\)\(\bar X\)
Poisson\((\lambda)\)\(\frac1\lambda\)\(\frac\lambda n\)\(\bar X\)
\(N(\mu,\sigma^2)\), \(\sigma^2\) known\(\frac1{\sigma^2}\)\(\frac{\sigma^2}n\)\(\bar X\)
\(N(\mu,\sigma^2)\), \(\mu\) known, \(\theta=\sigma^2\)\(\frac1{2\sigma^4}\)\(\frac{2\sigma^4}n\)\(\frac1n\sum(X_i-\mu)^2\)
Exp(rate \(\lambda\))\(\frac1{\lambda^2}\)\(\frac{\lambda^2}n\)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

  1. 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.

  2. 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\)).

  3. 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é.)

  4. 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.

    Show solution

    Mean: \((n-1)\frac{\lambda}{n-1}=\lambda\). Variance: \((n-1)^2\frac{\lambda^2}{(n-1)(n-2)}-\lambda^2=\lambda^2\big(\frac{n-1}{n-2}-1\big)=\frac{\lambda^2}{n-2}\). Efficiency \(\frac{\lambda^2/n}{\lambda^2/(n-2)}=\frac{n-2}n\lt1\), tends to 1 (asymptotically efficient).

  5. hard

    \(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\).)

  6. 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]\).
  • CRB: \(\text{Var}W\ge\tau'(\theta)^2/(nI_1(\theta))\). Proof: \(\text{Cov}(W,S)=\tau'\) + Cauchy-Schwarz.
  • 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\)).

WLLN proof: Chebyshev, \(P(|\bar X_n-\mu|\ge\varepsilon)\le\frac{\text{Var}\bar X_n}{\varepsilon^2}=\frac{\sigma^2}{n\varepsilon^2}\to0\).

Central limit theorem

\(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

  1. 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.)

  2. easy

    \(n=36\) i.i.d. with \(\mu=10\), \(\sigma=3\) (distribution unknown). Approximate \(P(\bar X\gt11)\).

    Show solution

    \(\sigma/\sqrt n=0.5\): \(P(Z\gt2)\approx0.0228\).

  3. medium

    Poisson: use the delta method to find the asymptotic distribution of \(\log\bar X\) and of \(2\sqrt{\bar X}\).

    Show solution

    \(g=\log\): \(g'=1/\lambda\), variance \(\lambda/\lambda^2=1/\lambda\): \(\sqrt n(\log\bar X-\log\lambda)\to N(0,1/\lambda)\). \(g=2\sqrt\cdot\): \(g'=1/\sqrt\lambda\), variance \(\lambda/\lambda=1\): \(\sqrt n(2\sqrt{\bar X}-2\sqrt\lambda)\to N(0,1)\), free of \(\lambda\) (variance-stabilising transformation).

  4. medium

    Bernoulli: find the asymptotic distribution of the MLE of the odds \(p/(1-p)\).

    Show solution

    \(g(p)=\frac p{1-p}\), \(g'(p)=\frac1{(1-p)^2}\). \(\sqrt n(\hat p-p)\to N(0,p(1-p))\). So \(\sqrt n\big(g(\hat p)-g(p)\big)\to N\big(0,\frac{p(1-p)}{(1-p)^4}\big)=N\big(0,\frac{p}{(1-p)^3}\big)\).

  5. hard

    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)\).

  6. 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\).
  • MLE: consistent, \(\sqrt n(\hat\theta-\theta)\to N(0,1/I_1(\theta))\), asymptotically efficient (regular models).

B1.15 Confidence intervals: pivotal method, coverage, optimal length, approximate (Wald) intervals

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\).

Standard intervals (normal data unless stated)
ParameterPivot\(1-\alpha\) CI
\(\mu\), \(\sigma\) known\(\frac{\bar X-\mu}{\sigma/\sqrt n}\sim N(0,1)\)\(\bar X\pm z_{\alpha/2}\frac{\sigma}{\sqrt n}\)
\(\mu\), \(\sigma\) unknown\(\frac{\bar X-\mu}{S/\sqrt n}\sim t_{n-1}\)\(\bar X\pm t_{n-1,\alpha/2}\frac{S}{\sqrt n}\)
\(\sigma^2\)\(\frac{(n-1)S^2}{\sigma^2}\sim\chi^2_{n-1}\)\(\Big[\frac{(n-1)S^2}{\chi^2_{n-1,\alpha/2}},\frac{(n-1)S^2}{\chi^2_{n-1,1-\alpha/2}}\Big]\)
Exp rate \(\lambda\)\(2\lambda\sum X_i\sim\chi^2_{2n}\)\(\Big[\frac{\chi^2_{2n,1-\alpha/2}}{2\sum X_i},\frac{\chi^2_{2n,\alpha/2}}{2\sum X_i}\Big]\)
Uniform\((0,\theta)\)\(X_{(n)}/\theta\), CDF \(t^n\)\(\big[X_{(n)},\ X_{(n)}/\alpha^{1/n}\big]\)
any, large \(n\) (Wald)\(\frac{\hat\theta-\theta}{\text{se}(\hat\theta)}\approx N(0,1)\)\(\hat\theta\pm z_{\alpha/2}/\sqrt{I_n(\hat\theta)}\)
proportion \(p\)\(\frac{\hat p-p}{\sqrt{\hat p(1-\hat p)/n}}\approx N(0,1)\)\(\hat p\pm z_{\alpha/2}\sqrt{\hat p(1-\hat p)/n}\)

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

  1. 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]\).

  2. easy

    \(\sigma=10\). How large must \(n\) be for a 95% CI of half-width at most 2?

    Show solution

    \(n\ge(1.96\cdot10/2)^2=96.04\Rightarrow n=97\) (always round up).

  3. medium

    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]\).

  4. 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]\).

  5. 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\).

  6. 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\).
  • \(\bar X\pm t_{n-1,\alpha/2}S/\sqrt n\); \(\big[(n-1)S^2/\chi^2_{\text{upper}},(n-1)S^2/\chi^2_{\text{lower}}\big]\); Wald \(\hat\theta\pm z\,\text{se}\).
  • 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\) truecorrecttype I error (prob. \(\le\alpha\))
\(H_1\) truetype II errorcorrect (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\).

θ0 1 α θ
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

  1. 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).

  2. 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.

  3. medium

    In Example 1 (\(n=25\), \(\sigma=1\), \(c=z_{0.05}\)), compute \(\beta(0)\), \(\beta(-0.2)\), \(\beta(0.3)\).

    Show solution

    \(\beta(\theta)=1-\Phi(1.645-5\theta)\). \(\beta(0)=0.05\); \(\beta(-0.2)=1-\Phi(2.645)\approx0.0041\); \(\beta(0.3)=1-\Phi(0.145)\approx0.442\).

  4. medium

    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\).

  5. 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\).

  6. 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\).
  • One-sided \(z\)-test power: \(1-\Phi\big(z_\alpha-\sqrt n(\theta-\theta_0)/\sigma\big)\).
  • Sample size: \(n\ge((z_\alpha+z_\beta)\sigma/\delta)^2\).

B1.17 Neyman-Pearson lemma, UMP tests, monotone likelihood ratio, Karlin-Rubin

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

  1. 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\).

  2. 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.)

  3. 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\)).

  4. 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).

  5. 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).

  6. 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.
  • MLR in \(T\) + one-sided hypotheses → Karlin-Rubin: reject iff \(T\gt t_0\), UMP.
  • 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

  1. 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%.

  2. 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.

  3. 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}\)).

  4. 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%.

  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.

  6. 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.
  • p-value \(=\sup_{\Theta_0}P_\theta(W\ge w_{obs})\); reject iff \(p\le\alpha\).
  • 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
θ0 θ̂ LR: height Wald: width Score: slope ℓ(θ)
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)}\).

  • Wald: \(Z_W=\frac{0.6-0.5}{\sqrt{0.6\cdot0.4/100}}=\frac{0.1}{0.04899}=2.041\).
  • Score: \(\ell'(p)=\frac{y}{p}-\frac{n-y}{1-p}\); at 0.5: \(120-80=40\); \(I_n(0.5)=400\); \(Z_S=40/20=2.000\) (= \(\frac{\hat p-p_0}{\sqrt{p_0(1-p_0)/n}}\), the usual proportion test).
  • LR: \(2\big[60\log\frac{0.6}{0.5}+40\log\frac{0.4}{0.5}\big]=2[10.939-8.926]=4.027\) vs \(\chi^2_{1,0.05}=3.841\).

All reject at 5% (\(2.041^2=4.17\), \(2^2=4.00\), \(4.03\), all \(\gt3.841\)).

Example 2 (Poisson). \(n=40\), \(\bar x=2.5\), \(H_0:\lambda=2\). \(I_n(\lambda)=n/\lambda\). Wald: \(W=(2.5-2)^2\cdot40/2.5=4.0\). Score: \(\ell'(2)=n(\bar x/2-1)=10\), \(S=10^2/(40/2)=5.0\). LR: \(2n[\bar x\log(\bar x/\lambda_0)-(\bar x-\lambda_0)]=80[0.5579-0.5]=4.63\). All \(\gt3.841\): reject.

n <- 100; y <- 60; p0 <- 0.5; ph <- y / n
wald  <- (ph - p0) / sqrt(ph * (1 - ph) / n)            # 2.041
score <- (ph - p0) / sqrt(p0 * (1 - p0) / n)            # 2.000
lr    <- 2 * (y * log(ph / p0) + (n - y) * log((1 - ph) / (1 - p0)))  # 4.027
c(wald^2, score^2, lr) > qchisq(0.95, 1)                # TRUE TRUE TRUE
prop.test(y, n, p = p0, correct = FALSE)$statistic      # X-squared = 4 (score test)

Exercises

  1. easy

    Which of the three tests requires (a) only the MLE, (b) only \(\theta_0\), (c) both?

    Show solution

    (a) Wald; (b) score; (c) LR (needs \(\ell(\hat\theta)\) and \(\ell(\theta_0)\)).

  2. easy

    Poisson, \(n=50\), \(\bar x=3.2\). Wald test of \(H_0:\lambda=2.5\) at 5%?

    Show solution

    se \(=\sqrt{3.2/50}=0.253\); \(Z_W=0.7/0.253=2.77\gt1.96\): reject. (Equivalently 2.5 is outside the Wald CI [2.70, 3.70].)

  3. medium

    Exp(rate \(\lambda\)), \(n=20\), \(\bar x=0.4\), \(H_0:\lambda=2\). Compute the three statistics (\(I_1=1/\lambda^2\)).

    Show solution

    \(\hat\lambda=2.5\). Wald: \((0.5)^2\cdot20/2.5^2=0.8\). Score: \(\ell'(2)=n/2-n\bar x=10-8=2\), \(I_n(2)=20/4=5\), \(S=4/5=0.8\). LR: \(2n[\log(\hat\lambda/\lambda_0)-1+\lambda_0\bar x]=40[0.2231-1+0.8]=0.926\). All \(\lt3.841\): do not reject.

  4. medium

    Show that for the normal mean with \(\sigma^2\) known, \(-2\log\lambda=W=S=\frac{n(\bar x-\theta_0)^2}{\sigma^2}\).

    Show solution

    \(\ell(\theta)=c-\frac{1}{2\sigma^2}\sum(x_i-\theta)^2\), \(\hat\theta=\bar x\). LR: \(2[\ell(\bar x)-\ell(\theta_0)]=\frac{n(\bar x-\theta_0)^2}{\sigma^2}\). Wald: \((\bar x-\theta_0)^2\cdot n/\sigma^2\). Score: \(\ell'(\theta_0)=n(\bar x-\theta_0)/\sigma^2\), squared over \(n/\sigma^2\): same.

  5. hard

    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\).

  6. 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.

Remember

  • LR: \(2[\ell(\hat\theta)-\ell(\theta_0)]\to\chi^2_\nu\).
  • Wald: \((\hat\theta-\theta_0)^2I_n(\hat\theta)\to\chi^2_1\). Score: \(\ell'(\theta_0)^2/I_n(\theta_0)\to\chi^2_1\).
  • 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.

One sample
TestAssumptionsStatisticNull distribution
\(z\) for \(\mu\)normal (or large \(n\)), \(\sigma\) known\(\frac{\bar X-\mu_0}{\sigma/\sqrt n}\)\(N(0,1)\)
\(t\) for \(\mu\)normal, \(\sigma\) unknown\(\frac{\bar X-\mu_0}{S/\sqrt n}\)\(t_{n-1}\)
proportion \(p\)large \(n\)\(\frac{\hat p-p_0}{\sqrt{p_0(1-p_0)/n}}\)\(\approx N(0,1)\)
variance \(\sigma^2\)normal\(\frac{(n-1)S^2}{\sigma_0^2}\)\(\chi^2_{n-1}\)
Two independent samples
TestAssumptionsStatisticNull distribution
difference of means, variances knownnormal or large \(n\)\(\frac{\bar X-\bar Y-\Delta_0}{\sqrt{\sigma_1^2/n_1+\sigma_2^2/n_2}}\)\(N(0,1)\)
pooled \(t\)normal, \(\sigma_1=\sigma_2\) unknown\(\frac{\bar X-\bar Y-\Delta_0}{S_p\sqrt{1/n_1+1/n_2}}\), \(S_p^2=\frac{(n_1-1)S_1^2+(n_2-1)S_2^2}{n_1+n_2-2}\)\(t_{n_1+n_2-2}\)
Welch \(t\)normal, \(\sigma_1\neq\sigma_2\)\(\frac{\bar X-\bar Y-\Delta_0}{\sqrt{S_1^2/n_1+S_2^2/n_2}}\)\(\approx t_\nu\), \(\nu=\frac{(S_1^2/n_1+S_2^2/n_2)^2}{\frac{(S_1^2/n_1)^2}{n_1-1}+\frac{(S_2^2/n_2)^2}{n_2-1}}\)
difference of proportionslarge \(n_1,n_2\)\(\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%.

Example 4 (paired). Differences 1.2, 0.8, −0.3, 1.5, 0.9, 0.4, 1.1, 0.6: \(\bar d=0.775\), \(s_d=0.555\), \(t=\frac{0.775}{0.555/\sqrt8}=3.95\), df 7, \(t_{7,0.025}=2.365\): reject (p \(\approx0.006\)).

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

  1. 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\).

  2. 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\).

  3. 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\)).

  4. 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.

  5. 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).

  6. 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\)).
  • Wrong degrees of freedom: \(n-1\) (one sample), \(n_1+n_2-2\) (pooled), \(n-1\) pairs (paired).
  • 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:30 check 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

ProposalTheory you illustrateKey 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 meanwhen the LLN/CLT fail (B1.14)\(\bar X\sim\) Cauchy\((\theta,1)\) for every \(n\): never converges
Cauchy location: MLEnumerical 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 medianorder statistics, asymptotic relative efficiencymedian: \(\sqrt n(\tilde X-\theta)\to N(0,\pi^2/4)\approx N(0,2.47)\); ARE \(=2/2.47\approx0.81\)
Entropy of an exponential modelinvariance of MLE, delta method, bias\(H(\lambda)=1-\log\lambda\); MLE \(1+\log\bar X\); asymptotic variance \(1/n\)
Geometric model: MoM and MLE of \(P(X=2)\), harmonic meanplug-in/invariance, bias, MSE, delta method\(P(X=2)=p(1-p)\), \(\hat p=1/\bar X\); exact definition of the harmonic-mean estimator: check the proposal text
MLE of \(\alpha\) for a Beta distributionnumerical MLE (Newton-Raphson), Fisher informationsee B1.10 exercise 6; compare with MoM

The exact titles and the full list of 10 proposals are only partly in the brief; the choice of topic is free. check the proposal page for details.

How to structure a simulation study (ADEMP)
  1. Aims: which theorem / question? (e.g. "Is the median or the MLE better for Cauchy location for small \(n\)?")
  2. Data-generating mechanism: model, true parameter values, sample sizes (e.g. \(n=10,25,50,100,500\)).
  3. Estimand: what is estimated (\(\theta\), \(\pi\), \(H(\lambda)\)...).
  4. Methods: the estimators/tests/CIs compared.
  5. 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

  1. 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\).

  2. 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\).

  3. 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\).

  4. 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.

  5. 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:

\[ \mathbf{u}+\mathbf{v} = (u_1+v_1,\dots,u_n+v_n), \qquad c\,\mathbf{u} = (c u_1,\dots,c u_n). \]

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.

u=(1,2) v=(3,1) u+v=(4,3)
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

  1. easy

    Let \(\mathbf{u}=(2,-1,0)\), \(\mathbf{v}=(1,3,-2)\). Compute \(3\mathbf{u}-2\mathbf{v}\).

    Show solution

    \(3\mathbf{u}=(6,-3,0)\), \(2\mathbf{v}=(2,6,-4)\). Difference: \((4,-9,4)\).

  2. easy

    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)\).

  3. 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).

  4. 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\).

  5. 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).

  6. 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:

\[ \begin{pmatrix}1 & 2 & 0 & 1\\ 0 & 1 & 1 & 1\\ 1 & 3 & 1 & 2\end{pmatrix} \xrightarrow{R_3-R_1} \begin{pmatrix}1 & 2 & 0 & 1\\ 0 & 1 & 1 & 1\\ 0 & 1 & 1 & 1\end{pmatrix} \xrightarrow{R_3-R_2} \begin{pmatrix}1 & 2 & 0 & 1\\ 0 & 1 & 1 & 1\\ 0 & 0 & 0 & 0\end{pmatrix}. \]

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

  1. 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.)

  2. 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).

  3. medium

    For which \(k\) are \((1,1,0)\), \((1,0,1)\), \((0,1,k)\) a basis of \(\mathbb{R}^3\)?

    Show solution

    \(\det\begin{pmatrix}1 & 1 & 0\\ 1 & 0 & 1\\ 0 & 1 & k\end{pmatrix}\) (vectors as columns) \(=1(0\cdot k-1\cdot1)-1(1\cdot k-0)+0 = -1-k\). Basis iff \(k\neq-1\). For \(k=-1\): \((0,1,-1)=(1,1,0)-(1,0,1)\).

  4. medium

    Find a basis and the dimension of \(\operatorname{Span}\{(1,2,1),(2,4,2),(1,0,1),(0,1,0)\}\).

    Show solution

    Columns \(\begin{pmatrix}1 & 2 & 1 & 0\\ 2 & 4 & 0 & 1\\ 1 & 2 & 1 & 0\end{pmatrix}\). \(R_2-2R_1\), \(R_3-R_1\): \(\begin{pmatrix}1 & 2 & 1 & 0\\ 0 & 0 & -2 & 1\\ 0 & 0 & 0 & 0\end{pmatrix}\). Pivots in columns 1 and 3: dimension 2, basis \(\{(1,2,1),(1,0,1)\}\). (Check: \((0,1,0)=\tfrac12(1,2,1)-\tfrac12(1,0,1)\).)

  5. hard

    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.
  6. 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.
  • Basis = independent + spanning. \(n\) vectors in \(\mathbb{R}^n\): basis \(\iff\det\neq0\).
  • \(\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).

Map of \(\mathbb{R}^2\)Matrix
Rotation by angle \(\theta\) (counter-clockwise)\(\begin{pmatrix}\cos\theta & -\sin\theta\\ \sin\theta & \cos\theta\end{pmatrix}\)
Reflection in the \(x\)-axis\(\begin{pmatrix}1 & 0\\ 0 & -1\end{pmatrix}\)
Projection onto the \(x\)-axis\(\begin{pmatrix}1 & 0\\ 0 & 0\end{pmatrix}\)
Scaling by \(a\) and \(b\)\(\begin{pmatrix}a & 0\\ 0 & b\end{pmatrix}\)

Definition: Null space (kernel) and column space (image)

\(\operatorname{Nul}A=\{\mathbf x: A\mathbf x=\mathbf 0\}\subseteq\mathbb{R}^n\); \(\operatorname{Col}A=\operatorname{Span}\{\text{columns of }A\}=\{A\mathbf x\}\subseteq\mathbb{R}^m\). \(\operatorname{rank}A=\dim\operatorname{Col}A\).

Theorem (rank-nullity)

For an \(m\times n\) matrix: \(\operatorname{rank}A+\dim\operatorname{Nul}A = n\) (number of columns). Equivalently: pivot columns + free columns = all columns. \(T\) is one-to-one iff \(\operatorname{Nul}A=\{\mathbf 0\}\); onto \(\mathbb{R}^m\) iff \(\operatorname{rank}A=m\).

Worked examples

Example 1 (\(AB\neq BA\)). \(A=\begin{pmatrix}1 & 2\\ 3 & 4\end{pmatrix}\), \(B=\begin{pmatrix}0 & 1\\ 1 & 0\end{pmatrix}\). \(AB=\begin{pmatrix}2 & 1\\ 4 & 3\end{pmatrix}\) (columns of \(A\) swapped) but \(BA=\begin{pmatrix}3 & 4\\ 1 & 2\end{pmatrix}\) (rows swapped).

Example 2 (sizes). \(A=\begin{pmatrix}1 & 0 & 2\\ -1 & 3 & 1\end{pmatrix}\) (2×3), \(B=\begin{pmatrix}2 & 1\\ 0 & 1\\ 1 & -1\end{pmatrix}\) (3×2). \(AB\) is 2×2: entry (1,1) \(=1\cdot2+0\cdot0+2\cdot1=4\); (1,2) \(=1-0-2=-1\); (2,1) \(=-2+0+1=-1\); (2,2) \(=-1+3-1=1\). So \(AB=\begin{pmatrix}4 & -1\\ -1 & 1\end{pmatrix}\), while \(BA\) is 3×3: \(\begin{pmatrix}1 & 3 & 5\\ -1 & 3 & 1\\ 2 & -3 & 1\end{pmatrix}\).

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

  1. 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?

    Show solution

    \(AB\): 3×4. \(BA\): (2×4)(3×2), 4≠3, not defined. \(ABC\): (3×4)(4×3) = 3×3. \(CA\): (4×3)(3×2) = 4×2. \(B^TA^T\) = \((AB)^T\): 4×3.

  2. easy

    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)\).

  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\).

  4. 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.

  5. 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.

    Show solution

    \(R_2-2R_1\) gives \(\begin{pmatrix}1 & 2 & 3\\ 0 & 0 & 0\end{pmatrix}\): one pivot (column 1), so rank 1, \(\operatorname{Col}A=\operatorname{Span}\{(1,2)\}\). Free variables \(x_2=s,\ x_3=t\), \(x_1=-2s-3t\): \(\mathbf x=s(-2,1,0)+t(-3,0,1)\). Basis of Nul: \(\{(-2,1,0),(-3,0,1)\}\), dimension 2. Check: \(1+2=3\) columns ✓.

  6. exam

    (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\).

\[ \left(\begin{array}{ccc|c}1 & 2 & -1 & 6\\ 2 & 5 & 1 & 11\\ 1 & 3 & 1 & 6\end{array}\right) \xrightarrow[R_3-R_1]{R_2-2R_1} \left(\begin{array}{ccc|c}1 & 2 & -1 & 6\\ 0 & 1 & 3 & -1\\ 0 & 1 & 2 & 0\end{array}\right) \xrightarrow{R_3-R_2} \left(\begin{array}{ccc|c}1 & 2 & -1 & 6\\ 0 & 1 & 3 & -1\\ 0 & 0 & -1 & 1\end{array}\right). \]

Back substitution: \(z=-1\); \(y=-1-3z=2\); \(x=6-2y+z=1\). Unique solution \((1,2,-1)\) (rank 3 = \(n\)).

Example 2 (parameter, mock style). Discuss, as \(k\in\mathbb{R}\) varies,

\[ \left(\begin{array}{ccc|c}1 & 2 & -1 & 1\\ 0 & 1 & k & 2\\ 0 & 0 & k^2-1 & k+1\end{array}\right). \]

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.
  • \(k=-1\): row 3 becomes \([0\ 0\ 0\mid 0]\): rank 2 = 2 \(\lt 3\): infinitely many (\(\infty^1\)). With \(z=t\) free: \(y=2+t\), \(x=1-2(2+t)+t=-3-t\). Solutions \((-3-t,\ 2+t,\ t)\).

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

  1. 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\).

  2. 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\).

  3. 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.

  4. medium

    Solve by row reduction: \(x+2y+3z=1,\ 2x+4y+7z=3,\ 3x+6y+10z=4\).

    Show solution

    \(R_2-2R_1\): \((0,0,1\mid1)\); \(R_3-3R_1\): \((0,0,1\mid1)\); \(R_3-R_2\): zero row. Rank 2 = rank of augmented, \(n=3\): \(\infty^1\). \(z=1\), \(y=t\) free, \(x=1-2t-3=-2-2t\). Solutions \((-2-2t,\ t,\ 1)\). Check \(t=0\): \((-2,0,1)\): \(-2+3=1\) ✓, \(-4+7=3\) ✓, \(-6+10=4\) ✓.

  5. hard

    Discuss \(x+y+z=1,\ x+2y+kz=2,\ 2x+3y+(k+1)z=k\).

    Show solution

    \(R_2-R_1\): \((0,1,k-1\mid1)\). \(R_3-2R_1\): \((0,1,k-1\mid k-2)\). Then \(R_3-R_2\): \((0,0,0\mid k-3)\). So \(\operatorname{rank}A=2\) for every \(k\) (indeed row 3 of \(A\) = row 1 + row 2, \(\det A=0\)).

    • \(k\neq3\): \(0=k-3\neq0\): no solutions.
    • \(k=3\): rank 2 = 2 \(\lt3\): \(\infty^1\) solutions. \(y=1-2t\), \(z=t\), \(x=1-y-z=t\): \((t,\ 1-2t,\ t)\).

    There is no value with a unique solution.

  6. exam

    (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\).
    • \(k=2\): row 3 is \([0\,0\,0\mid0]\); rows 1-2: \(x-y+2z=3,\ 2y+z=1\). Rank 2 = 2 \(\lt\)3: infinitely many. \(z=t\), \(y=(1-t)/2\), \(x=3+y-2z=\frac{7-5t}{2}\).
    • \(k=0\): matrix \(\left(\begin{array}{ccc|c}1 & -1 & 2 & 3\\ 0 & 0 & 1 & 1\\ 0 & 0 & -2 & -2\end{array}\right)\); \(R_3+2R_2\) gives a zero row. Rank 2 = 2 \(\lt\)3: infinitely many: \(z=1\), \(y=s\), \(x=1+s\).

    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\).
  • \(\det(AB)=\det A\det B\); \(\det A^T=\det A\); \(\det(cA)=c^n\det A\); \(\det A^{-1}=1/\det A\).
  • Two equal (or proportional) rows/columns, or a zero row \(\Rightarrow\det A=0\).
  • \(\det A\neq0\iff A\) invertible \(\iff\) columns independent \(\iff\operatorname{rank}A=n\).

Definition: Inverse

\(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.)

Worked examples

Example 1 (cofactor expansion). \(A=\begin{pmatrix}2 & 1 & 3\\ 0 & -1 & 4\\ 1 & 2 & 0\end{pmatrix}\). Expand along column 1 (one zero):

\[ \det A = 2\det\begin{pmatrix}-1 & 4\\ 2 & 0\end{pmatrix} - 0 + 1\cdot\det\begin{pmatrix}1 & 3\\ -1 & 4\end{pmatrix} = 2(0-8)+(4+3) = -9. \]

Example 2 (2×2 inverse). \(\begin{pmatrix}4 & 7\\ 2 & 6\end{pmatrix}^{-1}=\frac1{10}\begin{pmatrix}6 & -7\\ -2 & 4\end{pmatrix}=\begin{pmatrix}0.6 & -0.7\\ -0.2 & 0.4\end{pmatrix}\).

Example 3 (Gauss–Jordan). \(C=\begin{pmatrix}1 & 0 & 1\\ 0 & 1 & 1\\ 1 & 1 & 0\end{pmatrix}\).

\[ \left(\begin{array}{ccc|ccc}1 & 0 & 1 & 1 & 0 & 0\\ 0 & 1 & 1 & 0 & 1 & 0\\ 1 & 1 & 0 & 0 & 0 & 1\end{array}\right) \xrightarrow{R_3-R_1-R_2} \left(\begin{array}{ccc|ccc}1 & 0 & 1 & 1 & 0 & 0\\ 0 & 1 & 1 & 0 & 1 & 0\\ 0 & 0 & -2 & -1 & -1 & 1\end{array}\right) \] \[ \xrightarrow{R_3/(-2)} \left(\begin{array}{ccc|ccc}1 & 0 & 1 & 1 & 0 & 0\\ 0 & 1 & 1 & 0 & 1 & 0\\ 0 & 0 & 1 & \tfrac12 & \tfrac12 & -\tfrac12\end{array}\right) \xrightarrow[R_2-R_3]{R_1-R_3} \left(\begin{array}{ccc|ccc}1 & 0 & 0 & \tfrac12 & -\tfrac12 & \tfrac12\\ 0 & 1 & 0 & -\tfrac12 & \tfrac12 & \tfrac12\\ 0 & 0 & 1 & \tfrac12 & \tfrac12 & -\tfrac12\end{array}\right). \]

So \(C^{-1}=\frac12\begin{pmatrix}1 & -1 & 1\\ -1 & 1 & 1\\ 1 & 1 & -1\end{pmatrix}\).

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\).

Exercises

  1. easy

    Compute \(\det\begin{pmatrix}2 & 0 & 0 & 0\\ 1 & 3 & 0 & 0\\ 4 & 5 & -1 & 0\\ 7 & 8 & 9 & 2\end{pmatrix}\).

    Show solution

    Lower triangular: product of diagonal \(2\cdot3\cdot(-1)\cdot2=-12\).

  2. easy

    For which \(k\) is \(\begin{pmatrix}k & 2\\ 2 & k\end{pmatrix}\) invertible?

    Show solution

    \(\det=k^2-4=(k-2)(k+2)\): invertible iff \(k\neq\pm2\).

  3. medium

    Compute \(\det\begin{pmatrix}1 & 1 & 1\\ 1 & 2 & 3\\ 1 & 4 & k\end{pmatrix}\) using row operations.

    Show solution

    \(R_2-R_1\), \(R_3-R_1\) (det unchanged): \(\begin{pmatrix}1 & 1 & 1\\ 0 & 1 & 2\\ 0 & 3 & k-1\end{pmatrix}\). \(R_3-3R_2\): \(\begin{pmatrix}1 & 1 & 1\\ 0 & 1 & 2\\ 0 & 0 & k-7\end{pmatrix}\). Triangular: \(\det=k-7\).

  4. medium

    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

    \[ A^{-1}=\frac{1}{-7}\begin{pmatrix}-1 & -2 & 2\\ -3 & 1 & -1\\ 6 & -2 & -5\end{pmatrix}=\frac17\begin{pmatrix}1 & 2 & -2\\ 3 & -1 & 1\\ -6 & 2 & 5\end{pmatrix}. \]

    Check row 1 of \(A\) times column 1: \(\frac17(1+6+0)=1\) ✓; row 1 times column 2: \(\frac17(2-2+0)=0\) ✓.

  5. hard

    \(A,B\) are 3×3 with \(\det A=2\), \(\det B=-3\). Compute \(\det(A^{-1}B^T)\), \(\det(-B)\) and \(\det(3AB^{-1})\).

    Show solution

    \(\det(A^{-1}B^T)=\frac12\cdot(-3)=-\frac32\). \(\det(-B)=(-1)^3(-3)=3\). \(\det(3AB^{-1})=3^3\cdot2\cdot\frac1{-3}=-18\).

  6. exam

    (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.
  • \(\det(AB)=\det A\det B\), \(\det(cA)=c^n\det A\), \(\det A^{-1}=1/\det A\).
  • \(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\|}\).

Inequalities

Cauchy–Schwarz: \(|\mathbf u\cdot\mathbf v|\le\|\mathbf u\|\|\mathbf v\|\). Triangle: \(\|\mathbf u+\mathbf v\|\le\|\mathbf u\|+\|\mathbf v\|\). Pythagoras: \(\mathbf u\perp\mathbf v\iff\|\mathbf u+\mathbf v\|^2=\|\mathbf u\|^2+\|\mathbf v\|^2\).

Definition: Orthogonal, orthonormal

\(\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 3 (Gram–Schmidt). \(\mathbf x_1=(1,1,0)\), \(\mathbf x_2=(1,0,1)\). \(\mathbf v_1=(1,1,0)\); \(\mathbf v_2=(1,0,1)-\frac12(1,1,0)=(\tfrac12,-\tfrac12,1)\) (or, scaled, \((1,-1,2)\)). Check \(\mathbf v_1\cdot(1,-1,2)=0\) ✓. Orthonormal basis: \(\frac1{\sqrt2}(1,1,0),\ \frac1{\sqrt6}(1,-1,2)\).

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

  1. easy

    Normalize \((2,-1,2)\).

    Show solution

    \(\|\cdot\|=\sqrt{4+1+4}=3\); unit vector \((\tfrac23,-\tfrac13,\tfrac23)\).

  2. easy

    For which \(k\) is \((1,k,2)\perp(3,1,-1)\)?

    Show solution

    \(3+k-2=0\Rightarrow k=-1\).

  3. 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)\) ✓.

  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).

  5. hard

    Apply Gram–Schmidt to \((1,1,0),(1,0,1),(0,1,1)\) and give an orthonormal basis of \(\mathbb{R}^3\).

    Show solution

    \(\mathbf v_1=(1,1,0)\), \(\mathbf v_2=(\tfrac12,-\tfrac12,1)\) (Example 3). For \(\mathbf x_3=(0,1,1)\): \(\frac{\mathbf x_3\cdot\mathbf v_1}{\mathbf v_1\cdot\mathbf v_1}=\frac12\), \(\frac{\mathbf x_3\cdot\mathbf v_2}{\mathbf v_2\cdot\mathbf v_2}=\frac{1/2}{3/2}=\frac13\).

    \[ \mathbf v_3=(0,1,1)-\tfrac12(1,1,0)-\tfrac13(\tfrac12,-\tfrac12,1)=(-\tfrac23,\tfrac23,\tfrac23). \]

    Orthonormal: \(\frac1{\sqrt2}(1,1,0)\), \(\frac1{\sqrt6}(1,-1,2)\), \(\frac1{\sqrt3}(-1,1,1)\).

  6. exam

    (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.

Remember

  • \(\|\mathbf u\|=\sqrt{\mathbf u\cdot\mathbf u}\); \(\perp\iff\) dot product 0.
  • Projection \(\hat{\mathbf y}=\sum\frac{\mathbf y\cdot\mathbf u_j}{\mathbf u_j\cdot\mathbf u_j}\mathbf u_j\) (orthogonal basis); residual \(\perp W\); closest point.
  • \(Q^TQ=I\Rightarrow Q^{-1}=Q^T\), \(\det Q=\pm1\). Gram–Schmidt: subtract projections.

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\).

Useful facts

  • 2×2: \(p(\lambda)=\lambda^2-(\operatorname{tr}A)\lambda+\det A\).
  • 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).

Worked examples

Example 1. \(A=\begin{pmatrix}4 & 1\\ 2 & 3\end{pmatrix}\). \(\operatorname{tr}=7\), \(\det=10\): \(p(\lambda)=\lambda^2-7\lambda+10=(\lambda-2)(\lambda-5)\).

  • \(\lambda=5\): \(A-5I=\begin{pmatrix}-1 & 1\\ 2 & -2\end{pmatrix}\Rightarrow -v_1+v_2=0\Rightarrow\mathbf v=(1,1)\).
  • \(\lambda=2\): \(A-2I=\begin{pmatrix}2 & 1\\ 2 & 1\end{pmatrix}\Rightarrow 2v_1+v_2=0\Rightarrow\mathbf v=(1,-2)\).

\(P=\begin{pmatrix}1 & 1\\ 1 & -2\end{pmatrix}\), \(D=\begin{pmatrix}5 & 0\\ 0 & 2\end{pmatrix}\), \(P^{-1}=\frac13\begin{pmatrix}2 & 1\\ 1 & -1\end{pmatrix}\). Then

\[ A^5=PD^5P^{-1}=\tfrac13\begin{pmatrix}1 & 1\\ 1 & -2\end{pmatrix}\begin{pmatrix}2\cdot3125 & 3125\\ 32 & -32\end{pmatrix}=\begin{pmatrix}2094 & 1031\\ 2062 & 1063\end{pmatrix}. \]

(Checked numerically.) Check eigenvector: \(A(1,-2)=(4-2,\ 2-6)=(2,-4)=2(1,-2)\) ✓.

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

  1. 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.

  2. 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.

  3. medium

    Find eigenvalues and eigenvectors of \(\begin{pmatrix}5 & -2\\ 1 & 2\end{pmatrix}\) and diagonalize it.

    Show solution

    \(\operatorname{tr}=7\), \(\det=10+2=12\): \(\lambda^2-7\lambda+12=(\lambda-3)(\lambda-4)\). \(\lambda=3\): \(\begin{pmatrix}2 & -2\\ 1 & -1\end{pmatrix}\Rightarrow(1,1)\). \(\lambda=4\): \(\begin{pmatrix}1 & -2\\ 1 & -2\end{pmatrix}\Rightarrow(2,1)\). \(P=\begin{pmatrix}1 & 2\\ 1 & 1\end{pmatrix}\), \(D=\operatorname{diag}(3,4)\).

  4. medium

    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.

  5. 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.

  6. 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.
  • \(A=PDP^{-1}\iff n\) independent eigenvectors; \(A^k=PD^kP^{-1}\).

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)\).

Example 2. \(Q=x^2+4xy+y^2\): \(A=\begin{pmatrix}1 & 2\\ 2 & 1\end{pmatrix}\), \(\det=-3\lt0\): indefinite (eigenvalues \(3,-1\)). Check: \(Q(1,1)=6\gt0\), \(Q(1,-1)=-2\lt0\).

Example 3 (orthogonal diagonalization). \(A=\begin{pmatrix}2 & 1\\ 1 & 2\end{pmatrix}\), eigenvalues 3, 1, eigenvectors \((1,1),(-1,1)\) (orthogonal ✓). \(Q=\frac1{\sqrt2}\begin{pmatrix}1 & -1\\ 1 & 1\end{pmatrix}\), \(D=\operatorname{diag}(3,1)\), \(A=QDQ^T\).

Example 4 (3×3). \(A=\begin{pmatrix}2 & 1 & 0\\ 1 & 2 & 1\\ 0 & 1 & 2\end{pmatrix}\): \(D_1=2\), \(D_2=3\), \(D_3=2(4-1)-1(2-0)=4\). All positive: PD. (Eigenvalues \(2-\sqrt2,\ 2,\ 2+\sqrt2\), all positive ✓.)

Exercises

  1. easy

    Write the symmetric matrix of \(Q=5x^2+4xy+2y^2\) and of \(Q=x_1^2-3x_2^2+4x_1x_3-2x_2x_3\).

    Show solution

    \(\begin{pmatrix}5 & 2\\ 2 & 2\end{pmatrix}\); \(\begin{pmatrix}1 & 0 & 2\\ 0 & -3 & -1\\ 2 & -1 & 0\end{pmatrix}\) (halve the mixed coefficients).

  2. easy

    Classify \(Q=-2x^2+2xy-3y^2\).

    Show solution

    \(A=\begin{pmatrix}-2 & 1\\ 1 & -3\end{pmatrix}\): \(D_1=-2\lt0\), \(D_2=6-1=5\gt0\): negative definite.

  3. medium

    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.

  4. 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 ✓.

  5. hard

    Find the principal axes of \(Q=5x^2+4xy+2y^2\) and write \(Q\) without the cross term.

    Show solution

    \(A=\begin{pmatrix}5 & 2\\ 2 & 2\end{pmatrix}\), \(\lambda^2-7\lambda+6=(\lambda-6)(\lambda-1)\). \(\lambda=6\): \(\begin{pmatrix}-1 & 2\\ 2 & -4\end{pmatrix}\Rightarrow(2,1)\); \(\lambda=1\): \((-1,2)\). With \(u=\frac{2x+y}{\sqrt5}\), \(v=\frac{-x+2y}{\sqrt5}\): \(Q=6u^2+v^2\). Positive definite.

  6. exam

    (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:

\[ A=\sigma_1\mathbf u_1\mathbf v_1^T+\sigma_2\mathbf u_2\mathbf v_2^T+\dots+\sigma_r\mathbf u_r\mathbf v_r^T. \]

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\).
  • Square \(A\): \(|\det A|=\sigma_1\cdots\sigma_n\). Symmetric PSD \(A\): singular values = eigenvalues; symmetric in general: \(\sigma_i=|\lambda_i|\).
  • Frobenius norm: \(\|A\|_F^2=\sum_{ij}a_{ij}^2=\sigma_1^2+\dots+\sigma_r^2\).

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 v_1=\frac1{\sqrt2}(1,1)\), \(\mathbf v_2=\frac1{\sqrt2}(1,-1)\).
  • \(\mathbf u_1=\frac1{\sqrt3}A\mathbf v_1=\frac1{\sqrt3}\cdot\frac1{\sqrt2}(2,1,1)=\frac1{\sqrt6}(2,1,1)\); \(\mathbf u_2=A\mathbf v_2=\frac1{\sqrt2}(0,-1,1)\).
  • \(\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)
singular values: [7.871 0.372 0.09  0.076 0.   ]
1 rel. error: 0.0495 check: True storage: 12 of 30
2 rel. error: 0.0149 check: True storage: 24 of 30
3 rel. error: 0.0096 check: True storage: 36 of 30

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

  1. 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\)).

  2. 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}\)?

    Show solution

    Rank 30. \(A_{10}\): \(10(100+80+1)=1810\) numbers (versus 8000).

  3. medium

    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)\).

  4. 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\).

  5. hard

    Compute the SVD of \(A=\begin{pmatrix}3 & 2 & 2\\ 2 & 3 & -2\end{pmatrix}\) (use \(AA^T\), it is smaller).

    Show solution

    \(AA^T=\begin{pmatrix}17 & 8\\ 8 & 17\end{pmatrix}\), eigenvalues 25, 9: \(\sigma_1=5,\ \sigma_2=3\). \(\mathbf u_1=\frac1{\sqrt2}(1,1)\), \(\mathbf u_2=\frac1{\sqrt2}(1,-1)\). Right vectors: \(\mathbf v_i=\frac1{\sigma_i}A^T\mathbf u_i\): \(\mathbf v_1=\frac1{5\sqrt2}(5,5,0)=\frac1{\sqrt2}(1,1,0)\); \(\mathbf v_2=\frac1{3\sqrt2}(1,-1,4)=\frac1{\sqrt{18}}(1,-1,4)\). \(\mathbf v_3\) completes the basis: orthogonal to both, \(\frac13(2,-2,-1)\). So \(A=U\begin{pmatrix}5 & 0 & 0\\ 0 & 3 & 0\end{pmatrix}V^T\).

  6. exam

    (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\).

Remember

  • \(A=U\Sigma V^T\); \(\sigma_i=\sqrt{\lambda_i(A^TA)}\); \(\mathbf u_i=A\mathbf v_i/\sigma_i\).
  • rank = number of \(\sigma_i\gt0\); \(\sigma_1=\max\|A\mathbf x\|\).
  • Best rank-\(k\): \(A_k=\sum_{i\le k}\sigma_i\mathbf u_i\mathbf v_i^T\), error \(\sigma_{k+1}\) (2-norm), \(\sqrt{\sum_{i\gt k}\sigma_i^2}\) (Frobenius).

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
RepresentationWhat it storesMemoryGood 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 listfor 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.

Worked examples

12345 e1e2e3e4e5
Graph \(G_1\): 5 nodes, 5 edges \(e_1=\{1,2\}, e_2=\{1,3\}, e_3=\{2,3\}, e_4=\{3,4\}, e_5=\{4,5\}\).

Example 1 (all representations of \(G_1\)). Degrees \(2,2,3,2,1\), sum \(10=2\cdot5\) ✓. Adjacency list: 1: [2,3]; 2: [1,3]; 3: [1,2,4]; 4: [3,5]; 5: [4].

\[ A=\begin{pmatrix}0 & 1 & 1 & 0 & 0\\ 1 & 0 & 1 & 0 & 0\\ 1 & 1 & 0 & 1 & 0\\ 0 & 0 & 1 & 0 & 1\\ 0 & 0 & 0 & 1 & 0\end{pmatrix},\quad L=D-A=\begin{pmatrix}2 & -1 & -1 & 0 & 0\\ -1 & 2 & -1 & 0 & 0\\ -1 & -1 & 3 & -1 & 0\\ 0 & 0 & -1 & 2 & -1\\ 0 & 0 & 0 & -1 & 1\end{pmatrix}. \]

Oriented incidence matrix (each edge from the smaller to the larger node, \(+1\) tail, \(-1\) head), columns \(e_1,\dots,e_5\):

\[ B=\begin{pmatrix}1 & 1 & 0 & 0 & 0\\ -1 & 0 & 1 & 0 & 0\\ 0 & -1 & -1 & 1 & 0\\ 0 & 0 & 0 & -1 & 1\\ 0 & 0 & 0 & 0 & -1\end{pmatrix},\qquad BB^T=L\ \text{(verified)}. \]

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

  1. 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).

  2. 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.

  3. medium

    Write \(L\) for the path 1-2-3 and check that its eigenvalues are 0, 1, 3. What eigenvector goes with 0?

    Show solution

    \(L=\begin{pmatrix}1 & -1 & 0\\ -1 & 2 & -1\\ 0 & -1 & 1\end{pmatrix}\). \(\operatorname{tr}L=4=0+1+3\) ✓. \(L(1,1,1)=\mathbf 0\) (eigenvalue 0). \(L(1,0,-1)=(1,0,-1)\) (eigenvalue 1). \(L(1,-2,1)=(3,-6,3)\) (eigenvalue 3). Eigenvector for 0: \((1,1,1)\).

  4. medium

    For the triangle 1-2-3, write an oriented incidence matrix and verify \(BB^T=L\).

    Show solution

    Edges \(e_1=1\to2\), \(e_2=1\to3\), \(e_3=2\to3\): \(B=\begin{pmatrix}1 & 1 & 0\\ -1 & 0 & 1\\ 0 & -1 & -1\end{pmatrix}\). \(BB^T\): diagonal \(1+1=2\), \(1+1=2\), \(1+1=2\); entry (1,2) = \(1\cdot(-1)+1\cdot0+0\cdot1=-1\), (1,3) = \(0-1+0=-1\), (2,3) = \(0+0-1=-1\). So \(BB^T=\begin{pmatrix}2 & -1 & -1\\ -1 & 2 & -1\\ -1 & -1 & 2\end{pmatrix}=D-A\) ✓ (eigenvalues 0, 3, 3).

  5. hard

    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).

  6. 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.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.

Definition: Google matrix and PageRank vector

\[ G=d\,M+\frac{1-d}{n}\,\mathbf 1\mathbf 1^T,\qquad G\mathbf r=\mathbf r,\quad r_i\ge0,\ \sum_i r_i=1. \]

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

123
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.

\[ M=\begin{pmatrix}0 & 0 & 1\\ \tfrac12 & 0 & 0\\ \tfrac12 & 1 & 0\end{pmatrix}. \]

Solve \(M\mathbf r=\mathbf r\): \(r_1=r_3\), \(r_2=\frac12r_1\), \(r_3=\frac12r_1+r_2=r_1\) ✓. With \(r_1+r_2+r_3=1\): \(r_1(1+\frac12+1)=1\Rightarrow\mathbf r=(0.4,\ 0.2,\ 0.4)\). Pages 1 and 3 tie; page 2 is last (only half of page 1's vote).

Example 2 (with damping \(d=0.85\), power iteration). \(\mathbf r^{(0)}=(\frac13,\frac13,\frac13)\). Since \(\sum r=1\), \(G\mathbf r=0.85\,M\mathbf r+0.05\,\mathbf 1\).

  • \(M\mathbf r^{(0)}=(\frac13,\frac16,\frac12)\), so \(\mathbf r^{(1)}=0.85(\frac13,\frac16,\frac12)+0.05=(0.3333,\ 0.1917,\ 0.4750)\).
  • \(\mathbf r^{(2)}=(0.4538,\ 0.1917,\ 0.3546)\), \(\mathbf r^{(3)}=(0.3514,\ 0.2428,\ 0.4058)\), ...
  • Limit (exact, from \((I-0.85M)\mathbf r=0.05\cdot\mathbf 1\)): \(\mathbf r=\frac1{1769}(686,\ 380,\ 703)\approx(0.3878,\ 0.2148,\ 0.3974)\).

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

  1. 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\).

  2. 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.

  3. medium

    Graph: \(1\to2,\ 1\to3,\ 2\to1,\ 3\to1,\ 3\to2\). Find \(M\) and the PageRank without damping.

    Show solution

    \(M=\begin{pmatrix}0 & 1 & \frac12\\ \frac12 & 0 & \frac12\\ \frac12 & 0 & 0\end{pmatrix}\). \(r_3=\frac12r_1\); \(r_2=\frac12r_1+\frac12r_3=\frac34r_1\); check \(r_1=r_2+\frac12r_3=\frac34r_1+\frac14r_1\) ✓. Sum: \(r_1(1+\frac34+\frac12)=1\Rightarrow r_1=\frac49\). \(\mathbf r=(\frac49,\frac13,\frac29)\).

  4. medium

    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)\).

  5. 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\).

  6. 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.
  • \(G=dM+\frac{1-d}n\mathbf 1\mathbf 1^T\); \(\mathbf r=G\mathbf r\), \(\sum r_i=1\); \(d\approx0.85\).
  • 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\):

\[ f(\mathbf a+\mathbf h)=f(\mathbf a)+\nabla f(\mathbf a)\cdot\mathbf h+o(\|\mathbf h\|),\quad\text{i.e.}\quad \lim_{\mathbf h\to\mathbf 0}\frac{f(\mathbf a+\mathbf h)-f(\mathbf a)-\nabla f(\mathbf a)\cdot\mathbf h}{\|\mathbf h\|}=0. \]

Theorems

  • 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:

\[ f_x=2x\,e^{1+x^2+y^2},\qquad f_y=2y\,e^{1+x^2+y^2},\qquad \nabla f(x,y)=2e^{1+x^2+y^2}(x,y). \]

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

  1. 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\).

  2. easy

    Compute \(\nabla g\) and the Hessian of \(g(x,y)=x^2y+3y\).

    Show solution

    \(g_x=2xy\), \(g_y=x^2+3\). \(g_{xx}=2y\), \(g_{xy}=g_{yx}=2x\), \(g_{yy}=0\). \(H=\begin{pmatrix}2y & 2x\\ 2x & 0\end{pmatrix}\).

  3. medium

    For \(f(x,y)=x^2+xy^2\): gradient and Hessian at \((1,2)\), and the tangent plane there.

    Show solution

    \(f_x=2x+y^2\), \(f_y=2xy\): \(\nabla f(1,2)=(6,4)\). \(f_{xx}=2\), \(f_{xy}=2y=4\), \(f_{yy}=2x=2\): \(H=\begin{pmatrix}2 & 4\\ 4 & 2\end{pmatrix}\). \(f(1,2)=5\); tangent plane \(z=5+6(x-1)+4(y-2)\).

  4. medium

    Check that \(f_{xy}=f_{yx}\) for \(f(x,y)=xe^y+y^2\sin x\).

    Show solution

    \(f_x=e^y+y^2\cos x\Rightarrow f_{xy}=e^y+2y\cos x\). \(f_y=xe^y+2y\sin x\Rightarrow f_{yx}=e^y+2y\cos x\). Equal ✓ (Schwarz).

  5. hard

    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).

  6. 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)\).

Remember

  • \(\nabla f=(f_x,f_y)\): steepest ascent, \(\perp\) level curves; \(D_{\mathbf u}f=\nabla f\cdot\mathbf u\).
  • \(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]\)

  1. Interior: solve \(\nabla f=\mathbf 0\); keep only points strictly inside. (Classifying them is optional for the global question, but often asked.)
  2. Each of the 4 sides: substitute (e.g. \(x=a\)) to get a one-variable function, find its critical points inside the side.
  3. The 4 corners.
  4. Table of all candidate values: the largest is the global max, the smallest the global min.

Worked examples

Example 1. \(f(x,y)=x^3-3x+y^2\). \(f_x=3x^2-3=0\Rightarrow x=\pm1\); \(f_y=2y=0\Rightarrow y=0\). \(H=\begin{pmatrix}6x & 0\\ 0 & 2\end{pmatrix}\).

  • \((1,0)\): \(\Delta=12\gt0\), \(f_{xx}=6\gt0\): local min, \(f=-2\).
  • \((-1,0)\): \(\Delta=-12\lt0\): saddle, \(f=2\).

No global min on \(\mathbb{R}^2\): \(f(x,0)=x^3-3x\to-\infty\) as \(x\to-\infty\).

Example 2. \(f=x^3+y^3-3xy\). \(f_x=3x^2-3y=0\Rightarrow y=x^2\); \(f_y=3y^2-3x=0\Rightarrow x=y^2=x^4\Rightarrow x(x^3-1)=0\): \(x=0\) or \(x=1\). Critical points \((0,0)\), \((1,1)\). \(H=\begin{pmatrix}6x & -3\\ -3 & 6y\end{pmatrix}\). \((0,0)\): \(\Delta=-9\): saddle. \((1,1)\): \(\Delta=36-9=27\gt0\), \(f_{xx}=6\gt0\): local min, \(f=-1\).

Example 3 (mock type). Global max and min of \(f(x,y)=(y-2)x^2-y^2\) on \(R=[-1,1]\times[-1,3]\).

Step 1, interior. \(f_x=2x(y-2)\), \(f_y=x^2-2y\). \(f_x=0\iff x=0\) or \(y=2\). If \(x=0\): \(f_y=-2y=0\Rightarrow(0,0)\). If \(y=2\): \(x^2=4\Rightarrow(\pm2,2)\), outside \(R\) (\(|x|\le1\)). Only \((0,0)\), with \(f=0\). (Classification: \(H=\begin{pmatrix}2(y-2) & 2x\\ 2x & -2\end{pmatrix}=\begin{pmatrix}-4 & 0\\ 0 & -2\end{pmatrix}\), \(\Delta=8\gt0\), \(f_{xx}\lt0\): local max.)

Step 2, sides.

  • \(x=\pm1\), \(y\in[-1,3]\): \(g(y)=y-2-y^2\), \(g'=1-2y=0\Rightarrow y=\frac12\): \(f(\pm1,\frac12)=\frac12-2-\frac14=-\frac74\).
  • \(y=-1\), \(x\in[-1,1]\): \(f=-3x^2-1\), critical at \(x=0\): \(f(0,-1)=-1\).
  • \(y=3\), \(x\in[-1,1]\): \(f=x^2-9\), critical at \(x=0\): \(f(0,3)=-9\).

Step 3, corners. \(f(\pm1,-1)=-3-1=-4\); \(f(\pm1,3)=1-9=-8\).

Point\((0,0)\)\((\pm1,\frac12)\)\((0,-1)\)\((0,3)\)\((\pm1,-1)\)\((\pm1,3)\)
\(f\)0\(-\frac74\)\(-1\)\(-9\)\(-4\)\(-8\)

Step 4. Global max \(=0\) at \((0,0)\); global min \(=-9\) at \((0,3)\). (Confirmed numerically on a fine grid.)

Exercises

  1. easy

    Find and classify the critical points of \(f=x^2+xy+y^2-3x\).

    Show solution

    \(f_x=2x+y-3=0\), \(f_y=x+2y=0\Rightarrow x=-2y\Rightarrow-3y=3\Rightarrow y=-1,\ x=2\). \(H=\begin{pmatrix}2 & 1\\ 1 & 2\end{pmatrix}\), \(\Delta=3\gt0\), \(f_{xx}\gt0\): local (in fact global, \(H\) constant PD) min, \(f(2,-1)=4-2+1-6=-3\).

  2. medium

    Classify the critical points of \(f=x^4+y^4-4xy\).

    Show solution

    \(f_x=4x^3-4y=0\Rightarrow y=x^3\); \(f_y=4y^3-4x=0\Rightarrow x=y^3=x^9\Rightarrow x=0,\pm1\). Points \((0,0),(1,1),(-1,-1)\). \(H=\begin{pmatrix}12x^2 & -4\\ -4 & 12y^2\end{pmatrix}\). \((0,0)\): \(\Delta=-16\): saddle. \((\pm1,\pm1)\): \(\Delta=144-16=128\gt0\), \(f_{xx}=12\gt0\): local min, \(f=-2\).

  3. medium

    \(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.

  4. medium

    Classify the critical point of \(f=x^2+xy-y^2\).

    Show solution

    \(f_x=2x+y=0\), \(f_y=x-2y=0\Rightarrow(0,0)\). \(H=\begin{pmatrix}2 & 1\\ 1 & -2\end{pmatrix}\), \(\Delta=-4-1=-5\lt0\): saddle.

  5. hard

    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\).)

  6. 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]\).

    Corners: \(f(\pm3,0)=-18\), \(f(\pm3,4)=18-16=2\).

    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

\[ \nabla f(x^*,y^*)=\lambda\nabla g(x^*,y^*),\qquad g(x^*,y^*)=c. \]

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)\).

\[ \mathcal L_x=y-\lambda=0,\quad\mathcal L_y=x-\lambda=0,\quad x+y=10\ \Rightarrow\ x=y=\lambda=5,\ f=25. \]

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\).

  • \(x=0\Rightarrow y=\pm1\) (\(\lambda=1\)): \(f=1\).
  • \(y=0\Rightarrow x=\pm2\) (\(\lambda=4\)): \(f=4\).
  • \(\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

  1. 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.

  2. 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\).

  3. medium

    Maximize \(3x+4y\) on \(x^2+y^2=25\).

    Show solution

    \(3=2\lambda x\), \(4=2\lambda y\Rightarrow\frac yx=\frac43\). \(x^2+\frac{16}9x^2=25\Rightarrow x=\pm3\). \((3,4)\): \(f=25\) (max), \((-3,-4)\): \(-25\) (min). (Cauchy–Schwarz: \(|3x+4y|\le5\cdot5\).)

  4. hard

    (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.)

  5. 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.

    Show solution

    Interior: \(f_x=2x-2=0,\ f_y=2y=0\Rightarrow(1,0)\), inside (\(1\lt4\)), \(f=-1\).

    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.

Remember

  • \(\mathcal L=f-\lambda(g-c)\); solve \(\nabla f=\lambda\nabla g\), \(g=c\).
  • 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):

\[ \nabla f=\sum_j\lambda_j\nabla g_j,\qquad \lambda_j\ge0,\qquad g_j\le c_j,\qquad \lambda_j\,(g_j-c_j)=0\ \ (\text{complementary slackness}). \]

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)

  1. Draw each constraint as a line and shade the allowed side (test the origin).
  2. Find the vertices: intersect pairs of boundary lines; keep only feasible intersections.
  3. 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).
  4. If the region is unbounded, check whether the objective can grow without limit.

Worked examples

Example 1 (active constraint). Minimize \(f=(x-2)^2+(y-2)^2\) s.t. \(x+y\le2\). Equivalently maximize \(-f\).

  • 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\).

(3,1) z=9(4,0)(0,2) x+y=4x+3y=62x+3y=9 xy
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\)0896

Maximum \(z=9\) at \((3,1)\), confirmed with SciPy:

from scipy.optimize import linprog
# maximize 2x + 3y  ->  minimize -2x - 3y
res = linprog(c=[-2, -3],
              A_ub=[[1, 1], [1, 3]], b_ub=[4, 6],
              bounds=[(0, None), (0, None)])
print(res.status, res.x, -res.fun)
0 [3. 1.] 9.0

Exercises

  1. easy

    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)\).

  2. 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.

  3. 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)\).

  4. hard

    Maximize \(z=5x+4y\) s.t. \(6x+4y\le24\), \(x+2y\le6\), \(-x+y\le1\), \(y\le2\), \(x,y\ge0\).

    Show solution

    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).

  5. 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.
  • Outcomes: unique / multiple / unbounded / infeasible.

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\).

Least squares loss

\[ L(\mathbf w)=\|X\mathbf w-\mathbf y\|^2=\sum_{i=1}^m(w_0+w_1x_i-y_i)^2,\qquad \nabla L=2X^T(X\mathbf w-\mathbf y),\qquad H_L=2X^TX. \]

(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

\[ \mathbf w^{(k+1)}=\mathbf w^{(k)}-\eta\,\nabla f(\mathbf w^{(k)}), \]

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)\).

\[ X=\begin{pmatrix}1 & 0\\ 1 & 1\\ 1 & 2\\ 1 & 3\end{pmatrix},\quad X^TX=\begin{pmatrix}4 & 6\\ 6 & 14\end{pmatrix},\quad X^T\mathbf y=\begin{pmatrix}12\\ 23\end{pmatrix}. \]

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

  1. easy

    Write the loss \(L(w_0,w_1)\) and the design matrix for the points \((0,1),(1,2),(2,4)\).

    Show solution

    \(L=(w_0-1)^2+(w_0+w_1-2)^2+(w_0+2w_1-4)^2\); \(X=\begin{pmatrix}1 & 0\\ 1 & 1\\ 1 & 2\end{pmatrix}\), \(\mathbf y=(1,2,4)\).

  2. medium

    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\).

  3. 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\).

  4. 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.

  5. 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).

Remember

  • \(L=\|X\mathbf w-\mathbf y\|^2\), \(\nabla L=2X^T(X\mathbf w-\mathbf y)\), convex; normal equations \(X^TX\hat{\mathbf w}=X^T\mathbf y\).
  • GD: \(\mathbf w\leftarrow\mathbf w-\eta\nabla f\); quadratic: \(0\lt\eta\lt2/\lambda_{\max}(H)\).
  • Least squares fit = projection of \(\mathbf y\) onto \(\operatorname{Col}X\).

B2.18 Discrete dynamical systems: fixed points, stability, cobweb; logistic and Ricker models

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.

x₀=0.1x*≈0.643y=xy=2.8x(1−x)1
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\).

Ricker model

\(x_{t+1}=x_t\,e^{r(1-x_t/K)}\), \(r\gt0\) growth rate, \(K\gt0\) carrying capacity. Fixed points: \(0\) and \(K\). \(f'(x)=e^{r(1-x/K)}\left(1-\frac{rx}{K}\right)\): \(f'(0)=e^r\gt1\) (0 unstable), \(f'(K)=1-r\).

  • \(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).

Worked examples

Example 1 (affine). \(x_{t+1}=0.5x_t+3\), \(x_0=0.2\). \(x^*=\frac3{1-0.5}=6\); \(x_t=6-5.8\cdot0.5^t\): \(3.1,\ 4.55,\ 5.275,\ 5.6375,\dots\to6\). Stable, monotone (\(a=0.5\in(0,1)\)).

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

  1. 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\).

  2. easy

    Solve \(x_{t+1}=-0.5x_t+6\) with \(x_0=0\). Describe the behaviour.

    Show solution

    \(x^*=\frac{6}{1.5}=4\); \(x_t=4+(-0.5)^t(0-4)=4-4(-0.5)^t\): 0, 6, 3, 4.5, 3.75, ... Converges to 4, oscillating (\(a=-0.5\)).

  3. medium

    Study \(x_{t+1}=\sqrt{x_t+2}\), \(x_0\ge0\).

    Show solution

    \(\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.

  4. 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).

  5. 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\).

    Show solution

    Product rule: \(f'(x)=e^{r(1-x/K)}+x\,e^{r(1-x/K)}\cdot(-\frac rK)=e^{r(1-x/K)}(1-\frac{rx}K)\). At \(x=K\): \(e^0(1-r)=1-r\). Stable iff \(|1-r|\lt1\iff0\lt r\lt2\).

    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\).

  6. 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

\[ \mathbf x(t)=c_1e^{\lambda_1t}\mathbf v_1+c_2e^{\lambda_2t}\mathbf v_2, \]

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}\).

EigenvaluesType of the originStability
real, both \(\lt0\)node (sink)asymptotically stable
real, both \(\gt0\)node (source)unstable
real, opposite signssaddleunstable
\(\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

saddle: λ = 3, −1stable spiral: λ = −1 ± 2i
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\):

\[ \mathbf x(t)=2e^{3t}\begin{pmatrix}1\\1\end{pmatrix}+e^{-t}\begin{pmatrix}1\\-1\end{pmatrix},\quad x(t)=2e^{3t}+e^{-t},\ y(t)=2e^{3t}-e^{-t}. \]

(Checked: \(\mathbf x'-A\mathbf x=\mathbf 0\).) Only solutions starting exactly on the line \(y=-x\) go to the origin.

Example 2 (stable node). \(A=\begin{pmatrix}-2 & 1\\ 1 & -2\end{pmatrix}\): \(\tau=-4\), \(\Delta=3\), \(\tau^2-4\Delta=4\ge0\): eigenvalues \(-1\) (\((1,1)\)) and \(-3\) (\((1,-1)\)). Stable node: \(\mathbf x(t)=c_1e^{-t}(1,1)+c_2e^{-3t}(1,-1)\to\mathbf 0\), arriving tangent to \((1,1)\) (the slow direction).

Example 3 (spiral and center). \(A=\begin{pmatrix}-1 & -2\\ 2 & -1\end{pmatrix}\): \(\tau=-2\), \(\Delta=5\), \(\tau^2-4\Delta=-16\lt0\): \(\lambda=-1\pm2i\), stable spiral. Direction: at \((1,0)\) the velocity is \((-1,2)\), upward: counter-clockwise. \(A=\begin{pmatrix}0 & 1\\ -4 & 0\end{pmatrix}\) (the oscillator \(x''=-4x\)): \(\tau=0\), \(\Delta=4\): \(\lambda=\pm2i\), center, ellipses \(4x^2+y^2=\text{const}\).

Exercises

  1. easy

    Find and classify the equilibria of \(x'=x(1-x)\) and of \(x'=x^2-4\).

    Show solution

    First: \(x^*=0,1\); \(f'(x)=1-2x\): \(f'(0)=1\) unstable, \(f'(1)=-1\) stable. Second: \(x^*=\pm2\); \(f'(x)=2x\): \(f'(2)=4\) unstable, \(f'(-2)=-4\) stable.

  2. easy

    Classify the origin for \(A=\begin{pmatrix}3 & 1\\ 1 & 3\end{pmatrix}\), \(\begin{pmatrix}1 & -1\\ 1 & 1\end{pmatrix}\), \(\begin{pmatrix}2 & -5\\ 1 & -2\end{pmatrix}\).

    Show solution

    (a) \(\tau=6,\Delta=8\), \(\tau^2-4\Delta=4\): \(\lambda=4,2\): unstable node. (b) \(\tau=2,\Delta=2\), \(\tau^2-4\Delta=-4\): \(\lambda=1\pm i\): unstable spiral. (c) \(\tau=0,\Delta=-4+5=1\): \(\lambda=\pm i\): center.

  3. medium

    Write \(x''+3x'+2x=0\) as a first-order system, classify the origin and give the general solution.

    Show solution

    \(y=x'\): \(x'=y\), \(y'=-2x-3y\), \(A=\begin{pmatrix}0 & 1\\ -2 & -3\end{pmatrix}\), \(\tau=-3,\Delta=2\): \(\lambda^2+3\lambda+2=0\Rightarrow\lambda=-1,-2\). Stable node. Eigenvectors \((1,-1)\) and \((1,-2)\): \(\mathbf x(t)=c_1e^{-t}(1,-1)+c_2e^{-2t}(1,-2)\), so \(x(t)=c_1e^{-t}+c_2e^{-2t}\).

  4. medium

    Solve \(\mathbf x'=\begin{pmatrix}-2 & 1\\ 1 & -2\end{pmatrix}\mathbf x\), \(\mathbf x(0)=(3,1)\).

    Show solution

    Eigenpairs \(-1,(1,1)\) and \(-3,(1,-1)\) (Example 2). \((3,1)=c_1(1,1)+c_2(1,-1)\Rightarrow c_1=2,c_2=1\). \(\mathbf x(t)=2e^{-t}(1,1)+e^{-3t}(1,-1)\): \(x=2e^{-t}+e^{-3t}\), \(y=2e^{-t}-e^{-3t}\). Check \(t=0\): \((3,1)\) ✓.

  5. hard

    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.)

  6. 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\).
  • \(\mathbf x'=A\mathbf x\): \(\mathbf x=c_1e^{\lambda_1t}\mathbf v_1+c_2e^{\lambda_2t}\mathbf v_2\).
  • \(\Delta\lt0\) saddle; \(\Delta\gt0\): node or spiral (\(\tau^2-4\Delta\)); stable iff \(\tau\lt0,\Delta\gt0\); \(\tau=0,\Delta\gt0\) center.

Practice under exam conditions

Mock exams

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 stated check: 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

TermMeaning
Relation / tuple / attributetable / row / column
Degree / cardinality# columns / # rows
Schema / instancestructure (stable) / current rows
Superkeyno two rows agree on it
Key (candidate)minimal superkey
Primary keychosen key, UNIQUE + NOT NULL
Foreign keyvalue 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

FamilyExample
Key-value (opaque value)Redis, DynamoDB
Document (JSON/BSON)MongoDB, CouchDB
Column-familyCassandra, HBase
GraphNeo4j

CAP: with a partition choose C or A. BASE: Basically Available, Soft state, Eventual consistency. Mongo: db → collection → document (_id) → field; embed vs reference.

MongoDB find

db.movies.find(
  { year: { $gte: 1995, $lte: 2005 },          // implicit AND
    genres: "Drama",                            // any array element
    director: { $ne: "X" },                     // also matches missing
    genres: { $in: ["A", "B"] },  /* $nin */
    $or: [ { a: 1 }, { b: { $lt: 2 } } ],       /* $and */
    year: { $not: { $gte: 2000 } },
    ratings: { $exists: false },                // budget: null = null OR missing
    year: { $type: "string" },  cast: { $size: 2 },
    ratings: { $elemMatch: { source: "IMDb", score: { $gte: 9 } } },
    "cast.name": "Keanu Reeves" },              // dot notation needs quotes
  { _id: 0, title: 1 }                          // no 1/0 mixing (except _id)
).sort({ year: -1 }).limit(3)
db.movies.countDocuments({ ... })

(One key per object in real code: repeated keys above are just a list.)

MongoDB aggregation

db.movies.aggregate([
  { $match: { director: "Christopher Nolan" } },   // WHERE
  { $unwind: "$ratings" },                         // drops empty/missing
  { $match: { "ratings.source": "IMDb" } },
  { $group: { _id: "$director",                    // _id: null = all
      n: { $sum: 1 }, tot: { $sum: "$ratings.votes" },
      avg: { $avg: "$ratings.score" }, titles: { $push: "$title" } } },
  { $match: { n: { $gte: 2 } } },                  // HAVING
  { $set: { big: { $cond: [ { $gte: ["$n", 3] }, "yes", "no" ] } } },
  { $project: { _id: 0, director: "$_id", n: 1,
      k: { $size: { $ifNull: ["$titles", []] } }, t: { $type: "$avg" } } },
  { $sort: { n: -1, director: 1 } }
])

Field values need "$field"; filter field names do not. $set keeps other fields, $project drops them.

R framework: syntax and traps

Basics & workspace

x <- 5; (y <- x * 2)     # () prints
?mean; ??regression       # help
ls(); rm(x); rm(list = ls())
getwd(); setwd("C:/data")
install.packages("dplyr") # once
library(dplyr)            # each session
stats::filter             # avoid masking

Types & coercion

CodeResult
typeof(5) / typeof(5L)"double" / "integer"
c(1, "a", TRUE)"1" "a" "TRUE"
c(1, TRUE)1 1
as.numeric("x")NA + warning
5L / 2L, 5 %/% 2, -5 %% 22.5, 2, 1
NA & FALSE, NA | TRUEFALSE, TRUE
c(1, NULL), length(NULL)1, 0

logical < integer < double < complex < character

Vectors

x <- c(10, 20, 30, 40)
x[2]; x[-1]; x[x > 15]; x[6]   # 20 | 20 30 40 | 20 30 40 | NA
x[0]                           # numeric(0)
seq(1, 10, by = 3)             # 1 4 7 10
rep(1:2, times = 2); rep(1:2, each = 2)
1:n-1  # = (1:n) - 1   !
1:4 + 1:2                      # 2 4 4 6 (recycling)
which(x > 15); sort(x); order(x)
sum(x, na.rm = TRUE); is.na(x)

Matrices & arrays

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

CallReturns
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

ggplot2 & patchwork

ggplot(df, aes(x, y, colour = grp)) +
  geom_point(size = 2) +          # constants outside aes
  geom_smooth(method = "lm") +
  facet_wrap(~ var) +
  labs(title = "", x = "", y = "") +
  theme_minimal()
geom_bar()  # counts | geom_col() # uses y
p1 | p2; p1 / p2; (p1 | p2) / p3
ggsave("p.png", p)

Random numbers

set.seed(1)
rnorm(n, mean, sd); runif(n, a, b)
rbinom(n, size, prob); rpois(n, lambda)
sample(x, k, replace = TRUE, prob = p)
sample(5)            # permutation of 1:5
replicate(B, expr)
dnorm(0) 0.3989  pnorm(1.96) 0.975
qnorm(0.975) 1.96  pnorm(0) 0.5

d = density/pmf, p = CDF, q = quantile, r = draws

Packages & debugging

usethis::create_package("pkg")
#' @export      (roxygen)
devtools::document(); load_all(); check(); install()
traceback()            # call stack after error
browser()              # pause here (n, s, c, Q)
debug(f); debugonce(f); undebug(f)
tryCatch(expr, error = function(e) ...,
         warning = function(w) ..., finally = ...)

DESCRIPTION (deps) · NAMESPACE (exports) · R/ · man/

Inference & Quarto

r <- t.test(x, mu = 5)       # one-sample
t.test(y ~ g, data = df)     # Welch default
t.test(a, b, paired = TRUE)
prop.test(45, 100); chisq.test(tab)
r$p.value; r$conf.int; r$estimate
#| echo: false    hide code, show output
#| eval: false    show code, don't run
#| include: false run, show nothing
# inline: `r mean(x)`

Python, NumPy, pandas, scikit-learn

Numbers and truthiness

7 / 2   # 3.5 (always float)
7 // 2  # 3     -7 // 2  # -4 (floor)
-7 % 2  # 1 (sign of divisor)
2 ** 3 ** 2   # 512 (right-assoc.)
round(2.5)    # 2 (half to even)
True + True   # 2
# falsy: False None 0 0.0 "" [] () {} set()
0 or "x"      # 'x'   2 and []  # []
1 < x < 5     # chained comparison

Slicing s[start:stop:step]

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

Plots

fig, ax = plt.subplots(1, 2, figsize=(10, 4))
ax[0].hist(x, bins=20)
ax[1].scatter(x, y)
ax[1].set(title="...", xlabel="... (unit)",
          ylabel="...")
ax[1].legend(); fig.tight_layout()
fig.savefig("out.png")
sns.boxplot(data=df, x="g", y="v", ax=ax)
# distribution: hist/box; relation: scatter
# time: line; categories: bar

IQR outlier: outside \([Q_1 - 1.5\,IQR,\ Q_3 + 1.5\,IQR]\).

scikit-learn

Xtr, Xte, ytr, yte = train_test_split(
    X, y, test_size=.3, random_state=0,
    stratify=y)
m = make_pipeline(StandardScaler(),
                  KNeighborsClassifier(5))
m.fit(Xtr, ytr); m.predict(Xte); m.score(Xte, yte)
LogisticRegression(); DecisionTreeClassifier(max_depth=3)
confusion_matrix(yt, yp)  # rows = TRUE
KMeans(n_clusters=3, n_init=10).fit(Xs).labels_
silhouette_score(Xs, labels)  # [-1, 1]

\(P = \frac{TP}{TP+FP}\), \(R = \frac{TP}{TP+FN}\), \(F_1 = \frac{2PR}{P+R}\), accuracy \(= \frac{TP+TN}{n}\).

Project essentials

  • Deadline 30 Nov 2026, 23:59 CET, last GitHub commit; invite the instructor; register via the form (dates check).
  • Evaluation Dec 1/2/3: 5-10 min talk + questions on code and course topics.
  • Criteria: GitHub, organisation, I/O, manipulation, scientific computing, visualisation, presentation.
  • Notebook/app imports your package; README, .gitignore, requirements.txt.
  • AI: assistant only, never core logic; explain everything.

Statistical Theory: distributions, estimators, intervals, tests

Distributions (mean, variance)

FamilyMeanVar
Bernoulli\((p)\)\(p\)\(p(1-p)\)
Bin\((n,p)\)\(np\)\(np(1-p)\)
Poisson\((\lambda)\)\(\lambda\)\(\lambda\)
Geom\((p)\) on \(1,2,\dots\)\(1/p\)\((1-p)/p^2\)
U\((a,b)\)\((a+b)/2\)\((b-a)^2/12\)
Exp (rate \(\lambda\))\(1/\lambda\)\(1/\lambda^2\)
Gamma\((\alpha,\beta)\) scale\(\alpha\beta\)\(\alpha\beta^2\)
\(N(\mu,\sigma^2)\)\(\mu\)\(\sigma^2\)
Beta\((\alpha,\beta)\)\(\frac{\alpha}{\alpha+\beta}\)\(\frac{\alpha\beta}{(\alpha+\beta)^2(\alpha+\beta+1)}\)
\(\chi^2_p\)\(p\)\(2p\)

MGFs: Bin \((1-p+pe^t)^n\); Pois \(e^{\lambda(e^t-1)}\); Exp \(\frac{\lambda}{\lambda-t}\); \(N\): \(e^{\mu t+\sigma^2t^2/2}\). \(E X^n=M^{(n)}(0)\).

Probability basics

\(P(A\cup B)=P(A)+P(B)-P(A\cap B)\); \(P(A\mid B)=P(A\cap B)/P(B)\); Bayes \(P(C_i\mid A)=\frac{P(A\mid C_i)P(C_i)}{\sum_jP(A\mid C_j)P(C_j)}\).

\(\text{Var}X=EX^2-(EX)^2\); \(\text{Var}(aX+bY)=a^2\text{Var}X+b^2\text{Var}Y+2ab\,\text{Cov}\); \(\rho=\frac{\text{Cov}}{\sigma_X\sigma_Y}\); \(EX=E[E(X\mid Y)]\).

Transform (monotone): \(f_Y(y)=f_X(g^{-1}(y))\,|\tfrac{d}{dy}g^{-1}(y)|\). Chebyshev \(P(|X-\mu|\ge k)\le\sigma^2/k^2\).

Max: \(F^n\); min: \(1-(1-F)^n\). ECDF: \(nF_n(x)\sim\text{Bin}(n,F(x))\). Buffon: \(2\ell/(\pi d)\).

Sampling from the normal

\(\bar X\sim N(\mu,\sigma^2/n)\), \(\frac{(n-1)S^2}{\sigma^2}\sim\chi^2_{n-1}\), independent; \(\frac{\bar X-\mu}{S/\sqrt n}\sim t_{n-1}\); \(\frac{S_1^2/\sigma_1^2}{S_2^2/\sigma_2^2}\sim F_{n_1-1,n_2-1}\).

\(t_p=Z/\sqrt{V/p}\); \(F_{p,q}=\frac{U/p}{V/q}\). \(E S^2=\sigma^2\), \(\text{Var}S^2=\frac{2\sigma^4}{n-1}\).

Point estimation

Sufficiency: \(f(\mathbf x\mid\theta)=g(T(\mathbf x)\mid\theta)h(\mathbf x)\). Exp. family: \(\sum t_j(X_i)\). U\((0,\theta)\): \(X_{(n)}\).

MoM: \(\bar X=E_\theta X\), \(\frac1n\sum X_i^2=E_\theta X^2\).

MLE: solve \(\ell'(\theta)=0\), check \(\ell''\lt0\); support depends on \(\theta\) → order statistic. Invariance \(\widehat{\tau(\theta)}=\tau(\hat\theta)\).

Bayes (squared loss = posterior mean): Beta\((\alpha+y,\beta+n-y)\); Poisson–Gamma\((a+\sum x,b+n)\); Exp–Gamma\((a+n,b+\sum x)\) (rate).

\(\text{MSE}=\text{Var}+\text{Bias}^2\).

Fisher information & CRB

\(I_1=E[(\partial_\theta\log f)^2]=-E[\partial^2_\theta\log f]\); \(I_n=nI_1\).

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)\).

Slutsky: \(X_n\xrightarrow{d}X\), \(Y_n\xrightarrow{P}a\) ⇒ \(X_nY_n\xrightarrow{d}aX\).

Delta: \(\sqrt n(g(Y_n)-g(\theta))\xrightarrow{d}N(0,g'(\theta)^2\sigma^2)\).

MLE: \(\sqrt n(\hat\theta-\theta)\xrightarrow{d}N(0,1/I_1(\theta))\).

Confidence intervals (upper quantiles)

\(\bar X\pm z_{\alpha/2}\sigma/\sqrt n\); \(\bar X\pm t_{n-1,\alpha/2}S/\sqrt n\)

\(\sigma^2\): \(\big[\frac{(n-1)S^2}{\chi^2_{n-1,\alpha/2}},\frac{(n-1)S^2}{\chi^2_{n-1,1-\alpha/2}}\big]\)

Exp rate: \(2\lambda\sum X_i\sim\chi^2_{2n}\). U\((0,\theta)\): \([X_{(n)},X_{(n)}\alpha^{-1/n}]\).

Wald: \(\hat\theta\pm z_{\alpha/2}/\sqrt{I_n(\hat\theta)}\); \(\hat p\pm z_{\alpha/2}\sqrt{\hat p(1-\hat p)/n}\). \(n\ge(z_{\alpha/2}\sigma/E)^2\).

\(z_{0.05}=1.645\), \(z_{0.025}=1.960\), \(z_{0.005}=2.576\).

Testing theory

\(\beta(\theta)=P_\theta(\text{reject})\); size \(\sup_{\Theta_0}\beta\); level: size \(\le\alpha\).

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\).

Classical tests

TestStatisticNull dist.
mean, \(\sigma\) known\(\frac{\bar X-\mu_0}{\sigma/\sqrt n}\)\(N(0,1)\)
mean, \(\sigma\) unknown\(\frac{\bar X-\mu_0}{S/\sqrt n}\)\(t_{n-1}\)
proportion\(\frac{\hat p-p_0}{\sqrt{p_0(1-p_0)/n}}\)\(\approx N(0,1)\)
variance\(\frac{(n-1)S^2}{\sigma_0^2}\)\(\chi^2_{n-1}\)
pooled two-sample\(\frac{\bar X-\bar Y}{S_p\sqrt{1/n_1+1/n_2}}\)\(t_{n_1+n_2-2}\)
two proportions\(\frac{\hat p_1-\hat p_2}{\sqrt{\hat p(1-\hat p)(1/n_1+1/n_2)}}\)\(\approx N(0,1)\)
two variances\(S_1^2/S_2^2\)\(F_{n_1-1,n_2-1}\)
paired\(\frac{\bar D}{S_D/\sqrt n}\)\(t_{n-1}\)

R quick reference

qnorm(0.975); qt(0.975, df); qchisq(0.95, df); qf(0.95, d1, d2)
pnorm(z, lower.tail = FALSE)
t.test(x, mu = m0); t.test(x, y, var.equal = TRUE); t.test(a, b, paired = TRUE)
prop.test(c(y1, y2), c(n1, n2), correct = FALSE); var.test(x, y)
optimize(function(t) -sum(dcauchy(x, t, log = TRUE)), c(-10, 10))
ecdf(x); median(x); set.seed(1); replicate(R, expr)

Mathematics: linear algebra, graphs, optimization, dynamics

Span, independence, basis

  • \(\mathbf b\in\operatorname{Span}\{\mathbf v_i\}\iff[\mathbf v_1\cdots\mathbf v_p|\mathbf b]\) consistent.
  • Independent \(\iff\) pivot in every column.
  • \(n\) vectors in \(\mathbb{R}^n\): basis \(\iff\det\neq0\).
  • \(\dim\operatorname{Span}=\operatorname{rank}\) = # pivots. Parameter: factor \(\det\), then study each root.

Systems (Rouché–Capelli)

  • Consistent \(\iff\operatorname{rank}A=\operatorname{rank}[A|\mathbf b]=r\).
  • \(r=n\): one solution; \(r\lt n\): \(\infty^{n-r}\).
  • Row \([0\cdots0\mid c\neq0]\): no solution.
  • rank + nullity = # columns.

Determinant & inverse

  • \(\det\begin{pmatrix}a & b\\ c & d\end{pmatrix}=ad-bc\), inverse \(\frac1{ad-bc}\begin{pmatrix}d & -b\\ -c & a\end{pmatrix}\).
  • \(\det(AB)=\det A\det B\), \(\det A^T=\det A\), \(\det(cA)=c^n\det A\), \(\det A^{-1}=1/\det A\).
  • Row swap: sign change; add multiple of row: no change.
  • \((AB)^{-1}=B^{-1}A^{-1}\), \((AB)^T=B^TA^T\). Gauss–Jordan \([A|I]\to[I|A^{-1}]\).

Orthogonality

  • \(\|\mathbf u\|=\sqrt{\mathbf u\cdot\mathbf u}\), \(\cos\theta=\frac{\mathbf u\cdot\mathbf v}{\|\mathbf u\|\|\mathbf v\|}\).
  • \(\operatorname{proj}_{\mathbf u}\mathbf y=\frac{\mathbf y\cdot\mathbf u}{\mathbf u\cdot\mathbf u}\mathbf u\); residual \(\perp\) subspace.
  • Gram–Schmidt: \(\mathbf v_k=\mathbf x_k-\sum_{j\lt k}\frac{\mathbf x_k\cdot\mathbf v_j}{\mathbf v_j\cdot\mathbf v_j}\mathbf v_j\).
  • \(Q^TQ=I\Rightarrow Q^{-1}=Q^T\), \(\det Q=\pm1\).

Eigen & quadratic forms

  • \(\det(A-\lambda I)=0\); 2×2: \(\lambda^2-\operatorname{tr}A\,\lambda+\det A=0\).
  • \(\sum\lambda=\operatorname{tr}\), \(\prod\lambda=\det\); triangular: diagonal.
  • \(A=PDP^{-1}\iff n\) indep. eigenvectors; \(A^k=PD^kP^{-1}\).
  • Symmetric: \(A=QDQ^T\). PD \(\iff\) all \(\lambda\gt0\iff D_1,D_2,\dots\gt0\); ND: \(D_1\lt0,D_2\gt0,\dots\); 2×2 \(\det\lt0\): indefinite.
  • \(ax^2+2bxy+cy^2\leftrightarrow\begin{pmatrix}a & b\\ b & c\end{pmatrix}\).

SVD

  • \(A=U\Sigma V^T\), \(\sigma_i=\sqrt{\lambda_i(A^TA)}\), \(\mathbf v_i\) eigenvectors of \(A^TA\), \(\mathbf u_i=A\mathbf v_i/\sigma_i\).
  • rank = # \(\sigma_i\gt0\); \(\max_{\|\mathbf x\|=1}\|A\mathbf x\|=\sigma_1\); \(\|A\|_F^2=\sum\sigma_i^2\).
  • \(A_k=\sum_{i\le k}\sigma_i\mathbf u_i\mathbf v_i^T\): error \(\sigma_{k+1}\) (2-norm), \(\sqrt{\sum_{i\gt k}\sigma_i^2}\) (Frobenius); storage \(k(m+n+1)\).

Graphs

  • \(\sum\deg=2m\). Matrix \(O(n^2)\), list \(O(n+m)\).
  • \((A^k)_{ij}\) = # walks of length \(k\); \(\operatorname{tr}A^3=6\,\)#triangles.
  • \(L=D-A=BB^T\), PSD, \(\mathbf x^TL\mathbf x=\sum_{ij\in E}(x_i-x_j)^2\); # zero eigenvalues = # components; \(\operatorname{tr}L=2m\).
  • BFS: queue, layers, shortest paths (unweighted). DFS: stack. Both \(O(n+m)\) (list), \(O(n^2)\) (matrix).
  • Diameter = max distance.

PageRank

  • \(M_{ij}=1/\text{outdeg}(j)\) if \(j\to i\) (columns sum to 1); dangling column \(\to\frac1n\mathbf 1\).
  • \(\mathbf r=dM\mathbf r+\frac{1-d}n\mathbf 1\), \(\sum r_i=1\), \(d\approx0.85\).
  • Power iteration \(\mathbf r\leftarrow G\mathbf r\) from \(\frac1n\mathbf 1\). No in-links: \(r_i=\frac{1-d}n\).

Several variables

  • \(\nabla f=(f_x,f_y)\), \(D_{\mathbf u}f=\nabla f\cdot\mathbf u\), steepest ascent, \(\perp\) level curves.
  • \(C^1\Rightarrow\) differentiable \(\Rightarrow\) continuous (partials alone: not enough).
  • Tangent plane \(z=f(a,b)+f_x(x-a)+f_y(y-b)\).
  • \(H=\begin{pmatrix}f_{xx} & f_{xy}\\ f_{xy} & f_{yy}\end{pmatrix}\); \(\frac{\partial}{\partial x}e^{g}=g_xe^{g}\).

Optimization

  • FOC \(\nabla f=\mathbf 0\). \(\Delta=f_{xx}f_{yy}-f_{xy}^2\): \(\gt0,f_{xx}\gt0\) min; \(\gt0,f_{xx}\lt0\) max; \(\lt0\) saddle; \(=0\) ?
  • Closed bounded set: interior points + each side (with endpoints) + corners, compare (Weierstrass).
  • Lagrange: \(\nabla f=\lambda\nabla g\), \(g=c\); \(\lambda=df^*/dc\).
  • KKT (max, \(g\le c\)): \(\nabla f=\lambda\nabla g\), \(\lambda\ge0\), \(\lambda(g-c)=0\).
  • LP: optimum at a vertex; list feasible vertices, evaluate.
  • LS: \(X^TX\hat{\mathbf w}=X^T\mathbf y\); GD \(\mathbf w\leftarrow\mathbf w-\eta\nabla f\), \(\eta\lt2/\lambda_{\max}(H)\).

Dynamical systems

Discrete \(x_{t+1}=f(x_t)\)Continuous \(x'=f(x)\)
Equilibrium\(f(x^*)=x^*\)\(f(x^*)=0\)
Stable if\(|f'(x^*)|\lt1\)\(f'(x^*)\lt0\)
Linear systemall \(|\lambda|\lt1\)all \(\operatorname{Re}\lambda\lt0\)
  • Affine: \(x_t=x^*+a^t(x_0-x^*)\), \(x^*=\frac b{1-a}\).
  • Logistic \(rx(1-x)\): \(x^*=1-\frac1r\), \(f'=2-r\), stable \(1\lt r\lt3\). Ricker \(xe^{r(1-x/K)}\): \(x^*=K\), \(f'=1-r\), stable \(0\lt r\lt2\).
  • \(\mathbf x'=A\mathbf x\): \(\mathbf x=c_1e^{\lambda_1t}\mathbf v_1+c_2e^{\lambda_2t}\mathbf v_2\). \(\det\lt0\) saddle; \(\det\gt0\): node if \(\tau^2\ge4\det\), spiral if \(\lt\) (center if \(\tau=0\)); stable iff \(\tau\lt0,\det\gt0\).

Definitions

Glossary

0 terms from all modules. Filter by module or type to search.