├── .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 | [](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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------