├── .gitignore ├── lectures ├── _static │ ├── qe-logo.png │ ├── qe-logo-large.png │ ├── lectures-favicon.ico │ ├── lecture_specific │ │ ├── markov_chains_II │ │ │ ├── example4 │ │ │ ├── Irre_1.png │ │ │ ├── Irre_2.png │ │ │ ├── example4.png │ │ │ ├── Irre_2 │ │ │ ├── Irre_1 │ │ │ └── figures.ipynb │ │ ├── networks │ │ │ ├── mc.png │ │ │ ├── weighted.png │ │ │ ├── properties.png │ │ │ ├── poverty_trap_1.png │ │ │ ├── poverty_trap_2.png │ │ │ ├── properties │ │ │ ├── mc │ │ │ ├── poverty_trap_2 │ │ │ ├── poverty_trap_1 │ │ │ ├── weighted │ │ │ └── figures.ipynb │ │ ├── short_path │ │ │ ├── graph.png │ │ │ ├── graph2.png │ │ │ ├── graph3.png │ │ │ └── graph4.png │ │ ├── markov_chains_I │ │ │ ├── Temple.png │ │ │ ├── Hamilton.png │ │ │ ├── Hamilton │ │ │ ├── Temple │ │ │ └── figures.ipynb │ │ ├── troubleshooting │ │ │ └── launch.png │ │ ├── schelling │ │ │ ├── schelling_fig1.png │ │ │ ├── schelling_fig2.png │ │ │ ├── schelling_fig3.png │ │ │ └── schelling_fig4.png │ │ ├── cross_section │ │ │ ├── rank_size_fig1.png │ │ │ └── light_heavy_fig1.png │ │ ├── lake_model │ │ │ ├── lake_model_worker.png │ │ │ ├── lake_model_worker │ │ │ └── figures.ipynb │ │ ├── long_run_growth │ │ │ └── tooze_ch1_graph.png │ │ └── inequality │ │ │ ├── usa-gini-nwealth-tincome-lincome.csv │ │ │ └── data.ipynb │ └── quantecon-logo-transparent.png ├── datasets │ ├── caron.npy │ ├── dette.xlsx │ ├── fig_3.ods │ ├── fig_3.xlsx │ ├── assignat.xlsx │ ├── mpd2020.xlsx │ ├── chapter_3.xlsx │ ├── longprices.xls │ └── nom_balances.npy ├── plot-for-tom-gdp-1970-to-2018.png ├── zreferences.md ├── intro.md ├── status.md ├── _toc.yml ├── troubleshooting.md ├── about.md ├── _config.yml ├── graph.txt ├── commod_price.md ├── supply_demand_heterogeneity.md ├── pv.md ├── complex_and_trig.md ├── mle.md ├── laffer_adaptive.md ├── schelling.md ├── short_path.md └── cagan_adaptive.md ├── _notebook_repo ├── environment.yml └── README.md ├── environment.yml ├── README.md └── .github ├── dependabot.yml └── workflows ├── linkcheck.yml ├── cache.yml ├── collab.yml ├── ci.yml └── publish.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | lectures/_build 3 | .ipynb_checkpoints/ 4 | .virtual_documents/ 5 | _build/* 6 | -------------------------------------------------------------------------------- /lectures/_static/qe-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/qe-logo.png -------------------------------------------------------------------------------- /lectures/datasets/caron.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/datasets/caron.npy -------------------------------------------------------------------------------- /lectures/datasets/dette.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/datasets/dette.xlsx -------------------------------------------------------------------------------- /lectures/datasets/fig_3.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/datasets/fig_3.ods -------------------------------------------------------------------------------- /lectures/datasets/fig_3.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/datasets/fig_3.xlsx -------------------------------------------------------------------------------- /lectures/datasets/assignat.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/datasets/assignat.xlsx -------------------------------------------------------------------------------- /lectures/datasets/mpd2020.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/datasets/mpd2020.xlsx -------------------------------------------------------------------------------- /lectures/datasets/chapter_3.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/datasets/chapter_3.xlsx -------------------------------------------------------------------------------- /lectures/datasets/longprices.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/datasets/longprices.xls -------------------------------------------------------------------------------- /lectures/_static/qe-logo-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/qe-logo-large.png -------------------------------------------------------------------------------- /lectures/datasets/nom_balances.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/datasets/nom_balances.npy -------------------------------------------------------------------------------- /lectures/_static/lectures-favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lectures-favicon.ico -------------------------------------------------------------------------------- /lectures/plot-for-tom-gdp-1970-to-2018.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/plot-for-tom-gdp-1970-to-2018.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/markov_chains_II/example4: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=LR 3 | 0 4 | 1 5 | 0 -> 1 [label=1.0] 6 | 1 -> 0 [label=1.0] 7 | } 8 | -------------------------------------------------------------------------------- /_notebook_repo/environment.yml: -------------------------------------------------------------------------------- 1 | name: lecture-python-intro 2 | channels: 3 | - default 4 | dependencies: 5 | - python=3.10 6 | - anaconda=2023.03 7 | 8 | -------------------------------------------------------------------------------- /lectures/_static/quantecon-logo-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/quantecon-logo-transparent.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/networks/mc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/networks/mc.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/networks/weighted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/networks/weighted.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/short_path/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/short_path/graph.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/short_path/graph2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/short_path/graph2.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/short_path/graph3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/short_path/graph3.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/short_path/graph4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/short_path/graph4.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/networks/properties.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/networks/properties.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/markov_chains_I/Temple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/markov_chains_I/Temple.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/markov_chains_II/Irre_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/markov_chains_II/Irre_1.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/markov_chains_II/Irre_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/markov_chains_II/Irre_2.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/networks/poverty_trap_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/networks/poverty_trap_1.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/networks/poverty_trap_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/networks/poverty_trap_2.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/troubleshooting/launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/troubleshooting/launch.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/markov_chains_I/Hamilton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/markov_chains_I/Hamilton.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/markov_chains_II/example4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/markov_chains_II/example4.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/schelling/schelling_fig1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/schelling/schelling_fig1.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/schelling/schelling_fig2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/schelling/schelling_fig2.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/schelling/schelling_fig3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/schelling/schelling_fig3.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/schelling/schelling_fig4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/schelling/schelling_fig4.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/cross_section/rank_size_fig1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/cross_section/rank_size_fig1.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/lake_model/lake_model_worker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/lake_model/lake_model_worker.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/cross_section/light_heavy_fig1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/cross_section/light_heavy_fig1.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/long_run_growth/tooze_ch1_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantEcon/lecture-python-intro/HEAD/lectures/_static/lecture_specific/long_run_growth/tooze_ch1_graph.png -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/networks/properties: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=LR 3 | 1 4 | 2 5 | 3 6 | 1 -> 2 [label=0.7] 7 | 1 -> 3 [label=0.3] 8 | 2 -> 1 [label=1] 9 | 3 -> 1 [label=0.4] 10 | 3 -> 2 [label=0.6] 11 | } 12 | -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/networks/mc: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=LR 3 | ng 4 | mr 5 | sr 6 | ng -> ng [label=0.971] 7 | ng -> mr [label=0.029] 8 | mr -> ng [label=0.145] 9 | mr -> mr [label=0.778] 10 | mr -> sr [label=0.077] 11 | sr -> mr [label=0.508] 12 | sr -> sr [label=0.492] 13 | } 14 | -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/markov_chains_I/Hamilton: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=LR 3 | ng 4 | mr 5 | sr 6 | ng -> ng [label=0.971] 7 | ng -> mr [label=0.029] 8 | mr -> ng [label=0.145] 9 | mr -> mr [label=0.778] 10 | mr -> sr [label=0.077] 11 | sr -> mr [label=0.508] 12 | sr -> sr [label=0.492] 13 | } 14 | -------------------------------------------------------------------------------- /lectures/zreferences.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | text_representation: 4 | extension: .md 5 | format_name: myst 6 | kernelspec: 7 | display_name: Python 3 8 | language: python 9 | name: python3 10 | --- 11 | 12 | (references)= 13 | # References 14 | 15 | ```{bibliography} _static/quant-econ.bib 16 | ``` 17 | 18 | -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/networks/poverty_trap_2: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=LR 3 | poor [pos="0,0!"] 4 | "middle class" [pos="2,1!"] 5 | rich [pos="4,0!"] 6 | poor -> poor 7 | "middle class" -> poor 8 | "middle class" -> "middle class" 9 | "middle class" -> rich 10 | rich -> poor 11 | rich -> "middle class" 12 | rich -> rich 13 | } 14 | -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/lake_model/lake_model_worker: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=LR 3 | node [shape=circle] 4 | 1 [label="New entrants" color=blue] 5 | 2 [label=Unemployed] 6 | 3 [label=Employed] 7 | 1 -> 2 [label=b] 8 | 2 -> 3 [label="λ(1-d)"] 9 | 3 -> 2 [label="α(1-d)"] 10 | 2 -> 2 [label="(1-λ)(1-d)"] 11 | 3 -> 3 [label="(1-α)(1-d)"] 12 | } 13 | -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/markov_chains_II/Irre_2: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=LR 3 | poor 4 | "middle class" 5 | rich 6 | poor -> poor [label=1.0] 7 | "middle class" -> poor [label=0.1] 8 | "middle class" -> "middle class" [label=0.8] 9 | "middle class" -> rich [label=0.1] 10 | rich -> "middle class" [label=0.2] 11 | rich -> rich [label=0.8] 12 | } 13 | -------------------------------------------------------------------------------- /_notebook_repo/README.md: -------------------------------------------------------------------------------- 1 | # lecture-python-intro.notebooks 2 | 3 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/QuantEcon/lecture-python-intro.notebooks/master) 4 | 5 | 6 | 7 | **Note:** This README should be edited [here](https://github.com/quantecon/lecture-python-intro/_notebook_repo) 8 | -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/networks/poverty_trap_1: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=LR 3 | poor [pos="0,0!"] 4 | "middle class" [pos="2,1!"] 5 | rich [pos="4,0!"] 6 | poor -> poor 7 | poor -> "middle class" 8 | "middle class" -> poor 9 | "middle class" -> "middle class" 10 | "middle class" -> rich 11 | rich -> poor 12 | rich -> "middle class" 13 | rich -> rich 14 | } 15 | -------------------------------------------------------------------------------- /lectures/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | text_representation: 4 | extension: .md 5 | format_name: myst 6 | kernelspec: 7 | display_name: Python 3 8 | language: python 9 | name: python3 10 | --- 11 | 12 | # A First Course in Quantitative Economics with Python 13 | 14 | This lecture series provides an introduction to quantitative economics using Python. 15 | 16 | ```{tableofcontents} 17 | ``` 18 | 19 | -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/networks/weighted: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=LR 3 | poor 4 | "middle class" 5 | rich 6 | poor -> poor [label=0.9] 7 | poor -> "middle class" [label=0.1] 8 | "middle class" -> poor [label=0.4] 9 | "middle class" -> "middle class" [label=0.4] 10 | "middle class" -> rich [label=0.2] 11 | rich -> poor [label=0.1] 12 | rich -> "middle class" [label=0.1] 13 | rich -> rich [label=0.8] 14 | } 15 | -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/markov_chains_II/Irre_1: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=LR 3 | poor 4 | "middle class" 5 | rich 6 | poor -> poor [label=0.9] 7 | poor -> "middle class" [label=0.1] 8 | "middle class" -> poor [label=0.4] 9 | "middle class" -> "middle class" [label=0.4] 10 | "middle class" -> rich [label=0.2] 11 | rich -> poor [label=0.1] 12 | rich -> "middle class" [label=0.1] 13 | rich -> rich [label=0.8] 14 | } 15 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: quantecon 2 | channels: 3 | - default 4 | dependencies: 5 | - python=3.13 6 | - anaconda=2025.06 7 | - pip 8 | - pip: 9 | - jupyter-book==1.0.4post1 10 | - quantecon-book-theme==0.9.2 11 | - sphinx-tojupyter==0.3.1 12 | - sphinxext-rediraffe==0.2.7 13 | - sphinx-exercise==1.0.1 14 | - sphinx-proof==0.2.1 15 | - sphinxcontrib-youtube==1.4.1 16 | - sphinx-togglebutton==0.3.2 17 | - sphinx-reredirects==1.0.0 18 | 19 | 20 | -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/markov_chains_I/Temple: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=LR 3 | Growth 4 | Stagnation 5 | Collapse 6 | Growth -> Growth [label=0.68] 7 | Growth -> Stagnation [label=0.12] 8 | Growth -> Collapse [label=0.20] 9 | Stagnation -> Stagnation [label=0.24] 10 | Stagnation -> Growth [label=0.50] 11 | Stagnation -> Collapse [label=0.26] 12 | Collapse -> Collapse [label=0.46] 13 | Collapse -> Stagnation [label=0.18] 14 | Collapse -> Growth [label=0.36] 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A First Course in Quantitative Economics with Python 2 | 3 | An Undergraduate Lecture Series for the Foundations of Computational Economics 4 | 5 | ## Jupyter notebooks 6 | 7 | Jupyter notebook versions of each lecture are available for download 8 | via the website. 9 | 10 | ## Contributions 11 | 12 | To comment on the lectures please add to or open an issue in the issue tracker (see above). 13 | 14 | We welcome pull requests! 15 | 16 | Please read the [QuantEcon style guide](https://manual.quantecon.org/intro.html) first, so that you can match our style. 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | enable-beta-ecosystems: true 8 | updates: 9 | - package-ecosystem: github-actions 10 | directory: / 11 | commit-message: 12 | prefix: ⬆️ 13 | schedule: 14 | interval: weekly 15 | - package-ecosystem: "conda" 16 | directory: "/" 17 | schedule: 18 | interval: "weekly" -------------------------------------------------------------------------------- /lectures/status.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | text_representation: 4 | extension: .md 5 | format_name: myst 6 | kernelspec: 7 | display_name: Python 3 8 | language: python 9 | name: python3 10 | --- 11 | 12 | # Execution Statistics 13 | 14 | This table contains the latest execution statistics. 15 | 16 | ```{nb-exec-table} 17 | ``` 18 | 19 | (status:machine-details)= 20 | 21 | These lectures are built on `linux` instances through `github actions`. 22 | 23 | These lectures are using the following python version 24 | 25 | ```{code-cell} ipython 26 | !python --version 27 | ``` 28 | 29 | and the following package versions 30 | 31 | ```{code-cell} ipython 32 | :tags: [hide-output] 33 | !conda list 34 | ``` -------------------------------------------------------------------------------- /.github/workflows/linkcheck.yml: -------------------------------------------------------------------------------- 1 | name: Link Checker [Anaconda, Linux] 2 | on: 3 | schedule: 4 | # UTC 23:00 is early morning in Australia (9am) -- runs weekly on Sunday 5 | - cron: '0 23 * * 0' 6 | workflow_dispatch: 7 | jobs: 8 | link-checking: 9 | name: Link Checking 10 | runs-on: "ubuntu-latest" 11 | permissions: 12 | issues: write # required for peter-evans/create-issue-from-file 13 | steps: 14 | # Checkout the live site (html) 15 | - name: Checkout 16 | uses: actions/checkout@v5 17 | with: 18 | ref: gh-pages 19 | - name: Link Checker 20 | id: lychee 21 | uses: lycheeverse/lychee-action@v2 22 | with: 23 | fail: false 24 | args: --accept 403,503 *.html 25 | - name: Create Issue From File 26 | if: steps.lychee.outputs.exit_code != 0 27 | uses: peter-evans/create-issue-from-file@v6 28 | with: 29 | title: Link Checker Report 30 | content-filepath: ./lychee/out.md 31 | labels: report, automated issue, linkchecker -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/inequality/usa-gini-nwealth-tincome-lincome.csv: -------------------------------------------------------------------------------- 1 | year,n_wealth,t_income,l_income 2 | 1950,0.8257332034366366,0.44248654139458743,0.534294819877344 3 | 1953,0.805948758659935,0.4264544060935942,0.5158978980963682 4 | 1956,0.8121790488050612,0.44426942873399367,0.5349293526208106 5 | 1959,0.7952068741637912,0.43749348077061534,0.5213985948309414 6 | 1962,0.8086945076579386,0.44358431038536356,0.5345127915054446 7 | 1965,0.7904149225687949,0.4376371546666344,0.7487860020887701 8 | 1968,0.7982885066993503,0.4208620794438885,0.5242396427381534 9 | 1971,0.7911574835420282,0.4233344246090255,0.5576454812313462 10 | 1977,0.7571418922185215,0.46187678800902554,0.57044481100722 11 | 1983,0.749433540064301,0.4393456184644682,0.5662220844385925 12 | 1989,0.7715705301674285,0.5115249581654115,0.6013995687471289 13 | 1992,0.7508126614055305,0.4740650672076754,0.5983592657979544 14 | 1995,0.7569492388110274,0.4896552355840001,0.5969779516717039 15 | 1998,0.7603291991801172,0.49117441585168525,0.5774462841723346 16 | 2001,0.781611875050703,0.523909299468113,0.6042739644967232 17 | 2004,0.7700355469522372,0.48843503839032354,0.5981432201792916 18 | 2007,0.782141377648698,0.5197156312086207,0.6263452195753227 19 | 2010,0.825082529519342,0.5195972120145641,0.6453653328291843 20 | 2013,0.8227698931835299,0.5314001749843426,0.6498682917772886 21 | 2016,0.8342975903562537,0.55414000689009,0.6706846793375292 22 | -------------------------------------------------------------------------------- /.github/workflows/cache.yml: -------------------------------------------------------------------------------- 1 | name: Build Cache [using jupyter-book] 2 | on: 3 | schedule: 4 | # Execute cache weekly at 3am on Monday 5 | - cron: '0 3 * * 1' 6 | workflow_dispatch: 7 | jobs: 8 | tests: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v5 13 | - name: Setup Anaconda 14 | uses: conda-incubator/setup-miniconda@v3 15 | with: 16 | auto-update-conda: true 17 | auto-activate-base: true 18 | miniconda-version: 'latest' 19 | python-version: "3.13" 20 | environment-file: environment.yml 21 | activate-environment: quantecon 22 | - name: graphviz Support # TODO: required? 23 | run: | 24 | sudo apt-get -qq update && sudo apt-get install -y graphviz 25 | - name: Install latex dependencies 26 | run: | 27 | sudo apt-get -qq update 28 | sudo apt-get install -y \ 29 | texlive-latex-recommended \ 30 | texlive-latex-extra \ 31 | texlive-fonts-recommended \ 32 | texlive-fonts-extra \ 33 | texlive-xetex \ 34 | latexmk \ 35 | xindy \ 36 | dvipng \ 37 | cm-super 38 | - name: Build HTML 39 | shell: bash -l {0} 40 | run: | 41 | jb build lectures --path-output ./ -W --keep-going 42 | - name: Upload Execution Reports (HTML) 43 | uses: actions/upload-artifact@v5 44 | if: failure() 45 | with: 46 | name: execution-reports 47 | path: _build/html/reports 48 | - name: Upload "_build" folder (cache) 49 | uses: actions/upload-artifact@v5 50 | with: 51 | name: build-cache 52 | path: _build 53 | include-hidden-files: true -------------------------------------------------------------------------------- /.github/workflows/collab.yml: -------------------------------------------------------------------------------- 1 | name: Build Project on Google Collab (Execution) 2 | on: [pull_request] 3 | 4 | jobs: 5 | test: 6 | runs-on: quantecon-large 7 | container: 8 | image: us-docker.pkg.dev/colab-images/public/runtime:latest 9 | steps: 10 | - uses: actions/checkout@v5 11 | with: 12 | ref: ${{ github.event.pull_request.head.sha }} 13 | - name: Check for dockerenv file 14 | run: (ls /.dockerenv && echo Found dockerenv) || (echo No dockerenv) 15 | - name: Check python version 16 | shell: bash -l {0} 17 | run: | 18 | python --version 19 | - name: Display Pip Versions 20 | shell: bash -l {0} 21 | run: pip list 22 | - name: Download "build" folder (cache) 23 | uses: dawidd6/action-download-artifact@v11 24 | with: 25 | workflow: cache.yml 26 | branch: main 27 | name: build-cache 28 | path: _build 29 | # Install build software 30 | - name: Install Build Software 31 | shell: bash -l {0} 32 | run: | 33 | pip install jupyter-book==0.15.1 docutils==0.17.1 quantecon-book-theme==0.7.2 sphinx-tojupyter==0.3.0 sphinxext-rediraffe==0.2.7 sphinx-exercise==0.4.1 sphinxcontrib-youtube==1.1.0 sphinx-togglebutton==0.3.1 arviz==0.13.0 sphinx_proof==0.2.0 sphinx_reredirects==0.1.3 34 | # Build of HTML (Execution Testing) 35 | - name: Build HTML 36 | shell: bash -l {0} 37 | run: | 38 | jb build lectures --path-output ./ -n -W --keep-going 39 | - name: Upload Execution Reports 40 | uses: actions/upload-artifact@v5 41 | if: failure() 42 | with: 43 | name: execution-reports 44 | path: _build/html/reports 45 | - name: Preview Deploy to Netlify 46 | uses: nwtgck/actions-netlify@v3.0 47 | with: 48 | publish-dir: '_build/html/' 49 | production-branch: main 50 | github-token: ${{ secrets.GITHUB_TOKEN }} 51 | deploy-message: "Preview Deploy from GitHub Actions" 52 | env: 53 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} 54 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} 55 | -------------------------------------------------------------------------------- /lectures/_toc.yml: -------------------------------------------------------------------------------- 1 | format: jb-book 2 | root: intro 3 | parts: 4 | - caption: Introduction 5 | numbered: true 6 | chapters: 7 | - file: about 8 | - caption: Economic Data 9 | numbered: true 10 | chapters: 11 | - file: long_run_growth 12 | - file: business_cycle 13 | - file: inflation_history 14 | - file: french_rev 15 | - file: inequality 16 | - caption: Foundations 17 | numbered: true 18 | chapters: 19 | - file: intro_supply_demand 20 | - file: linear_equations 21 | - file: complex_and_trig 22 | - file: geom_series 23 | - caption: "Linear Dynamics: Finite Horizons" 24 | numbered: true 25 | chapters: 26 | - file: pv 27 | - file: cons_smooth 28 | - file: tax_smooth 29 | - file: equalizing_difference 30 | - file: cagan_ree 31 | - file: cagan_adaptive 32 | - caption: "Linear Dynamics: Infinite Horizons" 33 | numbered: true 34 | chapters: 35 | - file: eigen_I 36 | - file: greek_square 37 | - caption: Probability and Distributions 38 | numbered: true 39 | chapters: 40 | - file: prob_dist 41 | - file: lln_clt 42 | - file: monte_carlo 43 | - file: heavy_tails 44 | - file: schelling 45 | - caption: Nonlinear Dynamics 46 | numbered: true 47 | chapters: 48 | - file: scalar_dynam 49 | - file: solow 50 | - file: cobweb 51 | - file: olg 52 | - file: commod_price 53 | - caption: Monetary-Fiscal Policy Interactions 54 | numbered: true 55 | chapters: 56 | - file: money_inflation 57 | - file: unpleasant 58 | - file: money_inflation_nonlinear 59 | - file: laffer_adaptive 60 | - caption: Stochastic Dynamics 61 | numbered: true 62 | chapters: 63 | - file: ar1_processes 64 | - file: markov_chains_I 65 | - file: markov_chains_II 66 | - file: time_series_with_matrices 67 | - caption: Optimization 68 | numbered: true 69 | chapters: 70 | - file: lp_intro 71 | - file: short_path 72 | - caption: Modeling in Higher Dimensions 73 | numbered: true 74 | chapters: 75 | - file: eigen_II 76 | - file: input_output 77 | - file: lake_model 78 | - file: networks 79 | - caption: Markets and Competitive Equilibrium 80 | numbered: true 81 | chapters: 82 | - file: supply_demand_multiple_goods 83 | - file: supply_demand_heterogeneity 84 | - caption: Estimation 85 | numbered: true 86 | chapters: 87 | - file: simple_linear_regression 88 | - file: mle 89 | - caption: Other 90 | numbered: true 91 | chapters: 92 | - file: troubleshooting 93 | - file: zreferences 94 | - file: status 95 | -------------------------------------------------------------------------------- /lectures/troubleshooting.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | text_representation: 4 | extension: .md 5 | format_name: myst 6 | kernelspec: 7 | display_name: Python 3 8 | language: python 9 | name: python3 10 | --- 11 | 12 | (troubleshooting)= 13 | ```{raw} html 14 |
15 | 16 | QuantEcon 17 | 18 |
19 | ``` 20 | 21 | # Troubleshooting 22 | 23 | This page is for readers experiencing errors when running the code from the lectures. 24 | 25 | ## Fixing your local environment 26 | 27 | The basic assumption of the lectures is that code in a lecture should execute whenever 28 | 29 | 1. it is executed in a Jupyter notebook and 30 | 1. the notebook is running on a machine with the latest version of Anaconda Python. 31 | 32 | You have installed Anaconda, haven't you, following the instructions in [this lecture](https://python-programming.quantecon.org/getting_started.html)? 33 | 34 | Assuming that you have, the most common source of problems for our readers is that their Anaconda distribution is not up to date. 35 | 36 | [Here's a useful article](https://www.anaconda.com/blog/keeping-anaconda-date) 37 | on how to update Anaconda. 38 | 39 | Another option is to simply remove Anaconda and reinstall. 40 | 41 | You also need to keep the external code libraries, such as [QuantEcon.py](https://quantecon.org/quantecon-py) up to date. 42 | 43 | For this task you can either 44 | 45 | * use conda install -y quantecon on the command line, or 46 | * execute !conda install -y quantecon within a Jupyter notebook. 47 | 48 | If your local environment is still not working you can do two things. 49 | 50 | First, you can use a remote machine instead, by clicking on the Launch Notebook icon available for each lecture 51 | 52 | ```{image} _static/lecture_specific/troubleshooting/launch.png 53 | 54 | ``` 55 | 56 | Second, you can report an issue, so we can try to fix your local set up. 57 | 58 | We like getting feedback on the lectures so please don't hesitate to get in 59 | touch. 60 | 61 | ## Reporting an issue 62 | 63 | One way to give feedback is to raise an issue through our [issue tracker](https://github.com/QuantEcon/lecture-python/issues). 64 | 65 | Please be as specific as possible. Tell us where the problem is and as much 66 | detail about your local set up as you can provide. 67 | 68 | Finally, you can provide direct feedback to [contact@quantecon.org](mailto:contact@quantecon.org) 69 | 70 | -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/lake_model/figures.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "3c1ab515-e765-48f5-bfaf-35d4f95581fd", 6 | "metadata": {}, 7 | "source": [ 8 | "# Figures for Lake Model Lecture" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 1, 14 | "id": "45727b3e-f531-4a31-b838-840ccf9d3c02", 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stdout", 19 | "output_type": "stream", 20 | "text": [ 21 | "Requirement already satisfied: graphviz in /Users/mmcky/anaconda3/envs/quantecon/lib/python3.11/site-packages (0.20.3)\n" 22 | ] 23 | } 24 | ], 25 | "source": [ 26 | "!pip install graphviz" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 2, 32 | "id": "68e95ec8-877e-416a-a8ff-665715dfe222", 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "from graphviz import Digraph" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 5, 42 | "id": "92a57207-4d79-4d7b-98c2-aea1a1e35f57", 43 | "metadata": {}, 44 | "outputs": [ 45 | { 46 | "data": { 47 | "text/plain": [ 48 | "'lake_model_worker.png'" 49 | ] 50 | }, 51 | "execution_count": 5, 52 | "metadata": {}, 53 | "output_type": "execute_result" 54 | } 55 | ], 56 | "source": [ 57 | "# Create Digraph object\n", 58 | "G = Digraph(format='png')\n", 59 | "G.attr(rankdir='LR')\n", 60 | "\n", 61 | "# Add nodes\n", 62 | "G.attr('node', shape='circle')\n", 63 | "G.node('1', 'New entrants', color='blue')\n", 64 | "G.node('2', 'Unemployed')\n", 65 | "G.node('3', 'Employed')\n", 66 | "\n", 67 | "# Add edges\n", 68 | "G.edge('1', '2', label='b')\n", 69 | "G.edge('2', '3', label='λ(1-d)')\n", 70 | "G.edge('3', '2', label='α(1-d)')\n", 71 | "G.edge('2', '2', label='(1-λ)(1-d)')\n", 72 | "G.edge('3', '3', label='(1-α)(1-d)')\n", 73 | "\n", 74 | "# Save Plot\n", 75 | "G.render(filename='lake_model_worker')" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "id": "f6c2af58-4d38-44c3-adb0-d1a92a344e51", 82 | "metadata": {}, 83 | "outputs": [], 84 | "source": [] 85 | } 86 | ], 87 | "metadata": { 88 | "kernelspec": { 89 | "display_name": "Python 3 (ipykernel)", 90 | "language": "python", 91 | "name": "python3" 92 | }, 93 | "language_info": { 94 | "codemirror_mode": { 95 | "name": "ipython", 96 | "version": 3 97 | }, 98 | "file_extension": ".py", 99 | "mimetype": "text/x-python", 100 | "name": "python", 101 | "nbconvert_exporter": "python", 102 | "pygments_lexer": "ipython3", 103 | "version": "3.11.7" 104 | } 105 | }, 106 | "nbformat": 4, 107 | "nbformat_minor": 5 108 | } 109 | -------------------------------------------------------------------------------- /lectures/about.md: -------------------------------------------------------------------------------- 1 | # About These Lectures 2 | 3 | 4 | ## About 5 | 6 | This lecture series introduces quantitative economics using elementary 7 | mathematics and statistics plus computer code written in 8 | [Python](https://www.python.org/). 9 | 10 | The lectures emphasize simulation and visualization through code as a way to 11 | convey ideas, rather than focusing on mathematical details. 12 | 13 | Although the presentation is quite novel, the ideas are rather foundational. 14 | 15 | We emphasize the deep and fundamental importance of economic theory, as well 16 | as the value of analyzing data and understanding stylized facts. 17 | 18 | The lectures can be used for university courses, self-study, reading groups or 19 | workshops. 20 | 21 | Researchers and policy professionals might also find some parts of the series 22 | valuable for their work. 23 | 24 | We hope the lectures will be of interest to students of economics 25 | who want to learn both economics and computing, as well as students from 26 | fields such as computer science and engineering who are curious about 27 | economics. 28 | 29 | ## Level 30 | 31 | The lecture series is aimed at undergraduate students. 32 | 33 | The level of the lectures varies from truly introductory (suitable for first 34 | year undergraduates or even high school students) to more intermediate. 35 | 36 | The 37 | more intermediate lectures require comfort with linear algebra and some 38 | mathematical maturity (e.g., calmly reading theorems and trying to understand 39 | their meaning). 40 | 41 | In general, easier lectures occur earlier in the lecture 42 | series and harder lectures occur later. 43 | 44 | We assume that readers have covered the easier parts of the QuantEcon lecture 45 | series [on Python 46 | programming](https://python-programming.quantecon.org/intro.html). 47 | 48 | In 49 | particular, readers should be familiar with basic Python syntax including 50 | Python functions. Knowledge of classes and Matplotlib will be beneficial but 51 | not essential. 52 | 53 | ## Credits 54 | 55 | In building this lecture series, we had invaluable assistance from research 56 | assistants at QuantEcon, as well as our QuantEcon colleagues. Without their 57 | help this series would not have been possible. 58 | 59 | In particular, we sincerely thank and give credit to 60 | 61 | - [Aakash Gupta](https://github.com/AakashGfude) 62 | - [Shu Hu](https://github.com/shlff) 63 | - Jiacheng Li 64 | - [Jiarui Zhang](https://github.com/Jiarui-ZH) 65 | - [Smit Lunagariya](https://github.com/Smit-create) 66 | - [Maanasee Sharma](https://github.com/maanasee) 67 | - [Matthew McKay](https://github.com/mmcky) 68 | - [Margaret Beisenbek](https://github.com/mbek0605) 69 | - [Phoebe Grosser](https://github.com/pgrosser1) 70 | - [Longye Tian](https://github.com/longye-tian) 71 | - [Humphrey Yang](https://github.com/HumphreyYang) 72 | - [Sylvia Zhao](https://github.com/SylviaZhaooo) 73 | 74 | We also thank Noritaka Kudoh for encouraging us to start this project and providing thoughtful suggestions. 75 | 76 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build HTML [using jupyter-book] 2 | on: [pull_request] 3 | jobs: 4 | preview: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout 8 | uses: actions/checkout@v5 9 | - name: Setup Anaconda 10 | uses: conda-incubator/setup-miniconda@v3 11 | with: 12 | auto-update-conda: true 13 | auto-activate-base: true 14 | miniconda-version: 'latest' 15 | python-version: "3.13" 16 | environment-file: environment.yml 17 | activate-environment: quantecon 18 | - name: Graphics Support #TODO: Review if graphviz is needed 19 | run: | 20 | sudo apt-get -qq update && sudo apt-get install -y graphviz 21 | - name: Install latex dependencies 22 | run: | 23 | sudo apt-get -qq update 24 | sudo apt-get install -y \ 25 | texlive-latex-recommended \ 26 | texlive-latex-extra \ 27 | texlive-fonts-recommended \ 28 | texlive-fonts-extra \ 29 | texlive-xetex \ 30 | latexmk \ 31 | xindy \ 32 | dvipng \ 33 | cm-super 34 | - name: Display Conda Environment Versions 35 | shell: bash -l {0} 36 | run: conda list 37 | - name: Display Pip Versions 38 | shell: bash -l {0} 39 | run: pip list 40 | - name: Download "build" folder (cache) 41 | uses: dawidd6/action-download-artifact@v11 42 | with: 43 | workflow: cache.yml 44 | branch: main 45 | name: build-cache 46 | path: _build 47 | # Build Assets (Download Notebooks and PDF via LaTeX) 48 | - name: Build PDF from LaTeX 49 | shell: bash -l {0} 50 | run: | 51 | jb build lectures --builder pdflatex --path-output ./ -n --keep-going 52 | mkdir -p _build/html/_pdf 53 | cp -u _build/latex/*.pdf _build/html/_pdf 54 | - name: Upload Execution Reports (LaTeX) 55 | uses: actions/upload-artifact@v5 56 | if: failure() 57 | with: 58 | name: execution-reports 59 | path: _build/latex/reports 60 | - name: Build Download Notebooks (sphinx-tojupyter) 61 | shell: bash -l {0} 62 | run: | 63 | jb build lectures --path-output ./ --builder=custom --custom-builder=jupyter 64 | mkdir -p _build/html/_notebooks 65 | cp -u _build/jupyter/*.ipynb _build/html/_notebooks 66 | # Build HTML (Website) 67 | # BUG: rm .doctress to remove `sphinx` rendering issues for ipywidget mimetypes 68 | # and clear the sphinx cache for building final HTML documents. 69 | - name: Build HTML 70 | shell: bash -l {0} 71 | run: | 72 | rm -r _build/.doctrees 73 | jb build lectures --path-output ./ -nW --keep-going 74 | - name: Upload Execution Reports (HTML) 75 | uses: actions/upload-artifact@v5 76 | if: failure() 77 | with: 78 | name: execution-reports 79 | path: _build/html/reports 80 | - name: Preview Deploy to Netlify 81 | uses: nwtgck/actions-netlify@v3.0 82 | with: 83 | publish-dir: '_build/html/' 84 | production-branch: main 85 | github-token: ${{ secrets.GITHUB_TOKEN }} 86 | deploy-message: "Preview Deploy from GitHub Actions" 87 | env: 88 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} 89 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} 90 | -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/markov_chains_I/figures.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "43b11347-069e-4c50-b093-4635361408fc", 6 | "metadata": {}, 7 | "source": [ 8 | "# Figures for Markov Chains I" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 1, 14 | "id": "38b0e8a2-acab-4b27-95b3-96174c7d6863", 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stdout", 19 | "output_type": "stream", 20 | "text": [ 21 | "Requirement already satisfied: graphviz in /Users/mmcky/anaconda3/envs/quantecon/lib/python3.11/site-packages (0.20.3)\n" 22 | ] 23 | } 24 | ], 25 | "source": [ 26 | "!pip install graphviz" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 2, 32 | "id": "606da6ac-e8b7-49c6-9d88-142e18e02bba", 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "from graphviz import Digraph" 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "id": "87430938-745b-45d3-9895-937bc357432d", 42 | "metadata": {}, 43 | "source": [ 44 | "## Example 1\n", 45 | "\n", 46 | "Hamilton on US unemployment data" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 3, 52 | "id": "eff7a6b5-98d4-4f26-b72f-7100a911644e", 53 | "metadata": {}, 54 | "outputs": [ 55 | { 56 | "data": { 57 | "text/plain": [ 58 | "'Hamilton.png'" 59 | ] 60 | }, 61 | "execution_count": 3, 62 | "metadata": {}, 63 | "output_type": "execute_result" 64 | } 65 | ], 66 | "source": [ 67 | "dot = Digraph(format='png')\n", 68 | "dot.attr(rankdir='LR')\n", 69 | "dot.node(\"ng\")\n", 70 | "dot.node(\"mr\")\n", 71 | "dot.node(\"sr\")\n", 72 | "\n", 73 | "dot.edge(\"ng\", \"ng\", label=\"0.971\")\n", 74 | "dot.edge(\"ng\", \"mr\", label=\"0.029\")\n", 75 | "dot.edge(\"mr\", \"ng\", label=\"0.145\")\n", 76 | "\n", 77 | "dot.edge(\"mr\", \"mr\", label=\"0.778\")\n", 78 | "dot.edge(\"mr\", \"sr\", label=\"0.077\")\n", 79 | "dot.edge(\"sr\", \"mr\", label=\"0.508\")\n", 80 | "\n", 81 | "dot.edge(\"sr\", \"sr\", label=\"0.492\")\n", 82 | "dot\n", 83 | "\n", 84 | "dot.render(filename='Hamilton')" 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "id": "7effdaa6-ee97-4634-811c-1b90d7e95543", 90 | "metadata": {}, 91 | "source": [ 92 | "## Exercise 1\n", 93 | "\n", 94 | "Solution 2:" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 4, 100 | "id": "bb57212a-c0f2-4922-a549-0edea60ec590", 101 | "metadata": {}, 102 | "outputs": [ 103 | { 104 | "data": { 105 | "text/plain": [ 106 | "'Temple.png'" 107 | ] 108 | }, 109 | "execution_count": 4, 110 | "metadata": {}, 111 | "output_type": "execute_result" 112 | } 113 | ], 114 | "source": [ 115 | "dot = Digraph(format='png')\n", 116 | "dot.attr(rankdir='LR')\n", 117 | "dot.node(\"Growth\")\n", 118 | "dot.node(\"Stagnation\")\n", 119 | "dot.node(\"Collapse\")\n", 120 | "\n", 121 | "dot.edge(\"Growth\", \"Growth\", label=\"0.68\")\n", 122 | "dot.edge(\"Growth\", \"Stagnation\", label=\"0.12\")\n", 123 | "dot.edge(\"Growth\", \"Collapse\", label=\"0.20\")\n", 124 | "\n", 125 | "dot.edge(\"Stagnation\", \"Stagnation\", label=\"0.24\")\n", 126 | "dot.edge(\"Stagnation\", \"Growth\", label=\"0.50\")\n", 127 | "dot.edge(\"Stagnation\", \"Collapse\", label=\"0.26\")\n", 128 | "\n", 129 | "dot.edge(\"Collapse\", \"Collapse\", label=\"0.46\")\n", 130 | "dot.edge(\"Collapse\", \"Stagnation\", label=\"0.18\")\n", 131 | "dot.edge(\"Collapse\", \"Growth\", label=\"0.36\")\n", 132 | "\n", 133 | "dot\n", 134 | "\n", 135 | "dot.render(filename='Temple')" 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": null, 141 | "id": "7aca758c-e819-4d01-a2aa-c5fd36f0e2ad", 142 | "metadata": {}, 143 | "outputs": [], 144 | "source": [] 145 | } 146 | ], 147 | "metadata": { 148 | "kernelspec": { 149 | "display_name": "Python 3 (ipykernel)", 150 | "language": "python", 151 | "name": "python3" 152 | }, 153 | "language_info": { 154 | "codemirror_mode": { 155 | "name": "ipython", 156 | "version": 3 157 | }, 158 | "file_extension": ".py", 159 | "mimetype": "text/x-python", 160 | "name": "python", 161 | "nbconvert_exporter": "python", 162 | "pygments_lexer": "ipython3", 163 | "version": "3.11.7" 164 | } 165 | }, 166 | "nbformat": 4, 167 | "nbformat_minor": 5 168 | } 169 | -------------------------------------------------------------------------------- /lectures/_config.yml: -------------------------------------------------------------------------------- 1 | title: A First Course in Quantitative Economics with Python 2 | author: Thomas J. Sargent & John Stachurski 3 | logo: _static/qe-logo.png 4 | description: This website presents introductory lectures on computational economics, designed and written by Thomas J. Sargent and John Stachurski. 5 | 6 | parse: 7 | myst_enable_extensions: # default extensions to enable in the myst parser. See https://myst-parser.readthedocs.io/en/latest/using/syntax-optional.html 8 | - amsmath 9 | - colon_fence 10 | - deflist 11 | - dollarmath 12 | - html_admonition 13 | - html_image 14 | - linkify 15 | - replacements 16 | - smartquotes 17 | - substitution 18 | - tasklist 19 | 20 | only_build_toc_files: true 21 | execute: 22 | execute_notebooks: "cache" 23 | timeout: 600 # 10 minutes 24 | exclude_patterns: 25 | - '_static/*' 26 | 27 | html: 28 | baseurl: https://intro.quantecon.org/ 29 | 30 | bibtex_bibfiles: 31 | - _static/quant-econ.bib 32 | 33 | latex: 34 | latex_documents: 35 | targetname: quantecon-python-intro.tex 36 | 37 | sphinx: 38 | extra_extensions: [sphinx_multitoc_numbering, sphinxext.rediraffe, sphinx_exercise, sphinx_togglebutton, sphinx.ext.intersphinx, sphinx_proof, sphinx_tojupyter, sphinx_reredirects] 39 | config: 40 | bibtex_reference_style: author_year 41 | # false-positive links 42 | linkcheck_ignore: ['https://doi.org/https://doi.org/10.2307/1235116', 'https://math.stackexchange.com/*', 'https://stackoverflow.com/*'] 43 | # myst-nb config 44 | nb_render_image_options: 45 | width: 80% 46 | nb_code_prompt_show: "Show {type}" 47 | suppress_warnings: [mystnb.unknown_mime_type, myst.domains] 48 | proof_minimal_theme: true 49 | # ------------- 50 | html_js_files: 51 | - https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js 52 | html_favicon: _static/lectures-favicon.ico 53 | html_theme: quantecon_book_theme 54 | html_static_path: ['_static'] 55 | html_theme_options: 56 | authors: 57 | - name: Thomas J. Sargent 58 | url: http://www.tomsargent.com/ 59 | - name: John Stachurski 60 | url: https://johnstachurski.net/ 61 | dark_logo: quantecon-logo-transparent.png 62 | header_organisation_url: https://quantecon.org 63 | header_organisation: QuantEcon 64 | repository_url: https://github.com/QuantEcon/lecture-python-intro 65 | nb_repository_url: https://github.com/QuantEcon/lecture-python-intro.notebooks 66 | twitter: quantecon 67 | twitter_logo_url: https://assets.quantecon.org/img/qe-twitter-logo.png 68 | og_logo_url: https://assets.quantecon.org/img/qe-og-logo.png 69 | description: This website presents introductory lectures on computational economics, designed and written by Thomas J. Sargent and John Stachurski. 70 | keywords: Python, QuantEcon, Quantitative Economics, Economics, Sloan, Alfred P. Sloan Foundation, Tom J. Sargent, John Stachurski 71 | analytics: 72 | google_analytics_id: G-QDS1YRJNGM 73 | launch_buttons: 74 | colab_url : https://colab.research.google.com 75 | thebe : false # Add a thebe button to pages (requires the repository to run on Binder) 76 | intersphinx_mapping: 77 | intermediate: 78 | - https://python.quantecon.org/ 79 | - null 80 | pyprog: 81 | - https://python-programming.quantecon.org/ 82 | - null 83 | intro: 84 | - https://intro.quantecon.org/ 85 | - null 86 | dle: 87 | - https://dle.quantecon.org/ 88 | - null 89 | dps: 90 | - https://dps.quantecon.org/ 91 | - null 92 | eqm: 93 | - https://eqm.quantecon.org/ 94 | - null 95 | stats: 96 | - https://stats.quantecon.org/ 97 | - null 98 | tools: 99 | - https://tools-techniques.quantecon.org/ 100 | - null 101 | dynam: 102 | - https://dynamics.quantecon.org/ 103 | - null 104 | mathjax3_config: 105 | tex: 106 | macros: 107 | "argmax" : "arg\\,max" 108 | "argmin" : "arg\\,min" 109 | mathjax_path: https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js 110 | rediraffe_redirects: 111 | index_toc.md: intro.md 112 | # Remote Redirects 113 | redirects: 114 | ak2: https://python.quantecon.org/ak2.html 115 | tojupyter_static_file_path: ["_static"] 116 | tojupyter_target_html: true 117 | tojupyter_urlpath: "https://intro.quantecon.org/" 118 | tojupyter_image_urlpath: "https://intro.quantecon.org/_static/" 119 | tojupyter_lang_synonyms: ["ipython", "ipython3", "python"] 120 | tojupyter_kernels: 121 | python3: 122 | kernelspec: 123 | display_name: "Python" 124 | language: python3 125 | name: python3 126 | file_extension: ".py" 127 | tojupyter_images_markdown: true -------------------------------------------------------------------------------- /lectures/graph.txt: -------------------------------------------------------------------------------- 1 | node0, node1 0.04, node8 11.11, node14 72.21 2 | node1, node46 1247.25, node6 20.59, node13 64.94 3 | node2, node66 54.18, node31 166.80, node45 1561.45 4 | node3, node20 133.65, node6 2.06, node11 42.43 5 | node4, node75 3706.67, node5 0.73, node7 1.02 6 | node5, node45 1382.97, node7 3.33, node11 34.54 7 | node6, node31 63.17, node9 0.72, node10 13.10 8 | node7, node50 478.14, node9 3.15, node10 5.85 9 | node8, node69 577.91, node11 7.45, node12 3.18 10 | node9, node70 2454.28, node13 4.42, node20 16.53 11 | node10, node89 5352.79, node12 1.87, node16 25.16 12 | node11, node94 4961.32, node18 37.55, node20 65.08 13 | node12, node84 3914.62, node24 34.32, node28 170.04 14 | node13, node60 2135.95, node38 236.33, node40 475.33 15 | node14, node67 1878.96, node16 2.70, node24 38.65 16 | node15, node91 3597.11, node17 1.01, node18 2.57 17 | node16, node36 392.92, node19 3.49, node38 278.71 18 | node17, node76 783.29, node22 24.78, node23 26.45 19 | node18, node91 3363.17, node23 16.23, node28 55.84 20 | node19, node26 20.09, node20 0.24, node28 70.54 21 | node20, node98 3523.33, node24 9.81, node33 145.80 22 | node21, node56 626.04, node28 36.65, node31 27.06 23 | node22, node72 1447.22, node39 136.32, node40 124.22 24 | node23, node52 336.73, node26 2.66, node33 22.37 25 | node24, node66 875.19, node26 1.80, node28 14.25 26 | node25, node70 1343.63, node32 36.58, node35 45.55 27 | node26, node47 135.78, node27 0.01, node42 122.00 28 | node27, node65 480.55, node35 48.10, node43 246.24 29 | node28, node82 2538.18, node34 21.79, node36 15.52 30 | node29, node64 635.52, node32 4.22, node33 12.61 31 | node30, node98 2616.03, node33 5.61, node35 13.95 32 | node31, node98 3350.98, node36 20.44, node44 125.88 33 | node32, node97 2613.92, node34 3.33, node35 1.46 34 | node33, node81 1854.73, node41 3.23, node47 111.54 35 | node34, node73 1075.38, node42 51.52, node48 129.45 36 | node35, node52 17.57, node41 2.09, node50 78.81 37 | node36, node71 1171.60, node54 101.08, node57 260.46 38 | node37, node75 269.97, node38 0.36, node46 80.49 39 | node38, node93 2767.85, node40 1.79, node42 8.78 40 | node39, node50 39.88, node40 0.95, node41 1.34 41 | node40, node75 548.68, node47 28.57, node54 53.46 42 | node41, node53 18.23, node46 0.28, node54 162.24 43 | node42, node59 141.86, node47 10.08, node72 437.49 44 | node43, node98 2984.83, node54 95.06, node60 116.23 45 | node44, node91 807.39, node46 1.56, node47 2.14 46 | node45, node58 79.93, node47 3.68, node49 15.51 47 | node46, node52 22.68, node57 27.50, node67 65.48 48 | node47, node50 2.82, node56 49.31, node61 172.64 49 | node48, node99 2564.12, node59 34.52, node60 66.44 50 | node49, node78 53.79, node50 0.51, node56 10.89 51 | node50, node85 251.76, node53 1.38, node55 20.10 52 | node51, node98 2110.67, node59 23.67, node60 73.79 53 | node52, node94 1471.80, node64 102.41, node66 123.03 54 | node53, node72 22.85, node56 4.33, node67 88.35 55 | node54, node88 967.59, node59 24.30, node73 238.61 56 | node55, node84 86.09, node57 2.13, node64 60.80 57 | node56, node76 197.03, node57 0.02, node61 11.06 58 | node57, node86 701.09, node58 0.46, node60 7.01 59 | node58, node83 556.70, node64 29.85, node65 34.32 60 | node59, node90 820.66, node60 0.72, node71 0.67 61 | node60, node76 48.03, node65 4.76, node67 1.63 62 | node61, node98 1057.59, node63 0.95, node64 4.88 63 | node62, node91 132.23, node64 2.94, node76 38.43 64 | node63, node66 4.43, node72 70.08, node75 56.34 65 | node64, node80 47.73, node65 0.30, node76 11.98 66 | node65, node94 594.93, node66 0.64, node73 33.23 67 | node66, node98 395.63, node68 2.66, node73 37.53 68 | node67, node82 153.53, node68 0.09, node70 0.98 69 | node68, node94 232.10, node70 3.35, node71 1.66 70 | node69, node99 247.80, node70 0.06, node73 8.99 71 | node70, node76 27.18, node72 1.50, node73 8.37 72 | node71, node89 104.50, node74 8.86, node91 284.64 73 | node72, node76 15.32, node84 102.77, node92 133.06 74 | node73, node83 52.22, node76 1.40, node90 243.00 75 | node74, node81 1.07, node76 0.52, node78 8.08 76 | node75, node92 68.53, node76 0.81, node77 1.19 77 | node76, node85 13.18, node77 0.45, node78 2.36 78 | node77, node80 8.94, node78 0.98, node86 64.32 79 | node78, node98 355.90, node81 2.59 80 | node79, node81 0.09, node85 1.45, node91 22.35 81 | node80, node92 121.87, node88 28.78, node98 264.34 82 | node81, node94 99.78, node89 39.52, node92 99.89 83 | node82, node91 47.44, node88 28.05, node93 11.99 84 | node83, node94 114.95, node86 8.75, node88 5.78 85 | node84, node89 19.14, node94 30.41, node98 121.05 86 | node85, node97 94.51, node87 2.66, node89 4.90 87 | node86, node97 85.09 88 | node87, node88 0.21, node91 11.14, node92 21.23 89 | node88, node93 1.31, node91 6.83, node98 6.12 90 | node89, node97 36.97, node99 82.12 91 | node90, node96 23.53, node94 10.47, node99 50.99 92 | node91, node97 22.17 93 | node92, node96 10.83, node97 11.24, node99 34.68 94 | node93, node94 0.19, node97 6.71, node99 32.77 95 | node94, node98 5.91, node96 2.03 96 | node95, node98 6.17, node99 0.27 97 | node96, node98 3.32, node97 0.43, node99 5.87 98 | node97, node98 0.30 99 | node98, node99 0.33 100 | node99, 101 | -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/markov_chains_II/figures.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "b5a640be-e2e4-4841-bcda-95f6660fd9fe", 6 | "metadata": {}, 7 | "source": [ 8 | "# Figures for Markov Chain II" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 1, 14 | "id": "f8a64d7b-2ffd-4974-a1dd-ec14d8e44102", 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stdout", 19 | "output_type": "stream", 20 | "text": [ 21 | "Collecting graphviz\n", 22 | " Using cached graphviz-0.20.3-py3-none-any.whl.metadata (12 kB)\n", 23 | "Using cached graphviz-0.20.3-py3-none-any.whl (47 kB)\n", 24 | "Installing collected packages: graphviz\n", 25 | "Successfully installed graphviz-0.20.3\n" 26 | ] 27 | } 28 | ], 29 | "source": [ 30 | "!pip install graphviz" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 2, 36 | "id": "43d55bea-fd00-4583-8d89-830151b6c36c", 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "from graphviz import Digraph" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "id": "4185ae75-3a1c-4f89-ad74-1950d344ba56", 46 | "metadata": {}, 47 | "source": [ 48 | "## Irreducibility" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 3, 54 | "id": "53a8bb26-a6e8-421c-abcc-535031d5a69b", 55 | "metadata": {}, 56 | "outputs": [ 57 | { 58 | "data": { 59 | "text/plain": [ 60 | "'Irre_1.png'" 61 | ] 62 | }, 63 | "execution_count": 3, 64 | "metadata": {}, 65 | "output_type": "execute_result" 66 | } 67 | ], 68 | "source": [ 69 | "dot = Digraph(format='png')\n", 70 | "dot.attr(rankdir='LR')\n", 71 | "dot.node(\"poor\")\n", 72 | "dot.node(\"middle class\")\n", 73 | "dot.node(\"rich\")\n", 74 | "\n", 75 | "dot.edge(\"poor\", \"poor\", label=\"0.9\")\n", 76 | "dot.edge(\"poor\", \"middle class\", label=\"0.1\")\n", 77 | "dot.edge(\"middle class\", \"poor\", label=\"0.4\")\n", 78 | "dot.edge(\"middle class\", \"middle class\", label=\"0.4\")\n", 79 | "dot.edge(\"middle class\", \"rich\", label=\"0.2\")\n", 80 | "dot.edge(\"rich\", \"poor\", label=\"0.1\")\n", 81 | "dot.edge(\"rich\", \"middle class\", label=\"0.1\")\n", 82 | "dot.edge(\"rich\", \"rich\", label=\"0.8\")\n", 83 | "\n", 84 | "dot\n", 85 | "dot.render(filename='Irre_1')" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": 4, 91 | "id": "4e96fd64-a1ab-4a6e-a5d6-a64767f6181e", 92 | "metadata": {}, 93 | "outputs": [ 94 | { 95 | "data": { 96 | "text/plain": [ 97 | "'Irre_2.png'" 98 | ] 99 | }, 100 | "execution_count": 4, 101 | "metadata": {}, 102 | "output_type": "execute_result" 103 | } 104 | ], 105 | "source": [ 106 | "dot = Digraph(format='png')\n", 107 | "dot.attr(rankdir='LR')\n", 108 | "dot.node(\"poor\")\n", 109 | "dot.node(\"middle class\")\n", 110 | "dot.node(\"rich\")\n", 111 | "\n", 112 | "dot.edge(\"poor\", \"poor\", label=\"1.0\")\n", 113 | "dot.edge(\"middle class\", \"poor\", label=\"0.1\")\n", 114 | "dot.edge(\"middle class\", \"middle class\", label=\"0.8\")\n", 115 | "dot.edge(\"middle class\", \"rich\", label=\"0.1\")\n", 116 | "dot.edge(\"rich\", \"middle class\", label=\"0.2\")\n", 117 | "dot.edge(\"rich\", \"rich\", label=\"0.8\")\n", 118 | "\n", 119 | "dot\n", 120 | "dot.render(filename='Irre_2')" 121 | ] 122 | }, 123 | { 124 | "cell_type": "markdown", 125 | "id": "1d7441a9-7753-4922-8276-3d26a26798cf", 126 | "metadata": {}, 127 | "source": [ 128 | "## Example 4" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": 5, 134 | "id": "850376db-6922-4440-af89-c50b0e6b5050", 135 | "metadata": {}, 136 | "outputs": [ 137 | { 138 | "data": { 139 | "text/plain": [ 140 | "'example4.png'" 141 | ] 142 | }, 143 | "execution_count": 5, 144 | "metadata": {}, 145 | "output_type": "execute_result" 146 | } 147 | ], 148 | "source": [ 149 | "dot = Digraph(format='png')\n", 150 | "dot.attr(rankdir='LR')\n", 151 | "dot.node(\"0\")\n", 152 | "dot.node(\"1\")\n", 153 | "\n", 154 | "dot.edge(\"0\", \"1\", label=\"1.0\")\n", 155 | "dot.edge(\"1\", \"0\", label=\"1.0\")\n", 156 | "\n", 157 | "dot\n", 158 | "dot.render(filename='example4')" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": null, 164 | "id": "5f9c4db0-812a-4131-803f-024ae5b61772", 165 | "metadata": {}, 166 | "outputs": [], 167 | "source": [] 168 | } 169 | ], 170 | "metadata": { 171 | "kernelspec": { 172 | "display_name": "Python 3 (ipykernel)", 173 | "language": "python", 174 | "name": "python3" 175 | }, 176 | "language_info": { 177 | "codemirror_mode": { 178 | "name": "ipython", 179 | "version": 3 180 | }, 181 | "file_extension": ".py", 182 | "mimetype": "text/x-python", 183 | "name": "python", 184 | "nbconvert_exporter": "python", 185 | "pygments_lexer": "ipython3", 186 | "version": "3.11.7" 187 | } 188 | }, 189 | "nbformat": 4, 190 | "nbformat_minor": 5 191 | } 192 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Build & Publish to GH-PAGES 2 | on: 3 | push: 4 | tags: 5 | - 'publish*' 6 | jobs: 7 | publish: 8 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v5 13 | - name: Setup Anaconda 14 | uses: conda-incubator/setup-miniconda@v3 15 | with: 16 | auto-update-conda: true 17 | auto-activate-base: true 18 | miniconda-version: 'latest' 19 | python-version: "3.13" 20 | environment-file: environment.yml 21 | activate-environment: quantecon 22 | - name: Install latex dependencies 23 | run: | 24 | sudo apt-get -qq update 25 | sudo apt-get install -y \ 26 | texlive-latex-recommended \ 27 | texlive-latex-extra \ 28 | texlive-fonts-recommended \ 29 | texlive-fonts-extra \ 30 | texlive-xetex \ 31 | latexmk \ 32 | xindy \ 33 | dvipng \ 34 | cm-super 35 | - name: Display Conda Environment Versions 36 | shell: bash -l {0} 37 | run: conda list 38 | - name: Display Pip Versions 39 | shell: bash -l {0} 40 | run: pip list 41 | - name: Download "build" folder (cache) 42 | uses: dawidd6/action-download-artifact@v11 43 | with: 44 | workflow: cache.yml 45 | branch: main 46 | name: build-cache 47 | path: _build 48 | # Build Assets (Download Notebooks and PDF via LaTeX) 49 | - name: Build PDF from LaTeX 50 | shell: bash -l {0} 51 | run: | 52 | jb build lectures --builder pdflatex --path-output ./ -n --keep-going 53 | - name: Copy LaTeX PDF for GH-PAGES 54 | shell: bash -l {0} 55 | run: | 56 | mkdir -p _build/html/_pdf 57 | cp -u _build/latex/*.pdf _build/html/_pdf 58 | - name: Build Download Notebooks (sphinx-tojupyter) 59 | shell: bash -l {0} 60 | run: | 61 | jb build lectures --path-output ./ --builder=custom --custom-builder=jupyter 62 | - name: Copy Download Notebooks for GH-PAGES 63 | shell: bash -l {0} 64 | run: | 65 | mkdir -p _build/html/_notebooks 66 | cp -u _build/jupyter/*.ipynb _build/html/_notebooks 67 | # Build HTML (Website) 68 | # BUG: rm .doctress to remove `sphinx` rendering issues for ipywidget mimetypes 69 | # and clear the sphinx cache for building final HTML documents. 70 | - name: Build HTML 71 | shell: bash -l {0} 72 | run: | 73 | rm -r _build/.doctrees 74 | jb build lectures --path-output ./ 75 | # Create HTML archive for release assets 76 | - name: Create HTML archive 77 | shell: bash -l {0} 78 | run: | 79 | tar -czf lecture-python-intro-html-${{ github.ref_name }}.tar.gz -C _build/html . 80 | sha256sum lecture-python-intro-html-${{ github.ref_name }}.tar.gz > html-checksum.txt 81 | 82 | # Create metadata manifest 83 | cat > html-manifest.json << EOF 84 | { 85 | "tag": "${{ github.ref_name }}", 86 | "commit": "${{ github.sha }}", 87 | "timestamp": "$(date -Iseconds)", 88 | "size_mb": $(du -sm _build/html | cut -f1), 89 | "file_count": $(find _build/html -type f | wc -l) 90 | } 91 | EOF 92 | - name: Upload archives to release 93 | uses: softprops/action-gh-release@v1 94 | with: 95 | files: | 96 | lecture-python-intro-html-${{ github.ref_name }}.tar.gz 97 | html-checksum.txt 98 | html-manifest.json 99 | env: 100 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 101 | - name: Deploy to Netlify 102 | uses: nwtgck/actions-netlify@v3.0 103 | with: 104 | publish-dir: '_build/html/' 105 | production-branch: main 106 | github-token: ${{ secrets.GITHUB_TOKEN }} 107 | deploy-message: "Deploy from GitHub Actions" 108 | env: 109 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} 110 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} 111 | - name: Deploy website to gh-pages 112 | uses: peaceiris/actions-gh-pages@v4 113 | with: 114 | github_token: ${{ secrets.GITHUB_TOKEN }} 115 | publish_dir: _build/html/ 116 | cname: intro.quantecon.org 117 | - name: Upload "_build" folder (cache) 118 | uses: actions/upload-artifact@v5 119 | with: 120 | name: build-publish 121 | path: _build 122 | # Sync notebooks 123 | - name: Prepare lecture-python-intro.notebooks sync 124 | shell: bash -l {0} 125 | run: | 126 | mkdir -p _build/lecture-python-intro.notebooks 127 | cp -a _notebook_repo/. _build/lecture-python-intro.notebooks 128 | cp _build/jupyter/*.ipynb _build/lecture-python-intro.notebooks 129 | ls -a _build/lecture-python-intro.notebooks 130 | - name: Commit latest notebooks to lecture-python-intro.notebooks 131 | uses: cpina/github-action-push-to-another-repository@main 132 | env: 133 | API_TOKEN_GITHUB: ${{ secrets.QUANTECON_SERVICES_PAT }} 134 | with: 135 | source-directory: '_build/lecture-python-intro.notebooks/' 136 | destination-repository-username: 'QuantEcon' 137 | destination-repository-name: 'lecture-python-intro.notebooks' 138 | commit-message: 'auto publishing updates to notebooks' 139 | destination-github-username: 'quantecon-services' 140 | user-email: services@quantecon.org 141 | -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/inequality/data.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "258b4bc9-2964-470a-8010-05c2162f5e05", 7 | "metadata": {}, 8 | "outputs": [ 9 | { 10 | "name": "stdout", 11 | "output_type": "stream", 12 | "text": [ 13 | "Requirement already satisfied: wbgapi in /Users/longye/anaconda3/lib/python3.10/site-packages (1.0.12)\n", 14 | "Requirement already satisfied: plotly in /Users/longye/anaconda3/lib/python3.10/site-packages (5.22.0)\n", 15 | "Requirement already satisfied: requests in /Users/longye/anaconda3/lib/python3.10/site-packages (from wbgapi) (2.31.0)\n", 16 | "Requirement already satisfied: tabulate in /Users/longye/anaconda3/lib/python3.10/site-packages (from wbgapi) (0.9.0)\n", 17 | "Requirement already satisfied: PyYAML in /Users/longye/anaconda3/lib/python3.10/site-packages (from wbgapi) (6.0)\n", 18 | "Requirement already satisfied: tenacity>=6.2.0 in /Users/longye/anaconda3/lib/python3.10/site-packages (from plotly) (8.4.1)\n", 19 | "Requirement already satisfied: packaging in /Users/longye/anaconda3/lib/python3.10/site-packages (from plotly) (23.1)\n", 20 | "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/longye/anaconda3/lib/python3.10/site-packages (from requests->wbgapi) (1.26.16)\n", 21 | "Requirement already satisfied: charset-normalizer<4,>=2 in /Users/longye/anaconda3/lib/python3.10/site-packages (from requests->wbgapi) (2.0.4)\n", 22 | "Requirement already satisfied: idna<4,>=2.5 in /Users/longye/anaconda3/lib/python3.10/site-packages (from requests->wbgapi) (3.4)\n", 23 | "Requirement already satisfied: certifi>=2017.4.17 in /Users/longye/anaconda3/lib/python3.10/site-packages (from requests->wbgapi) (2024.6.2)\n" 24 | ] 25 | } 26 | ], 27 | "source": [ 28 | "!pip install wbgapi plotly\n", 29 | "\n", 30 | "import pandas as pd\n", 31 | "import numpy as np\n", 32 | "import matplotlib.pyplot as plt\n", 33 | "import random as rd\n", 34 | "import wbgapi as wb\n", 35 | "import plotly.express as px\n", 36 | "\n", 37 | "url = 'https://media.githubusercontent.com/media/QuantEcon/high_dim_data/main/SCF_plus/SCF_plus_mini.csv'\n", 38 | "df = pd.read_csv(url)\n", 39 | "df_income_wealth = df.dropna()" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 4, 45 | "id": "9630a07a-fce5-474e-92af-104e67e82be5", 46 | "metadata": {}, 47 | "outputs": [ 48 | { 49 | "name": "stdout", 50 | "output_type": "stream", 51 | "text": [ 52 | "Requirement already satisfied: quantecon in /Users/longye/anaconda3/lib/python3.10/site-packages (0.7.1)\n", 53 | "Requirement already satisfied: requests in /Users/longye/anaconda3/lib/python3.10/site-packages (from quantecon) (2.31.0)\n", 54 | "Requirement already satisfied: numpy>=1.17.0 in /Users/longye/anaconda3/lib/python3.10/site-packages (from quantecon) (1.26.3)\n", 55 | "Requirement already satisfied: numba>=0.49.0 in /Users/longye/anaconda3/lib/python3.10/site-packages (from quantecon) (0.59.1)\n", 56 | "Requirement already satisfied: sympy in /Users/longye/anaconda3/lib/python3.10/site-packages (from quantecon) (1.12)\n", 57 | "Requirement already satisfied: scipy>=1.5.0 in /Users/longye/anaconda3/lib/python3.10/site-packages (from quantecon) (1.12.0)\n", 58 | "Requirement already satisfied: llvmlite<0.43,>=0.42.0dev0 in /Users/longye/anaconda3/lib/python3.10/site-packages (from numba>=0.49.0->quantecon) (0.42.0)\n", 59 | "Requirement already satisfied: certifi>=2017.4.17 in /Users/longye/anaconda3/lib/python3.10/site-packages (from requests->quantecon) (2024.6.2)\n", 60 | "Requirement already satisfied: idna<4,>=2.5 in /Users/longye/anaconda3/lib/python3.10/site-packages (from requests->quantecon) (3.4)\n", 61 | "Requirement already satisfied: charset-normalizer<4,>=2 in /Users/longye/anaconda3/lib/python3.10/site-packages (from requests->quantecon) (2.0.4)\n", 62 | "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/longye/anaconda3/lib/python3.10/site-packages (from requests->quantecon) (1.26.16)\n", 63 | "Requirement already satisfied: mpmath>=0.19 in /Users/longye/anaconda3/lib/python3.10/site-packages (from sympy->quantecon) (1.3.0)\n" 64 | ] 65 | } 66 | ], 67 | "source": [ 68 | "!pip install quantecon\n", 69 | "import quantecon as qe\n", 70 | "\n", 71 | "varlist = ['n_wealth', # net wealth \n", 72 | " 't_income', # total income\n", 73 | " 'l_income'] # labor income\n", 74 | "\n", 75 | "df = df_income_wealth\n", 76 | "years = df.year.unique()\n", 77 | "\n", 78 | "# create lists to store Gini for each inequality measure\n", 79 | "results = {}\n", 80 | "\n", 81 | "for var in varlist:\n", 82 | " # create lists to store Gini\n", 83 | " gini_yr = []\n", 84 | " for year in years:\n", 85 | " # repeat the observations according to their weights\n", 86 | " counts = list(round(df[df['year'] == year]['weights'] ))\n", 87 | " y = df[df['year'] == year][var].repeat(counts)\n", 88 | " y = np.asarray(y)\n", 89 | " \n", 90 | " rd.shuffle(y) # shuffle the sequence\n", 91 | " \n", 92 | " # calculate and store Gini\n", 93 | " gini = qe.gini_coefficient(y)\n", 94 | " gini_yr.append(gini)\n", 95 | " \n", 96 | " results[var] = gini_yr\n", 97 | "\n", 98 | "# Convert to DataFrame\n", 99 | "results = pd.DataFrame(results, index=years)\n", 100 | "results.to_csv(\"usa-gini-nwealth-tincome-lincome.csv\", index_label='year')" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": null, 106 | "id": "d59e876b-2f77-4fa7-b79a-8e455ad82d43", 107 | "metadata": {}, 108 | "outputs": [], 109 | "source": [] 110 | } 111 | ], 112 | "metadata": { 113 | "kernelspec": { 114 | "display_name": "Python 3 (ipykernel)", 115 | "language": "python", 116 | "name": "python3" 117 | }, 118 | "language_info": { 119 | "codemirror_mode": { 120 | "name": "ipython", 121 | "version": 3 122 | }, 123 | "file_extension": ".py", 124 | "mimetype": "text/x-python", 125 | "name": "python", 126 | "nbconvert_exporter": "python", 127 | "pygments_lexer": "ipython3", 128 | "version": "3.10.12" 129 | } 130 | }, 131 | "nbformat": 4, 132 | "nbformat_minor": 5 133 | } 134 | -------------------------------------------------------------------------------- /lectures/_static/lecture_specific/networks/figures.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "4f255add-dde2-4452-a242-a2894610a0e8", 6 | "metadata": {}, 7 | "source": [ 8 | "# Figures for Networks" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 1, 14 | "id": "59d90e63-793c-41b0-8e52-3fce04b007e9", 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stdout", 19 | "output_type": "stream", 20 | "text": [ 21 | "Requirement already satisfied: graphviz in /Users/mmcky/anaconda3/envs/quantecon/lib/python3.11/site-packages (0.20.3)\n" 22 | ] 23 | } 24 | ], 25 | "source": [ 26 | "!pip install graphviz" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 2, 32 | "id": "56974a99-d93c-4e03-b9b4-99a6259dfd9b", 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "from graphviz import Digraph" 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "id": "79c35279-6a32-43cf-b7ee-ef955a962080", 42 | "metadata": {}, 43 | "source": [ 44 | "## Markov chains" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 3, 50 | "id": "a6cd7b6c-55a6-4e7f-a464-76d3514ad6cc", 51 | "metadata": {}, 52 | "outputs": [ 53 | { 54 | "data": { 55 | "text/plain": [ 56 | "'mc.png'" 57 | ] 58 | }, 59 | "execution_count": 3, 60 | "metadata": {}, 61 | "output_type": "execute_result" 62 | } 63 | ], 64 | "source": [ 65 | "dot = Digraph(format='png')\n", 66 | "dot.attr(rankdir='LR')\n", 67 | "\n", 68 | "dot.node(\"ng\")\n", 69 | "dot.node(\"mr\")\n", 70 | "dot.node(\"sr\")\n", 71 | "\n", 72 | "dot.edge(\"ng\", \"ng\", label=\"0.971\")\n", 73 | "dot.edge(\"ng\", \"mr\", label=\"0.029\")\n", 74 | "dot.edge(\"mr\", \"ng\", label=\"0.145\")\n", 75 | "dot.edge(\"mr\", \"mr\", label=\"0.778\")\n", 76 | "dot.edge(\"mr\", \"sr\", label=\"0.077\")\n", 77 | "dot.edge(\"sr\", \"mr\", label=\"0.508\")\n", 78 | "dot.edge(\"sr\", \"sr\", label=\"0.492\")\n", 79 | "\n", 80 | "dot\n", 81 | "dot.render(filename='mc')" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "id": "53ffff16-f367-45f1-9e8f-0964cde5c8e9", 87 | "metadata": {}, 88 | "source": [ 89 | "## Poverty trap" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": 4, 95 | "id": "306687a2-a76d-43ce-afb2-83380cccf258", 96 | "metadata": {}, 97 | "outputs": [ 98 | { 99 | "data": { 100 | "text/plain": [ 101 | "'poverty_trap_1.png'" 102 | ] 103 | }, 104 | "execution_count": 4, 105 | "metadata": {}, 106 | "output_type": "execute_result" 107 | } 108 | ], 109 | "source": [ 110 | "dot = Digraph(format='png',engine = \"neato\")\n", 111 | "dot.attr(rankdir='LR')\n", 112 | "dot.node(\"poor\", pos='0,0!')\n", 113 | "dot.node(\"middle class\", pos='2,1!')\n", 114 | "dot.node(\"rich\", pos='4,0!')\n", 115 | "\n", 116 | "dot.edge(\"poor\", \"poor\")\n", 117 | "dot.edge(\"poor\", \"middle class\")\n", 118 | "dot.edge(\"middle class\", \"poor\")\n", 119 | "dot.edge(\"middle class\", \"middle class\")\n", 120 | "dot.edge(\"middle class\", \"rich\")\n", 121 | "dot.edge(\"rich\", \"poor\")\n", 122 | "dot.edge(\"rich\", \"middle class\")\n", 123 | "dot.edge(\"rich\", \"rich\")\n", 124 | "\n", 125 | "dot\n", 126 | "dot.render(filename='poverty_trap_1')" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": 5, 132 | "id": "599034ae-48c3-4fdb-bcbb-e13b054e7ff0", 133 | "metadata": {}, 134 | "outputs": [ 135 | { 136 | "data": { 137 | "text/plain": [ 138 | "'poverty_trap_2.png'" 139 | ] 140 | }, 141 | "execution_count": 5, 142 | "metadata": {}, 143 | "output_type": "execute_result" 144 | } 145 | ], 146 | "source": [ 147 | "dot = Digraph(format='png',engine=\"neato\")\n", 148 | "dot.attr(rankdir='LR')\n", 149 | "dot.node(\"poor\", pos='0,0!')\n", 150 | "dot.node(\"middle class\", pos='2,1!')\n", 151 | "dot.node(\"rich\", pos='4,0!')\n", 152 | "\n", 153 | "dot.edge(\"poor\", \"poor\")\n", 154 | "dot.edge(\"middle class\", \"poor\")\n", 155 | "dot.edge(\"middle class\", \"middle class\")\n", 156 | "dot.edge(\"middle class\", \"rich\")\n", 157 | "dot.edge(\"rich\", \"poor\")\n", 158 | "dot.edge(\"rich\", \"middle class\")\n", 159 | "dot.edge(\"rich\", \"rich\")\n", 160 | "\n", 161 | "dot\n", 162 | "dot.render(filename='poverty_trap_2')" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "id": "78888c53-c5f2-4766-9943-321cc91cb9af", 168 | "metadata": {}, 169 | "source": [ 170 | "## Weighted directed graph" 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": 6, 176 | "id": "3aa21b51-3b55-4d31-ad0c-880e896b7a48", 177 | "metadata": {}, 178 | "outputs": [ 179 | { 180 | "data": { 181 | "text/plain": [ 182 | "'weighted.png'" 183 | ] 184 | }, 185 | "execution_count": 6, 186 | "metadata": {}, 187 | "output_type": "execute_result" 188 | } 189 | ], 190 | "source": [ 191 | "dot = Digraph(format='png')\n", 192 | "\n", 193 | "dot.attr(rankdir='LR')\n", 194 | "dot.node(\"poor\")\n", 195 | "dot.node(\"middle class\")\n", 196 | "dot.node(\"rich\")\n", 197 | "\n", 198 | "dot.edge(\"poor\", \"poor\", label='0.9')\n", 199 | "dot.edge(\"poor\", \"middle class\", label='0.1')\n", 200 | "dot.edge(\"middle class\", \"poor\", label='0.4')\n", 201 | "dot.edge(\"middle class\", \"middle class\", label='0.4')\n", 202 | "dot.edge(\"middle class\", \"rich\", label='0.2')\n", 203 | "dot.edge(\"rich\", \"poor\", label='0.1')\n", 204 | "dot.edge(\"rich\", \"middle class\", label='0.1')\n", 205 | "dot.edge(\"rich\", \"rich\", label='0.8')\n", 206 | "\n", 207 | "dot\n", 208 | "dot.render(filename='weighted')" 209 | ] 210 | }, 211 | { 212 | "cell_type": "markdown", 213 | "id": "e51acec4-a10b-4b9b-a757-835acff0d965", 214 | "metadata": {}, 215 | "source": [ 216 | "## Properties" 217 | ] 218 | }, 219 | { 220 | "cell_type": "code", 221 | "execution_count": 7, 222 | "id": "a88964ae-0fed-4e07-b075-1db97fbfb5f3", 223 | "metadata": {}, 224 | "outputs": [ 225 | { 226 | "data": { 227 | "text/plain": [ 228 | "'properties.png'" 229 | ] 230 | }, 231 | "execution_count": 7, 232 | "metadata": {}, 233 | "output_type": "execute_result" 234 | } 235 | ], 236 | "source": [ 237 | "dot = Digraph(format='png')\n", 238 | "\n", 239 | "dot.attr(rankdir='LR')\n", 240 | "dot.node('1')\n", 241 | "dot.node('2')\n", 242 | "dot.node('3')\n", 243 | "\n", 244 | "dot.edge('1', '2', label='0.7')\n", 245 | "dot.edge('1', '3', label='0.3')\n", 246 | "dot.edge('2', '1', label='1')\n", 247 | "dot.edge('3', '1', label='0.4')\n", 248 | "dot.edge('3', '2', label='0.6')\n", 249 | "\n", 250 | "dot\n", 251 | "dot.render(filename='properties')" 252 | ] 253 | }, 254 | { 255 | "cell_type": "code", 256 | "execution_count": null, 257 | "id": "8e6703ea-321a-47d8-b4c8-9f4013e699df", 258 | "metadata": {}, 259 | "outputs": [], 260 | "source": [] 261 | } 262 | ], 263 | "metadata": { 264 | "kernelspec": { 265 | "display_name": "Python 3 (ipykernel)", 266 | "language": "python", 267 | "name": "python3" 268 | }, 269 | "language_info": { 270 | "codemirror_mode": { 271 | "name": "ipython", 272 | "version": 3 273 | }, 274 | "file_extension": ".py", 275 | "mimetype": "text/x-python", 276 | "name": "python", 277 | "nbconvert_exporter": "python", 278 | "pygments_lexer": "ipython3", 279 | "version": "3.11.7" 280 | } 281 | }, 282 | "nbformat": 4, 283 | "nbformat_minor": 5 284 | } 285 | -------------------------------------------------------------------------------- /lectures/commod_price.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | text_representation: 4 | extension: .md 5 | format_name: myst 6 | kernelspec: 7 | display_name: Python 3 (ipykernel) 8 | language: python 9 | name: python3 10 | --- 11 | 12 | 13 | 14 | 15 | # Commodity Prices 16 | 17 | ## Outline 18 | 19 | For more than half of all countries around the globe, [commodities](https://en.wikipedia.org/wiki/Commodity) account for [the majority of total exports](https://unctad.org/publication/commodities-and-development-report-2019). 20 | 21 | Examples of commodities include copper, diamonds, iron ore, lithium, cotton 22 | and coffee beans. 23 | 24 | In this lecture we give an introduction to the theory of commodity prices. 25 | 26 | The lecture is quite advanced relative to other lectures in this series. 27 | 28 | We need to compute an equilibrium, and that equilibrium is described by a 29 | price function. 30 | 31 | We will solve an equation where the price function is the unknown. 32 | 33 | This is harder than solving an equation for an unknown number, or vector. 34 | 35 | The lecture will discuss one way to solve a [functional equation](https://en.wikipedia.org/wiki/Functional_equation) (an equation where the unknown object is a function). 36 | 37 | For this lecture we need the `yfinance` library. 38 | 39 | ```{code-cell} ipython3 40 | :tags: [hide-output] 41 | !pip install yfinance 42 | ``` 43 | 44 | We will use the following imports 45 | 46 | 47 | ```{code-cell} ipython3 48 | import numpy as np 49 | import yfinance as yf 50 | import matplotlib.pyplot as plt 51 | from scipy.interpolate import interp1d 52 | from scipy.optimize import brentq 53 | from scipy.stats import beta 54 | ``` 55 | 56 | ## Data 57 | 58 | The figure below shows the price of cotton in USD since the start of 2016. 59 | 60 | ```{code-cell} ipython3 61 | :tags: [hide-input, hide-output] 62 | 63 | s = yf.download('CT=F', '2016-1-1', '2023-4-1')['Close'] 64 | ``` 65 | 66 | ```{code-cell} ipython3 67 | :tags: [hide-input] 68 | 69 | fig, ax = plt.subplots() 70 | 71 | ax.plot(s, marker='o', alpha=0.5, ms=1) 72 | ax.set_ylabel('cotton price in USD', fontsize=12) 73 | ax.set_xlabel('date', fontsize=12) 74 | 75 | plt.show() 76 | ``` 77 | 78 | The figure shows surprisingly large movements in the price of cotton. 79 | 80 | What causes these movements? 81 | 82 | In general, prices depend on the choices and actions of 83 | 84 | 1. suppliers, 85 | 2. consumers, and 86 | 3. speculators. 87 | 88 | Our focus will be on the interaction between these parties. 89 | 90 | We will connect them together in a dynamic model of supply and demand, called 91 | the *competitive storage model*. 92 | 93 | This model was developed by 94 | {cite}`samuelson1971stochastic`, 95 | {cite}`wright1982economic`, {cite}`scheinkman1983simple`, 96 | {cite}`deaton1992on`, {cite}`deaton1996competitive`, and 97 | {cite}`chambers1996theory`. 98 | 99 | 100 | 101 | 102 | ## The competitive storage model 103 | 104 | In the competitive storage model, commodities are assets that 105 | 106 | 1. can be traded by speculators and 107 | 1. have intrinsic value to consumers. 108 | 109 | Total demand is the sum of consumer demand and demand by speculators. 110 | 111 | Supply is exogenous, depending on "harvests". 112 | 113 | ```{note} 114 | These days, goods such as basic computer chips and integrated circuits are 115 | often treated as commodities in financial markets, being highly standardized, 116 | and, for these kinds of commodities, the word "harvest" is not 117 | appropriate. 118 | 119 | Nonetheless, we maintain it for simplicity. 120 | ``` 121 | 122 | The equilibrium price is determined competitively. 123 | 124 | It is a function of the current state (which determines 125 | current harvests and predicts future harvests). 126 | 127 | 128 | 129 | ## The model 130 | 131 | Consider a market for a single commodity, whose price is given at $t$ by 132 | $p_t$. 133 | 134 | The harvest of the commodity at time $t$ is $Z_t$. 135 | 136 | We assume that the sequence $\{ Z_t \}_{t \geq 1}$ is IID with common density function $\phi$, where $\phi$ is nonnegative. 137 | 138 | Speculators can store the commodity between periods, with $I_t$ units 139 | purchased in the current period yielding $\alpha I_t$ units in the next. 140 | 141 | Here the parameter $\alpha \in (0,1)$ is a depreciation rate for the commodity. 142 | 143 | For simplicity, the risk free interest rate is taken to be 144 | zero, so expected profit on purchasing $I_t$ units is 145 | 146 | $$ 147 | \mathbb{E}_t \, p_{t+1} \cdot \alpha I_t - p_t I_t 148 | = (\alpha \mathbb{E}_t \, p_{t+1} - p_t) I_t 149 | $$ 150 | 151 | 152 | Here $\mathbb{E}_t \, p_{t+1}$ is the expectation of $p_{t+1}$ taken at time 153 | $t$. 154 | 155 | 156 | 157 | ## Equilibrium 158 | 159 | In this section we define the equilibrium and discuss how to compute it. 160 | 161 | ### Equilibrium conditions 162 | 163 | Speculators are assumed to be risk neutral, which means that they buy the 164 | commodity whenever expected profits are positive. 165 | 166 | As a consequence, if expected profits are positive, then the market is not in 167 | equilibrium. 168 | 169 | Hence, to be in equilibrium, prices must satisfy the "no-arbitrage" 170 | condition 171 | 172 | $$ 173 | \alpha \mathbb{E}_t \, p_{t+1} - p_t \leq 0 174 | $$ (eq:arbi) 175 | 176 | This means that if the expected price is lower than the current price, there is no room for arbitrage. 177 | 178 | Profit maximization gives the additional condition 179 | 180 | $$ 181 | \alpha \mathbb{E}_t \, p_{t+1} - p_t < 0 \text{ implies } I_t = 0 182 | $$ (eq:pmco) 183 | 184 | 185 | We also require that the market clears, with supply equaling demand in each period. 186 | 187 | We assume that consumers generate demand quantity $D(p)$ corresponding to 188 | price $p$. 189 | 190 | Let $P := D^{-1}$ be the inverse demand function. 191 | 192 | 193 | Regarding quantities, 194 | 195 | * supply is the sum of carryover by speculators and the current harvest, and 196 | * demand is the sum of purchases by consumers and purchases by speculators. 197 | 198 | Mathematically, 199 | 200 | * supply is given by $X_t = \alpha I_{t-1} + Z_t$, which takes values in $S := \mathbb R_+$, while 201 | * demand $ = D(p_t) + I_t$ 202 | 203 | Thus, the market equilibrium condition is 204 | 205 | $$ 206 | \alpha I_{t-1} + Z_t = D(p_t) + I_t 207 | $$ (eq:mkeq) 208 | 209 | 210 | The initial condition $X_0 \in S$ is treated as given. 211 | 212 | 213 | 214 | 215 | ### An equilibrium function 216 | 217 | How can we find an equilibrium? 218 | 219 | Our path of attack will be to seek a system of prices that depend only on the 220 | current state. 221 | 222 | (Our solution method involves using an [ansatz](https://en.wikipedia.org/wiki/Ansatz), which is an educated guess --- in this case for the price function.) 223 | 224 | In other words, we take a function $p$ on $S$ and set $p_t = p(X_t)$ for every $t$. 225 | 226 | Prices and quantities then follow 227 | 228 | $$ 229 | p_t = p(X_t), \quad I_t = X_t - D(p_t), \quad X_{t+1} = \alpha I_t + Z_{t+1} 230 | $$ (eq:eosy) 231 | 232 | 233 | We choose $p$ so that these prices and quantities satisfy the equilibrium 234 | conditions above. 235 | 236 | More precisely, we seek a $p$ such that [](eq:arbi) and [](eq:pmco) hold for 237 | the corresponding system [](eq:eosy). 238 | 239 | 240 | $$ 241 | p^*(x) = \max 242 | \left\{ 243 | \alpha \int_0^\infty p^*(\alpha I(x) + z) \phi(z)dz, P(x) 244 | \right\} 245 | \qquad (x \in S) 246 | $$ (eq:dopf) 247 | 248 | where 249 | 250 | $$ 251 | I(x) := x - D(p^*(x)) 252 | \qquad (x \in S) 253 | $$ (eq:einvf) 254 | 255 | It turns out that such a $p^*$ will suffice, in the sense that [](eq:arbi) 256 | and [](eq:pmco) hold for the corresponding system [](eq:eosy). 257 | 258 | To see this, observe first that 259 | 260 | $$ 261 | \mathbb{E}_t \, p_{t+1} 262 | = \mathbb{E}_t \, p^*(X_{t+1}) 263 | = \mathbb{E}_t \, p^*(\alpha I(X_t) + Z_{t+1}) 264 | = \int_0^\infty p^*(\alpha I(X_t) + z) \phi(z)dz 265 | $$ 266 | 267 | Thus [](eq:arbi) requires that 268 | 269 | $$ 270 | \alpha \int_0^\infty p^*(\alpha I(X_t) + z) \phi(z)dz \leq p^*(X_t) 271 | $$ 272 | 273 | This inequality is immediate from [](eq:dopf). 274 | 275 | Second, regarding [](eq:pmco), suppose that 276 | 277 | $$ 278 | \alpha \int_0^\infty p^*(\alpha I(X_t) + z) \phi(z)dz < p^*(X_t) 279 | $$ 280 | 281 | Then by [](eq:dopf) we have $p^*(X_t) = P(X_t)$ 282 | 283 | But then $D(p^*(X_t)) = X_t$ and $I_t = I(X_t) = 0$. 284 | 285 | As a consequence, both [](eq:arbi) and [](eq:pmco) hold. 286 | 287 | We have found an equilibrium, which verifies the ansatz. 288 | 289 | 290 | ### Computing the equilibrium 291 | 292 | We now know that an equilibrium can be obtained by finding a function $p^*$ 293 | that satisfies [](eq:dopf). 294 | 295 | It can be shown that, under mild conditions there is exactly one function on 296 | $S$ satisfying [](eq:dopf). 297 | 298 | Moreover, we can compute this function using successive approximation. 299 | 300 | This means that we start with a guess of the function and then update it using 301 | [](eq:dopf). 302 | 303 | This generates a sequence of functions $p_1, p_2, \ldots$ 304 | 305 | We continue until this process converges, in the sense that $p_k$ and 306 | $p_{k+1}$ are very close together. 307 | 308 | Then we take the final $p_k$ that we computed as our approximation of $p^*$. 309 | 310 | To implement our update step, it is helpful if we put [](eq:dopf) and 311 | [](eq:einvf) together. 312 | 313 | This leads us to the update rule 314 | 315 | $$ 316 | p_{k+1}(x) = \max 317 | \left\{ 318 | \alpha \int_0^\infty p_k(\alpha ( x - D(p_{k+1}(x))) + z) \phi(z)dz, P(x) 319 | \right\} 320 | $$ (eq:dopf2) 321 | 322 | In other words, we take $p_k$ as given and, at each $x$, solve for $q$ in 323 | 324 | $$ 325 | q = \max 326 | \left\{ 327 | \alpha \int_0^\infty p_k(\alpha ( x - D(q)) + z) \phi(z)dz, P(x) 328 | \right\} 329 | $$ (eq:dopf3) 330 | 331 | Actually we can't do this at every $x$, so instead we do it on a grid of 332 | points $x_1, \ldots, x_n$. 333 | 334 | Then we get the corresponding values $q_1, \ldots, q_n$. 335 | 336 | Then we compute $p_{k+1}$ as the linear interpolation of 337 | the values $q_1, \ldots, q_n$ over the grid $x_1, \ldots, x_n$. 338 | 339 | Then we repeat, seeking convergence. 340 | 341 | 342 | ## Code 343 | 344 | The code below implements this iterative process, starting from $p_0 = P$. 345 | 346 | The distribution $\phi$ is set to a shifted Beta distribution (although many 347 | other choices are possible). 348 | 349 | The integral in [](eq:dopf3) is computed via {ref}`Monte Carlo `. 350 | 351 | 352 | ```{code-cell} ipython3 353 | α, a, c = 0.8, 1.0, 2.0 354 | beta_a, beta_b = 5, 5 355 | mc_draw_size = 250 356 | gridsize = 150 357 | grid_max = 35 358 | grid = np.linspace(a, grid_max, gridsize) 359 | 360 | beta_dist = beta(5, 5) 361 | Z = a + beta_dist.rvs(mc_draw_size) * c # Shock observations 362 | D = P = lambda x: 1.0 / x 363 | tol = 1e-4 364 | 365 | 366 | def T(p_array): 367 | 368 | new_p = np.empty_like(p_array) 369 | 370 | # Interpolate to obtain p as a function. 371 | p = interp1d(grid, 372 | p_array, 373 | fill_value=(p_array[0], p_array[-1]), 374 | bounds_error=False) 375 | 376 | # Update 377 | for i, x in enumerate(grid): 378 | 379 | h = lambda q: q - max(α * np.mean(p(α * (x - D(q)) + Z)), P(x)) 380 | new_p[i] = brentq(h, 1e-8, 100) 381 | 382 | return new_p 383 | 384 | 385 | fig, ax = plt.subplots() 386 | 387 | price = P(grid) 388 | ax.plot(grid, price, alpha=0.5, lw=1, label="inverse demand curve") 389 | error = tol + 1 390 | while error > tol: 391 | new_price = T(price) 392 | error = max(np.abs(new_price - price)) 393 | price = new_price 394 | 395 | ax.plot(grid, price, 'k-', alpha=0.5, lw=2, label=r'$p^*$') 396 | ax.legend() 397 | ax.set_xlabel('$x$') 398 | ax.set_ylabel("prices") 399 | 400 | plt.show() 401 | ``` 402 | 403 | The figure above shows the inverse demand curve $P$, which is also $p_0$, as 404 | well as our approximation of $p^*$. 405 | 406 | Once we have an approximation of $p^*$, we can simulate a time series of 407 | prices. 408 | 409 | 410 | ```{code-cell} ipython3 411 | # Turn the price array into a price function 412 | p_star = interp1d(grid, 413 | price, 414 | fill_value=(price[0], price[-1]), 415 | bounds_error=False) 416 | 417 | def carry_over(x): 418 | return α * (x - D(p_star(x))) 419 | 420 | def generate_cp_ts(init=1, n=50): 421 | X = np.empty(n) 422 | X[0] = init 423 | for t in range(n-1): 424 | Z = a + c * beta_dist.rvs() 425 | X[t+1] = carry_over(X[t]) + Z 426 | return p_star(X) 427 | 428 | fig, ax = plt.subplots() 429 | ax.plot(generate_cp_ts(), label="price") 430 | ax.set_xlabel("time") 431 | ax.legend() 432 | plt.show() 433 | ``` 434 | -------------------------------------------------------------------------------- /lectures/supply_demand_heterogeneity.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | text_representation: 4 | extension: .md 5 | format_name: myst 6 | format_version: 0.13 7 | jupytext_version: 1.14.5 8 | kernelspec: 9 | display_name: Python 3 (ipykernel) 10 | language: python 11 | name: python3 12 | --- 13 | 14 | (supply_demand_heterogeneity)= 15 | # Market Equilibrium with Heterogeneity 16 | 17 | ## Overview 18 | 19 | In the {doc}`previous lecture 20 | `, we studied competitive equilibria in an economy with many goods. 21 | 22 | While the results of the study were informative, we used a strong simplifying assumption: all of the agents in the economy are identical. 23 | 24 | In the real world, households, firms and other economic agents differ from one another along many dimensions. 25 | 26 | In this lecture, we introduce heterogeneity across consumers by allowing their preferences and endowments to differ. 27 | 28 | We will examine competitive equilibrium in this setting. 29 | 30 | We will also show how a "representative consumer" can be constructed. 31 | 32 | Here are some imports: 33 | 34 | ```{code-cell} ipython3 35 | import numpy as np 36 | from scipy.linalg import inv 37 | ``` 38 | 39 | ## An simple example 40 | 41 | Let's study a simple example of **pure exchange** economy without production. 42 | 43 | There are two consumers who differ in their endowment vectors $e_i$ and their bliss-point vectors $b_i$ for $i=1,2$. 44 | 45 | The total endowment is $e_1 + e_2$. 46 | 47 | A competitive equilibrium requires that 48 | 49 | $$ 50 | c_1 + c_2 = e_1 + e_2 51 | $$ 52 | 53 | Assume the demand curves 54 | 55 | $$ 56 | c_i = (\Pi^\top \Pi )^{-1}(\Pi^\top b_i - \mu_i p ) 57 | $$ 58 | 59 | Competitive equilibrium then requires that 60 | 61 | $$ 62 | e_1 + e_2 = 63 | (\Pi^\top \Pi)^{-1}(\Pi^\top (b_1 + b_2) - (\mu_1 + \mu_2) p ) 64 | $$ 65 | 66 | which, after a line or two of linear algebra, implies that 67 | 68 | $$ 69 | (\mu_1 + \mu_2) p = \Pi^\top(b_1+ b_2) - \Pi^\top \Pi (e_1 + e_2) 70 | $$ (eq:old6) 71 | 72 | We can normalize prices by setting $\mu_1 + \mu_2 =1$ and then solving 73 | 74 | $$ 75 | \mu_i(p,e) = \frac{p^\top (\Pi^{-1} b_i - e_i)}{p^\top (\Pi^\top \Pi )^{-1} p} 76 | $$ (eq:old7) 77 | 78 | for $\mu_i, i = 1,2$. 79 | 80 | ```{exercise-start} 81 | :label: sdh_ex1 82 | ``` 83 | 84 | Show that, up to normalization by a positive scalar, the same competitive equilibrium price vector that you computed in the preceding two-consumer economy would prevail in a single-consumer economy in which a single **representative consumer** has utility function 85 | 86 | $$ 87 | -.5 (\Pi c -b) ^\top (\Pi c -b ) 88 | $$ 89 | 90 | and endowment vector $e$, where 91 | 92 | $$ 93 | b = b_1 + b_2 94 | $$ 95 | 96 | and 97 | 98 | $$ 99 | e = e_1 + e_2 . 100 | $$ 101 | 102 | ```{exercise-end} 103 | ``` 104 | 105 | ## Pure exchange economy 106 | 107 | Let's further explore a pure exchange economy with $n$ goods and $m$ people. 108 | 109 | ### Competitive equilibrium 110 | 111 | We'll compute a competitive equilibrium. 112 | 113 | To compute a competitive equilibrium of a pure exchange economy, we use the fact that 114 | 115 | - Relative prices in a competitive equilibrium are the same as those in a special single person or representative consumer economy with preference $\Pi$ and $b=\sum_i b_i$, and endowment $e = \sum_i e_{i}$. 116 | 117 | We can use the following steps to compute a competitive equilibrium: 118 | 119 | - First we solve the single representative consumer economy by normalizing $\mu = 1$. Then, we renormalize the price vector by using the first consumption good as a numeraire. 120 | 121 | - Next we use the competitive equilibrium prices to compute each consumer's marginal utility of wealth: 122 | 123 | $$ 124 | \mu_{i}=\frac{-W_{i}+p^{\top}\left(\Pi^{-1}b_{i}-e_{i}\right)}{p^{\top}(\Pi^{\top}\Pi)^{-1}p}$$ 125 | 126 | - Finally we compute a competitive equilibrium allocation by using the demand curves: 127 | 128 | $$ 129 | c_{i}=\Pi^{-1}b_{i}-(\Pi^{\top}\Pi)^{-1}\mu_{i}p 130 | $$ 131 | 132 | 133 | ### Designing some Python code 134 | 135 | 136 | Below we shall construct a Python class with the following attributes: 137 | 138 | * **Preferences** in the form of 139 | 140 | * an $n \times n$ positive definite matrix $\Pi$ 141 | * an $n \times 1$ vector of bliss points $b$ 142 | 143 | * **Endowments** in the form of 144 | 145 | * an $n \times 1$ vector $e$ 146 | * a scalar "wealth" $W$ with default value $0$ 147 | 148 | 149 | The class will include a test to make sure that $b \gg \Pi e $ and raise an exception if it is violated 150 | (at some threshold level we'd have to specify). 151 | 152 | * **A Person** in the form of a pair that consists of 153 | 154 | * **Preferences** and **Endowments** 155 | 156 | * **A Pure Exchange Economy** will consist of 157 | 158 | * a collection of $m$ **persons** 159 | 160 | * $m=1$ for our single-agent economy 161 | * $m=2$ for our illustrations of a pure exchange economy 162 | 163 | * an equilibrium price vector $p$ (normalized somehow) 164 | * an equilibrium allocation $c_1, c_2, \ldots, c_m$ -- a collection of $m$ vectors of dimension $n \times 1$ 165 | 166 | Now let's proceed to code. 167 | 168 | ```{code-cell} ipython3 169 | class ExchangeEconomy: 170 | def __init__(self, 171 | Π, 172 | bs, 173 | es, 174 | Ws=None, 175 | thres=1.5): 176 | """ 177 | Set up the environment for an exchange economy 178 | 179 | Args: 180 | Π (np.array): shared matrix of substitution 181 | bs (list): all consumers' bliss points 182 | es (list): all consumers' endowments 183 | Ws (list): all consumers' wealth 184 | thres (float): a threshold set to test b >> Pi e violated 185 | """ 186 | n, m = Π.shape[0], len(bs) 187 | 188 | # check non-satiation 189 | for b, e in zip(bs, es): 190 | if np.min(b / np.max(Π @ e)) <= thres: 191 | raise Exception('set bliss points further away') 192 | 193 | if Ws == None: 194 | Ws = np.zeros(m) 195 | else: 196 | if sum(Ws) != 0: 197 | raise Exception('invalid wealth distribution') 198 | 199 | self.Π, self.bs, self.es, self.Ws, self.n, self.m = Π, bs, es, Ws, n, m 200 | 201 | def competitive_equilibrium(self): 202 | """ 203 | Compute the competitive equilibrium prices and allocation 204 | """ 205 | Π, bs, es, Ws = self.Π, self.bs, self.es, self.Ws 206 | n, m = self.n, self.m 207 | slope_dc = inv(Π.T @ Π) 208 | Π_inv = inv(Π) 209 | 210 | # aggregate 211 | b = sum(bs) 212 | e = sum(es) 213 | 214 | # compute price vector with mu=1 and renormalize 215 | p = Π.T @ b - Π.T @ Π @ e 216 | p = p / p[0] 217 | 218 | # compute marginal utility of wealth 219 | μ_s = [] 220 | c_s = [] 221 | A = p.T @ slope_dc @ p 222 | 223 | for i in range(m): 224 | μ_i = (-Ws[i] + p.T @ (Π_inv @ bs[i] - es[i])) / A 225 | c_i = Π_inv @ bs[i] - μ_i * slope_dc @ p 226 | μ_s.append(μ_i) 227 | c_s.append(c_i) 228 | 229 | for c_i in c_s: 230 | if any(c_i < 0): 231 | print('allocation: ', c_s) 232 | raise Exception('negative allocation: equilibrium does not exist') 233 | 234 | return p, c_s, μ_s 235 | ``` 236 | 237 | ## Implementation 238 | 239 | Next we use the class ``ExchangeEconomy`` defined above to study 240 | 241 | * a two-person economy without production, 242 | * a dynamic economy, and 243 | * an economy with risk and arrow securities. 244 | 245 | ### Two-person economy without production 246 | 247 | Here we study how competitive equilibrium $p, c_1, c_2$ respond to different $b_i$ and $e_i$, $i \in \{1, 2\}$. 248 | 249 | ```{code-cell} ipython3 250 | Π = np.array([[1, 0], 251 | [0, 1]]) 252 | 253 | bs = [np.array([5, 5]), # first consumer's bliss points 254 | np.array([5, 5])] # second consumer's bliss points 255 | 256 | es = [np.array([0, 2]), # first consumer's endowment 257 | np.array([2, 0])] # second consumer's endowment 258 | 259 | EE = ExchangeEconomy(Π, bs, es) 260 | p, c_s, μ_s = EE.competitive_equilibrium() 261 | 262 | print('Competitive equilibrium price vector:', p) 263 | print('Competitive equilibrium allocation:', c_s) 264 | ``` 265 | 266 | What happens if the first consumer likes the first good more and the second consumer likes the second good more? 267 | 268 | ```{code-cell} ipython3 269 | EE.bs = [np.array([6, 5]), # first consumer's bliss points 270 | np.array([5, 6])] # second consumer's bliss points 271 | 272 | p, c_s, μ_s = EE.competitive_equilibrium() 273 | 274 | print('Competitive equilibrium price vector:', p) 275 | print('Competitive equilibrium allocation:', c_s) 276 | ``` 277 | 278 | Let the first consumer be poorer. 279 | 280 | ```{code-cell} ipython3 281 | EE.es = [np.array([0.5, 0.5]), # first consumer's endowment 282 | np.array([1, 1])] # second consumer's endowment 283 | 284 | p, c_s, μ_s = EE.competitive_equilibrium() 285 | 286 | print('Competitive equilibrium price vector:', p) 287 | print('Competitive equilibrium allocation:', c_s) 288 | ``` 289 | 290 | Now let's construct an autarky (i.e., no-trade) equilibrium. 291 | 292 | ```{code-cell} ipython3 293 | EE.bs = [np.array([4, 6]), # first consumer's bliss points 294 | np.array([6, 4])] # second consumer's bliss points 295 | 296 | EE.es = [np.array([0, 2]), # first consumer's endowment 297 | np.array([2, 0])] # second consumer's endowment 298 | 299 | p, c_s, μ_s = EE.competitive_equilibrium() 300 | 301 | print('Competitive equilibrium price vector:', p) 302 | print('Competitive equilibrium allocation:', c_s) 303 | ``` 304 | 305 | Now let's redistribute endowments before trade. 306 | 307 | ```{code-cell} ipython3 308 | bs = [np.array([5, 5]), # first consumer's bliss points 309 | np.array([5, 5])] # second consumer's bliss points 310 | 311 | es = [np.array([1, 1]), # first consumer's endowment 312 | np.array([1, 1])] # second consumer's endowment 313 | 314 | Ws = [0.5, -0.5] 315 | EE_new = ExchangeEconomy(Π, bs, es, Ws) 316 | p, c_s, μ_s = EE_new.competitive_equilibrium() 317 | 318 | print('Competitive equilibrium price vector:', p) 319 | print('Competitive equilibrium allocation:', c_s) 320 | ``` 321 | 322 | ### A dynamic economy 323 | 324 | Now let's use the tricks described above to study a dynamic economy, one with two periods. 325 | 326 | ```{code-cell} ipython3 327 | beta = 0.95 328 | 329 | Π = np.array([[1, 0], 330 | [0, np.sqrt(beta)]]) 331 | 332 | bs = [np.array([5, np.sqrt(beta) * 5])] 333 | 334 | es = [np.array([1, 1])] 335 | 336 | EE_DE = ExchangeEconomy(Π, bs, es) 337 | p, c_s, μ_s = EE_DE.competitive_equilibrium() 338 | 339 | print('Competitive equilibrium price vector:', p) 340 | print('Competitive equilibrium allocation:', c_s) 341 | ``` 342 | 343 | ### Risk economy with arrow securities 344 | 345 | We use the tricks described above to interpret $c_1, c_2$ as "Arrow securities" that are state-contingent claims to consumption goods. 346 | 347 | ```{code-cell} ipython3 348 | prob = 0.7 349 | 350 | Π = np.array([[np.sqrt(prob), 0], 351 | [0, np.sqrt(1 - prob)]]) 352 | 353 | bs = [np.array([np.sqrt(prob) * 5, np.sqrt(1 - prob) * 5]), 354 | np.array([np.sqrt(prob) * 5, np.sqrt(1 - prob) * 5])] 355 | 356 | es = [np.array([1, 0]), 357 | np.array([0, 1])] 358 | 359 | EE_AS = ExchangeEconomy(Π, bs, es) 360 | p, c_s, μ_s = EE_AS.competitive_equilibrium() 361 | 362 | print('Competitive equilibrium price vector:', p) 363 | print('Competitive equilibrium allocation:', c_s) 364 | ``` 365 | 366 | ## Deducing a representative consumer 367 | 368 | In the class of multiple consumer economies that we are studying here, it turns out that there 369 | exists a single **representative consumer** whose preferences and endowments can be deduced from lists of preferences and endowments for separate individual consumers. 370 | 371 | Consider a multiple consumer economy with initial distribution of wealth $W_i$ satisfying $\sum_i W_{i}=0$ 372 | 373 | We allow an initial redistribution of wealth. 374 | 375 | We have the following objects 376 | 377 | 378 | - The demand curve: 379 | 380 | $$ 381 | c_{i}=\Pi^{-1}b_{i}-(\Pi^{\top}\Pi)^{-1}\mu_{i}p 382 | $$ 383 | 384 | - The marginal utility of wealth: 385 | 386 | $$ 387 | \mu_{i}=\frac{-W_{i}+p^{\top}\left(\Pi^{-1}b_{i}-e_{i}\right)}{p^{\top}(\Pi^{\top}\Pi)^{-1}p} 388 | $$ 389 | 390 | - Market clearing: 391 | 392 | $$ 393 | \sum c_{i}=\sum e_{i} 394 | $$ 395 | 396 | Denote aggregate consumption $\sum_i c_{i}=c$ and $\sum_i \mu_i = \mu$. 397 | 398 | Market clearing requires 399 | 400 | $$ 401 | \Pi^{-1}\left(\sum_{i}b_{i}\right)-(\Pi^{\top}\Pi)^{-1}p\left(\sum_{i}\mu_{i}\right)=\sum_{i}e_{i} 402 | $$ 403 | which, after a few steps, leads to 404 | 405 | $$ 406 | p=\mu^{-1}\left(\Pi^{\top}b-\Pi^{\top}\Pi e\right) 407 | $$ 408 | 409 | where 410 | 411 | $$ 412 | \mu = \sum_i\mu_{i}=\frac{0 + p^{\top}\left(\Pi^{-1}b-e\right)}{p^{\top}(\Pi^{\top}\Pi)^{-1}p}. 413 | $$ 414 | 415 | Now consider the representative consumer economy specified above. 416 | 417 | Denote the marginal utility of wealth of the representative consumer by $\tilde{\mu}$. 418 | 419 | The demand function is 420 | 421 | $$ 422 | c=\Pi^{-1}b-(\Pi^{\top}\Pi)^{-1}\tilde{\mu} p 423 | $$ 424 | 425 | Substituting this into the budget constraint gives 426 | 427 | $$ 428 | \tilde{\mu}=\frac{p^{\top}\left(\Pi^{-1}b-e\right)}{p^{\top}(\Pi^{\top}\Pi)^{-1}p} 429 | $$ 430 | 431 | In an equilibrium $c=e$, so 432 | 433 | $$ 434 | p=\tilde{\mu}^{-1}(\Pi^{\top}b-\Pi^{\top}\Pi e) 435 | $$ 436 | 437 | Thus, we have verified that, up to the choice of a numeraire in which to express absolute prices, the price 438 | vector in our representative consumer economy is the same as that in an underlying economy with multiple consumers. 439 | -------------------------------------------------------------------------------- /lectures/pv.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | text_representation: 4 | extension: .md 5 | format_name: myst 6 | format_version: 0.13 7 | jupytext_version: 1.14.5 8 | kernelspec: 9 | display_name: Python 3 (ipykernel) 10 | language: python 11 | name: python3 12 | --- 13 | 14 | # Present Values 15 | 16 | ## Overview 17 | 18 | This lecture describes the **present value model** that is a starting point 19 | of much asset pricing theory. 20 | 21 | Asset pricing theory is a component of theories about many economic decisions including 22 | 23 | * consumption 24 | * labor supply 25 | * education choice 26 | * demand for money 27 | 28 | In asset pricing theory, and in economic dynamics more generally, a basic topic is the relationship 29 | among different **time series**. 30 | 31 | A **time series** is a **sequence** indexed by time. 32 | 33 | In this lecture, we'll represent a sequence as a vector. 34 | 35 | So our analysis will typically boil down to studying relationships among vectors. 36 | 37 | Our main tools in this lecture will be 38 | 39 | * matrix multiplication, and 40 | * matrix inversion. 41 | 42 | We'll use the calculations described here in subsequent lectures, including {doc}`consumption smoothing `, {doc}`equalizing difference model `, and 43 | {doc}`monetarist theory of price levels `. 44 | 45 | Let's dive in. 46 | 47 | ## Analysis 48 | 49 | 50 | 51 | Let 52 | 53 | * $\{d_t\}_{t=0}^T $ be a sequence of dividends or "payouts" 54 | * $\{p_t\}_{t=0}^T $ be a sequence of prices of a claim on the continuation of 55 | the asset's payout stream from date $t$ on, namely, $\{d_s\}_{s=t}^T $ 56 | * $ \delta \in (0,1) $ be a one-period "discount factor" 57 | * $p_{T+1}^*$ be a terminal price of the asset at time $T+1$ 58 | 59 | We assume that the dividend stream $\{d_t\}_{t=0}^T $ and the terminal price 60 | $p_{T+1}^*$ are both exogenous. 61 | 62 | This means that they are determined outside the model. 63 | 64 | Assume the sequence of asset pricing equations 65 | 66 | $$ 67 | p_t = d_t + \delta p_{t+1}, \quad t = 0, 1, \ldots , T 68 | $$ (eq:Euler1) 69 | 70 | We say equation**s**, plural, because there are $T+1$ equations, one for each $t =0, 1, \ldots, T$. 71 | 72 | 73 | Equations {eq}`eq:Euler1` assert that price paid to purchase the asset at time $t$ equals the payout $d_t$ plus the price at time $t+1$ multiplied by a time discount factor $\delta$. 74 | 75 | Discounting tomorrow's price by multiplying it by $\delta$ accounts for the "value of waiting one period". 76 | 77 | We want to solve the system of $T+1$ equations {eq}`eq:Euler1` for the asset price sequence $\{p_t\}_{t=0}^T $ as a function of the dividend sequence $\{d_t\}_{t=0}^T $ and the exogenous terminal 78 | price $p_{T+1}^*$. 79 | 80 | A system of equations like {eq}`eq:Euler1` is an example of a linear **difference equation**. 81 | 82 | There are powerful mathematical methods available for solving such systems and they are well worth 83 | studying in their own right, being the foundation for the analysis of many interesting economic models. 84 | 85 | For an example, see {doc}`Samuelson multiplier-accelerator ` 86 | 87 | In this lecture, we'll solve system {eq}`eq:Euler1` using matrix multiplication and matrix inversion, basic tools from linear algebra introduced in {doc}`linear equations and matrix algebra `. 88 | 89 | We will use the following imports 90 | 91 | +++ 92 | 93 | ```{code-cell} ipython3 94 | import numpy as np 95 | import matplotlib.pyplot as plt 96 | ``` 97 | 98 | +++ 99 | 100 | ## Representing sequences as vectors 101 | 102 | The equations in system {eq}`eq:Euler1` can be arranged as follows: 103 | 104 | $$ 105 | \begin{aligned} 106 | p_0 & = d_0 + \delta p_1 \\ 107 | p_1 & = d_1 + \delta p_2 \\ 108 | \vdots \\ 109 | p_{T-1} & = d_{T-1} + \delta p_T \\ 110 | p_T & = d_T + \delta p^*_{T+1} 111 | \end{aligned} 112 | $$ (eq:Euler_stack) 113 | 114 | Write the system {eq}`eq:Euler_stack` of $T+1$ asset pricing equations as the single matrix equation 115 | 116 | $$ 117 | \begin{bmatrix} 1 & -\delta & 0 & 0 & \cdots & 0 & 0 \cr 118 | 0 & 1 & -\delta & 0 & \cdots & 0 & 0 \cr 119 | 0 & 0 & 1 & -\delta & \cdots & 0 & 0 \cr 120 | \vdots & \vdots & \vdots & \vdots & \vdots & 0 & 0 \cr 121 | 0 & 0 & 0 & 0 & \cdots & 1 & -\delta \cr 122 | 0 & 0 & 0 & 0 & \cdots & 0 & 1 \end{bmatrix} 123 | \begin{bmatrix} p_0 \cr p_1 \cr p_2 \cr \vdots \cr p_{T-1} \cr p_T 124 | \end{bmatrix} 125 | = \begin{bmatrix} 126 | d_0 \cr d_1 \cr d_2 \cr \vdots \cr d_{T-1} \cr d_T 127 | \end{bmatrix} 128 | + \begin{bmatrix} 129 | 0 \cr 0 \cr 0 \cr \vdots \cr 0 \cr \delta p_{T+1}^* 130 | \end{bmatrix} 131 | $$ (eq:pvpieq) 132 | 133 | +++ 134 | 135 | ```{exercise-start} 136 | :label: pv_ex_1 137 | ``` 138 | 139 | Carry out the matrix multiplication in [](eq:pvpieq) by hand and confirm that you 140 | recover the equations in [](eq:Euler_stack). 141 | 142 | ```{exercise-end} 143 | ``` 144 | 145 | In vector-matrix notation, we can write system {eq}`eq:pvpieq` as 146 | 147 | $$ 148 | A p = d + b 149 | $$ (eq:apdb) 150 | 151 | Here $A$ is the matrix on the left side of equation {eq}`eq:pvpieq`, while 152 | 153 | $$ 154 | p = 155 | \begin{bmatrix} 156 | p_0 \\ 157 | p_1 \\ 158 | \vdots \\ 159 | p_T 160 | \end{bmatrix}, 161 | \quad 162 | d = 163 | \begin{bmatrix} 164 | d_0 \\ 165 | d_1 \\ 166 | \vdots \\ 167 | d_T 168 | \end{bmatrix}, 169 | \quad \text{and} \quad 170 | b = 171 | \begin{bmatrix} 172 | 0 \\ 173 | 0 \\ 174 | \vdots \\ 175 | \delta p^*_{T+1} 176 | \end{bmatrix} 177 | $$ 178 | 179 | The solution for the vector of prices is 180 | 181 | $$ 182 | p = A^{-1}(d + b) 183 | $$ (eq:apdb_sol) 184 | 185 | 186 | For example, suppose that the dividend stream is 187 | 188 | $$ 189 | d_{t+1} = 1.05 d_t, \quad t = 0, 1, \ldots , T-1. 190 | $$ 191 | 192 | Let's write Python code to compute and plot the dividend stream. 193 | 194 | ```{code-cell} ipython3 195 | T = 6 196 | current_d = 1.0 197 | d = [] 198 | for t in range(T+1): 199 | d.append(current_d) 200 | current_d = current_d * 1.05 201 | 202 | fig, ax = plt.subplots() 203 | ax.plot(d, 'o', label='dividends') 204 | ax.legend() 205 | ax.set_xlabel('time') 206 | plt.show() 207 | ``` 208 | Now let's compute and plot the asset price. 209 | 210 | We set $\delta$ and $p_{T+1}^*$ to 211 | 212 | ```{code-cell} ipython3 213 | δ = 0.99 214 | p_star = 10.0 215 | ``` 216 | 217 | Let's build the matrix $A$ 218 | 219 | ```{code-cell} ipython3 220 | A = np.zeros((T+1, T+1)) 221 | for i in range(T+1): 222 | for j in range(T+1): 223 | if i == j: 224 | A[i, j] = 1 225 | if j < T: 226 | A[i, j+1] = -δ 227 | 228 | ``` 229 | 230 | Let's inspect $A$ 231 | 232 | ```{code-cell} ipython3 233 | A 234 | ``` 235 | 236 | Now let's solve for prices using {eq}`eq:apdb_sol`. 237 | 238 | ```{code-cell} ipython3 239 | b = np.zeros(T+1) 240 | b[-1] = δ * p_star 241 | p = np.linalg.solve(A, d + b) 242 | fig, ax = plt.subplots() 243 | ax.plot(p, 'o', label='asset price') 244 | ax.legend() 245 | ax.set_xlabel('time') 246 | plt.show() 247 | ``` 248 | 249 | 250 | Now let's consider a cyclically growing dividend sequence: 251 | 252 | $$ 253 | d_{t+1} = 1.01 d_t + 0.1 \sin t, \quad t = 0, 1, \ldots , T-1. 254 | $$ 255 | 256 | 257 | ```{code-cell} ipython3 258 | T = 100 259 | current_d = 1.0 260 | d = [] 261 | for t in range(T+1): 262 | d.append(current_d) 263 | current_d = current_d * 1.01 + 0.1 * np.sin(t) 264 | 265 | fig, ax = plt.subplots() 266 | ax.plot(d, 'o-', ms=4, alpha=0.8, label='dividends') 267 | ax.legend() 268 | ax.set_xlabel('time') 269 | plt.show() 270 | ``` 271 | 272 | ```{exercise-start} 273 | :label: pv_ex_cyc 274 | ``` 275 | 276 | Compute the corresponding asset price sequence when $p^*_{T+1} = 0$ and $\delta 277 | = 0.98$. 278 | 279 | ```{exercise-end} 280 | ``` 281 | 282 | ```{solution-start} pv_ex_cyc 283 | :class: dropdown 284 | ``` 285 | 286 | We proceed as above after modifying parameters and consequently the matrix $A$. 287 | 288 | ```{code-cell} ipython3 289 | δ = 0.98 290 | p_star = 0.0 291 | A = np.zeros((T+1, T+1)) 292 | for i in range(T+1): 293 | for j in range(T+1): 294 | if i == j: 295 | A[i, j] = 1 296 | if j < T: 297 | A[i, j+1] = -δ 298 | 299 | b = np.zeros(T+1) 300 | b[-1] = δ * p_star 301 | p = np.linalg.solve(A, d + b) 302 | fig, ax = plt.subplots() 303 | ax.plot(p, 'o-', ms=4, alpha=0.8, label='asset price') 304 | ax.legend() 305 | ax.set_xlabel('time') 306 | plt.show() 307 | 308 | ``` 309 | 310 | The weighted averaging associated with the present value calculation largely 311 | eliminates the cycles. 312 | 313 | 314 | ```{solution-end} 315 | ``` 316 | 317 | ## Analytical expressions 318 | 319 | By the [inverse matrix theorem](https://en.wikipedia.org/wiki/Invertible_matrix), a matrix $B$ is the inverse of $A$ whenever $A B$ is the identity. 320 | 321 | It can be verified that the inverse of the matrix $A$ in {eq}`eq:pvpieq` is 322 | 323 | 324 | $$ A^{-1} = 325 | \begin{bmatrix} 326 | 1 & \delta & \delta^2 & \cdots & \delta^{T-1} & \delta^T \cr 327 | 0 & 1 & \delta & \cdots & \delta^{T-2} & \delta^{T-1} \cr 328 | \vdots & \vdots & \vdots & \cdots & \vdots & \vdots \cr 329 | 0 & 0 & 0 & \cdots & 1 & \delta \cr 330 | 0 & 0 & 0 & \cdots & 0 & 1 \cr 331 | \end{bmatrix} 332 | $$ (eq:Ainv) 333 | 334 | 335 | 336 | ```{exercise-start} 337 | :label: pv_ex_2 338 | ``` 339 | 340 | Check this by showing that $A A^{-1}$ is equal to the identity matrix. 341 | 342 | ```{exercise-end} 343 | ``` 344 | 345 | 346 | If we use the expression {eq}`eq:Ainv` in {eq}`eq:apdb_sol` and perform the indicated matrix multiplication, we shall find that 347 | 348 | $$ 349 | p_t = \sum_{s=t}^T \delta^{s-t} d_s + \delta^{T+1-t} p_{T+1}^* 350 | $$ (eq:ptpveq) 351 | 352 | Pricing formula {eq}`eq:ptpveq` asserts that two components sum to the asset price 353 | $p_t$: 354 | 355 | * a **fundamental component** $\sum_{s=t}^T \delta^{s-t} d_s$ that equals the **discounted present value** of prospective dividends 356 | 357 | * a **bubble component** $\delta^{T+1-t} p_{T+1}^*$ 358 | 359 | The fundamental component is pinned down by the discount factor $\delta$ and the 360 | payout of the asset (in this case, dividends). 361 | 362 | The bubble component is the part of the price that is not pinned down by 363 | fundamentals. 364 | 365 | It is sometimes convenient to rewrite the bubble component as 366 | 367 | $$ 368 | c \delta^{-t} 369 | $$ 370 | 371 | where 372 | 373 | $$ 374 | c \equiv \delta^{T+1}p_{T+1}^* 375 | $$ 376 | 377 | +++ 378 | 379 | ## More about bubbles 380 | 381 | For a few moments, let's focus on the special case of an asset that never pays dividends, in which case 382 | 383 | $$ 384 | \begin{bmatrix} 385 | d_0 \cr d_1 \cr d_2 \cr \vdots \cr d_{T-1} \cr d_T 386 | \end{bmatrix} = 387 | \begin{bmatrix} 388 | 0 \cr 0 \cr 0 \cr \vdots \cr 0 \cr 0 389 | \end{bmatrix} 390 | $$ 391 | 392 | +++ 393 | 394 | In this case system {eq}`eq:Euler1` of our $T+1$ asset pricing equations takes the 395 | form of the single matrix equation 396 | 397 | $$ 398 | \begin{bmatrix} 1 & -\delta & 0 & 0 & \cdots & 0 & 0 \cr 399 | 0 & 1 & -\delta & 0 & \cdots & 0 & 0 \cr 400 | 0 & 0 & 1 & -\delta & \cdots & 0 & 0 \cr 401 | \vdots & \vdots & \vdots & \vdots & \vdots & 0 & 0 \cr 402 | 0 & 0 & 0 & 0 & \cdots & 1 & -\delta \cr 403 | 0 & 0 & 0 & 0 & \cdots & 0 & 1 \end{bmatrix} 404 | \begin{bmatrix} p_0 \cr p_1 \cr p_2 \cr \vdots \cr p_{T-1} \cr p_T 405 | \end{bmatrix} = 406 | \begin{bmatrix} 407 | 0 \cr 0 \cr 0 \cr \vdots \cr 0 \cr \delta p_{T+1}^* 408 | \end{bmatrix} 409 | $$ (eq:pieq2) 410 | 411 | Evidently, if $p_{T+1}^* = 0$, a price vector $p$ of all entries zero 412 | solves this equation and the only the **fundamental** component of our pricing 413 | formula {eq}`eq:ptpveq` is present. 414 | 415 | But let's activate the **bubble** component by setting 416 | 417 | $$ 418 | p_{T+1}^* = c \delta^{-(T+1)} 419 | $$ (eq:eqbubbleterm) 420 | 421 | for some positive constant $c$. 422 | 423 | In this case, when we multiply both sides of {eq}`eq:pieq2` by 424 | the matrix $A^{-1}$ presented in equation {eq}`eq:Ainv`, we 425 | find that 426 | 427 | $$ 428 | p_t = c \delta^{-t} 429 | $$ (eq:bubble) 430 | 431 | 432 | ## Gross rate of return 433 | 434 | Define the gross rate of return on holding the asset from period $t$ to period $t+1$ 435 | as 436 | 437 | $$ 438 | R_t = \frac{p_{t+1}}{p_t} 439 | $$ (eq:rateofreturn) 440 | 441 | Substituting equation {eq}`eq:bubble` into equation {eq}`eq:rateofreturn` confirms that an asset whose sole source of value is a bubble earns a gross rate of return 442 | 443 | $$ 444 | R_t = \delta^{-1} > 1 , t = 0, 1, \ldots, T 445 | $$ 446 | 447 | 448 | ## Exercises 449 | 450 | 451 | ```{exercise-start} 452 | :label: pv_ex_a 453 | ``` 454 | 455 | Assume that $g >1$ and that $\delta g \in (0,1)$. Give analytical expressions for an asset price $p_t$ under the 456 | following settings for $d$ and $p_{T+1}^*$: 457 | 458 | 1. $p_{T+1}^* = 0, d_t = g^t d_0$ (a modified version of the Gordon growth formula) 459 | 1. $p_{T+1}^* = \frac{g^{T+1} d_0}{1- \delta g}, d_t = g^t d_0$ (the plain vanilla Gordon growth formula) 460 | 1. $p_{T+1}^* = 0, d_t = 0$ (price of a worthless stock) 461 | 1. $p_{T+1}^* = c \delta^{-(T+1)}, d_t = 0$ (price of a pure bubble stock) 462 | 463 | 464 | ```{exercise-end} 465 | ``` 466 | 467 | ```{solution-start} pv_ex_a 468 | :class: dropdown 469 | ``` 470 | 471 | Plugging each of the above $p_{T+1}^*, d_t$ pairs into Equation {eq}`eq:ptpveq` yields: 472 | 473 | 1. $ p_t = \sum^T_{s=t} \delta^{s-t} g^s d_0 = d_t \frac{1 - (\delta g)^{T+1-t}}{1 - \delta g}$ 474 | 2. $p_t = \sum^T_{s=t} \delta^{s-t} g^s d_0 + \frac{\delta^{T+1-t} g^{T+1} d_0}{1 - \delta g} = \frac{d_t}{1 - \delta g}$ 475 | 3. $p_t = 0$ 476 | 4. $p_t = c \delta^{-t}$ 477 | 478 | 479 | ```{solution-end} 480 | ``` -------------------------------------------------------------------------------- /lectures/complex_and_trig.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | text_representation: 4 | extension: .md 5 | format_name: myst 6 | kernelspec: 7 | display_name: Python 3 8 | language: python 9 | name: python3 10 | --- 11 | 12 | (complex_and_trig)= 13 | ```{raw} html 14 |
15 | 16 | QuantEcon 17 | 18 |
19 | ``` 20 | 21 | ```{index} single: python 22 | ``` 23 | 24 | # Complex Numbers and Trigonometry 25 | 26 | ## Overview 27 | 28 | This lecture introduces some elementary mathematics and trigonometry. 29 | 30 | Useful and interesting in its own right, these concepts reap substantial rewards when studying dynamics generated 31 | by linear difference equations or linear differential equations. 32 | 33 | For example, these tools are keys to understanding outcomes attained by Paul 34 | Samuelson (1939) {cite}`Samuelson1939` in his classic paper on interactions 35 | between the investment accelerator and the Keynesian consumption function, our 36 | topic in the lecture {doc}`Samuelson Multiplier Accelerator `. 37 | 38 | In addition to providing foundations for Samuelson's work and extensions of 39 | it, this lecture can be read as a stand-alone quick reminder of key results 40 | from elementary high school trigonometry. 41 | 42 | So let's dive in. 43 | 44 | ### Complex Numbers 45 | 46 | A complex number has a **real part** $x$ and a purely **imaginary part** $y$. 47 | 48 | The Euclidean, polar, and trigonometric forms of a complex number $z$ are: 49 | 50 | $$ 51 | z = x + iy = re^{i\theta} = r(\cos{\theta} + i \sin{\theta}) 52 | $$ 53 | 54 | The second equality above is known as **Euler's formula** 55 | 56 | - [Euler](https://en.wikipedia.org/wiki/Leonhard_Euler) contributed many other formulas too! 57 | 58 | The complex conjugate $\bar z$ of $z$ is defined as 59 | 60 | $$ 61 | \bar z = x - iy = r e^{-i \theta} = r (\cos{\theta} - i \sin{\theta} ) 62 | $$ 63 | 64 | The value $x$ is the **real** part of $z$ and $y$ is the 65 | **imaginary** part of $z$. 66 | 67 | The symbol $| z |$ = $\sqrt{\bar{z}\cdot z} = r$ represents the **modulus** of $z$. 68 | 69 | The value $r$ is the Euclidean distance of vector $(x,y)$ from the 70 | origin: 71 | 72 | $$ 73 | r = |z| = \sqrt{x^2 + y^2} 74 | $$ 75 | 76 | The value $\theta$ is the angle of $(x,y)$ with respect to the real axis. 77 | 78 | Evidently, the tangent of $\theta$ is $\left(\frac{y}{x}\right)$. 79 | 80 | Therefore, 81 | 82 | $$ 83 | \theta = \tan^{-1} \Big( \frac{y}{x} \Big) 84 | $$ 85 | 86 | Three elementary trigonometric functions are 87 | 88 | $$ 89 | \cos{\theta} = \frac{x}{r} = \frac{e^{i\theta} + e^{-i\theta}}{2} , \quad 90 | \sin{\theta} = \frac{y}{r} = \frac{e^{i\theta} - e^{-i\theta}}{2i} , \quad 91 | \tan{\theta} = \frac{y}{x} 92 | $$ 93 | 94 | We'll need the following imports: 95 | 96 | ```{code-cell} ipython 97 | import matplotlib.pyplot as plt 98 | plt.rcParams["figure.figsize"] = (11, 5) #set default figure size 99 | import numpy as np 100 | from sympy import (Symbol, symbols, Eq, nsolve, sqrt, cos, sin, simplify, 101 | init_printing, integrate) 102 | ``` 103 | 104 | ### An Example 105 | 106 | ```{prf:example} 107 | :label: ct_ex_com 108 | 109 | Consider the complex number $z = 1 + \sqrt{3} i$. 110 | 111 | For $z = 1 + \sqrt{3} i$, $x = 1$, $y = \sqrt{3}$. 112 | 113 | It follows that $r = 2$ and 114 | $\theta = \tan^{-1}(\sqrt{3}) = \frac{\pi}{3} = 60^o$. 115 | ``` 116 | 117 | Let's use Python to plot the trigonometric form of the complex number 118 | $z = 1 + \sqrt{3} i$. 119 | 120 | ```{code-cell} python3 121 | # Abbreviate useful values and functions 122 | π = np.pi 123 | 124 | 125 | # Set parameters 126 | r = 2 127 | θ = π/3 128 | x = r * np.cos(θ) 129 | x_range = np.linspace(0, x, 1000) 130 | θ_range = np.linspace(0, θ, 1000) 131 | 132 | # Plot 133 | fig = plt.figure(figsize=(8, 8)) 134 | ax = plt.subplot(111, projection='polar') 135 | 136 | ax.plot((0, θ), (0, r), marker='o', color='b') # Plot r 137 | ax.plot(np.zeros(x_range.shape), x_range, color='b') # Plot x 138 | ax.plot(θ_range, x / np.cos(θ_range), color='b') # Plot y 139 | ax.plot(θ_range, np.full(θ_range.shape, 0.1), color='r') # Plot θ 140 | 141 | ax.margins(0) # Let the plot starts at origin 142 | 143 | ax.set_title("Trigonometry of complex numbers", va='bottom', 144 | fontsize='x-large') 145 | 146 | ax.set_rmax(2) 147 | ax.set_rticks((0.5, 1, 1.5, 2)) # Less radial ticks 148 | ax.set_rlabel_position(-88.5) # Get radial labels away from plotted line 149 | 150 | ax.text(θ, r+0.01 , r'$z = x + iy = 1 + \sqrt{3}\, i$') # Label z 151 | ax.text(θ+0.2, 1 , '$r = 2$') # Label r 152 | ax.text(0-0.2, 0.5, '$x = 1$') # Label x 153 | ax.text(0.5, 1.2, r'$y = \sqrt{3}$') # Label y 154 | ax.text(0.25, 0.15, r'$\theta = 60^o$') # Label θ 155 | 156 | ax.grid(True) 157 | plt.show() 158 | ``` 159 | 160 | ## De Moivre's Theorem 161 | 162 | de Moivre's theorem states that: 163 | 164 | $$ 165 | (r(\cos{\theta} + i \sin{\theta}))^n = 166 | r^n e^{in\theta} = 167 | r^n(\cos{n\theta} + i \sin{n\theta}) 168 | $$ 169 | 170 | To prove de Moivre's theorem, note that 171 | 172 | $$ 173 | (r(\cos{\theta} + i \sin{\theta}))^n = \big( re^{i\theta} \big)^n 174 | $$ 175 | 176 | and compute. 177 | 178 | ## Applications of de Moivre's Theorem 179 | 180 | ### Example 1 181 | 182 | We can use de Moivre's theorem to show that 183 | $r = \sqrt{x^2 + y^2}$. 184 | 185 | We have 186 | 187 | $$ 188 | \begin{aligned} 189 | 1 &= e^{i\theta} e^{-i\theta} \\ 190 | &= (\cos{\theta} + i \sin{\theta})(\cos{(\text{-}\theta)} + i \sin{(\text{-}\theta)}) \\ 191 | &= (\cos{\theta} + i \sin{\theta})(\cos{\theta} - i \sin{\theta}) \\ 192 | &= \cos^2{\theta} + \sin^2{\theta} \\ 193 | &= \frac{x^2}{r^2} + \frac{y^2}{r^2} 194 | \end{aligned} 195 | $$ 196 | 197 | and thus 198 | 199 | $$ 200 | x^2 + y^2 = r^2 201 | $$ 202 | 203 | We recognize this as a theorem of **Pythagoras**. 204 | 205 | ### Example 2 206 | 207 | Let $z = re^{i\theta}$ and $\bar{z} = re^{-i\theta}$ so that $\bar{z}$ is the **complex conjugate** of $z$. 208 | 209 | $(z, \bar z)$ form a **complex conjugate pair** of complex numbers. 210 | 211 | Let $a = pe^{i\omega}$ and $\bar{a} = pe^{-i\omega}$ be 212 | another complex conjugate pair. 213 | 214 | For each element of a sequence of integers $n = 0, 1, 2, \ldots, $. 215 | 216 | To do so, we can apply de Moivre's formula. 217 | 218 | Thus, 219 | 220 | $$ 221 | \begin{aligned} 222 | x_n &= az^n + \bar{a}\bar{z}^n \\ 223 | &= p e^{i\omega} (re^{i\theta})^n + p e^{-i\omega} (re^{-i\theta})^n \\ 224 | &= pr^n e^{i (\omega + n\theta)} + pr^n e^{-i (\omega + n\theta)} \\ 225 | &= pr^n [\cos{(\omega + n\theta)} + i \sin{(\omega + n\theta)} + 226 | \cos{(\omega + n\theta)} - i \sin{(\omega + n\theta)}] \\ 227 | &= 2 pr^n \cos{(\omega + n\theta)} 228 | \end{aligned} 229 | $$ 230 | 231 | ### Example 3 232 | 233 | This example provides machinery that is at the heard of Samuelson's analysis of his multiplier-accelerator model {cite}`Samuelson1939`. 234 | 235 | Thus, consider a **second-order linear difference equation** 236 | 237 | $$ 238 | x_{n+2} = c_1 x_{n+1} + c_2 x_n 239 | $$ 240 | 241 | whose **characteristic polynomial** is 242 | 243 | $$ 244 | z^2 - c_1 z - c_2 = 0 245 | $$ 246 | 247 | or 248 | 249 | $$ 250 | (z^2 - c_1 z - c_2 ) = (z - z_1)(z- z_2) = 0 251 | $$ 252 | 253 | has roots $z_1, z_1$. 254 | 255 | A **solution** is a sequence $\{x_n\}_{n=0}^\infty$ that satisfies 256 | the difference equation. 257 | 258 | Under the following circumstances, we can apply our example 2 formula to 259 | solve the difference equation 260 | 261 | - the roots $z_1, z_2$ of the characteristic polynomial of the 262 | difference equation form a complex conjugate pair 263 | - the values $x_0, x_1$ are given initial conditions 264 | 265 | To solve the difference equation, recall from example 2 that 266 | 267 | $$ 268 | x_n = 2 pr^n \cos{(\omega + n\theta)} 269 | $$ 270 | 271 | where $\omega, p$ are coefficients to be determined from 272 | information encoded in the initial conditions $x_1, x_0$. 273 | 274 | Since 275 | $x_0 = 2 p \cos{\omega}$ and $x_1 = 2 pr \cos{(\omega + \theta)}$ 276 | the ratio of $x_1$ to $x_0$ is 277 | 278 | $$ 279 | \frac{x_1}{x_0} = \frac{r \cos{(\omega + \theta)}}{\cos{\omega}} 280 | $$ 281 | 282 | We can solve this equation for $\omega$ then solve for $p$ using $x_0 = 2 pr^0 \cos{(\omega + n\theta)}$. 283 | 284 | With the `sympy` package in Python, we are able to solve and plot the 285 | dynamics of $x_n$ given different values of $n$. 286 | 287 | In this example, we set the initial values: - $r = 0.9$ - 288 | $\theta = \frac{1}{4}\pi$ - $x_0 = 4$ - 289 | $x_1 = r \cdot 2\sqrt{2} = 1.8 \sqrt{2}$. 290 | 291 | We first numerically solve for $\omega$ and $p$ using 292 | `nsolve` in the `sympy` package based on the above initial 293 | condition: 294 | 295 | ```{code-cell} python3 296 | # Set parameters 297 | r = 0.9 298 | θ = π/4 299 | x0 = 4 300 | x1 = 2 * r * sqrt(2) 301 | 302 | # Define symbols to be calculated 303 | ω, p = symbols('ω p', real=True) 304 | 305 | # Solve for ω 306 | ## Note: we choose the solution near 0 307 | eq1 = Eq(x1/x0 - r * cos(ω+θ) / cos(ω), 0) 308 | ω = nsolve(eq1, ω, 0) 309 | ω = float(ω) 310 | print(f'ω = {ω:1.3f}') 311 | 312 | # Solve for p 313 | eq2 = Eq(x0 - 2 * p * cos(ω), 0) 314 | p = nsolve(eq2, p, 0) 315 | p = float(p) 316 | print(f'p = {p:1.3f}') 317 | ``` 318 | 319 | Using the code above, we compute that 320 | $\omega = 0$ and $p = 2$. 321 | 322 | Then we plug in the values we solve for $\omega$ and $p$ 323 | and plot the dynamic. 324 | 325 | ```{code-cell} python3 326 | # Define range of n 327 | max_n = 30 328 | n = np.arange(0, max_n+1, 0.01) 329 | 330 | # Define x_n 331 | x = lambda n: 2 * p * r**n * np.cos(ω + n * θ) 332 | 333 | # Plot 334 | fig, ax = plt.subplots(figsize=(12, 8)) 335 | 336 | ax.plot(n, x(n)) 337 | ax.set(xlim=(0, max_n), ylim=(-5, 5), xlabel='$n$', ylabel='$x_n$') 338 | 339 | # Set x-axis in the middle of the plot 340 | ax.spines['bottom'].set_position('center') 341 | ax.spines['right'].set_color('none') 342 | ax.spines['top'].set_color('none') 343 | ax.xaxis.set_ticks_position('bottom') 344 | ax.yaxis.set_ticks_position('left') 345 | 346 | ticklab = ax.xaxis.get_ticklabels()[0] # Set x-label position 347 | trans = ticklab.get_transform() 348 | ax.xaxis.set_label_coords(31, 0, transform=trans) 349 | 350 | ticklab = ax.yaxis.get_ticklabels()[0] # Set y-label position 351 | trans = ticklab.get_transform() 352 | ax.yaxis.set_label_coords(0, 5, transform=trans) 353 | 354 | ax.grid() 355 | plt.show() 356 | ``` 357 | 358 | ### Trigonometric Identities 359 | 360 | We can obtain a complete suite of trigonometric identities by 361 | appropriately manipulating polar forms of complex numbers. 362 | 363 | We'll get many of them by deducing implications of the equality 364 | 365 | $$ 366 | e^{i(\omega + \theta)} = e^{i\omega} e^{i\theta} 367 | $$ 368 | 369 | For example, we'll calculate identities for 370 | 371 | $\cos{(\omega + \theta)}$ and $\sin{(\omega + \theta)}$. 372 | 373 | Using the sine and cosine formulas presented at the beginning of this 374 | lecture, we have: 375 | 376 | $$ 377 | \begin{aligned} 378 | \cos{(\omega + \theta)} = \frac{e^{i(\omega + \theta)} + e^{-i(\omega + \theta)}}{2} \\ 379 | \sin{(\omega + \theta)} = \frac{e^{i(\omega + \theta)} - e^{-i(\omega + \theta)}}{2i} 380 | \end{aligned} 381 | $$ 382 | 383 | We can also obtain the trigonometric identities as follows: 384 | 385 | $$ 386 | \begin{aligned} 387 | \cos{(\omega + \theta)} + i \sin{(\omega + \theta)} 388 | &= e^{i(\omega + \theta)} \\ 389 | &= e^{i\omega} e^{i\theta} \\ 390 | &= (\cos{\omega} + i \sin{\omega})(\cos{\theta} + i \sin{\theta}) \\ 391 | &= (\cos{\omega}\cos{\theta} - \sin{\omega}\sin{\theta}) + 392 | i (\cos{\omega}\sin{\theta} + \sin{\omega}\cos{\theta}) 393 | \end{aligned} 394 | $$ 395 | 396 | Since both real and imaginary parts of the above formula should be 397 | equal, we get: 398 | 399 | $$ 400 | \begin{aligned} 401 | \cos{(\omega + \theta)} = \cos{\omega}\cos{\theta} - \sin{\omega}\sin{\theta} \\ 402 | \sin{(\omega + \theta)} = \cos{\omega}\sin{\theta} + \sin{\omega}\cos{\theta} 403 | \end{aligned} 404 | $$ 405 | 406 | The equations above are also known as the **angle sum identities**. We 407 | can verify the equations using the `simplify` function in the 408 | `sympy` package: 409 | 410 | ```{code-cell} python3 411 | # Define symbols 412 | ω, θ = symbols('ω θ', real=True) 413 | 414 | # Verify 415 | print("cos(ω)cos(θ) - sin(ω)sin(θ) =", 416 | simplify(cos(ω)*cos(θ) - sin(ω) * sin(θ))) 417 | print("cos(ω)sin(θ) + sin(ω)cos(θ) =", 418 | simplify(cos(ω)*sin(θ) + sin(ω) * cos(θ))) 419 | ``` 420 | 421 | ### Trigonometric Integrals 422 | 423 | We can also compute the trigonometric integrals using polar forms of 424 | complex numbers. 425 | 426 | For example, we want to solve the following integral: 427 | 428 | $$ 429 | \int_{-\pi}^{\pi} \cos(\omega) \sin(\omega) \, d\omega 430 | $$ 431 | 432 | Using Euler's formula, we have: 433 | 434 | $$ 435 | \begin{aligned} 436 | \int \cos(\omega) \sin(\omega) \, d\omega 437 | &= 438 | \int 439 | \frac{(e^{i\omega} + e^{-i\omega})}{2} 440 | \frac{(e^{i\omega} - e^{-i\omega})}{2i} 441 | \, d\omega \\ 442 | &= 443 | \frac{1}{4i} 444 | \int 445 | e^{2i\omega} - e^{-2i\omega} 446 | \, d\omega \\ 447 | &= 448 | \frac{1}{4i} 449 | \bigg( \frac{-i}{2} e^{2i\omega} - \frac{i}{2} e^{-2i\omega} + C_1 \bigg) \\ 450 | &= 451 | -\frac{1}{8} 452 | \bigg[ \bigg(e^{i\omega}\bigg)^2 + \bigg(e^{-i\omega}\bigg)^2 - 2 \bigg] + C_2 \\ 453 | &= 454 | -\frac{1}{8} (e^{i\omega} - e^{-i\omega})^2 + C_2 \\ 455 | &= 456 | \frac{1}{2} \bigg( \frac{e^{i\omega} - e^{-i\omega}}{2i} \bigg)^2 + C_2 \\ 457 | &= \frac{1}{2} \sin^2(\omega) + C_2 458 | \end{aligned} 459 | $$ 460 | 461 | and thus: 462 | 463 | $$ 464 | \int_{-\pi}^{\pi} \cos(\omega) \sin(\omega) \, d\omega = 465 | \frac{1}{2}\sin^2(\pi) - \frac{1}{2}\sin^2(-\pi) = 0 466 | $$ 467 | 468 | We can verify the analytical as well as numerical results using 469 | `integrate` in the `sympy` package: 470 | 471 | ```{code-cell} python3 472 | # Set initial printing 473 | init_printing(use_latex="mathjax") 474 | 475 | ω = Symbol('ω') 476 | print('The analytical solution for integral of cos(ω)sin(ω) is:') 477 | integrate(cos(ω) * sin(ω), ω) 478 | ``` 479 | 480 | ```{code-cell} python3 481 | print('The numerical solution for the integral of cos(ω)sin(ω) \ 482 | from -π to π is:') 483 | integrate(cos(ω) * sin(ω), (ω, -π, π)) 484 | ``` 485 | 486 | ### Exercises 487 | 488 | ```{exercise} 489 | :label: complex_ex1 490 | 491 | We invite the reader to verify analytically and with the `sympy` package the following two equalities: 492 | 493 | $$ 494 | \int_{-\pi}^{\pi} \cos (\omega)^2 \, d\omega = \pi 495 | $$ 496 | 497 | $$ 498 | \int_{-\pi}^{\pi} \sin (\omega)^2 \, d\omega = \pi 499 | $$ 500 | ``` 501 | 502 | ```{solution-start} complex_ex1 503 | :class: dropdown 504 | ``` 505 | 506 | Let's import symbolic $\pi$ from `sympy` 507 | 508 | ```{code-cell} ipython3 509 | # Import symbolic π from sympy 510 | from sympy import pi 511 | ``` 512 | 513 | ```{code-cell} ipython3 514 | print('The analytical solution for the integral of cos(ω)**2 \ 515 | from -π to π is:') 516 | 517 | integrate(cos(ω)**2, (ω, -pi, pi)) 518 | ``` 519 | 520 | ```{code-cell} ipython3 521 | print('The analytical solution for the integral of sin(ω)**2 \ 522 | from -π to π is:') 523 | 524 | integrate(sin(ω)**2, (ω, -pi, pi)) 525 | ``` 526 | 527 | ```{solution-end} 528 | ``` 529 | -------------------------------------------------------------------------------- /lectures/mle.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | text_representation: 4 | extension: .md 5 | format_name: myst 6 | format_version: 0.13 7 | jupytext_version: 1.15.2 8 | kernelspec: 9 | display_name: Python 3 (ipykernel) 10 | language: python 11 | name: python3 12 | --- 13 | 14 | # Maximum Likelihood Estimation 15 | 16 | ```{code-cell} ipython3 17 | from scipy.stats import lognorm, pareto, expon 18 | import numpy as np 19 | from scipy.integrate import quad 20 | import matplotlib.pyplot as plt 21 | import pandas as pd 22 | from math import exp 23 | ``` 24 | 25 | ## Introduction 26 | 27 | Consider a situation where a policymaker is trying to estimate how much revenue 28 | a proposed wealth tax will raise. 29 | 30 | The proposed tax is 31 | 32 | $$ 33 | h(w) = 34 | \begin{cases} 35 | a w & \text{if } w \leq \bar w \\ 36 | a \bar{w} + b (w-\bar{w}) & \text{if } w > \bar w 37 | \end{cases} 38 | $$ 39 | 40 | where $w$ is wealth. 41 | 42 | ```{prf:example} 43 | :label: mle_ex_wt 44 | 45 | For example, if $a = 0.05$, $b = 0.1$, and $\bar w = 2.5$, this means 46 | 47 | * a 5% tax on wealth up to 2.5 and 48 | * a 10% tax on wealth in excess of 2.5. 49 | 50 | The unit is 100,000, so $w= 2.5$ means 250,000 dollars. 51 | ``` 52 | Let's go ahead and define $h$: 53 | 54 | ```{code-cell} ipython3 55 | def h(w, a=0.05, b=0.1, w_bar=2.5): 56 | if w <= w_bar: 57 | return a * w 58 | else: 59 | return a * w_bar + b * (w - w_bar) 60 | ``` 61 | 62 | For a population of size $N$, where individual $i$ has wealth $w_i$, total revenue raised by 63 | the tax will be 64 | 65 | $$ 66 | T = \sum_{i=1}^{N} h(w_i) 67 | $$ 68 | 69 | We wish to calculate this quantity. 70 | 71 | The problem we face is that, in most countries, wealth is not observed for all individuals. 72 | 73 | Collecting and maintaining accurate wealth data for all individuals or households in a country 74 | is just too hard. 75 | 76 | So let's suppose instead that we obtain a sample $w_1, w_2, \cdots, w_n$ telling us the wealth of $n$ randomly selected individuals. 77 | 78 | For our exercise we are going to use a sample of $n = 10,000$ observations from wealth data in the US in 2016. 79 | 80 | ```{code-cell} ipython3 81 | n = 10_000 82 | ``` 83 | 84 | The data is derived from the 85 | [Survey of Consumer Finances](https://en.wikipedia.org/wiki/Survey_of_Consumer_Finances) (SCF). 86 | 87 | 88 | The following code imports this data and reads it into an array called `sample`. 89 | 90 | ```{code-cell} ipython3 91 | :tags: [hide-input] 92 | 93 | url = 'https://media.githubusercontent.com/media/QuantEcon/high_dim_data/update_scf_noweights/SCF_plus/SCF_plus_mini_no_weights.csv' 94 | df = pd.read_csv(url) 95 | df = df.dropna() 96 | df = df[df['year'] == 2016] 97 | df = df.loc[df['n_wealth'] > 1 ] #restrcting data to net worth > 1 98 | rv = df['n_wealth'].sample(n=n, random_state=1234) 99 | rv = rv.to_numpy() / 100_000 100 | sample = rv 101 | ``` 102 | 103 | Let's histogram this sample. 104 | 105 | ```{code-cell} ipython3 106 | fig, ax = plt.subplots() 107 | ax.set_xlim(-1, 20) 108 | density, edges = np.histogram(sample, bins=5000, density=True) 109 | prob = density * np.diff(edges) 110 | plt.stairs(prob, edges, fill=True, alpha=0.8, label=r"unit: $\$100,000$") 111 | plt.ylabel("prob") 112 | plt.xlabel("net wealth") 113 | plt.legend() 114 | plt.show() 115 | ``` 116 | 117 | The histogram shows that many people have very low wealth and a few people have 118 | very high wealth. 119 | 120 | 121 | We will take the full population size to be 122 | 123 | ```{code-cell} ipython3 124 | N = 100_000_000 125 | ``` 126 | 127 | How can we estimate total revenue from the full population using only the sample data? 128 | 129 | Our plan is to assume that wealth of each individual is a draw from a distribution with density $f$. 130 | 131 | If we obtain an estimate of $f$ we can then approximate $T$ as follows: 132 | 133 | $$ 134 | T = \sum_{i=1}^{N} h(w_i) 135 | = N \frac{1}{N} \sum_{i=1}^{N} h(w_i) 136 | \approx N \int_{0}^{\infty} h(w)f(w) dw 137 | $$ (eq:est_rev) 138 | 139 | (The sample mean should be close to the mean by the law of large numbers.) 140 | 141 | The problem now is: how do we estimate $f$? 142 | 143 | 144 | ## Maximum likelihood estimation 145 | 146 | [Maximum likelihood estimation](https://en.wikipedia.org/wiki/Maximum_likelihood_estimation) 147 | is a method of estimating an unknown distribution. 148 | 149 | Maximum likelihood estimation has two steps: 150 | 151 | 1. Guess what the underlying distribution is (e.g., normal with mean $\mu$ and 152 | standard deviation $\sigma$). 153 | 2. Estimate the parameter values (e.g., estimate $\mu$ and $\sigma$ for the 154 | normal distribution) 155 | 156 | One possible assumption for the wealth is that each 157 | $w_i$ is [log-normally distributed](https://en.wikipedia.org/wiki/Log-normal_distribution), 158 | with parameters $\mu \in (-\infty,\infty)$ and $\sigma \in (0,\infty)$. 159 | 160 | (This means that $\ln w_i$ is normally distributed with mean $\mu$ and standard deviation $\sigma$.) 161 | 162 | You can see that this assumption is not completely unreasonable because, if we 163 | histogram log wealth instead of wealth, the picture starts to look something 164 | like a bell-shaped curve. 165 | 166 | ```{code-cell} ipython3 167 | ln_sample = np.log(sample) 168 | fig, ax = plt.subplots() 169 | ax.hist(ln_sample, density=True, bins=200, histtype='stepfilled', alpha=0.8) 170 | plt.show() 171 | ``` 172 | 173 | Now our job is to obtain the maximum likelihood estimates of $\mu$ and $\sigma$, which 174 | we denote by $\hat{\mu}$ and $\hat{\sigma}$. 175 | 176 | These estimates can be found by maximizing the likelihood function given the 177 | data. 178 | 179 | The pdf of a lognormally distributed random variable $X$ is given by: 180 | 181 | $$ 182 | f(x, \mu, \sigma) 183 | = \frac{1}{x}\frac{1}{\sigma \sqrt{2\pi}} 184 | \exp\left(\frac{-1}{2}\left(\frac{\ln x-\mu}{\sigma}\right)\right)^2 185 | $$ 186 | 187 | For our sample $w_1, w_2, \cdots, w_n$, the [likelihood function](https://en.wikipedia.org/wiki/Likelihood_function) is given by 188 | 189 | $$ 190 | L(\mu, \sigma | w_i) = \prod_{i=1}^{n} f(w_i, \mu, \sigma) 191 | $$ 192 | 193 | The likelihood function can be viewed as both 194 | 195 | * the joint distribution of the sample (which is assumed to be IID) and 196 | * the "likelihood" of parameters $(\mu, \sigma)$ given the data. 197 | 198 | Taking logs on both sides gives us the log likelihood function, which is 199 | 200 | $$ 201 | \begin{aligned} 202 | \ell(\mu, \sigma | w_i) 203 | & = \ln \left[ \prod_{i=1}^{n} f(w_i, \mu, \sigma) \right] \\ 204 | & = -\sum_{i=1}^{n} \ln w_i 205 | - \frac{n}{2} \ln(2\pi) - \frac{n}{2} \ln \sigma^2 - \frac{1}{2\sigma^2} 206 | \sum_{i=1}^n (\ln w_i - \mu)^2 207 | \end{aligned} 208 | $$ 209 | 210 | To find where this function is maximised we find its partial derivatives wrt $\mu$ and $\sigma ^2$ and equate them to $0$. 211 | 212 | Let's first find the maximum likelihood estimate (MLE) of $\mu$ 213 | 214 | $$ 215 | \frac{\delta \ell}{\delta \mu} 216 | = - \frac{1}{2\sigma^2} \times 2 \sum_{i=1}^n (\ln w_i - \mu) = 0 \\ 217 | \implies \sum_{i=1}^n \ln w_i - n \mu = 0 \\ 218 | \implies \hat{\mu} = \frac{\sum_{i=1}^n \ln w_i}{n} 219 | $$ 220 | 221 | Now let's find the MLE of $\sigma$ 222 | 223 | $$ 224 | \frac{\delta \ell}{\delta \sigma^2} 225 | = - \frac{n}{2\sigma^2} + \frac{1}{2\sigma^4} 226 | \sum_{i=1}^n (\ln w_i - \mu)^2 = 0 \\ 227 | \implies \frac{n}{2\sigma^2} = 228 | \frac{1}{2\sigma^4} \sum_{i=1}^n (\ln w_i - \mu)^2 \\ 229 | \implies \hat{\sigma} = 230 | \left( \frac{\sum_{i=1}^{n}(\ln w_i - \hat{\mu})^2}{n} \right)^{1/2} 231 | $$ 232 | 233 | Now that we have derived the expressions for $\hat{\mu}$ and $\hat{\sigma}$, 234 | let's compute them for our wealth sample. 235 | 236 | ```{code-cell} ipython3 237 | μ_hat = np.mean(ln_sample) 238 | μ_hat 239 | ``` 240 | 241 | ```{code-cell} ipython3 242 | num = (ln_sample - μ_hat)**2 243 | σ_hat = (np.mean(num))**(1/2) 244 | σ_hat 245 | ``` 246 | 247 | Let's plot the lognormal pdf using the estimated parameters against our sample data. 248 | 249 | ```{code-cell} ipython3 250 | dist_lognorm = lognorm(σ_hat, scale = exp(μ_hat)) 251 | x = np.linspace(0,50,10000) 252 | 253 | fig, ax = plt.subplots() 254 | ax.set_xlim(-1,20) 255 | 256 | ax.hist(sample, density=True, bins=5_000, histtype='stepfilled', alpha=0.5) 257 | ax.plot(x, dist_lognorm.pdf(x), 'k-', lw=0.5, label='lognormal pdf') 258 | ax.legend() 259 | plt.show() 260 | ``` 261 | 262 | Our estimated lognormal distribution appears to be a reasonable fit for the overall data. 263 | 264 | We now use {eq}`eq:est_rev` to calculate total revenue. 265 | 266 | We will compute the integral using numerical integration via SciPy's 267 | [quad](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.quad.html) 268 | function 269 | 270 | ```{code-cell} ipython3 271 | def total_revenue(dist): 272 | integral, _ = quad(lambda x: h(x) * dist.pdf(x), 0, 100_000) 273 | T = N * integral 274 | return T 275 | ``` 276 | 277 | ```{code-cell} ipython3 278 | tr_lognorm = total_revenue(dist_lognorm) 279 | tr_lognorm 280 | ``` 281 | 282 | (Our unit was 100,000 dollars, so this means that actual revenue is 100,000 283 | times as large.) 284 | 285 | 286 | ## Pareto distribution 287 | 288 | We mentioned above that using maximum likelihood estimation requires us to make 289 | a prior assumption of the underlying distribution. 290 | 291 | Previously we assumed that the distribution is lognormal. 292 | 293 | Suppose instead we assume that $w_i$ are drawn from the 294 | [Pareto Distribution](https://en.wikipedia.org/wiki/Pareto_distribution) 295 | with parameters $b$ and $x_m$. 296 | 297 | In this case, the maximum likelihood estimates are known to be 298 | 299 | $$ 300 | \hat{b} = \frac{n}{\sum_{i=1}^{n} \ln (w_i/\hat{x_m})} 301 | \quad \text{and} \quad 302 | \hat{x}_m = \min_{i} w_i 303 | $$ 304 | 305 | Let's calculate them. 306 | 307 | ```{code-cell} ipython3 308 | xm_hat = min(sample) 309 | xm_hat 310 | ``` 311 | 312 | ```{code-cell} ipython3 313 | den = np.log(sample/xm_hat) 314 | b_hat = 1/np.mean(den) 315 | b_hat 316 | ``` 317 | 318 | Now let's recompute total revenue. 319 | 320 | ```{code-cell} ipython3 321 | dist_pareto = pareto(b = b_hat, scale = xm_hat) 322 | tr_pareto = total_revenue(dist_pareto) 323 | tr_pareto 324 | ``` 325 | 326 | The number is very different! 327 | 328 | ```{code-cell} ipython3 329 | tr_pareto / tr_lognorm 330 | ``` 331 | 332 | We see that choosing the right distribution is extremely important. 333 | 334 | 335 | 336 | Let's compare the fitted Pareto distribution to the histogram: 337 | 338 | ```{code-cell} ipython3 339 | fig, ax = plt.subplots() 340 | ax.set_xlim(-1, 20) 341 | ax.set_ylim(0,1.75) 342 | 343 | ax.hist(sample, density=True, bins=5_000, histtype='stepfilled', alpha=0.5) 344 | ax.plot(x, dist_pareto.pdf(x), 'k-', lw=0.5, label='Pareto pdf') 345 | ax.legend() 346 | 347 | plt.show() 348 | ``` 349 | 350 | We observe that in this case the fit for the Pareto distribution is not very 351 | good, so we can probably reject it. 352 | 353 | ## What is the best distribution? 354 | 355 | There is no "best" distribution --- every choice we make is an assumption. 356 | 357 | All we can do is try to pick a distribution that fits the data well. 358 | 359 | The plots above suggested that the lognormal distribution is optimal. 360 | 361 | However when we inspect the upper tail (the richest people), the Pareto distribution may be a better fit. 362 | 363 | To see this, let's now set a minimum threshold of net worth in our dataset. 364 | 365 | We set an arbitrary threshold of $500,000 and read the data into `sample_tail`. 366 | 367 | ```{code-cell} ipython3 368 | :tags: [hide-input] 369 | 370 | df_tail = df.loc[df['n_wealth'] > 500_000 ] 371 | df_tail.head() 372 | rv_tail = df_tail['n_wealth'].sample(n=10_000, random_state=4321) 373 | rv_tail = rv_tail.to_numpy() 374 | sample_tail = rv_tail/500_000 375 | ``` 376 | 377 | Let's plot this data. 378 | 379 | ```{code-cell} ipython3 380 | fig, ax = plt.subplots() 381 | ax.set_xlim(0,50) 382 | ax.hist(sample_tail, density=True, bins=500, histtype='stepfilled', alpha=0.8) 383 | plt.show() 384 | ``` 385 | 386 | Now let's try fitting some distributions to this data. 387 | 388 | 389 | ### Lognormal distribution for the right hand tail 390 | 391 | Let's start with the lognormal distribution 392 | 393 | We estimate the parameters again and plot the density against our data. 394 | 395 | ```{code-cell} ipython3 396 | ln_sample_tail = np.log(sample_tail) 397 | μ_hat_tail = np.mean(ln_sample_tail) 398 | num_tail = (ln_sample_tail - μ_hat_tail)**2 399 | σ_hat_tail = (np.mean(num_tail))**(1/2) 400 | dist_lognorm_tail = lognorm(σ_hat_tail, scale = exp(μ_hat_tail)) 401 | 402 | fig, ax = plt.subplots() 403 | ax.set_xlim(0,50) 404 | ax.hist(sample_tail, density=True, bins=500, histtype='stepfilled', alpha=0.5) 405 | ax.plot(x, dist_lognorm_tail.pdf(x), 'k-', lw=0.5, label='lognormal pdf') 406 | ax.legend() 407 | plt.show() 408 | ``` 409 | 410 | While the lognormal distribution was a good fit for the entire dataset, 411 | it is not a good fit for the right hand tail. 412 | 413 | 414 | ### Pareto distribution for the right hand tail 415 | 416 | Let's now assume the truncated dataset has a Pareto distribution. 417 | 418 | We estimate the parameters again and plot the density against our data. 419 | 420 | ```{code-cell} ipython3 421 | xm_hat_tail = min(sample_tail) 422 | den_tail = np.log(sample_tail/xm_hat_tail) 423 | b_hat_tail = 1/np.mean(den_tail) 424 | dist_pareto_tail = pareto(b = b_hat_tail, scale = xm_hat_tail) 425 | 426 | fig, ax = plt.subplots() 427 | ax.set_xlim(0, 50) 428 | ax.set_ylim(0,0.65) 429 | ax.hist(sample_tail, density=True, bins= 500, histtype='stepfilled', alpha=0.5) 430 | ax.plot(x, dist_pareto_tail.pdf(x), 'k-', lw=0.5, label='pareto pdf') 431 | plt.show() 432 | ``` 433 | 434 | The Pareto distribution is a better fit for the right hand tail of our dataset. 435 | 436 | ### So what is the best distribution? 437 | 438 | As we said above, there is no "best" distribution --- each choice is an 439 | assumption. 440 | 441 | We just have to test what we think are reasonable distributions. 442 | 443 | One test is to plot the data against the fitted distribution, as we did. 444 | 445 | There are other more rigorous tests, such as the [Kolmogorov-Smirnov test](https://en.wikipedia.org/wiki/Kolmogorov%E2%80%93Smirnov_test). 446 | 447 | We omit such advanced topics (but encourage readers to study them once 448 | they have completed these lectures). 449 | 450 | ## Exercises 451 | 452 | ```{exercise-start} 453 | :label: mle_ex1 454 | ``` 455 | Suppose we assume wealth is [exponentially](https://en.wikipedia.org/wiki/Exponential_distribution) 456 | distributed with parameter $\lambda > 0$. 457 | 458 | The maximum likelihood estimate of $\lambda$ is given by 459 | 460 | $$ 461 | \hat{\lambda} = \frac{n}{\sum_{i=1}^n w_i} 462 | $$ 463 | 464 | 1. Compute $\hat{\lambda}$ for our initial sample. 465 | 2. Use $\hat{\lambda}$ to find the total revenue 466 | 467 | ```{exercise-end} 468 | ``` 469 | 470 | ```{solution-start} mle_ex1 471 | :class: dropdown 472 | ``` 473 | 474 | ```{code-cell} ipython3 475 | λ_hat = 1/np.mean(sample) 476 | λ_hat 477 | ``` 478 | 479 | ```{code-cell} ipython3 480 | dist_exp = expon(scale = 1/λ_hat) 481 | tr_expo = total_revenue(dist_exp) 482 | tr_expo 483 | ``` 484 | 485 | ```{solution-end} 486 | ``` 487 | 488 | ```{exercise-start} 489 | :label: mle_ex2 490 | ``` 491 | 492 | Plot the exponential distribution against the sample and check if it is a good fit or not. 493 | 494 | ```{exercise-end} 495 | ``` 496 | 497 | ```{solution-start} mle_ex2 498 | :class: dropdown 499 | ``` 500 | 501 | ```{code-cell} ipython3 502 | fig, ax = plt.subplots() 503 | ax.set_xlim(-1, 20) 504 | 505 | ax.hist(sample, density=True, bins=5000, histtype='stepfilled', alpha=0.5) 506 | ax.plot(x, dist_exp.pdf(x), 'k-', lw=0.5, label='exponential pdf') 507 | ax.legend() 508 | 509 | plt.show() 510 | ``` 511 | 512 | Clearly, this distribution is not a good fit for our data. 513 | 514 | ```{solution-end} 515 | ``` 516 | -------------------------------------------------------------------------------- /lectures/laffer_adaptive.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | text_representation: 4 | extension: .md 5 | format_name: myst 6 | format_version: 0.13 7 | jupytext_version: 1.14.5 8 | kernelspec: 9 | display_name: Python 3 (ipykernel) 10 | language: python 11 | name: python3 12 | --- 13 | 14 | # Laffer Curves with Adaptive Expectations 15 | 16 | ## Overview 17 | 18 | This lecture studies stationary and dynamic **Laffer curves** in the inflation tax rate in a non-linear version of the model studied in this lecture {doc}`money_inflation`. 19 | 20 | As in the lecture {doc}`money_inflation`, this lecture uses the log-linear version of the demand function for money that {cite}`Cagan` used in his classic paper in place of the linear demand function used in this lecture {doc}`money_inflation`. 21 | 22 | But now, instead of assuming ''rational expectations'' in the form of ''perfect foresight'', 23 | we'll adopt the ''adaptive expectations'' assumption used by {cite}`Cagan` and {cite}`Friedman1956`. 24 | 25 | This means that instead of assuming that expected inflation $\pi_t^*$ is described by the "perfect foresight" or "rational expectations" hypothesis 26 | 27 | $$ 28 | \pi_t^* = p_{t+1} - p_t 29 | $$ 30 | 31 | that we adopted in lectures {doc}`money_inflation` and lectures {doc}`money_inflation_nonlinear`, we'll now assume that $\pi_t^*$ is determined by the adaptive expectations hypothesis described in equation {eq}`eq:adaptex` reported below. 32 | 33 | We shall discover that changing our hypothesis about expectations formation in this way will change some our findings and leave others intact. In particular, we shall discover that 34 | 35 | * replacing rational expectations with adaptive expectations leaves the two stationary inflation rates unchanged, but that $\ldots$ 36 | * it reverses the perverse dynamics by making the **lower** stationary inflation rate the one to which the system typically converges 37 | * a more plausible comparative dynamic outcome emerges in which now inflation can be **reduced** by running **lower** government deficits 38 | 39 | These more plausible comparative dynamics underlie the "old time religion" that states that 40 | "inflation is always and everywhere caused by government deficits". 41 | 42 | These issues were studied by {cite}`bruno1990seigniorage`. 43 | 44 | Their purpose was to reverse what they thought were counter intuitive 45 | predictions of their model under rational expectations (i.e., perfect foresight in this context) 46 | by dropping rational expectations and instead assuming that people form expectations about future inflation rates according to the "adaptive expectations" scheme {eq}`eq:adaptex` described below. 47 | 48 | ```{note} 49 | {cite}`sargent1989least` had studied another way of selecting stationary equilibrium that involved replacing rational expectations with a model of learning via least squares regression. 50 | {cite}`marcet2003recurrent` and {cite}`sargent2009conquest` extended that work and applied it to study recurrent high-inflation episodes in Latin America. 51 | ``` 52 | 53 | ## The model 54 | 55 | Let 56 | 57 | * $m_t$ be the log of the money supply at the beginning of time $t$ 58 | * $p_t$ be the log of the price level at time $t$ 59 | * $\pi_t^*$ be the public's expectation of the rate of inflation between $t$ and $t+1$ 60 | 61 | The law of motion of the money supply is 62 | 63 | $$ 64 | \exp(m_{t+1}) - \exp(m_t) = g \exp(p_t) 65 | $$ (eq:ada_msupply) 66 | 67 | where $g$ is the part of government expenditures financed by printing money. 68 | 69 | Notice that equation {eq}`eq:ada_msupply` implies that 70 | 71 | $$ 72 | m_{t+1} = \log[ \exp(m_t) + g \exp(p_t)] 73 | $$ (eq:ada_msupply2) 74 | 75 | The demand function for money is 76 | 77 | $$ 78 | m_{t+1} - p_t = -\alpha \pi_t^* 79 | $$ (eq:ada_mdemand) 80 | 81 | where $\alpha \geq 0$. 82 | 83 | Expectations of inflation are governed by 84 | 85 | $$ 86 | \pi_{t}^* = (1-\delta) (p_t - p_{t-1}) + \delta \pi_{t-1}^* 87 | $$ (eq:adaptex) 88 | 89 | where $\delta \in (0,1)$ 90 | 91 | ## Computing an equilibrium sequence 92 | 93 | Equation the expressions for $m_{t+1}$ provided by {eq}`eq:ada_mdemand` and {eq}`eq:ada_msupply2` and use equation {eq}`eq:adaptex` to eliminate $\pi_t^*$ to obtain 94 | the following equation for $p_t$: 95 | 96 | $$ 97 | \log[ \exp(m_t) + g \exp(p_t)] - p_t = -\alpha [(1-\delta) (p_t - p_{t-1}) + \delta \pi_{t-1}^*] 98 | $$ (eq:pequation) 99 | 100 | **Pseudo-code** 101 | 102 | Here is the pseudo-code for our algorithm. 103 | 104 | Starting at time $0$ with initial conditions $(m_0, \pi_{-1}^*, p_{-1})$, for each $t \geq 0$ 105 | deploy the following steps in order: 106 | 107 | * solve {eq}`eq:pequation` for $p_t$ 108 | * solve equation {eq}`eq:adaptex` for $\pi_t^*$ 109 | * solve equation {eq}`eq:ada_msupply2` for $m_{t+1}$ 110 | 111 | This completes the algorithm. 112 | 113 | 114 | ## Claims or conjectures 115 | 116 | 117 | It will turn out that 118 | 119 | * if they exist, limiting values $\overline \pi$ and $\overline \mu$ will be equal 120 | 121 | * if limiting values exist, there are two possible limiting values, one high, one low 122 | 123 | * unlike the outcome in lecture {doc}`money_inflation_nonlinear`, for almost all initial log price levels and expected inflation rates $p_0, \pi_{t}^*$, the limiting $\overline \pi = \overline \mu$ is the **lower** steady state value 124 | 125 | * for each of the two possible limiting values $\bar \pi$ ,there is a unique initial log price level $p_0$ that implies that $\pi_t = \mu_t = \bar \mu$ for all $t \geq 0$ 126 | 127 | * this unique initial log price level solves $\log(\exp(m_0) + g \exp(p_0)) - p_0 = - \alpha \bar \pi $ 128 | 129 | * the preceding equation for $p_0$ comes from $m_1 - p_0 = - \alpha \bar \pi$ 130 | 131 | ## Limiting values of inflation rate 132 | 133 | As in our earlier lecture {doc}`money_inflation_nonlinear`, we can compute the two prospective limiting values for $\bar \pi$ by studying the steady-state Laffer curve. 134 | 135 | Thus, in a **steady state** 136 | 137 | $$ 138 | m_{t+1} - m_t = p_{t+1} - p_t = x \quad \forall t , 139 | $$ 140 | 141 | where $x > 0 $ is a common rate of growth of logarithms of the money supply and price level. 142 | 143 | A few lines of algebra yields the following equation that $x$ satisfies 144 | 145 | $$ 146 | \exp(-\alpha x) - \exp(-(1 + \alpha) x) = g 147 | $$ (eq:ada_steadypi) 148 | 149 | where we require that 150 | 151 | $$ 152 | g \leq \max_{x: x \geq 0} \exp(-\alpha x) - \exp(-(1 + \alpha) x) , 153 | $$ (eq:ada_revmax) 154 | 155 | so that it is feasible to finance $g$ by printing money. 156 | 157 | The left side of {eq}`eq:ada_steadypi` is steady state revenue raised by printing money. 158 | 159 | The right side of {eq}`eq:ada_steadypi` is the quantity of time $t$ goods that the government raises by printing money. 160 | 161 | Soon we'll plot the left and right sides of equation {eq}`eq:ada_steadypi`. 162 | 163 | But first we'll write code that computes a steady-state 164 | $\bar \pi$. 165 | 166 | Let's start by importing some libraries 167 | 168 | ```{code-cell} ipython3 169 | from collections import namedtuple 170 | import numpy as np 171 | import matplotlib.pyplot as plt 172 | from matplotlib.ticker import MaxNLocator 173 | from matplotlib.cm import get_cmap 174 | from matplotlib.colors import to_rgba 175 | import matplotlib 176 | from scipy.optimize import root, fsolve 177 | ``` 178 | 179 | +++ {"user_expressions": []} 180 | 181 | Let's create a `namedtuple` to store the parameters of the model 182 | 183 | ```{code-cell} ipython3 184 | LafferAdaptive = namedtuple('LafferAdaptive', 185 | ["m0", # log of the money supply at t=0 186 | "α", # sensitivity of money demand 187 | "g", # government expenditure 188 | "δ"]) 189 | 190 | # Create a Cagan Laffer model 191 | def create_model(α=0.5, m0=np.log(100), g=0.35, δ=0.9): 192 | return LafferAdaptive(α=α, m0=m0, g=g, δ=δ) 193 | 194 | model = create_model() 195 | ``` 196 | 197 | Now we write code that computes steady-state $\bar \pi$s. 198 | 199 | ```{code-cell} ipython3 200 | # Define formula for π_bar 201 | def solve_π(x, α, g): 202 | return np.exp(-α * x) - np.exp(-(1 + α) * x) - g 203 | 204 | def solve_π_bar(model, x0): 205 | π_bar = fsolve(solve_π, x0=x0, xtol=1e-10, args=(model.α, model.g))[0] 206 | return π_bar 207 | 208 | # Solve for the two steady state of π 209 | π_l = solve_π_bar(model, x0=0.6) 210 | π_u = solve_π_bar(model, x0=3.0) 211 | print(f'The two steady state of π are: {π_l, π_u}') 212 | ``` 213 | 214 | We find two steady state $\bar \pi$ values 215 | 216 | ## Steady-state Laffer curve 217 | 218 | The following figure plots the steady-state Laffer curve together with the two stationary inflation rates. 219 | 220 | ```{code-cell} ipython3 221 | --- 222 | mystnb: 223 | figure: 224 | caption: Seigniorage as function of steady-state inflation. The dashed brown lines 225 | indicate $\pi_l$ and $\pi_u$. 226 | name: laffer_curve_adaptive 227 | width: 500px 228 | --- 229 | def compute_seign(x, α): 230 | return np.exp(-α * x) - np.exp(-(1 + α) * x) 231 | 232 | def plot_laffer(model, πs): 233 | α, g = model.α, model.g 234 | 235 | # Generate π values 236 | x_values = np.linspace(0, 5, 1000) 237 | 238 | # Compute corresponding seigniorage values for the function 239 | y_values = compute_seign(x_values, α) 240 | 241 | # Plot the function 242 | plt.plot(x_values, y_values, 243 | label=f'$exp((-{α})x) - exp(- (1- {α}) x)$') 244 | for π, label in zip(πs, ['$\pi_l$', '$\pi_u$']): 245 | plt.text(π, plt.gca().get_ylim()[0]*2, 246 | label, horizontalalignment='center', 247 | color='brown', size=10) 248 | plt.axvline(π, color='brown', linestyle='--') 249 | plt.axhline(g, color='red', linewidth=0.5, 250 | linestyle='--', label='g') 251 | plt.xlabel('$\pi$') 252 | plt.ylabel('seigniorage') 253 | plt.legend() 254 | plt.grid(True) 255 | plt.show() 256 | 257 | # Steady state Laffer curve 258 | plot_laffer(model, (π_l, π_u)) 259 | ``` 260 | 261 | ## Associated initial price levels 262 | 263 | Now that we have our hands on the two possible steady states, we can compute two initial log price levels $p_{-1}$, which as initial conditions, imply that $\pi_t = \bar \pi $ for all $t \geq 0$. 264 | 265 | In particular, to initiate a fixed point of the dynamic Laffer curve dynamics, we set 266 | 267 | $$ 268 | p_{-1} = m_0 + \alpha \pi^* 269 | $$ 270 | 271 | ```{code-cell} ipython3 272 | def solve_p_init(model, π_star): 273 | m0, α = model.m0, model.α 274 | return m0 + α*π_star 275 | 276 | 277 | # Compute two initial price levels associated with π_l and π_u 278 | p_l, p_u = map(lambda π: solve_p_init(model, π), (π_l, π_u)) 279 | print('Associated initial p_{-1}s', f'are: {p_l, p_u}') 280 | ``` 281 | 282 | ### Verification 283 | 284 | To start, let's write some code to verify that if we initial $\pi_{-1}^*,p_{-1}$ appropriately, the inflation rate $\pi_t$ will be constant for all $t \geq 0$ (at either $\pi_u$ or $\pi_l$ depending on the initial condition) 285 | 286 | The following code verifies this. 287 | 288 | ```{code-cell} ipython3 289 | def solve_laffer_adapt(p_init, π_init, model, num_steps): 290 | m0, α, δ, g = model.m0, model.α, model.δ, model.g 291 | 292 | m_seq = np.nan * np.ones(num_steps+1) 293 | π_seq = np.nan * np.ones(num_steps) 294 | p_seq = np.nan * np.ones(num_steps) 295 | μ_seq = np.nan * np.ones(num_steps) 296 | 297 | m_seq[1] = m0 298 | π_seq[0] = π_init 299 | p_seq[0] = p_init 300 | 301 | for t in range(1, num_steps): 302 | # Solve p_t 303 | def p_t(pt): 304 | return np.log(np.exp(m_seq[t]) + g * np.exp(pt)) \ 305 | - pt + α * ((1-δ)*(pt - p_seq[t-1]) + δ*π_seq[t-1]) 306 | 307 | p_seq[t] = root(fun=p_t, x0=p_seq[t-1]).x[0] 308 | 309 | # Solve π_t 310 | π_seq[t] = (1-δ) * (p_seq[t]-p_seq[t-1]) + δ*π_seq[t-1] 311 | 312 | # Solve m_t 313 | m_seq[t+1] = np.log(np.exp(m_seq[t]) + g*np.exp(p_seq[t])) 314 | 315 | # Solve μ_t 316 | μ_seq[t] = m_seq[t+1] - m_seq[t] 317 | 318 | return π_seq, μ_seq, m_seq, p_seq 319 | ``` 320 | 321 | Compute limiting values starting from $p_{-1}$ associated with $\pi_l$ 322 | 323 | ```{code-cell} ipython3 324 | π_seq, μ_seq, m_seq, p_seq = solve_laffer_adapt(p_l, π_l, model, 50) 325 | 326 | # Check steady state m_{t+1} - m_t and p_{t+1} - p_t 327 | print('m_{t+1} - m_t:', m_seq[-1] - m_seq[-2]) 328 | print('p_{t+1} - p_t:', p_seq[-1] - p_seq[-2]) 329 | 330 | # Check if exp(-αx) - exp(-(1 + α)x) = g 331 | eq_g = lambda x: np.exp(-model.α * x) - np.exp(-(1 + model.α) * x) 332 | 333 | print('eq_g == g:', np.isclose(eq_g(m_seq[-1] - m_seq[-2]), model.g)) 334 | ``` 335 | 336 | Compute limiting values starting from $p_{-1}$ associated with $\pi_u$ 337 | 338 | ```{code-cell} ipython3 339 | π_seq, μ_seq, m_seq, p_seq = solve_laffer_adapt(p_u, π_u, model, 50) 340 | 341 | # Check steady state m_{t+1} - m_t and p_{t+1} - p_t 342 | print('m_{t+1} - m_t:', m_seq[-1] - m_seq[-2]) 343 | print('p_{t+1} - p_t:', p_seq[-1] - p_seq[-2]) 344 | 345 | # Check if exp(-αx) - exp(-(1 + α)x) = g 346 | eq_g = lambda x: np.exp(-model.α * x) - np.exp(-(1 + model.α) * x) 347 | 348 | print('eq_g == g:', np.isclose(eq_g(m_seq[-1] - m_seq[-2]), model.g)) 349 | ``` 350 | 351 | ## Slippery side of Laffer curve dynamics 352 | 353 | We are now equipped to compute time series starting from different $p_{-1}, \pi_{-1}^*$ settings, analogous to those in this lecture {doc}`money_inflation` and this lecture {doc}`money_inflation_nonlinear`. 354 | 355 | Now we'll study how outcomes unfold when we start $p_{-1}, \pi_{-1}^*$ away from a stationary point of the dynamic Laffer curve, i.e., away from either $\pi_u$ or $ \pi_l$. 356 | 357 | To construct a perturbation pair $\check p_{-1}, \check \pi_{-1}^*$we'll implement the following pseudo code: 358 | 359 | * set $\check \pi_{-1}^* $ not equal to one of the stationary points $\pi_u$ or $ \pi_l$. 360 | * set $\check p_{-1} = m_0 + \alpha \check \pi_{-1}^*$ 361 | 362 | ```{code-cell} ipython3 363 | :tags: [hide-cell] 364 | 365 | def draw_iterations(π0s, model, line_params, π_bars, num_steps): 366 | fig, axes = plt.subplots(4, 1, figsize=(8, 12), sharex=True) 367 | 368 | for ax in axes[:2]: 369 | ax.set_yscale('log') 370 | 371 | for i, π0 in enumerate(π0s): 372 | p0 = model.m0 + model.α*π0 373 | π_seq, μ_seq, m_seq, p_seq = solve_laffer_adapt(p0, π0, model, num_steps) 374 | 375 | axes[0].plot(np.arange(num_steps), m_seq[1:], **line_params) 376 | axes[1].plot(np.arange(-1, num_steps-1), p_seq, **line_params) 377 | axes[2].plot(np.arange(-1, num_steps-1), π_seq, **line_params) 378 | axes[3].plot(np.arange(num_steps), μ_seq, **line_params) 379 | 380 | axes[2].axhline(y=π_bars[0], color='grey', linestyle='--', lw=1.5, alpha=0.6) 381 | axes[2].axhline(y=π_bars[1], color='grey', linestyle='--', lw=1.5, alpha=0.6) 382 | axes[2].text(num_steps * 1.07, π_bars[0], r'$\pi_l$', verticalalignment='center', 383 | color='grey', size=10) 384 | axes[2].text(num_steps * 1.07, π_bars[1], r'$\pi_u$', verticalalignment='center', 385 | color='grey', size=10) 386 | 387 | axes[0].set_ylabel('$m_t$') 388 | axes[1].set_ylabel('$p_t$') 389 | axes[2].set_ylabel(r'$\pi_t$') 390 | axes[3].set_ylabel(r'$\mu_t$') 391 | axes[3].set_xlabel('timestep') 392 | axes[3].xaxis.set_major_locator(MaxNLocator(integer=True)) 393 | 394 | plt.tight_layout() 395 | plt.show() 396 | ``` 397 | 398 | Let's simulate the result generated by varying the initial $\pi_{-1}$ and corresponding $p_{-1}$ 399 | 400 | ```{code-cell} ipython3 401 | --- 402 | mystnb: 403 | figure: 404 | caption: Starting from different initial values of $\pi_0$, paths of $m_t$ (top 405 | panel, log scale for $m$), $p_t$ (second panel, log scale for $p$), $\pi_t$ (third panel), and $\mu_t$ (bottom 406 | panel) 407 | name: pi0_path 408 | width: 500px 409 | --- 410 | πs = np.linspace(π_l, π_u, 10) 411 | 412 | line_params = {'lw': 1.5, 413 | 'marker': 'o', 414 | 'markersize': 3} 415 | 416 | π_bars = (π_l, π_u) 417 | draw_iterations(πs, model, line_params, π_bars, num_steps=80) 418 | ``` 419 | -------------------------------------------------------------------------------- /lectures/schelling.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | text_representation: 4 | extension: .md 5 | format_name: myst 6 | format_version: 0.13 7 | jupytext_version: 1.14.1 8 | kernelspec: 9 | display_name: Python 3 (ipykernel) 10 | language: python 11 | name: python3 12 | --- 13 | 14 | (schelling)= 15 | ```{raw} html 16 |
17 | 18 | QuantEcon 19 | 20 |
21 | ``` 22 | 23 | # Racial Segregation 24 | 25 | ```{index} single: Schelling Segregation Model 26 | ``` 27 | 28 | ```{index} single: Models; Schelling's Segregation Model 29 | ``` 30 | 31 | ## Outline 32 | 33 | In 1969, Thomas C. Schelling developed a simple but striking model of racial 34 | segregation {cite}`Schelling1969`. 35 | 36 | His model studies the dynamics of racially mixed neighborhoods. 37 | 38 | Like much of Schelling's work, the model shows how local interactions can lead 39 | to surprising aggregate outcomes. 40 | 41 | It studies a setting where agents (think of households) have relatively mild 42 | preference for neighbors of the same race. 43 | 44 | For example, these agents might be comfortable with a mixed race neighborhood 45 | but uncomfortable when they feel "surrounded" by people from a different race. 46 | 47 | Schelling illustrated the follow surprising result: in such a setting, mixed 48 | race neighborhoods are likely to be unstable, tending to collapse over time. 49 | 50 | In fact the model predicts strongly divided neighborhoods, with high levels of 51 | segregation. 52 | 53 | In other words, extreme segregation outcomes arise even though people's 54 | preferences are not particularly extreme. 55 | 56 | These extreme outcomes happen because of *interactions* between agents in the 57 | model (e.g., households in a city) that drive self-reinforcing dynamics in the 58 | model. 59 | 60 | These ideas will become clearer as the lecture unfolds. 61 | 62 | In recognition of his work on segregation and other research, Schelling was 63 | awarded the 2005 Nobel Prize in Economic Sciences (joint with Robert Aumann). 64 | 65 | 66 | Let's start with some imports: 67 | 68 | ```{code-cell} ipython3 69 | import matplotlib.pyplot as plt 70 | from random import uniform, seed 71 | from math import sqrt 72 | import numpy as np 73 | ``` 74 | 75 | ## The model 76 | 77 | In this section we will build a version of Schelling's model. 78 | 79 | ### Set-Up 80 | 81 | We will cover a variation of Schelling's model that is different from the 82 | original but also easy to program and, at the same time, captures his main 83 | idea. 84 | 85 | Suppose we have two types of people: orange people and green people. 86 | 87 | Assume there are $n$ of each type. 88 | 89 | These agents all live on a single unit square. 90 | 91 | Thus, the location (e.g, address) of an agent is just a point $(x, y)$, where 92 | $0 < x, y < 1$. 93 | 94 | * The set of all points $(x,y)$ satisfying $0 < x, y < 1$ is called the **unit square** 95 | * Below we denote the unit square by $S$ 96 | 97 | +++ 98 | 99 | ### Preferences 100 | 101 | We will say that an agent is *happy* if 5 or more of her 10 nearest neighbors are of the same type. 102 | 103 | An agent who is not happy is called *unhappy*. 104 | 105 | For example, 106 | 107 | * if an agent is orange and 5 of her 10 nearest neighbors are orange, then she is happy. 108 | * if an agent is green and 8 of her 10 nearest neighbors are orange, then she is unhappy. 109 | 110 | 'Nearest' is in terms of [Euclidean distance](https://en.wikipedia.org/wiki/Euclidean_distance). 111 | 112 | An important point to note is that agents are **not** averse to living in mixed areas. 113 | 114 | They are perfectly happy if half of their neighbors are of the other color. 115 | 116 | +++ 117 | 118 | ### Behavior 119 | 120 | Initially, agents are mixed together (integrated). 121 | 122 | In particular, we assume that the initial location of each agent is an 123 | independent draw from a bivariate uniform distribution on the unit square $S$. 124 | 125 | * First their $x$ coordinate is drawn from a uniform distribution on $(0,1)$ 126 | * Then, independently, their $y$ coordinate is drawn from the same distribution. 127 | 128 | Now, cycling through the set of all agents, each agent is now given the chance to stay or move. 129 | 130 | Each agent stays if they are happy and moves if they are unhappy. 131 | 132 | The algorithm for moving is as follows 133 | 134 | ```{prf:algorithm} Jump Chain Algorithm 135 | :label: move_algo 136 | 137 | 1. Draw a random location in $S$ 138 | 1. If happy at new location, move there 139 | 1. Otherwise, go to step 1 140 | 141 | ``` 142 | 143 | We cycle continuously through the agents, each time allowing an unhappy agent 144 | to move. 145 | 146 | We continue to cycle until no one wishes to move. 147 | 148 | +++ 149 | 150 | ## Results 151 | 152 | Let's now implement and run this simulation. 153 | 154 | In what follows, agents are modeled as [objects](https://python-programming.quantecon.org/python_oop.html). 155 | 156 | Here's an indication of their structure: 157 | 158 | ```{code-block} none 159 | * Data: 160 | 161 | * type (green or orange) 162 | * location 163 | 164 | * Methods: 165 | 166 | * determine whether happy or not given locations of other agents 167 | * If not happy, move 168 | * find a new location where happy 169 | ``` 170 | 171 | Let's build them. 172 | 173 | ```{code-cell} ipython3 174 | class Agent: 175 | 176 | def __init__(self, type): 177 | self.type = type 178 | self.draw_location() 179 | 180 | def draw_location(self): 181 | self.location = uniform(0, 1), uniform(0, 1) 182 | 183 | def get_distance(self, other): 184 | "Computes the euclidean distance between self and other agent." 185 | a = (self.location[0] - other.location[0])**2 186 | b = (self.location[1] - other.location[1])**2 187 | return sqrt(a + b) 188 | 189 | def happy(self, 190 | agents, # List of other agents 191 | num_neighbors=10, # No. of agents viewed as neighbors 192 | require_same_type=5): # How many neighbors must be same type 193 | """ 194 | True if a sufficient number of nearest neighbors are of the same 195 | type. 196 | """ 197 | 198 | distances = [] 199 | 200 | # Distances is a list of pairs (d, agent), where d is distance from 201 | # agent to self 202 | for agent in agents: 203 | if self != agent: 204 | distance = self.get_distance(agent) 205 | distances.append((distance, agent)) 206 | 207 | # Sort from smallest to largest, according to distance 208 | distances.sort() 209 | 210 | # Extract the neighboring agents 211 | neighbors = [agent for d, agent in distances[:num_neighbors]] 212 | 213 | # Count how many neighbors have the same type as self 214 | num_same_type = sum(self.type == agent.type for agent in neighbors) 215 | return num_same_type >= require_same_type 216 | 217 | def update(self, agents): 218 | "If not happy, then randomly choose new locations until happy." 219 | while not self.happy(agents): 220 | self.draw_location() 221 | ``` 222 | 223 | Here's some code that takes a list of agents and produces a plot showing their 224 | locations on the unit square. 225 | 226 | Orange agents are represented by orange dots and green ones are represented by 227 | green dots. 228 | 229 | ```{code-cell} ipython3 230 | def plot_distribution(agents, cycle_num): 231 | "Plot the distribution of agents after cycle_num rounds of the loop." 232 | x_values_0, y_values_0 = [], [] 233 | x_values_1, y_values_1 = [], [] 234 | # == Obtain locations of each type == # 235 | for agent in agents: 236 | x, y = agent.location 237 | if agent.type == 0: 238 | x_values_0.append(x) 239 | y_values_0.append(y) 240 | else: 241 | x_values_1.append(x) 242 | y_values_1.append(y) 243 | fig, ax = plt.subplots() 244 | plot_args = {'markersize': 8, 'alpha': 0.8} 245 | ax.set_facecolor('azure') 246 | ax.plot(x_values_0, y_values_0, 247 | 'o', markerfacecolor='orange', **plot_args) 248 | ax.plot(x_values_1, y_values_1, 249 | 'o', markerfacecolor='green', **plot_args) 250 | ax.set_title(f'Cycle {cycle_num-1}') 251 | plt.show() 252 | ``` 253 | 254 | And here's some pseudocode for the main loop, where we cycle through the 255 | agents until no one wishes to move. 256 | 257 | The pseudocode is 258 | 259 | ```{code-block} none 260 | plot the distribution 261 | while agents are still moving 262 | for agent in agents 263 | give agent the opportunity to move 264 | plot the distribution 265 | ``` 266 | 267 | The real code is below 268 | 269 | ```{code-cell} ipython3 270 | def run_simulation(num_of_type_0=600, 271 | num_of_type_1=600, 272 | max_iter=100_000, # Maximum number of iterations 273 | set_seed=1234): 274 | 275 | # Set the seed for reproducibility 276 | seed(set_seed) 277 | 278 | # Create a list of agents of type 0 279 | agents = [Agent(0) for i in range(num_of_type_0)] 280 | # Append a list of agents of type 1 281 | agents.extend(Agent(1) for i in range(num_of_type_1)) 282 | 283 | # Initialize a counter 284 | count = 1 285 | 286 | # Plot the initial distribution 287 | plot_distribution(agents, count) 288 | 289 | # Loop until no agent wishes to move 290 | while count < max_iter: 291 | print('Entering loop ', count) 292 | count += 1 293 | no_one_moved = True 294 | for agent in agents: 295 | old_location = agent.location 296 | agent.update(agents) 297 | if agent.location != old_location: 298 | no_one_moved = False 299 | if no_one_moved: 300 | break 301 | 302 | # Plot final distribution 303 | plot_distribution(agents, count) 304 | 305 | if count < max_iter: 306 | print(f'Converged after {count} iterations.') 307 | else: 308 | print('Hit iteration bound and terminated.') 309 | 310 | ``` 311 | 312 | Let's have a look at the results. 313 | 314 | ```{code-cell} ipython3 315 | run_simulation() 316 | ``` 317 | 318 | As discussed above, agents are initially mixed randomly together. 319 | 320 | But after several cycles, they become segregated into distinct regions. 321 | 322 | In this instance, the program terminated after a small number of cycles 323 | through the set of agents, indicating that all agents had reached a state of 324 | happiness. 325 | 326 | What is striking about the pictures is how rapidly racial integration breaks down. 327 | 328 | This is despite the fact that people in the model don't actually mind living mixed with the other type. 329 | 330 | Even with these preferences, the outcome is a high degree of segregation. 331 | 332 | 333 | 334 | ## Exercises 335 | 336 | ```{exercise-start} 337 | :label: schelling_ex1 338 | ``` 339 | 340 | The object oriented style that we used for coding above is neat but harder to 341 | optimize than procedural code (i.e., code based around functions rather than 342 | objects and methods). 343 | 344 | Try writing a new version of the model that stores 345 | 346 | * the locations of all agents as a 2D NumPy array of floats. 347 | * the types of all agents as a flat NumPy array of integers. 348 | 349 | Write functions that act on this data to update the model using the logic 350 | similar to that described above. 351 | 352 | However, implement the following two changes: 353 | 354 | 1. Agents are offered a move at random (i.e., selected randomly and given the 355 | opportunity to move). 356 | 2. After an agent has moved, flip their type with probability 0.01 357 | 358 | The second change introduces extra randomness into the model. 359 | 360 | (We can imagine that, every so often, an agent moves to a different city and, 361 | with small probability, is replaced by an agent of the other type.) 362 | 363 | ```{exercise-end} 364 | ``` 365 | 366 | ```{solution-start} schelling_ex1 367 | :class: dropdown 368 | ``` 369 | solution here 370 | 371 | ```{code-cell} ipython3 372 | from numpy.random import uniform, randint 373 | 374 | n = 1000 # number of agents (agents = 0, ..., n-1) 375 | k = 10 # number of agents regarded as neighbors 376 | require_same_type = 5 # want >= require_same_type neighbors of the same type 377 | 378 | def initialize_state(): 379 | locations = uniform(size=(n, 2)) 380 | types = randint(0, high=2, size=n) # label zero or one 381 | return locations, types 382 | 383 | 384 | def compute_distances_from_loc(loc, locations): 385 | """ Compute distance from location loc to all other points. """ 386 | return np.linalg.norm(loc - locations, axis=1) 387 | 388 | def get_neighbors(loc, locations): 389 | " Get all neighbors of a given location. " 390 | all_distances = compute_distances_from_loc(loc, locations) 391 | indices = np.argsort(all_distances) # sort agents by distance to loc 392 | neighbors = indices[:k] # keep the k closest ones 393 | return neighbors 394 | 395 | def is_happy(i, locations, types): 396 | happy = True 397 | agent_loc = locations[i, :] 398 | agent_type = types[i] 399 | neighbors = get_neighbors(agent_loc, locations) 400 | neighbor_types = types[neighbors] 401 | if sum(neighbor_types == agent_type) < require_same_type: 402 | happy = False 403 | return happy 404 | 405 | def count_happy(locations, types): 406 | " Count the number of happy agents. " 407 | happy_sum = 0 408 | for i in range(n): 409 | happy_sum += is_happy(i, locations, types) 410 | return happy_sum 411 | 412 | def update_agent(i, locations, types): 413 | " Move agent if unhappy. " 414 | moved = False 415 | while not is_happy(i, locations, types): 416 | moved = True 417 | locations[i, :] = uniform(), uniform() 418 | return moved 419 | 420 | def plot_distribution(locations, types, title, savepdf=False): 421 | " Plot the distribution of agents after cycle_num rounds of the loop." 422 | fig, ax = plt.subplots() 423 | colors = 'orange', 'green' 424 | for agent_type, color in zip((0, 1), colors): 425 | idx = (types == agent_type) 426 | ax.plot(locations[idx, 0], 427 | locations[idx, 1], 428 | 'o', 429 | markersize=8, 430 | markerfacecolor=color, 431 | alpha=0.8) 432 | ax.set_title(title) 433 | plt.show() 434 | 435 | def sim_random_select(max_iter=100_000, flip_prob=0.01, test_freq=10_000): 436 | """ 437 | Simulate by randomly selecting one household at each update. 438 | 439 | Flip the color of the household with probability `flip_prob`. 440 | 441 | """ 442 | 443 | locations, types = initialize_state() 444 | current_iter = 0 445 | 446 | while current_iter <= max_iter: 447 | 448 | # Choose a random agent and update them 449 | i = randint(0, n) 450 | moved = update_agent(i, locations, types) 451 | 452 | if flip_prob > 0: 453 | # flip agent i's type with probability epsilon 454 | U = uniform() 455 | if U < flip_prob: 456 | current_type = types[i] 457 | types[i] = 0 if current_type == 1 else 1 458 | 459 | # Every so many updates, plot and test for convergence 460 | if current_iter % test_freq == 0: 461 | cycle = current_iter / n 462 | plot_distribution(locations, types, f'iteration {current_iter}') 463 | if count_happy(locations, types) == n: 464 | print(f"Converged at iteration {current_iter}") 465 | break 466 | 467 | current_iter += 1 468 | 469 | if current_iter > max_iter: 470 | print(f"Terminating at iteration {current_iter}") 471 | ``` 472 | 473 | ```{solution-end} 474 | ``` 475 | 476 | +++ 477 | 478 | When we run this we again find that mixed neighborhoods break down and segregation emerges. 479 | 480 | Here's a sample run. 481 | 482 | ```{code-cell} ipython3 483 | sim_random_select(max_iter=50_000, flip_prob=0.01, test_freq=10_000) 484 | ``` 485 | 486 | ```{code-cell} ipython3 487 | 488 | ``` 489 | -------------------------------------------------------------------------------- /lectures/short_path.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | text_representation: 4 | extension: .md 5 | format_name: myst 6 | kernelspec: 7 | display_name: Python 3 8 | language: python 9 | name: python3 10 | --- 11 | 12 | (short_path)= 13 | ```{raw} html 14 |
15 | 16 | QuantEcon 17 | 18 |
19 | ``` 20 | 21 | # Shortest Paths 22 | 23 | ```{index} single: Dynamic Programming; Shortest Paths 24 | ``` 25 | 26 | ## Overview 27 | 28 | The shortest path problem is a [classic problem](https://en.wikipedia.org/wiki/Shortest_path) in mathematics and computer science with applications in 29 | 30 | * Economics (sequential decision making, analysis of social networks, etc.) 31 | * Operations research and transportation 32 | * Robotics and artificial intelligence 33 | * Telecommunication network design and routing 34 | * etc., etc. 35 | 36 | Variations of the methods we discuss in this lecture are used millions of times every day, in applications such as 37 | 38 | * Google Maps 39 | * routing packets on the internet 40 | 41 | For us, the shortest path problem also provides a nice introduction to the logic of **dynamic programming**. 42 | 43 | Dynamic programming is an extremely powerful optimization technique that we apply in many lectures on this site. 44 | 45 | The only scientific library we'll need in what follows is NumPy: 46 | 47 | ```{code-cell} python3 48 | import numpy as np 49 | ``` 50 | 51 | ## Outline of the problem 52 | 53 | The shortest path problem is one of finding how to traverse a [graph](https://en.wikipedia.org/wiki/Graph_%28mathematics%29) from one specified node to another at minimum cost. 54 | 55 | Consider the following graph 56 | 57 | ```{figure} /_static/lecture_specific/short_path/graph.png 58 | 59 | ``` 60 | 61 | We wish to travel from node (vertex) A to node G at minimum cost 62 | 63 | * Arrows (edges) indicate the movements we can take. 64 | * Numbers on edges indicate the cost of traveling that edge. 65 | 66 | (Graphs such as the one above are called weighted [directed graphs](https://en.wikipedia.org/wiki/Directed_graph).) 67 | 68 | Possible interpretations of the graph include 69 | 70 | * Minimum cost for supplier to reach a destination. 71 | * Routing of packets on the internet (minimize time). 72 | * etc., etc. 73 | 74 | For this simple graph, a quick scan of the edges shows that the optimal paths are 75 | 76 | * A, C, F, G at cost 8 77 | 78 | ```{figure} /_static/lecture_specific/short_path/graph4.png 79 | 80 | ``` 81 | 82 | * A, D, F, G at cost 8 83 | 84 | ```{figure} /_static/lecture_specific/short_path/graph3.png 85 | 86 | ``` 87 | 88 | ## Finding least-cost paths 89 | 90 | For large graphs, we need a systematic solution. 91 | 92 | Let $J(v)$ denote the minimum cost-to-go from node $v$, understood as the total cost from $v$ if we take the best route. 93 | 94 | Suppose that we know $J(v)$ for each node $v$, as shown below for the graph from the preceding example. 95 | 96 | ```{figure} /_static/lecture_specific/short_path/graph2.png 97 | 98 | ``` 99 | 100 | Note that $J(G) = 0$. 101 | 102 | The best path can now be found as follows 103 | 104 | 1. Start at node $v = A$ 105 | 1. From current node $v$, move to any node that solves 106 | 107 | ```{math} 108 | :label: spprebell 109 | 110 | \min_{w \in F_v} \{ c(v, w) + J(w) \} 111 | ``` 112 | 113 | where 114 | 115 | * $F_v$ is the set of nodes that can be reached from $v$ in one step. 116 | * $c(v, w)$ is the cost of traveling from $v$ to $w$. 117 | 118 | Hence, if we know the function $J$, then finding the best path is almost trivial. 119 | 120 | But how can we find the cost-to-go function $J$? 121 | 122 | Some thought will convince you that, for every node $v$, 123 | the function $J$ satisfies 124 | 125 | ```{math} 126 | :label: spbell 127 | 128 | J(v) = \min_{w \in F_v} \{ c(v, w) + J(w) \} 129 | ``` 130 | 131 | This is known as the **Bellman equation**, after the mathematician [Richard Bellman](https://en.wikipedia.org/wiki/Richard_E._Bellman). 132 | 133 | The Bellman equation can be thought of as a restriction that $J$ must 134 | satisfy. 135 | 136 | What we want to do now is use this restriction to compute $J$. 137 | 138 | ## Solving for minimum cost-to-go 139 | 140 | Let's look at an algorithm for computing $J$ and then think about how to 141 | implement it. 142 | 143 | ### The algorithm 144 | 145 | The standard algorithm for finding $J$ is to start an initial guess and then iterate. 146 | 147 | This is a standard approach to solving nonlinear equations, often called 148 | the method of **successive approximations**. 149 | 150 | Our initial guess will be 151 | 152 | ```{math} 153 | :label: spguess 154 | 155 | J_0(v) = 0 \text{ for all } v 156 | ``` 157 | 158 | Now 159 | 160 | 1. Set $n = 0$ 161 | 1. Set $J_{n+1} (v) = \min_{w \in F_v} \{ c(v, w) + J_n(w) \}$ for all $v$ 162 | 1. If $J_{n+1}$ and $J_n$ are not equal then increment $n$, go to 2 163 | 164 | This sequence converges to $J$. 165 | 166 | Although we omit the proof, we'll prove similar claims in our other lectures 167 | on dynamic programming. 168 | 169 | ### Implementation 170 | 171 | Having an algorithm is a good start, but we also need to think about how to 172 | implement it on a computer. 173 | 174 | First, for the cost function $c$, we'll implement it as a matrix 175 | $Q$, where a typical element is 176 | 177 | $$ 178 | Q(v, w) 179 | = 180 | \begin{cases} 181 | & c(v, w) \text{ if } w \in F_v \\ 182 | & +\infty \text{ otherwise } 183 | \end{cases} 184 | $$ 185 | 186 | In this context $Q$ is usually called the **distance matrix**. 187 | 188 | We're also numbering the nodes now, with $A = 0$, so, for example 189 | 190 | $$ 191 | Q(1, 2) 192 | = 193 | \text{ the cost of traveling from B to C } 194 | $$ 195 | 196 | For example, for the simple graph above, we set 197 | 198 | ```{code-cell} python3 199 | from numpy import inf 200 | 201 | Q = np.array([[inf, 1, 5, 3, inf, inf, inf], 202 | [inf, inf, inf, 9, 6, inf, inf], 203 | [inf, inf, inf, inf, inf, 2, inf], 204 | [inf, inf, inf, inf, inf, 4, 8], 205 | [inf, inf, inf, inf, inf, inf, 4], 206 | [inf, inf, inf, inf, inf, inf, 1], 207 | [inf, inf, inf, inf, inf, inf, 0]]) 208 | ``` 209 | 210 | Notice that the cost of staying still (on the principle diagonal) is set to 211 | 212 | * `np.inf` for non-destination nodes --- moving on is required. 213 | * 0 for the destination node --- here is where we stop. 214 | 215 | For the sequence of approximations $\{J_n\}$ of the cost-to-go functions, we can use NumPy arrays. 216 | 217 | Let's try with this example and see how we go: 218 | 219 | ```{code-cell} python3 220 | nodes = range(7) # Nodes = 0, 1, ..., 6 221 | J = np.zeros_like(nodes, dtype=int) # Initial guess 222 | next_J = np.empty_like(nodes, dtype=int) # Stores updated guess 223 | 224 | max_iter = 500 225 | i = 0 226 | 227 | while i < max_iter: 228 | for v in nodes: 229 | # Minimize Q[v, w] + J[w] over all choices of w 230 | next_J[v] = np.min(Q[v, :] + J) 231 | 232 | if np.array_equal(next_J, J): 233 | break 234 | 235 | J[:] = next_J # Copy contents of next_J to J 236 | i += 1 237 | 238 | print("The cost-to-go function is", J) 239 | ``` 240 | 241 | This matches with the numbers we obtained by inspection above. 242 | 243 | But, importantly, we now have a methodology for tackling large graphs. 244 | 245 | ## Exercises 246 | 247 | 248 | ```{exercise-start} 249 | :label: short_path_ex1 250 | ``` 251 | 252 | The text below describes a weighted directed graph. 253 | 254 | The line `node0, node1 0.04, node8 11.11, node14 72.21` means that from node0 we can go to 255 | 256 | * node1 at cost 0.04 257 | * node8 at cost 11.11 258 | * node14 at cost 72.21 259 | 260 | No other nodes can be reached directly from node0. 261 | 262 | Other lines have a similar interpretation. 263 | 264 | Your task is to use the algorithm given above to find the optimal path and its cost. 265 | 266 | ```{note} 267 | You will be dealing with floating point numbers now, rather than 268 | integers, so consider replacing `np.equal()` with `np.allclose()`. 269 | ``` 270 | 271 | ```{code-cell} python3 272 | %%file graph.txt 273 | node0, node1 0.04, node8 11.11, node14 72.21 274 | node1, node46 1247.25, node6 20.59, node13 64.94 275 | node2, node66 54.18, node31 166.80, node45 1561.45 276 | node3, node20 133.65, node6 2.06, node11 42.43 277 | node4, node75 3706.67, node5 0.73, node7 1.02 278 | node5, node45 1382.97, node7 3.33, node11 34.54 279 | node6, node31 63.17, node9 0.72, node10 13.10 280 | node7, node50 478.14, node9 3.15, node10 5.85 281 | node8, node69 577.91, node11 7.45, node12 3.18 282 | node9, node70 2454.28, node13 4.42, node20 16.53 283 | node10, node89 5352.79, node12 1.87, node16 25.16 284 | node11, node94 4961.32, node18 37.55, node20 65.08 285 | node12, node84 3914.62, node24 34.32, node28 170.04 286 | node13, node60 2135.95, node38 236.33, node40 475.33 287 | node14, node67 1878.96, node16 2.70, node24 38.65 288 | node15, node91 3597.11, node17 1.01, node18 2.57 289 | node16, node36 392.92, node19 3.49, node38 278.71 290 | node17, node76 783.29, node22 24.78, node23 26.45 291 | node18, node91 3363.17, node23 16.23, node28 55.84 292 | node19, node26 20.09, node20 0.24, node28 70.54 293 | node20, node98 3523.33, node24 9.81, node33 145.80 294 | node21, node56 626.04, node28 36.65, node31 27.06 295 | node22, node72 1447.22, node39 136.32, node40 124.22 296 | node23, node52 336.73, node26 2.66, node33 22.37 297 | node24, node66 875.19, node26 1.80, node28 14.25 298 | node25, node70 1343.63, node32 36.58, node35 45.55 299 | node26, node47 135.78, node27 0.01, node42 122.00 300 | node27, node65 480.55, node35 48.10, node43 246.24 301 | node28, node82 2538.18, node34 21.79, node36 15.52 302 | node29, node64 635.52, node32 4.22, node33 12.61 303 | node30, node98 2616.03, node33 5.61, node35 13.95 304 | node31, node98 3350.98, node36 20.44, node44 125.88 305 | node32, node97 2613.92, node34 3.33, node35 1.46 306 | node33, node81 1854.73, node41 3.23, node47 111.54 307 | node34, node73 1075.38, node42 51.52, node48 129.45 308 | node35, node52 17.57, node41 2.09, node50 78.81 309 | node36, node71 1171.60, node54 101.08, node57 260.46 310 | node37, node75 269.97, node38 0.36, node46 80.49 311 | node38, node93 2767.85, node40 1.79, node42 8.78 312 | node39, node50 39.88, node40 0.95, node41 1.34 313 | node40, node75 548.68, node47 28.57, node54 53.46 314 | node41, node53 18.23, node46 0.28, node54 162.24 315 | node42, node59 141.86, node47 10.08, node72 437.49 316 | node43, node98 2984.83, node54 95.06, node60 116.23 317 | node44, node91 807.39, node46 1.56, node47 2.14 318 | node45, node58 79.93, node47 3.68, node49 15.51 319 | node46, node52 22.68, node57 27.50, node67 65.48 320 | node47, node50 2.82, node56 49.31, node61 172.64 321 | node48, node99 2564.12, node59 34.52, node60 66.44 322 | node49, node78 53.79, node50 0.51, node56 10.89 323 | node50, node85 251.76, node53 1.38, node55 20.10 324 | node51, node98 2110.67, node59 23.67, node60 73.79 325 | node52, node94 1471.80, node64 102.41, node66 123.03 326 | node53, node72 22.85, node56 4.33, node67 88.35 327 | node54, node88 967.59, node59 24.30, node73 238.61 328 | node55, node84 86.09, node57 2.13, node64 60.80 329 | node56, node76 197.03, node57 0.02, node61 11.06 330 | node57, node86 701.09, node58 0.46, node60 7.01 331 | node58, node83 556.70, node64 29.85, node65 34.32 332 | node59, node90 820.66, node60 0.72, node71 0.67 333 | node60, node76 48.03, node65 4.76, node67 1.63 334 | node61, node98 1057.59, node63 0.95, node64 4.88 335 | node62, node91 132.23, node64 2.94, node76 38.43 336 | node63, node66 4.43, node72 70.08, node75 56.34 337 | node64, node80 47.73, node65 0.30, node76 11.98 338 | node65, node94 594.93, node66 0.64, node73 33.23 339 | node66, node98 395.63, node68 2.66, node73 37.53 340 | node67, node82 153.53, node68 0.09, node70 0.98 341 | node68, node94 232.10, node70 3.35, node71 1.66 342 | node69, node99 247.80, node70 0.06, node73 8.99 343 | node70, node76 27.18, node72 1.50, node73 8.37 344 | node71, node89 104.50, node74 8.86, node91 284.64 345 | node72, node76 15.32, node84 102.77, node92 133.06 346 | node73, node83 52.22, node76 1.40, node90 243.00 347 | node74, node81 1.07, node76 0.52, node78 8.08 348 | node75, node92 68.53, node76 0.81, node77 1.19 349 | node76, node85 13.18, node77 0.45, node78 2.36 350 | node77, node80 8.94, node78 0.98, node86 64.32 351 | node78, node98 355.90, node81 2.59 352 | node79, node81 0.09, node85 1.45, node91 22.35 353 | node80, node92 121.87, node88 28.78, node98 264.34 354 | node81, node94 99.78, node89 39.52, node92 99.89 355 | node82, node91 47.44, node88 28.05, node93 11.99 356 | node83, node94 114.95, node86 8.75, node88 5.78 357 | node84, node89 19.14, node94 30.41, node98 121.05 358 | node85, node97 94.51, node87 2.66, node89 4.90 359 | node86, node97 85.09 360 | node87, node88 0.21, node91 11.14, node92 21.23 361 | node88, node93 1.31, node91 6.83, node98 6.12 362 | node89, node97 36.97, node99 82.12 363 | node90, node96 23.53, node94 10.47, node99 50.99 364 | node91, node97 22.17 365 | node92, node96 10.83, node97 11.24, node99 34.68 366 | node93, node94 0.19, node97 6.71, node99 32.77 367 | node94, node98 5.91, node96 2.03 368 | node95, node98 6.17, node99 0.27 369 | node96, node98 3.32, node97 0.43, node99 5.87 370 | node97, node98 0.30 371 | node98, node99 0.33 372 | node99, 373 | ``` 374 | 375 | ```{exercise-end} 376 | ``` 377 | 378 | ```{solution-start} short_path_ex1 379 | :class: dropdown 380 | ``` 381 | 382 | First let's write a function that reads in the graph data above and builds a distance matrix. 383 | 384 | ```{code-cell} python3 385 | num_nodes = 100 386 | destination_node = 99 387 | 388 | def map_graph_to_distance_matrix(in_file): 389 | 390 | # First let's set of the distance matrix Q with inf everywhere 391 | Q = np.full((num_nodes, num_nodes), np.inf) 392 | 393 | # Now we read in the data and modify Q 394 | with open(in_file) as infile: 395 | for line in infile: 396 | elements = line.split(',') 397 | node = elements.pop(0) 398 | node = int(node[4:]) # convert node description to integer 399 | if node != destination_node: 400 | for element in elements: 401 | destination, cost = element.split() 402 | destination = int(destination[4:]) 403 | Q[node, destination] = float(cost) 404 | Q[destination_node, destination_node] = 0 405 | return Q 406 | ``` 407 | 408 | In addition, let's write 409 | 410 | 1. a "Bellman operator" function that takes a distance matrix and current guess of J and returns an updated guess of J, and 411 | 1. a function that takes a distance matrix and returns a cost-to-go function. 412 | 413 | We'll use the algorithm described above. 414 | 415 | The minimization step is vectorized to make it faster. 416 | 417 | ```{code-cell} python3 418 | def bellman(J, Q): 419 | return np.min(Q + J, axis=1) 420 | 421 | 422 | def compute_cost_to_go(Q): 423 | num_nodes = Q.shape[0] 424 | J = np.zeros(num_nodes) # Initial guess 425 | max_iter = 500 426 | i = 0 427 | 428 | while i < max_iter: 429 | next_J = bellman(J, Q) 430 | if np.allclose(next_J, J): 431 | break 432 | else: 433 | J[:] = next_J # Copy contents of next_J to J 434 | i += 1 435 | 436 | return(J) 437 | ``` 438 | 439 | We used np.allclose() rather than testing exact equality because we are 440 | dealing with floating point numbers now. 441 | 442 | Finally, here's a function that uses the cost-to-go function to obtain the 443 | optimal path (and its cost). 444 | 445 | ```{code-cell} python3 446 | def print_best_path(J, Q): 447 | sum_costs = 0 448 | current_node = 0 449 | while current_node != destination_node: 450 | print(current_node) 451 | # Move to the next node and increment costs 452 | next_node = np.argmin(Q[current_node, :] + J) 453 | sum_costs += Q[current_node, next_node] 454 | current_node = next_node 455 | 456 | print(destination_node) 457 | print('Cost: ', sum_costs) 458 | ``` 459 | 460 | Okay, now we have the necessary functions, let's call them to do the job we were assigned. 461 | 462 | ```{code-cell} python3 463 | Q = map_graph_to_distance_matrix('graph.txt') 464 | J = compute_cost_to_go(Q) 465 | print_best_path(J, Q) 466 | ``` 467 | 468 | The total cost of the path should agree with $J[0]$ so let's check this. 469 | 470 | ```{code-cell} python3 471 | J[0] 472 | ``` 473 | 474 | ```{solution-end} 475 | ``` -------------------------------------------------------------------------------- /lectures/cagan_adaptive.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | text_representation: 4 | extension: .md 5 | format_name: myst 6 | format_version: 0.13 7 | jupytext_version: 1.14.5 8 | kernelspec: 9 | display_name: Python 3 (ipykernel) 10 | language: python 11 | name: python3 12 | --- 13 | 14 | # Monetarist Theory of Price Levels with Adaptive Expectations 15 | 16 | ## Overview 17 | 18 | 19 | This lecture is a sequel or prequel to {doc}`cagan_ree`. 20 | 21 | We'll use linear algebra to do some experiments with an alternative "monetarist" or "fiscal" theory of price levels. 22 | 23 | Like the model in {doc}`cagan_ree`, the model asserts that when a government persistently spends more than it collects in taxes and prints money to finance the shortfall, it puts upward pressure on the price level and generates persistent inflation. 24 | 25 | Instead of the "perfect foresight" or "rational expectations" version of the model in {doc}`cagan_ree`, our model in the present lecture is an "adaptive expectations" version of a model that {cite}`Cagan` used to study the monetary dynamics of hyperinflations. 26 | 27 | It combines these components: 28 | 29 | * a demand function for real money balances that asserts that the logarithm of the quantity of real balances demanded depends inversely on the public's expected rate of inflation 30 | 31 | * an **adaptive expectations** model that describes how the public's anticipated rate of inflation responds to past values of actual inflation 32 | 33 | * an equilibrium condition that equates the demand for money to the supply 34 | 35 | * an exogenous sequence of rates of growth of the money supply 36 | 37 | Our model stays quite close to Cagan's original specification. 38 | 39 | As in {doc}`pv` and {doc}`cons_smooth`, the only linear algebra operations that we'll be using are matrix multiplication and matrix inversion. 40 | 41 | To facilitate using linear matrix algebra as our principal mathematical tool, we'll use a finite horizon version of 42 | the model. 43 | 44 | ## Structure of the model 45 | 46 | Let 47 | 48 | * $ m_t $ be the log of the supply of nominal money balances; 49 | * $\mu_t = m_{t+1} - m_t $ be the net rate of growth of nominal balances; 50 | * $p_t $ be the log of the price level; 51 | * $\pi_t = p_{t+1} - p_t $ be the net rate of inflation between $t$ and $ t+1$; 52 | * $\pi_t^*$ be the public's expected rate of inflation between $t$ and $t+1$; 53 | * $T$ the horizon -- i.e., the last period for which the model will determine $p_t$ 54 | * $\pi_0^*$ public's initial expected rate of inflation between time $0$ and time $1$. 55 | 56 | 57 | The demand for real balances $\exp\left(m_t^d-p_t\right)$ is governed by the following version of the Cagan demand function 58 | 59 | $$ 60 | m_t^d - p_t = -\alpha \pi_t^* \: , \: \alpha > 0 ; \quad t = 0, 1, \ldots, T . 61 | $$ (eq:caganmd_ad) 62 | 63 | 64 | This equation asserts that the demand for real balances 65 | is inversely related to the public's expected rate of inflation with sensitivity $\alpha$. 66 | 67 | Equating the logarithm $m_t^d$ of the demand for money to the logarithm $m_t$ of the supply of money in equation {eq}`eq:caganmd_ad` and solving for the logarithm $p_t$ 68 | of the price level gives 69 | 70 | $$ 71 | p_t = m_t + \alpha \pi_t^* 72 | $$ (eq:eqfiscth1) 73 | 74 | Taking the difference between equation {eq}`eq:eqfiscth1` at time $t+1$ and at time 75 | $t$ gives 76 | 77 | $$ 78 | \pi_t = \mu_t + \alpha \pi_{t+1}^* - \alpha \pi_t^* 79 | $$ (eq:eqpipi) 80 | 81 | We assume that the expected rate of inflation $\pi_t^*$ is governed 82 | by the following adaptive expectations scheme proposed by {cite}`Friedman1956` and {cite}`Cagan`, where $\lambda\in [0,1]$ denotes the weight on expected inflation. 83 | 84 | $$ 85 | \pi_{t+1}^* = \lambda \pi_t^* + (1 -\lambda) \pi_t 86 | $$ (eq:adaptexpn) 87 | 88 | As exogenous inputs into the model, we take initial conditions $m_0, \pi_0^*$ 89 | and a money growth sequence $\mu = \{\mu_t\}_{t=0}^T$. 90 | 91 | As endogenous outputs of our model we want to find sequences $\pi = \{\pi_t\}_{t=0}^T, p = \{p_t\}_{t=0}^T$ as functions of the exogenous inputs. 92 | 93 | We'll do some mental experiments by studying how the model outputs vary as we vary 94 | the model inputs. 95 | 96 | ## Representing key equations with linear algebra 97 | 98 | We begin by writing the equation {eq}`eq:adaptexpn` adaptive expectations model for $\pi_t^*$ for $t=0, \ldots, T$ as 99 | 100 | $$ 101 | \begin{bmatrix} 1 & 0 & 0 & \cdots & 0 & 0 \cr 102 | -\lambda & 1 & 0 & \cdots & 0 & 0 \cr 103 | 0 & - \lambda & 1 & \cdots & 0 & 0 \cr 104 | \vdots & \vdots & \vdots & \cdots & \vdots & \vdots \cr 105 | 0 & 0 & 0 & \cdots & -\lambda & 1 106 | \end{bmatrix} 107 | \begin{bmatrix} \pi_0^* \cr 108 | \pi_1^* \cr 109 | \pi_2^* \cr 110 | \vdots \cr 111 | \pi_{T+1}^* 112 | \end{bmatrix} = 113 | (1-\lambda) \begin{bmatrix} 114 | 0 & 0 & 0 & \cdots & 0 \cr 115 | 1 & 0 & 0 & \cdots & 0 \cr 116 | 0 & 1 & 0 & \cdots & 0 \cr 117 | \vdots &\vdots & \vdots & \cdots & \vdots \cr 118 | 0 & 0 & 0 & \cdots & 1 \end{bmatrix} 119 | \begin{bmatrix}\pi_0 \cr \pi_1 \cr \pi_2 \cr \vdots \cr \pi_T 120 | \end{bmatrix} + 121 | \begin{bmatrix} \pi_0^* \cr 0 \cr 0 \cr \vdots \cr 0 \end{bmatrix} 122 | $$ 123 | 124 | Write this equation as 125 | 126 | $$ 127 | A \pi^* = (1-\lambda) B \pi + \pi_0^* 128 | $$ (eq:eq1) 129 | 130 | where the $(T+2) \times (T+2) $matrix $A$, the $(T+2)\times (T+1)$ matrix $B$, and the vectors $\pi^* , \pi_0, \pi_0^*$ 131 | are defined implicitly by aligning these two equations. 132 | 133 | Next we write the key equation {eq}`eq:eqpipi` in matrix notation as 134 | 135 | $$ 136 | \begin{bmatrix} 137 | \pi_0 \cr \pi_1 \cr \pi_1 \cr \vdots \cr \pi_T \end{bmatrix} 138 | = \begin{bmatrix} 139 | \mu_0 \cr \mu_1 \cr \mu_2 \cr \vdots \cr \mu_T \end{bmatrix} 140 | + \begin{bmatrix} - \alpha & \alpha & 0 & \cdots & 0 & 0 \cr 141 | 0 & -\alpha & \alpha & \cdots & 0 & 0 \cr 142 | 0 & 0 & -\alpha & \cdots & 0 & 0 \cr 143 | \vdots & \vdots & \vdots & \cdots & \alpha & 0 \cr 144 | 0 & 0 & 0 & \cdots & -\alpha & \alpha 145 | \end{bmatrix} 146 | \begin{bmatrix} \pi_0^* \cr 147 | \pi_1^* \cr 148 | \pi_2^* \cr 149 | \vdots \cr 150 | \pi_{T+1}^* 151 | \end{bmatrix} 152 | $$ 153 | 154 | Represent the previous equation system in terms of vectors and matrices as 155 | 156 | $$ 157 | \pi = \mu + C \pi^* 158 | $$ (eq:eq2) 159 | 160 | where the $(T+1) \times (T+2)$ matrix $C$ is defined implicitly to align this equation with the preceding 161 | equation system. 162 | 163 | ## Harvesting insights from our matrix formulation 164 | 165 | We now have all of the ingredients we need to solve for $\pi$ as 166 | a function of $\mu, \pi_0, \pi_0^*$. 167 | 168 | Combine equations {eq}`eq:eq1`and {eq}`eq:eq2` to get 169 | 170 | $$ 171 | \begin{aligned} 172 | A \pi^* & = (1-\lambda) B \pi + \pi_0^* \cr 173 | & = (1-\lambda) B \left[ \mu + C \pi^* \right] + \pi_0^* 174 | \end{aligned} 175 | $$ 176 | 177 | which implies that 178 | 179 | $$ 180 | \left[ A - (1-\lambda) B C \right] \pi^* = (1-\lambda) B \mu+ \pi_0^* 181 | $$ 182 | 183 | Multiplying both sides of the above equation by the inverse of the matrix on the left side gives 184 | 185 | $$ 186 | \pi^* = \left[ A - (1-\lambda) B C \right]^{-1} \left[ (1-\lambda) B \mu+ \pi_0^* \right] 187 | $$ (eq:eq4) 188 | 189 | Having solved equation {eq}`eq:eq4` for $\pi^*$, we can use equation {eq}`eq:eq2` to solve for $\pi$: 190 | 191 | $$ 192 | \pi = \mu + C \pi^* 193 | $$ 194 | 195 | 196 | We have thus solved for two of the key endogenous time series determined by our model, namely, the sequence $\pi^*$ 197 | of expected inflation rates and the sequence $\pi$ of actual inflation rates. 198 | 199 | Knowing these, we can then quickly calculate the associated sequence $p$ of the logarithm of the price level 200 | from equation {eq}`eq:eqfiscth1`. 201 | 202 | Let's fill in the details for this step. 203 | 204 | Since we now know $\mu$ it is easy to compute $m$. 205 | 206 | Thus, notice that we can represent the equations 207 | 208 | $$ 209 | m_{t+1} = m_t + \mu_t , \quad t = 0, 1, \ldots, T 210 | $$ 211 | 212 | as the matrix equation 213 | 214 | $$ 215 | \begin{bmatrix} 216 | 1 & 0 & 0 & \cdots & 0 & 0 \cr 217 | -1 & 1 & 0 & \cdots & 0 & 0 \cr 218 | 0 & -1 & 1 & \cdots & 0 & 0 \cr 219 | \vdots & \vdots & \vdots & \vdots & 0 & 0 \cr 220 | 0 & 0 & 0 & \cdots & 1 & 0 \cr 221 | 0 & 0 & 0 & \cdots & -1 & 1 222 | \end{bmatrix} 223 | \begin{bmatrix} 224 | m_1 \cr m_2 \cr m_3 \cr \vdots \cr m_T \cr m_{T+1} 225 | \end{bmatrix} 226 | = \begin{bmatrix} 227 | \mu_0 \cr \mu_1 \cr \mu_2 \cr \vdots \cr \mu_{T-1} \cr \mu_T 228 | \end{bmatrix} 229 | + \begin{bmatrix} 230 | m_0 \cr 0 \cr 0 \cr \vdots \cr 0 \cr 0 231 | \end{bmatrix} 232 | $$ (eq:eq101_ad) 233 | 234 | Multiplying both sides of equation {eq}`eq:eq101_ad` with the inverse of the matrix on the left will give 235 | 236 | $$ 237 | m_t = m_0 + \sum_{s=0}^{t-1} \mu_s, \quad t =1, \ldots, T+1 238 | $$ (eq:mcum_ad) 239 | 240 | Equation {eq}`eq:mcum_ad` shows that the log of the money supply at $t$ equals the log $m_0$ of the initial money supply 241 | plus accumulation of rates of money growth between times $0$ and $t$. 242 | 243 | We can then compute $p_t$ for each $t$ from equation {eq}`eq:eqfiscth1`. 244 | 245 | We can write a compact formula for $p $ as 246 | 247 | $$ 248 | p = m + \alpha \hat \pi^* 249 | $$ 250 | 251 | where 252 | 253 | $$ 254 | \hat \pi^* = \begin{bmatrix} \pi_0^* \cr 255 | \pi_1^* \cr 256 | \pi_2^* \cr 257 | \vdots \cr 258 | \pi_{T}^* 259 | \end{bmatrix}, 260 | $$ 261 | 262 | which is just $\pi^*$ with the last element dropped. 263 | 264 | ## Forecast errors and model computation 265 | 266 | Our computations will verify that 267 | 268 | $$ 269 | \hat \pi^* \neq \pi, 270 | $$ 271 | 272 | so that in general 273 | 274 | $$ 275 | \pi_t^* \neq \pi_t, \quad t = 0, 1, \ldots , T 276 | $$ (eq:notre) 277 | 278 | This outcome is typical in models in which adaptive expectations hypothesis like equation {eq}`eq:adaptexpn` appear as a 279 | component. 280 | 281 | In {doc}`cagan_ree`, we studied a version of the model that replaces hypothesis {eq}`eq:adaptexpn` with 282 | a "perfect foresight" or "rational expectations" hypothesis. 283 | 284 | But now, let's dive in and do some computations with the adaptive expectations version of the model. 285 | 286 | As usual, we'll start by importing some Python modules. 287 | 288 | ```{code-cell} ipython3 289 | import numpy as np 290 | from collections import namedtuple 291 | import matplotlib.pyplot as plt 292 | ``` 293 | 294 | ```{code-cell} ipython3 295 | Cagan_Adaptive = namedtuple("Cagan_Adaptive", 296 | ["α", "m0", "Eπ0", "T", "λ"]) 297 | 298 | def create_cagan_adaptive_model(α = 5, m0 = 1, Eπ0 = 0.5, T=80, λ = 0.9): 299 | return Cagan_Adaptive(α, m0, Eπ0, T, λ) 300 | 301 | md = create_cagan_adaptive_model() 302 | ``` 303 | +++ {"user_expressions": []} 304 | 305 | We solve the model and plot variables of interests using the following functions. 306 | 307 | ```{code-cell} ipython3 308 | def solve_cagan_adaptive(model, μ_seq): 309 | " Solve the Cagan model in finite time. " 310 | α, m0, Eπ0, T, λ = model 311 | 312 | A = np.eye(T+2, T+2) - λ*np.eye(T+2, T+2, k=-1) 313 | B = np.eye(T+2, T+1, k=-1) 314 | C = -α*np.eye(T+1, T+2) + α*np.eye(T+1, T+2, k=1) 315 | Eπ0_seq = np.append(Eπ0, np.zeros(T+1)) 316 | 317 | # Eπ_seq is of length T+2 318 | Eπ_seq = np.linalg.solve(A - (1-λ)*B @ C, (1-λ) * B @ μ_seq + Eπ0_seq) 319 | 320 | # π_seq is of length T+1 321 | π_seq = μ_seq + C @ Eπ_seq 322 | 323 | D = np.eye(T+1, T+1) - np.eye(T+1, T+1, k=-1) # D is the coefficient matrix in Equation (14.8) 324 | m0_seq = np.append(m0, np.zeros(T)) 325 | 326 | # m_seq is of length T+2 327 | m_seq = np.linalg.solve(D, μ_seq + m0_seq) 328 | m_seq = np.append(m0, m_seq) 329 | 330 | # p_seq is of length T+2 331 | p_seq = m_seq + α * Eπ_seq 332 | 333 | return π_seq, Eπ_seq, m_seq, p_seq 334 | ``` 335 | 336 | +++ {"user_expressions": []} 337 | 338 | ```{code-cell} ipython3 339 | def solve_and_plot(model, μ_seq): 340 | 341 | π_seq, Eπ_seq, m_seq, p_seq = solve_cagan_adaptive(model, μ_seq) 342 | 343 | T_seq = range(model.T+2) 344 | 345 | fig, ax = plt.subplots(5, 1, figsize=[5, 12], dpi=200) 346 | ax[0].plot(T_seq[:-1], μ_seq) 347 | ax[1].plot(T_seq[:-1], π_seq, label=r'$\pi_t$') 348 | ax[1].plot(T_seq, Eπ_seq, label=r'$\pi^{*}_{t}$') 349 | ax[2].plot(T_seq, m_seq - p_seq) 350 | ax[3].plot(T_seq, m_seq) 351 | ax[4].plot(T_seq, p_seq) 352 | 353 | y_labs = [r'$\mu$', r'$\pi$', r'$m - p$', r'$m$', r'$p$'] 354 | subplot_title = [r'Money supply growth', r'Inflation', r'Real balances', r'Money supply', r'Price level'] 355 | 356 | for i in range(5): 357 | ax[i].set_xlabel(r'$t$') 358 | ax[i].set_ylabel(y_labs[i]) 359 | ax[i].set_title(subplot_title[i]) 360 | 361 | ax[1].legend() 362 | plt.tight_layout() 363 | plt.show() 364 | 365 | return π_seq, Eπ_seq, m_seq, p_seq 366 | ``` 367 | 368 | +++ {"user_expressions": []} 369 | 370 | 371 | 372 | ## Technical condition for stability 373 | 374 | In constructing our examples, we shall assume that $(\lambda, \alpha)$ satisfy 375 | 376 | $$ 377 | \Bigl| \frac{\lambda-\alpha(1-\lambda)}{1-\alpha(1-\lambda)} \Bigr| < 1 378 | $$ (eq:suffcond) 379 | 380 | The source of this condition is the following string of deductions: 381 | 382 | $$ 383 | \begin{aligned} 384 | \pi_{t}&=\mu_{t}+\alpha\pi_{t+1}^{*}-\alpha\pi_{t}^{*}\\\pi_{t+1}^{*}&=\lambda\pi_{t}^{*}+(1-\lambda)\pi_{t}\\\pi_{t}&=\frac{\mu_{t}}{1-\alpha(1-\lambda)}-\frac{\alpha(1-\lambda)}{1-\alpha(1-\lambda)}\pi_{t}^{*}\\\implies\pi_{t}^{*}&=\frac{1}{\alpha(1-\lambda)}\mu_{t}-\frac{1-\alpha(1-\lambda)}{\alpha(1-\lambda)}\pi_{t}\\\pi_{t+1}&=\frac{\mu_{t+1}}{1-\alpha(1-\lambda)}-\frac{\alpha(1-\lambda)}{1-\alpha(1-\lambda)}\left(\lambda\pi_{t}^{*}+(1-\lambda)\pi_{t}\right)\\&=\frac{\mu_{t+1}}{1-\alpha(1-\lambda)}-\frac{\lambda}{1-\alpha(1-\lambda)}\mu_{t}+\frac{\lambda-\alpha(1-\lambda)}{1-\alpha(1-\lambda)}\pi_{t} 385 | \end{aligned} 386 | $$ 387 | 388 | By assuring that the coefficient on $\pi_t$ is less than one in absolute value, condition {eq}`eq:suffcond` assures stability of the dynamics of $\{\pi_t\}$ described by the last line of our string of deductions. 389 | 390 | The reader is free to study outcomes in examples that violate condition {eq}`eq:suffcond`. 391 | 392 | ```{code-cell} ipython3 393 | print(np.abs((md.λ - md.α*(1-md.λ))/(1 - md.α*(1-md.λ)))) 394 | ``` 395 | 396 | ## Experiments 397 | 398 | Now we'll turn to some experiments. 399 | 400 | ### Experiment 1 401 | 402 | We'll study a situation in which the rate of growth of the money supply is $\mu_0$ 403 | from $t=0$ to $t= T_1$ and then permanently falls to $\mu^*$ at $t=T_1$. 404 | 405 | Thus, let $T_1 \in (0, T)$. 406 | 407 | So where $\mu_0 > \mu^*$, we assume that 408 | 409 | $$ 410 | \mu_{t} = \begin{cases} 411 | \mu_0 , & t = 0, \ldots, T_1 -1 \\ 412 | \mu^* , & t \geq T_1 413 | \end{cases} 414 | $$ 415 | 416 | Notice that we studied exactly this experiment in a rational expectations version of the model in {doc}`cagan_ree`. 417 | 418 | So by comparing outcomes across the two lectures, we can learn about consequences of assuming adaptive expectations, as we do here, instead of rational expectations as we assumed in that other lecture. 419 | 420 | ```{code-cell} ipython3 421 | # Parameters for the experiment 1 422 | T1 = 60 423 | μ0 = 0.5 424 | μ_star = 0 425 | 426 | μ_seq_1 = np.append(μ0*np.ones(T1), μ_star*np.ones(md.T+1-T1)) 427 | 428 | # solve and plot 429 | π_seq_1, Eπ_seq_1, m_seq_1, p_seq_1 = solve_and_plot(md, μ_seq_1) 430 | ``` 431 | 432 | We invite the reader to compare outcomes with those under rational expectations studied in {doc}`cagan_ree`. 433 | 434 | Please note how the actual inflation rate $\pi_t$ "overshoots" its ultimate steady-state value at the time of the sudden reduction in the rate of growth of the money supply at time $T_1$. 435 | 436 | We invite you to explain to yourself the source of this overshooting and why it does not occur in the rational expectations version of the model. 437 | 438 | ### Experiment 2 439 | 440 | Now we'll do a different experiment, namely, a gradual stabilization in which the rate of growth of the money supply smoothly 441 | decline from a high value to a persistently low value. 442 | 443 | While price level inflation eventually falls, it falls more slowly than the driving force that ultimately causes it to fall, namely, the falling rate of growth of the money supply. 444 | 445 | The sluggish fall in inflation is explained by how anticipated inflation $\pi_t^*$ persistently exceeds actual inflation $\pi_t$ during the transition from a high inflation to a low inflation situation. 446 | 447 | ```{code-cell} ipython3 448 | # parameters 449 | ϕ = 0.9 450 | μ_seq_2 = np.array([ϕ**t * μ0 + (1-ϕ**t)*μ_star for t in range(md.T)]) 451 | μ_seq_2 = np.append(μ_seq_2, μ_star) 452 | 453 | 454 | # solve and plot 455 | π_seq_2, Eπ_seq_2, m_seq_2, p_seq_2 = solve_and_plot(md, μ_seq_2) 456 | ``` 457 | --------------------------------------------------------------------------------