diff --git a/.gitignore b/.gitignore index 5eae9fb..cb4b082 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ docs/book/_build/* */OG-ZAF_example_plots/* *ogzaf_example_output.csv */OG-ZAF-Example/* +*un_api_token.txt* diff --git a/CHANGELOG.md b/CHANGELOG.md index f29ed3a..832a95a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.0.3] - 2024-07-26 12:00:00 + +### Added + +- Updates the `.gitignore` to ignore any `un_api_token.txt` files +- Adds a reference to `OGZAF_references.bib` +- Changes all references to OG-USA in the documentation to OG-ZAF +- Updates `demographics.md` documentation to include better South Africa images, although we still need to finish the last three population images + + ## [0.0.2] - 2024-06-18 12:00:00 ### Added @@ -32,5 +42,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +[0.0.3]: https://github.com/EAPD-DRB/OG-ZAF/compare/v0.0.2...v0.0.3 [0.0.2]: https://github.com/EAPD-DRB/OG-ZAF/compare/v0.0.1...v0.0.2 [0.0.1]: https://github.com/EAPD-DRB/OG-ZAF/compare/v0.0.0...v0.0.1 diff --git a/docs/book/OGZAF_references.bib b/docs/book/OGZAF_references.bib index 45cd2fe..5c1a5ea 100755 --- a/docs/book/OGZAF_references.bib +++ b/docs/book/OGZAF_references.bib @@ -20,6 +20,14 @@ @ARTICLE{Armington:1969 pages = {159-178}, } +@ARTICLE{BusinessTech:2024, + AUTHOR = {{Business Tech}}, + TITLE = {`Reverse emigration' in South Africa - this is how many people actually came home}, + JOURNAL = {Business Tech}, + YEAR = {April 2, 2024}, + url = {https://businesstech.co.za/news/lifestyle/764879/reverse-emigration-in-south-africa-this-is-how-many-people-actually-came-home/} +} + @Article{EHS:2013, author={Michael Elsby and Bart Hobijn and Ayseful Sahin}, title={{The Decline of the U.S. Labor Share}}, diff --git a/docs/book/content/calibration/UBI.md b/docs/book/content/calibration/UBI.md index 6045647..38dc7f6 100644 --- a/docs/book/content/calibration/UBI.md +++ b/docs/book/content/calibration/UBI.md @@ -3,15 +3,15 @@ [TODO: This section is far along but needs to be updated.] -We have included the modeling of a universal basic income (UBI) policy directly in the theory and code for [`OG-Core`] on which dependency the `OG-USA` is based. UBI shows up in the household budget constraint {eq}`EqHHBC`, and is described in the [Budget Constraint](https://pslmodels.github.io/OG-Core/content/theory/households.html#budget-constraint) section of the Households chapter of the `OG-Core` documentation. We calculate the time series of a UBI matrix $ubi_{j,s,t}$ representing the UBI transfer to every household with head of household age $s$, lifetime income group $j$, in period $t$. We calculate the time series of this matrix from five parameters and some household composition data that we impose upon the existing demographics of `OG-USA`. +We have included the modeling of a universal basic income (UBI) policy directly in the theory and code for [`OG-Core`] on which dependency the `OG-ZAF` is based. UBI shows up in the household budget constraint {eq}`EqHHBC`, and is described in the [Budget Constraint](https://pslmodels.github.io/OG-Core/content/theory/households.html#budget-constraint) section of the Households chapter of the `OG-Core` documentation. We calculate the time series of a UBI matrix $ubi_{j,s,t}$ representing the UBI transfer to every household with head of household age $s$, lifetime income group $j$, in period $t$. We calculate the time series of this matrix from five parameters and some household composition data that we impose upon the existing demographics of `OG-ZAF`. (SecUBIcalc)= ## Calculating UBI - We calculate the time series of UBI household transfers in model units $ubi_{j,s,t)}$ and the time series of total UBI expenditures in model units $UBI_t$ from five parameters described in the [`ogusa_default_parameters.json`](https://github.com/PSLmodels/OG-USA/blob/master/ogusa/ogusa_default_parameters.json) file (`ubi_growthadj`, `ubi_nom_017`, `ubi_nom_1864`, `ubi_nom_65p`, and `ubi_nom_max`) interfaced with the `OG-USA` demographic dynamics over lifetime income groups $j$ and ages $s$, and multiplied by household composition matrices from the `Calibrate` class of the `OG-USA/ogusa/calibrate.py` module in the repository. + We calculate the time series of UBI household transfers in model units $ubi_{j,s,t)}$ and the time series of total UBI expenditures in model units $UBI_t$ from five parameters described in the [`ogzaf_default_parameters.json`](https://github.com/EAPD-DRB/OG-ZAF/blob/master/ogzaf/ogzaf_default_parameters.json) file (`ubi_growthadj`, `ubi_nom_017`, `ubi_nom_1864`, `ubi_nom_65p`, and `ubi_nom_max`) interfaced with the `OG-ZAF` demographic dynamics over lifetime income groups $j$ and ages $s$, and multiplied by household composition matrices from the `Calibrate` class of the `OG-ZAF/ogzaf/calibrate.py` module in the repository. - From the [OG-USA](https://github.com/PSLmodels/OG-USA) repository, we have four $S\times J$ matrices `ubi_num_017_mat`$_{j,s}$, `ubi_num_1864_mat`$_{j,s}$, and `ubi_num_65p_mat`$_{j,s}$ representing the number of children under age 0-17, number of adults ages 18-64, and the number of seniors age 65 and over, respectively, by lifetime ability group $j$ and age $s$ of head of household. Because our demographic age data match up well with head-of-household data from other datasets, we do not have to adjust the values in these matrices.[^HOH_age_dist_note] + From the [OG-ZAF](https://github.com/EAPD-DRB/OG-ZAF) repository, we have four $S\times J$ matrices `ubi_num_017_mat`$_{j,s}$, `ubi_num_1864_mat`$_{j,s}$, and `ubi_num_65p_mat`$_{j,s}$ representing the number of children under age 0-17, number of adults ages 18-64, and the number of seniors age 65 and over, respectively, by lifetime ability group $j$ and age $s$ of head of household. Because our demographic age data match up well with head-of-household data from other datasets, we do not have to adjust the values in these matrices.[^HOH_age_dist_note] Now we can solve for the dollar-valued (as opposed to model-unit-valued) UBI transfer to each household in the first period $ubi^{\$}_{j,s,t=0}$ in the following way. Let the parameter `ubi_nom_017` be the dollar value of the UBI transfer to each household per dependent child age 17 and under. Let the parameter `ubi_nom_1864` be the dollar value of the UBI transfer to each household per adult between the ages of 18 and 64. Let `ubi_nom_65p` be the dollar value of UBI transfer to each household per senior 65 and over. And let `ubi_nom_max` be the maximum UBI benefit per household. @@ -57,6 +57,6 @@ We have included the modeling of a universal basic income (UBI) policy directly (SecUBIfootnotes)= ## Footnotes -[^HOH_age_dist_note]: DeBacker and Evans compared the `OG-USA` age demographics $\hat{\omega}_{s,t}$ with the respective age demographics in Tax Policy Center's microsimulation model and in [Tax-Calculator](https://github.com/PSLmodels/Tax-Calculator)'s microsimulation model. The latter two microsimulation models' age demographics are based on head of household tax filer age distributions, whereas `OG-USA`'s demographics are based on the population age distribution. +[^HOH_age_dist_note]: DeBacker and Evans compared the `OG-ZAF` age demographics $\hat{\omega}_{s,t}$ with the respective age demographics in Tax Policy Center's microsimulation model and in [Tax-Calculator](https://github.com/PSLmodels/Tax-Calculator)'s microsimulation model. The latter two microsimulation models' age demographics are based on head of household tax filer age distributions, whereas `OG-ZAF`'s demographics are based on the population age distribution. -[^GrowthAdj_note]: We impose this requirement of `ubi_growthadj = False` when `g_y_annual < 0` in the [`ogusa_default_parameters.json`](https://github.com/PSLmodels/OG-USA/blob/master/ogusa/ogusa_default_parameters.json) "validators" specification of the parameter. +[^GrowthAdj_note]: We impose this requirement of `ubi_growthadj = False` when `g_y_annual < 0` in the [`ogzaf_default_parameters.json`](https://github.com/EAPD-DRB/OG-ZAF/blob/master/ogzaf/ogzaf_default_parameters.json) "validators" specification of the parameter. diff --git a/docs/book/content/calibration/demographics.md b/docs/book/content/calibration/demographics.md index 9ea90c3..d4d96de 100644 --- a/docs/book/content/calibration/demographics.md +++ b/docs/book/content/calibration/demographics.md @@ -1,14 +1,13 @@ --- jupytext: + formats: md:myst text_representation: extension: .md format_name: myst - format_version: '0.8' - jupytext_version: '1.4.1' kernelspec: display_name: Python 3 language: python - name: ogzaf-dev + name: python3 --- (Chap_Demog)= @@ -18,7 +17,7 @@ We start the `OG-ZAF` section on modeling the household with a description of th 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$.[^calibage_note] 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. +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$.[^calibage_note] 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, ```{math} @@ -56,12 +55,106 @@ We discuss the approach to estimating fertility rates $f_s$, mortality rates $\r In `OG-ZAF`, we assume that the fertility rates for each age cohort $f_s$ are constant across time. However, this assumption is conceptually straightforward to relax. 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". + {numref}`Figure %s ` was created using the [`ogcore.demographics.get_fert()`](https://github.com/PSLmodels/OG-Core/blob/master/ogcore/demographics.py#L146) function, which downloaded the data from the United National Data Portal API and plotted it in Python.[^un_data_portal] + + ```{code-cell} ipython3 + :tags: ["hide-input", "remove-output"] + + import numpy as np + import matplotlib.pyplot as plt + import ogcore.demographics as demog + + fert_rates = demog.get_fert( + totpers=100, + min_age=0, + max_age=99, + country_id="710", + start_year=2023, + end_year=2023, + graph=False, + plot_path=None, + download_path=None, + ) + plt.plot( + np.arange(1, 101), np.squeeze(fert_rates), linestyle="-", linewidth=1, + color="black" + ) + plt.scatter( + np.arange(1, 101), np.squeeze(fert_rates), color="red", marker="o", s=4 + ) + plt.grid( + visible=True, which='major', axis='both', color='0.5', linestyle='--', + linewidth=0.3 + ) + plt.xlabel(r"Age ($s$)") + plt.ylabel(r"Fertility rate ($f_s$)") + plt.savefig('fert_rates_zaf.png') + plt.show() + ``` + + ```{figure} ./images/fert_rates_zaf.png + --- + height: 400px + name: FigFertRatesZAF + --- + South Africa fertility rates by age $\left(f_s\right)$ for $E+S=100$: year 2023 + ``` + + 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$. + + (SecDemogMort)= ## Mortality rates - The mortality rates in our model $\rho_s$ 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 period $s$. We assume that the mortality rates for each age cohort $\rho_s$ are constant across time. 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) 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 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. + The mortality rates in our model $\rho_s$ 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$. We assume that the mortality rates for each age cohort $\rho_s$ are constant across time. But this assumption can be relaxed. 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. + + ```{code-cell} ipython3 + :tags: ["hide-input", "remove-output"] + + import numpy as np + import matplotlib.pyplot as plt + import ogcore.demographics as demog + + mort_rates, inf_mort_rate = demog.get_mort( + totpers=100, + min_age=0, + max_age=99, + country_id="710", + start_year=2023, + end_year=2023, + graph=False, + plot_path=None, + download_path=None, + ) + + plt.plot( + np.arange(0, 101), np.hstack((inf_mort_rate, np.squeeze(mort_rates))), + linestyle="-", linewidth=1, color="black" + ) + plt.scatter( + np.arange(0, 101), np.hstack((inf_mort_rate, np.squeeze(mort_rates))), + color="red", marker="o", s=4 + ) + plt.grid( + visible=True, which='major', axis='both', color='0.5', linestyle='--', + linewidth=0.3 + ) + plt.xlabel(r"Age ($s$)") + plt.ylabel(r"Mortality rate ($\rho_s$)") + plt.savefig('mort_rates_zaf.png') + plt.show() + ``` + + ```{figure} ./images/mort_rates_zaf.png + --- + height: 400px + name: FigMortRatesZAF + --- + South Africa mortility rates by age $\left(\rho_s\right)$ for $E+S=100$: year 2023 + ``` + (SecDemogImm)= ## Immigration rates @@ -74,9 +167,55 @@ We discuss the approach to estimating fertility rates $f_s$, mortality rates $\r i_{s+1} &= \frac{\omega_{s+1,t+1} - (1 - \rho_s)\omega_{s,t}}{\omega_{s+1,t}}\qquad\qquad\forall t\quad\text{and}\quad 1\leq s \leq E+S-1 ``` - + ```{code-cell} ipython3 + :tags: ["hide-input", "remove-output"] + + import numpy as np + import matplotlib.pyplot as plt + import ogcore.demographics as demog + + imm_rates = 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=2023, + end_year=2023, + graph=False, + plot_path=None, + download_path=None, + ) + + plt.plot( + np.arange(1, 101), np.squeeze(imm_rates), linestyle="-", linewidth=1, + color="black" + ) + plt.scatter( + np.arange(1, 101), np.squeeze(imm_rates), color="red", marker="o", s=4 + ) + plt.grid( + visible=True, which='major', axis='both', color='0.5', linestyle='--', + linewidth=0.3 + ) + plt.xlabel(r"Age ($s$)") + plt.ylabel(r"Immigration rate ($i_s$)") + plt.savefig('imm_rates_zaf.png') + plt.show() + ``` + + ```{figure} ./images/imm_rates_zaf.png + --- + height: 400px + name: FigImmRatesZAF + --- + South Africa immigration rates by age $\left(\rho_s\right)$ for $E+S=100$: year 2023 + ``` - We calculate our immigration rates for three different consecutive-year-periods of population distribution data (2010 through 2013). Our four years of population distribution by age data come from {cite}`Census:2015`. The immigration rates $i_s$ that we use in our model are the the residuals described in {eq}`EqPopImmRates` averaged across the three periods. {numref}`Figure %s ` shows the estimated immigration rates for $E+S=100$ and given the fertility rates from Section {ref}`SecDemogFert` and the mortality rates from Section {ref}`SecDemogMort`. + We calculate our immigration rates for the consecutive-year-periods of population distribution data 2022 and 2023. The immigration rates $i_s$ that we use in our model are the the residuals described in {eq}`EqPopImmRates` implied by these two consecutive periods. {numref}`Figure %s ` shows the estimated immigration rates for $E+S=100$ and given the fertility rates from Section {ref}`SecDemogFert` and the mortality rates from Section {ref}`SecDemogMort`. These immigration rates show large out-migration from South Africa.[^out_migration] At the end of Section {ref}`SecDemogPopSSTP`, 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. @@ -225,3 +364,5 @@ We discuss the approach to estimating fertility rates $f_s$, mortality rates $\r [^calibage_note]: Theoretically, the model works without loss of generality for $S\geq 3$. However, because we are calibrating the ages outside of the economy to be one-fourth of $S$ (e.g., ages 21 to 100 in the economy, and ages 1 to 20 outside of the economy), it is convenient for $S$ to be at least 4. [^houseprob_note]: The parameter $\rho_s$ is the probability that a household of age $s$ dies before age $s+1$. +[^un_data_portal]: Note that you might need a UN Data Portal API token to download the data directly from the United Nations Data Portal site. But the [`demographics.py`](https://github.com/PSLmodels/OG-Core/blob/master/ogcore/demographics.py) module will take the data from a pre-downloaded site if the API token is missing or fails. +[^out_migration]: Out migration in South Africa is clearly a significant trend. {cite}`BusinessTech:2024` reports that "since 2000, around 413,000 South Africans have emigrated to other countries - and in 2022, just under 28,000 made their way back." diff --git a/docs/book/content/calibration/exogenous_parameters.md b/docs/book/content/calibration/exogenous_parameters.md index 5e3e08a..a82a2a3 100644 --- a/docs/book/content/calibration/exogenous_parameters.md +++ b/docs/book/content/calibration/exogenous_parameters.md @@ -16,7 +16,7 @@ kernelspec: [TODO: This chapter needs heavy updating. Would be nice to do something similar to API chapter. But it is also nice to have references and descriptions as in the table below.] - In this chapter, list the exogenous inputs to the model, options, and where the values come from (weak calibration vs. strong calibration). Point to the respective chapters for some of the inputs. Mention the code in [`default_parameters.json`](https://github.com/PSLmodels/OG-USA/blob/master/ogusa/default_parameters.json) and [`parameters.py`](https://github.com/PSLmodels/OG-USA/blob/master/ogusa/parameters.py). + In this chapter, list the exogenous inputs to the model, options, and where the values come from (weak calibration vs. strong calibration). Point to the respective chapters for some of the inputs. Mention the code in [`ogzaf_default_parameters.json`](https://github.com/EAPD-DRB/OG-ZAF/blob/master/ogzaf/ogzaf_default_parameters.json) in the OG-ZAF repository and in [`default_parameters.json`](https://github.com/PSLmodels/OG-Core/blob/master/ogcore/default_parameters.json) and [`parameters.py`](https://github.com/PSLmodels/OG-Core/blob/master/ogcore/parameters.py) in the OG-Core repository.