CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
AllenDowney

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: AllenDowney/ModSimPy
Path: blob/master/chapters/chap08.ipynb
Views: 531
Kernel: Python 3 (ipykernel)

Printed and electronic copies of Modeling and Simulation in Python are available from No Starch Press and Bookshop.org and Amazon.

Projecting Population Growth

Modeling and Simulation in Python

Copyright 2021 Allen Downey

License: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International

# install Pint if necessary try: import pint except ImportError: !pip install pint
# download modsim.py if necessary from os.path import basename, exists def download(url): filename = basename(url) if not exists(filename): from urllib.request import urlretrieve local, _ = urlretrieve(url, filename) print('Downloaded ' + local) download('https://github.com/AllenDowney/ModSimPy/raw/master/' + 'modsim.py')
# import functions from modsim from modsim import *

Here's the data from the previous chapters, one last time.

download('https://raw.githubusercontent.com/AllenDowney/' + 'ModSimPy/master/data/World_population_estimates.html')
from pandas import read_html filename = 'World_population_estimates.html' tables = read_html(filename, header=0, index_col=0, decimal='M') table2 = tables[2] table2.columns = ['census', 'prb', 'un', 'maddison', 'hyde', 'tanton', 'biraben', 'mj', 'thomlinson', 'durand', 'clark']
un = table2.un / 1e9 census = table2.census / 1e9

And here are the functions from the previous chapter.

download('https://github.com/AllenDowney/ModSimPy/raw/master/' + 'chap06.py')
from chap06 import run_simulation def plot_estimates(): census.plot(style=':', label='US Census') un.plot(style='--', label='UN DESA') decorate(xlabel='Year', ylabel='World population (billions)')

In the previous chapter we developed a quadratic model of world population growth from 1950 to 2016. It is a simple model, but it fits the data well and the mechanisms it's based on are plausible.

In this chapter we'll use the quadratic model to generate projections of future growth, and compare our results to projections from actual demographers.

Generating Projections

Let's run the quadratic model, extending the results until 2100, and see how our projections compare to the professionals'.

Here's the quadratic growth function again.

def growth_func_quad(t, pop, system): return system.alpha * pop + system.beta * pop**2

And here are the system parameters.

t_0 = census.index[0] p_0 = census[t_0] system = System(t_0 = t_0, p_0 = p_0, alpha = 25 / 1000, beta = -1.8 / 1000, t_end = 2100)

With t_end=2100, we can generate the projection by calling run_simulation the usual way.

results = run_simulation(system, growth_func_quad)

Here are the last few values in the results.

show(results.tail())

Here's what the results look like.

results.plot(color='gray', label='model') decorate(xlabel='Year', ylabel='World population (billions)', title='Quadratic model projection')

According to the model, population growth will slow gradually after 2020, approaching 12.6 billion by 2100.

I am using the word projection deliberately, rather than prediction, with the following distinction: "prediction" implies something like "this is what we expect to happen, at least approximately"; "projection" implies something like "if this model is a good description of the system, and if nothing in the future causes the system parameters to change, this is what would happen."

Using "projection" leaves open the possibility that there are important things in the real world that are not captured in the model. It also suggests that, even if the model is good, the parameters we estimate based on the past might be different in the future.

The quadratic model we've been working with is based on the assumption that population growth is limited by the availability of resources; in that scenario, as the population approaches carrying capacity, birth rates fall and death rates rise because resources become scarce.

If that assumption is valid, we might be able to use actual population growth to estimate carrying capacity, provided we observe the transition into the population range where the growth rate starts to fall.

But in the case of world population growth, those conditions don't apply. Over the last 50 years, the net growth rate has leveled off, but not yet started to fall, so we don't have enough data to make a credible estimate of carrying capacity. And resource limitations are probably not the primary reason growth has slowed. As evidence, consider:

  • First, the death rate is not increasing; rather, it has declined from 1.9% in 1950 to 0.8% now (see http://modsimpy.com/mortality). So the decrease in net growth is due entirely to declining birth rates.

  • Second, the relationship between resources and birth rate is the opposite of what the model assumes; as nations develop and people become more wealthy, birth rates tend to fall.

We should not take too seriously the idea that this model can estimate carrying capacity. But the predictions of a model can be credible even if the assumptions of the model are not strictly true. For example, population growth might behave as if it is resource limited, even if the actual mechanism is something else.

In fact, demographers who study population growth often use models similar to ours. In the next section, we'll compare our projections to theirs.

Comparing Projections

From the same Wikipedia page where we got the past population estimates, we'll read table3, which contains projections for population growth over the next 50-100 years, generated by the U.S. Census, U.N. DESA, and the Population Reference Bureau.

table3 = tables[3]

The column names are long strings; for convenience, I'll replace them with abbreviations.

table3.columns = ['census', 'prb', 'un']

Here are the first few rows:

table3.head()

Some values are NaN, which indicates missing data, because some organizations did not publish projections for some years. The following function plots projections from the U.N. DESA and U.S. Census. It uses dropna to remove the NaN values from each series before plotting it.

def plot_projections(table): """Plot world population projections. table: DataFrame with columns 'un' and 'census' """ census_proj = table.census.dropna() / 1e9 un_proj = table.un.dropna() / 1e9 census_proj.plot(style=':', label='US Census') un_proj.plot(style='--', label='UN DESA') decorate(xlabel='Year', ylabel='World population (billions)')

Here are the professional projections compared to the results of the quadratic model.

results.plot(color='gray', label='model') plot_projections(table3) decorate(title='Quadratic model projection, with UN and Census')

The U.N. DESA expects the world population to reach 11 billion around 2100, and then level off. Projections by U.S. Census are a little lower, and they only go until 2050.

Summary

In this chapter we use the quadratic growth model to project world population growth between now and 2100.

Real demographers expect world population to grow more slowly than our model, probably because their models are broken down by region and country, where conditions are different, and they take into account expected economic development.

Nevertheless, their projections are qualitatively similar to ours, and theirs differ from each other almost as much as they differ from ours. So the results from the model, simple as it is, are not entirely unreasonable.

If you are interested in some of the factors that go into the professional projections, you might like this video by Hans Rosling about the demographic changes we expect this century: https://www.youtube.com/watch?v=ezVk1ahRF78.

Exercises

This chapter is available as a Jupyter notebook where you can read the text, run the code, and work on the exercises. You can access the notebooks at https://allendowney.github.io/ModSimPy/.

Exercise 1

The net growth rate of world population has been declining for several decades. That observation suggests one more way to generate more realistic projections, by extrapolating observed changes in growth rate.

To compute past growth rates, we'll use a function called diff, which computes the difference between successive elements in a Series. For example, here are the changes from one year to the next in census:

diff = census.diff() show(diff.head())

The first element is NaN because we don't have the data for 1949, so we can't compute the first difference.

If we divide these differences by the populations, the result is an estimate of the growth rate during each year:

alpha = census.diff() / census show(alpha.head())

The following function computes and plots the growth rates for the census and un estimates:

def plot_alpha(): alpha_census = census.diff() / census alpha_census.plot(style='.', label='US Census') alpha_un = un.diff() / un alpha_un.plot(style='.', label='UN DESA') decorate(xlabel='Year', ylabel='Net growth rate')

It uses style='.' to plot each data point with a small circle. And here's what it looks like.

plot_alpha()

Other than a bump around 1990, the net growth rate has been declining roughly linearly since 1970.

We can model the decline by fitting a line to this data and extrapolating into the future. Here's a function that takes a time stamp and computes a line that roughly fits the growth rates since 1970.

def alpha_func(t): intercept = 0.02 slope = -0.00021 return intercept + slope * (t - 1970)

To see what it looks like, I'll create an array of time stamps from 1960 to 2020 and use alpha_func to compute the corresponding growth rates.

t_array = linspace(1960, 2020, 5) alpha_array = alpha_func(t_array)

Here's what it looks like, compared to the data.

from matplotlib.pyplot import plot plot_alpha() plot(t_array, alpha_array, label='model', color='gray') decorate(ylabel='Net growth rate', title='Linear model of net growth rate')

If you don't like the slope and intercept I chose, feel free to adjust them.

Now, as an exercise, you can use this function to project world population until 2100.

  1. Create a System object that includes alpha_func as a system parameter.

  2. Define a growth function that uses alpha_func to compute the net growth rate at the given time t.

  3. Run a simulation from 1960 to 2100 with your growth function, and plot the results.

  4. Compare your projections with those from the US Census and UN.

# Solution goes here
# Solution goes here
# Solution goes here
# Solution goes here
# Solution goes here
# Solution goes here
# Solution goes here
# Solution goes here