Demographics#
Demographics are a key component of the macroeconmic model. [Nishiyama, 2015] and [DeBacker et al., 2019] have recently shown that demographic dynamics are likely the biggest influence on macroeconomic time series, exhibiting more influence than fiscal variables or household preference parameters.
In this chapter, we characterize the equations and parameters that govern the transition dynamics of the population distribution by age. In OG-ZAF
, we take the approach of taking mortality rates and fertility rates from outside estimates. But we estimate our immigration rates as residuals using the mortality rates, fertility rates, and at least two consecutive periods of population distribution data. This approach makes sense if one is modeling a country in which one is not confident in the immigration rate data. If the country has good immigration data, then the immigration residual approach we describe below can be skipped.
We define \(\omega_{s,t}\) as the number of households of age \(s\) alive at time \(t\). A measure \(\omega_{1,t}\) of households is born in each period \(t\) and live for up to \(E+S\) periods, with \(S\geq 4\).[1] Households are termed “youth”, and do not participate in market activity during ages \(1\leq s\leq E\). The households enter the workforce and economy in period \(E+1\) and remain in the workforce until they unexpectedly die or live until age \(s=E+S\). We model the population with households age \(s\leq E\) outside of the workforce and economy in order most closely match the empirical population dynamics.
The population of agents of each age in each period \(\omega_{s,t}\) evolves according to the following function,
where \(f_{s,t}\geq 0\) is an age-specific fertility rate, \(i_{s,t}\) is an age-specific net immigration rate, \(\rho_{s,t}\) is an age-specific mortality hazard rate, and \(\rho_{0,t}\) is an infant mortality rate.[2] The total population in the economy \(N_t\) at any period is simply the sum of households in the economy, the population growth rate in any period \(t\) from the previous period \(t-1\) is \(g_{n,t}\), \(\tilde{N}_t\) is the working age population, and \(\tilde{g}_{n,t}\) is the working age population growth rate in any period \(t\) from the previous period \(t-1\).
We discuss the approach to estimating fertility rates \(f_{s,t}\), mortality rates \(\rho_{s,t}\), and immigration rates \(i_{s,t}\) in Sections Fertility rates, Mortality rates, and Immigration rates.
Fertility rates#
Our data for South Africa fertility rates by age come from United Nations fertility rate data for a country for some range of years (at least one year) and by age. The country_id=710 is for South Africa. These data come from the United Nations Data Portal API for UN population data (see https://population.un.org/dataportal/about/dataapi). The UN variable code for Population by 1-year age groups and sex is “47” and that for Fertility rates by age of mother (1-year) is “68”.
Figure 1 was created using the ogcore.demographics.get_fert()
function, which downloaded the data from the United National Data Portal API and plotted it in Python.[3]
Show code cell source
import os
import ogcore.demographics as demog
plot_path = os.path.join(os.path.abspath(''), 'images')
fert_rates, fig = demog.get_fert(
totpers=100,
min_age=0,
max_age=99,
country_id="710",
start_year=YEAR_TO_PLOT,
end_year=YEAR_TO_PLOT,
graph=True,
plot_path=None,
download_path=None,
)
plt.savefig(os.path.join(plot_path, "fert_rates.png"), dpi=300)
plt.show()
The fertility rates in the UN data are births per 1,000 women of age-\(s\). We adjust the units of those rates to represent the number of births per total population of both men and women of age-\(s\).
Mortality rates#
The mortality rates in our model \(\rho_{s,t}\) are a one-period hazard rate and represent the probability of dying within one year, given that an household is alive at the beginning of the period in which they are age-\(s\). These data come from the United Nations Population Data Portal API for UN population data (see https://population.un.org/dataportal/about/dataapi). The model uses neonatal mortality rates (deaths per 1,000 live births, divided by 1,000) for the infant mortality rate from World Bank World Development Indicators, available at https://data.worldbank.org/indicator/SH.DYN.NMRT
The mortality rates are a population-weighted average of the male and female mortality rates by one-year age increments reported by United Nations. The maximum age in years in our model is truncated to 100-years old. In addition, we constrain the mortality rate to be 1.0 or 100 percent at the maximum age of 100.
Show code cell source
import matplotlib.pyplot as plt
import os
import ogcore.demographics as demog
plot_path = os.path.join(os.path.abspath(''), 'images')
mort_rates, _, fig = demog.get_mort(
totpers=100,
min_age=0,
max_age=99,
country_id="710",
start_year=YEAR_TO_PLOT,
end_year=YEAR_TO_PLOT,
graph=True,
plot_path=None,
download_path=None,
)
plt.xlabel(r"Age ($s$)")
plt.ylabel(r"Mortality rate ($\rho_s$)")
plt.savefig(os.path.join(plot_path, "mort_rates.png"), dpi=300)
plt.show()
Immigration rates#
Because of the difficulty in getting accurate immigration rate data by age, we estimate the immigration rates by age in our model \(i_s\) as the average residual that reconciles the current-period population distribution with next period’s population distribution given fertility rates \(f_s\) and mortality rates \(\rho_{s,t}\). Solving equations (3) for the immigration rate \(i_s\) gives the following characterization of the immigration rates in given population levels in any two consecutive periods \(\omega_{s,t}\) and \(\omega_{s,t+1}\) and the fertility rates \(f_s\) and mortality rates \(\rho_{s,t}\).
Show code cell source
import os
import matplotlib.pyplot as plt
import ogcore.demographics as demog
plot_path = os.path.join(os.path.abspath(''), 'images')
imm_rates, fig = demog.get_imm_rates(
totpers=100,
min_age=0,
max_age=99,
fert_rates=None,
mort_rates=None,
infmort_rates=None,
pop_dist=None,
country_id="710",
start_year=YEAR_TO_PLOT,
end_year=YEAR_TO_PLOT + 50,
graph=True,
plot_path=None,
download_path=None,
)
plt.savefig(os.path.join(plot_path, "imm_rates.png"), dpi=300)
plt.show()
We calculate our immigration rates for the consecutive-year-periods of population distribution data 2022 and 2023. The immigration rates \(i_{s,t}\) that we use in our model are the the residuals described in (8) implied by these two consecutive periods. Figure 3 shows the estimated immigration rates for \(E+S=100\) and given the fertility rates from Section Fertility rates and the mortality rates from Section Mortality rates. These immigration rates show large out-migration from South Africa.[4]
At the end of Section Population steady-state and transition path, we describe a small adjustment that we make to the immigration rates after a certain number of periods in order to make computation of the transition path equilibrium of the model compute more robustly.
Population steady-state and transition path#
This model requires information about mortality rates \(\rho_{s,t}\) in order to solve for the household’s problem each period. It also requires the steady-state stationary population distribution \(\bar{\omega}_{s}\) and population growth rate \(\bar{g}_n\) as well as the full transition path of the stationary population distribution \(\hat{\omega}_{s,t}\) and population grow rate \(\tilde{g}_{n,t}\) from the current state to the steady-state. To solve for the steady-state and the transition path of the stationary population distribution, we write the stationary population dynamic equations (9) and their matrix representation (10).
We can write system (10) more simply in the following way.
The stationary steady-state population distribution \(\boldsymbol{\bar{\omega}}\) is the eigenvector \(\boldsymbol{\omega}\) with eigenvalue \((1+\bar{g}_n)\) of the matrix \(\boldsymbol{\Omega}\) that satisfies the following version of (11).
Proposition
If the age \(s=1\) immigration rate is \(i_1>-(1-\rho_0)f_1\) and the other immigration rates are strictly positive \(i_s>0\) for all \(s\geq 2\) such that all elements of \(\boldsymbol{\Omega}\) are nonnegative, then there exists a unique positive real eigenvector \(\boldsymbol{\bar{\omega}}\) of the matrix \(\boldsymbol{\Omega}\), and it is a stable equilibrium.
Proof: First, note that the matrix \(\boldsymbol{\Omega}\) is square and non-negative. This is enough for a general version of the Perron-Frobenius Theorem to state that a positive real eigenvector exists with a positive real eigenvalue. This is not yet enough for uniqueness. For it to be unique by a version of the Perron-Fobenius Theorem, we need to know that the matrix is irreducible. This can be easily shown. The matrix is of the form
Where each * is strictly positive. It is clear to see that taking powers of the matrix causes the sub-diagonal positive elements to be moved down a row and another row of positive entries is added at the top. None of these go to zero since the elements were all non-negative to begin with.
Existence of an \(m \in \mathbb{N}\) such that \(\left(\bf\Omega^m\right)_{ij} \neq 0 ~~ ( > 0)\) is one of the definitions of an irreducible (primitive) matrix. It is equivalent to saying that the directed graph associated with the matrix is strongly connected. Now the Perron-Frobenius Theorem for irreducible matrices gives us that the equilibrium vector is unique.
We also know from that theorem that the eigenvalue associated with the positive real eigenvector will be real and positive. This eigenvalue, \(p\), is the Perron eigenvalue and it is the steady state population growth rate of the model. By the PF Theorem for irreducible matrices, \(| \lambda_i | \leq p\) for all eigenvalues \(\lambda_i\) and there will be exactly \(h\) eigenvalues that are equal, where \(h\) is the period of the matrix. Since our matrix \(\bf\Omega\) is aperiodic, the steady state growth rate is the unique largest eigenvalue in magnitude. This implies that almost all initial vectors will converge to this eigenvector under iteration.
For a full treatment and proof of the Perron-Frobenius Theorem, see [Suzumura, 1983]. Because the population growth process is exogenous to the model, we calibrate it to annual age data for age years \(s=1\) to \(s=100\).
Figure 4 shows the steady-state population distribution \(\boldsymbol{\bar{\omega}}\) and the population distribution after 120 periods \(\boldsymbol{\hat{\omega}}_{120}\). Although the two distributions look very close to each other, they are not exactly the same.
Further, we find that the maximum absolute difference between the population levels \(\hat{\omega}_{s,t}\) and \(\hat{\omega}_{s,t+1}\) was less than \(1\times 10^{-4}\) after 160 periods. That is to say, that after 160 periods, given the estimated mortality, fertility, and immigration rates, the population has not achieved its steady state. For convergence in our solution method over a reasonable time horizon, we want the population to reach a stationary distribution after \(T\) periods. To do this, we artificially impose that the population distribution in period \(t=120\) is the steady-state. As can be seen from Figure 4, this assumption is not very restrictive. Figure 5 shows the change in immigration rates that would make the period \(t=120\) population distribution equal be the steady-state. The maximum absolute difference between any two corresponding immigration rates in Figure 5 is very small.
We begin with 2023 population data and use the population transition matrix (11) to age it to the start year of the model (e.g., 2024 or 2025). We then use (11) to generate the transition path of the population distribution over the time period of the model. Figure 6 shows the progression from the 2023 population data to the fixed steady-state at period \(t=120\). The time path of the growth rate of the economically active population \(\tilde{g}_{n,t}\) is shown in Figure 6.
Show code cell source
import os
import ogcore.demographics as demog
import matplotlib.pyplot as plt
YEAR_TO_PLOT = 2023
plot_path = os.path.join(os.path.abspath(''), 'images')
fig = pp.plot_pop_growth(
p,
start_year=YEAR_TO_PLOT,
num_years_to_plot=150,
include_title=False,
path=None,
)
# Add average growth rate with this
plt.plot(
np.arange(YEAR_TO_PLOT, YEAR_TO_PLOT + 150),
np.ones(150) * np.mean(p.g_n[:150]),
linestyle="-",
linewidth=1,
color="red",
)
plt.xlabel(r"Model Period ($t$)")
plt.ylabel(r"Population Growth Rate ($g_{n,t}$)")
plt.savefig(os.path.join(plot_path, "population_growth_rates.png"), dpi=300)
plt.show()