Introduction to epidemiology · Lesson 5 of 12

The SIR epidemic curve

Why does an outbreak rise, peak and fall even when nobody does anything? This lesson builds the simplest model that answers that, the SIR model, and lets you drive it.

An example lesson built by Chalkwork Labs from public sources, to show the shapes. Your course is drafted from your material and checked by you.

1. Three groups of people

Picture a town of one million people. A new disease arrives. On day zero, one hundred people have it. Everyone else is healthy, and none of them has ever met this disease before.

Sort every person in the town into one of three groups:

Ssusceptible
→infection
Iinfected
→recovery
Rrecovered

Each group is a compartment. A person is always in exactly one of them. People move in one direction only: from S to I when they catch the disease, and from I to R when they recover. The three counts always add up to the whole town.

That is the whole picture. The model does not follow any one person. It only counts how many people move from one box to the next each day. Two rules decide those moves.

2. Two rules of movement

Rule 1: infection. Each infected person meets people every day. Some of those meetings would pass the disease on, if the other person could catch it. The contact rate, written β (beta), counts those meetings per person per day.

But a meeting only matters if the other person is susceptible. If half the town is already recovered, half of those meetings are wasted. So the number of new infections per day is β, times the number of infected people, times the share of the town still susceptible.

new infections per day = β × I × (S / N)

Here N is the town's population. S / N is the share still susceptible.

Rule 2: recovery. People stay infectious for a while and then recover. The recovery period, written D, is the average number of days a person stays infectious. Each day, about one D-th of the infected group recovers. Books write this share as γ (gamma), the recovery rate, so γ = 1 / D.

recoveries per day = I / D

Those two rules are the whole SIR model. Kermack and McKendrick wrote it down in 1927 (see Sources). The three counts change like this each day:

S falls by the new infections
I rises by the new infections and falls by the recoveries
R rises by the recoveries

One number sums up both rules. The basic reproduction number, R0 (say "R nought"), is how many people one infected person infects when everyone else is susceptible. It is the contacts per day, times the days a person stays infectious:

R0 = β × D

If R0 is above 1, each case leads to more than one new case and the outbreak grows. If R0 is below 1, it fades. R0 = 3 means each early case leads to about three more.

Why does the curve ever come down? Because of S / N. Every infection removes a susceptible person. As S shrinks, each infected person wastes more of their meetings. When the share still susceptible falls below 1 / R0, recoveries overtake new infections, and I starts to fall. Nothing else needs to happen. The next section shows that.

3. The signature chart

The chart runs the two rules forward, day by day, in a population of 100%. Move the sliders. Watch where the peak lands and how much of the town is ever infected.

susceptibleinfectedrecovered
–
peak day
–
peak share infected
–
final size (ever infected)
–
contact rate β per day
Shares of the town in each group, day by day. The dashed line marks the peak of the infected curve.

Three things to try:

The final size is the share of the town that is ever infected. It is never 100%. The outbreak runs out of susceptible people before it runs out of time. With R0 = 3, about % are ever infected and % are never touched.

What the model leaves out. Everyone meets everyone with the same chance. Nobody changes their behaviour. Immunity lasts. No one is born, dies or travels. The real curve in section 5 breaks several of these.

4. Herd immunity and vaccination

R0 counts new cases per case when everyone is susceptible. That is only true on day zero. On any later day, use the effective reproduction number, R: R0 times the share still susceptible.

R = R0 × (S / N)

The curve peaks on the day R drops to 1. Set R = 1 and solve for the share that must be immune:

herd immunity threshold = 1 − 1 / R0

This share is the herd immunity threshold. For R0 = 3 it is 1 − 1/3 = %. If that share of the town is immune before the outbreak starts, R starts at 1 and the curve never rises.

A vaccine moves people straight from S to R before day zero. Try it. The dashed grey curve is the same outbreak with no vaccine.

infected, no vaccineinfected, with vaccine
–
herd immunity threshold
–
R on day 0
–
peak share infected
–
final size, of the whole town

Two things to notice. First, vaccination helps long before the threshold. With R0 = 3 and half the town vaccinated, R on day 0 is , and the final size falls from % to %. Second, the threshold is not the final size. An outbreak that starts in a fully susceptible town does not stop at the threshold. It overshoots, because there are still many infected people around when R reaches 1.

R0herd immunity thresholdfinal size with no vaccine

Final sizes in this table come from the exact equation of the continuous model with a vanishing seed, so they differ by a fraction of a point from the day-by-day chart, which starts with 0.1% infected.

5. A real curve

Here is a real epidemic curve. It is the United Kingdom's first COVID-19 wave: new confirmed cases per million people, as a 7-day average, from 1 March to 30 June 2020. The data come from Our World in Data (CC BY 4.0, see Sources).

The real curve peaked on , day , at cases per million per day. The model overlay is the base run from section 3 (R0 = 3, D = 7), showing new infections per day. It is shifted so the peaks line up and scaled so they match in height. Only the shapes are being compared.

The left sides look alike: a steep rise. The right sides do not. The real curve falls more slowly and stays high for weeks. The model is not wrong so much as incomplete. Three candidate explanations, none of which the chart can settle on its own:

6. A worked example

Take the town from section 1: N = people, I = infected on day 0, R0 = , D = days.

StepWorkingResult
Contact rate βR0 / D = 3 / 7 per day
Recovery rate γ1 / D = 1 / 7 per day
New infections on day 0β × I × S / N = 0.4286 × 100 × 0.9999about people
Recoveries on day 0I / D = 100 / 7about people
Herd immunity threshold1 − 1 / R0 = 1 − 1/3% = people
Peak (day-by-day run)run the rules forwardday , % = people
Final size (day-by-day run)share ever infected by day 365% = people
Final size (exact equation)z = 1 − e−R0 z, solved for z%

On day 0, about 43 people catch the disease and about 14 recover. The infected group grows by about 29. That is R0 at work: early on, the gap between infections and recoveries is wide. By day the gap has closed, and the infected group is at its largest.

The day-by-day run and the exact equation agree to within a fraction of a point. The small gap comes from the seed: the run starts with 0.1% infected, and the exact formulas assume a vanishingly small seed.

Quick check

1. R0 = 3 and the recovery period doubles from 7 to 14 days. The contact rate β stays the same. What happens to R0?

2. Why does the infected curve fall, in a model where nobody changes their behaviour?

3. R0 = 3. Two thirds of a town are immune before the outbreak starts. A hundred people arrive infected. What does the model say?

7. Read the code

Show the working in code

Optional. The same calculation as Python you can step through.

Each block holds real Python that runs in this page. You will not write or edit any of it. Read the steps, run the block, then answer the question under it. The code works in shares of the town, so N = 1 and S + I + R = 1. It moves in tenths of a day so its numbers match the charts.

Block 1: One day of the model

Read the five steps, then press Run. Compare the three numbers with day 0: S 99.900%, I 0.100%, R 0.000%.

  1. Define a function that moves S, I and R forward by one day. It takes the three shares and the two settings.
  2. Ten small steps make one day. In each, apply rule 1 (infection) and rule 2 (recovery) to a tenth of a day.
  3. Move the shares: S loses the new infections, I gains them and loses the recoveries, R gains the recoveries. Return all three.
  4. Set R0 = 3 and D = 7 days, and derive β = R0 / D.
  5. Run one day from 0.1% infected and print the three shares as percentages.

After one day, I went from 0.100% to 0.132%. Why did R rise by less than I did?

Block 2: Trace the outbreak one day at a time

Press Next step five times. Watch S, I and R change in the panel after each step. The last step jumps to day 30.

  1. Set the day-0 shares and the two settings.
  2. Day 1: apply the one-day function once.
  3. Day 2: apply it again.
  4. Day 3: and again.
  5. Loop from day 4 to day 30, then print the shares.

Block 3: Run the whole outbreak

The sliders set R0 and D. The code runs a year, day by day, and reports the peak and the final size. Check that R0 = 3, D = 7 gives the numbers from section 3. Then change D only and watch which numbers move.

With R0 fixed at 3, you move D from 7 to 14. Which printed number stays the same?

Spot the mistake

Someone wrote the loop again without the helper function. It runs, and the number it prints is absurd. Click the line whose idea is wrong.

  • The settings are the same as before. Nothing wrong here.
  • The day-0 shares are the same as before. Nothing wrong here.
  • Sixty days is shorter than before, but a shorter run is not a mistake.
  • Ten small steps per day, as in Block 1. Fine.
  • Right. The infection term has lost S. Without it, the model forgets that infected people meet fewer and fewer susceptible people. Nothing ever slows the growth, S goes far below zero, and the "final size" is millions of percent.
  • Recovery is I / D, as in rule 2. Fine.
  • The bookkeeping is right. The wrong number comes from the infection term above.
  • Printing S is fine. A negative share is the symptom, not the cause.
  • Printing 1 − S is the right way to get the final size. The input is what is broken.

What to remember

Sources

Every number on this page is computed by export_04.py, which also downloads and trims the Our World in Data file. The simulation uses tenths of a day; the exact formulas quoted are from the continuous model.