@@ -28,22 +28,24 @@ kernelspec:
2828:depth: 2
2929```
303031+```{include} _admonition/gpu.md
32+```
33+3134In addition to what's in Anaconda, this lecture will need the following libraries:
32353336```{code-cell} ipython3
3437:tags: [hide-output]
353836-!pip install quantecon
37-!pip install --upgrade yfinance
39+!pip install --upgrade quantecon yfinance
3840```
39414042## Overview
414342-{doc}`Previously <intro:ar1_processes>` we learned about linear scalar-valued stochastic processes (AR(1) models).
44+Previously in {doc}`intro:ar1_processes`, we learned about linear scalar-valued stochastic processes (AR(1) models).
43454446Now we generalize these linear models slightly by allowing the multiplicative coefficient to be stochastic.
454746-Such processes are known as Kesten processes after German--American mathematician Harry Kesten (1931--2019)
48+Such processes are known as Kesten processes after German--American mathematician Harry Kesten (1931--2019).
47494850Although simple to write down, Kesten processes are interesting for at least two reasons:
4951@@ -58,20 +60,13 @@ Let's start with some imports:
5860import matplotlib.pyplot as plt
5961import numpy as np
6062import quantecon as qe
61-```
62-63-The following two lines are only added to avoid a `FutureWarning` caused by
64-compatibility issues between pandas and matplotlib.
65-66-```{code-cell} ipython3
67-from pandas.plotting import register_matplotlib_converters
68-register_matplotlib_converters()
63+import yfinance as yf
6964```
70657166Additional technical background related to this lecture can be found in the
72-monograph of {cite}`buraczewski2016stochastic`.
67+monograph by {cite}`buraczewski2016stochastic`.
736874-## Kesten Processes
69+## Kesten processes
75707671```{index} single: Kesten processes; heavy tails
7772```
@@ -97,7 +92,7 @@ In particular, we will assume that
9792* $\{a_t\}_{t \geq 1}$ is a nonnegative IID stochastic process and
9893* $\{\eta_t\}_{t \geq 1}$ is another nonnegative IID stochastic process, independent of the first.
9994100-### Example: GARCH Volatility
95+### Example: GARCH volatility
1019610297The GARCH model is common in financial applications, where time series such as asset returns exhibit time varying volatility.
10398@@ -107,18 +102,16 @@ Composite Index for the period 1st January 2006 to 1st November 2019.
107102(ndcode)=
108103109104```{code-cell} ipython3
110-import yfinance as yf
111-112-s = yf.download('^IXIC', '2006-1-1', '2019-11-1', auto_adjust=False)['Adj Close']
105+s = yf.download(
106+ "^IXIC", "2006-1-1", "2019-11-1", auto_adjust=False
107+)["Adj Close"]
113108114109r = s.pct_change()
115110116111fig, ax = plt.subplots()
117-118112ax.plot(r, alpha=0.7)
119-120-ax.set_ylabel('returns', fontsize=12)
121-ax.set_xlabel('date', fontsize=12)
113+ax.set_ylabel("returns", fontsize=12)
114+ax.set_xlabel("date", fontsize=12)
122115123116plt.show()
124117```
@@ -150,7 +143,7 @@ where $\{\zeta_t\}$ is again IID and independent of $\{\xi_t\}$.
150143151144The volatility sequence $\{\sigma_t^2 \}$, which drives the dynamics of returns, is a Kesten process.
152145153-### Example: Wealth Dynamics
146+### Example: wealth dynamics
154147155148Suppose that a given household saves a fixed fraction $s$ of its current wealth in every period.
156149@@ -171,7 +164,7 @@ is a Kesten process.
171164172165### Stationarity
173166174-In earlier lectures, such as the one on {doc}`AR(1) processes <intro:ar1_processes>`, we introduced the notion of a stationary distribution.
167+In earlier lectures, such as the one on {doc}`intro:ar1_processes`, we introduced the notion of a stationary distribution.
175168176169In the present context, we can define a stationary distribution as follows:
177170@@ -203,7 +196,7 @@ current state is drawn from $F^*$.
203196204197The equality in {eq}`kp_stationary` states that this distribution is unchanged.
205198206-### Cross-Sectional Interpretation
199+### Cross-sectional interpretation
207200208201There is an important cross-sectional interpretation of stationary distributions, discussed previously but worth repeating here.
209202@@ -241,7 +234,7 @@ next period as it is this period.
241234242235Since $y$ was chosen arbitrarily, the distribution is unchanged.
243236244-### Conditions for Stationarity
237+### Conditions for stationarity
245238246239The Kesten process $X_{t+1} = a_{t+1} X_t + \eta_{t+1}$ does not always
247240have a stationary distribution.
@@ -270,16 +263,16 @@ As one application of this result, we see that the wealth process
270263{eq}`wealth_dynam` will have a unique stationary distribution whenever
271264labor income has finite mean and $\mathbb E \ln R_t + \ln s < 0$.
272265273-## Heavy Tails
266+## Heavy tails
274267275268Under certain conditions, the stationary distribution of a Kesten process has
276269a Pareto tail.
277270278-(See our {doc}`earlier lecture <intro:heavy_tails>` on heavy-tailed distributions for background.)
271+(See our lecture {doc}`intro:heavy_tails` for background.)
279272280273This fact is significant for economics because of the prevalence of Pareto-tailed distributions.
281274282-### The Kesten--Goldie Theorem
275+### The Kesten--Goldie theorem
283276284277To state the conditions under which the stationary distribution of a Kesten process has a Pareto tail, we first recall that a random variable is called **nonarithmetic** if its distribution is not concentrated on $\{\dots, -2t, -t, 0, t, 2t, \ldots \}$ for any $t \geq 0$.
285278@@ -333,39 +326,39 @@ If this occurs for several concurrent periods, the effects compound each other,
333326334327This leads to spikes in the time series, which fill out the extreme right hand tail of the distribution.
335328336-The spikes in the time series are visible in the following simulation, which generates of 10 paths when $a_t$ and $b_t$ are lognormal.
329+The spikes in the time series are visible in the following simulation, which generates 10 paths when $a_t$ and $b_t$ are lognormal.
337330338331```{code-cell} ipython3
339332μ = -0.5
340333σ = 1.0
341334342-def kesten_ts(ts_length=100):
335+def kesten_ts(rng, ts_length=100):
343336 x = np.zeros(ts_length)
344- for t in range(ts_length-1):
345- a = np.exp(μ + σ * np.random.randn())
346- b = np.exp(np.random.randn())
337+ for t in range(ts_length - 1):
338+ a = np.exp(μ + σ * rng.standard_normal())
339+ b = np.exp(rng.standard_normal())
347340 x[t+1] = a * x[t] + b
348341 return x
349342350343fig, ax = plt.subplots()
351344352345num_paths = 10
353-np.random.seed(12)
346+rng = np.random.default_rng(12)
354347355348for i in range(num_paths):
356- ax.plot(kesten_ts())
349+ ax.plot(kesten_ts(rng))
357350358-ax.set(xlabel='time', ylabel='$X_t$')
351+ax.set(xlabel="time", ylabel="$X_t$")
359352plt.show()
360353```
361354362-## Application: Firm Dynamics
355+## Application: firm dynamics
363356364-As noted in our {doc}`lecture on heavy tails <intro:heavy_tails>`, for common measures of firm size such as revenue or employment, the US firm size distribution exhibits a Pareto tail (see, e.g., {cite}`axtell2001zipf`, {cite}`gabaix2016power`).
357+As noted in our lecture {doc}`intro:heavy_tails`, for common measures of firm size such as revenue or employment, the US firm size distribution exhibits a Pareto tail (see, e.g., {cite}`axtell2001zipf`, {cite}`gabaix2016power`).
365358366359Let us try to explain this rather striking fact using the Kesten--Goldie Theorem.
367360368-### Gibrat's Law
361+### Gibrat's law
369362370363It was postulated many years ago by Robert Gibrat {cite}`gibrat1931inegalites` that firm size evolves according to a simple rule whereby size next period is proportional to current size.
371364@@ -412,7 +405,7 @@ In the exercises you are asked to show that {eq}`firm_dynam` is more
412405consistent with the empirical findings presented above than Gibrat's law in
413406{eq}`firm_dynam_gb`.
414407415-### Heavy Tails
408+### Heavy tails
416409417410So what has this to do with Pareto tails?
418411@@ -460,22 +453,22 @@ Here is one solution:
460453years = 15
461454days = years * 250
462455463-def garch_ts(ts_length=days):
456+def garch_ts(rng, ts_length=days):
464457 σ2 = 0
465458 r = np.zeros(ts_length)
466- for t in range(ts_length-1):
467- ξ = np.random.randn()
459+ for t in range(ts_length - 1):
460+ ξ = rng.standard_normal()
468461 σ2 = α_0 + σ2 * (α_1 * ξ**2 + β)
469- r[t] = np.sqrt(σ2) * np.random.randn()
462+ r[t] = np.sqrt(σ2) * rng.standard_normal()
470463 return r
471464472465fig, ax = plt.subplots()
473466474-np.random.seed(12)
467+rng = np.random.default_rng(12)
475468476-ax.plot(garch_ts(), alpha=0.7)
469+ax.plot(garch_ts(rng), alpha=0.7)
477470478-ax.set(xlabel='time', ylabel='$\\sigma_t^2$')
471+ax.set(xlabel="time", ylabel="$\\sigma_t^2$")
479472plt.show()
480473```
481474@@ -673,41 +666,59 @@ s_init = 1.0 # initial condition for each firm
673666```
674667675668Here's one solution.
676-First we generate the observations:
669+670+We need to simulate a large cross-section of firms, so this is a good use case for JAX.
671+672+We store the firm parameters in a `NamedTuple` and write the update rule {eq}`firm_dynam_ee` for a single firm as a pure function.
673+674+```{code-cell} ipython3
675+import jax
676+import jax.numpy as jnp
677+from functools import partial
678+from typing import NamedTuple
679+680+681+class Firm(NamedTuple):
682+ μ_a: float
683+ σ_a: float
684+ μ_b: float
685+ σ_b: float
686+ μ_e: float
687+ σ_e: float
688+ s_bar: float
689+690+691+def update(s, key, firm):
692+ "Update a single firm's size s, given a fresh random key."
693+ a_key, b_key, e_key = jax.random.split(key, 3)
694+ a = jnp.exp(firm.μ_a + firm.σ_a * jax.random.normal(a_key))
695+ b = jnp.exp(firm.μ_b + firm.σ_b * jax.random.normal(b_key))
696+ e = jnp.exp(firm.μ_e + firm.σ_e * jax.random.normal(e_key))
697+ return jnp.where(s < firm.s_bar, e, a * s + b)
698+```
699+700+We simulate one firm for `T` periods with `lax.scan`, then use `vmap` to run all `M` firms in parallel.
701+702+```{code-cell} ipython3
703+@partial(jax.jit, static_argnames=("T", "M"))
704+def generate_draws(firm, s_init=1.0, T=500, M=1_000_000, seed=0):
705+ keys = jax.random.split(jax.random.PRNGKey(seed), M)
706+707+ def sim_firm(key):
708+ step_keys = jax.random.split(key, T)
709+ s_final, _ = jax.lax.scan(
710+ lambda s, k: (update(s, k, firm), None), s_init, step_keys
711+ )
712+ return s_final
713+714+ return jax.vmap(sim_firm)(keys)
715+```
716+717+Now we build the firm from the parameters above and generate the draws.
677718678719```{code-cell} ipython3
679-from numba import jit, prange
680-from numpy.random import randn
681-682-683-@jit(parallel=True)
684-def generate_draws(μ_a=-0.5,
685- σ_a=0.1,
686- μ_b=0.0,
687- σ_b=0.5,
688- μ_e=0.0,
689- σ_e=0.5,
690- s_bar=1.0,
691- T=500,
692- M=1_000_000,
693- s_init=1.0):
694-695- draws = np.empty(M)
696- for m in prange(M):
697- s = s_init
698- for t in range(T):
699- if s < s_bar:
700- new_s = np.exp(μ_e + σ_e * randn())
701- else:
702- a = np.exp(μ_a + σ_a * randn())
703- b = np.exp(μ_b + σ_b * randn())
704- new_s = a * s + b
705- s = new_s
706- draws[m] = s
707-708- return draws
709-710-data = generate_draws()
720+firm = Firm(μ_a, σ_a, μ_b, σ_b, μ_e, σ_e, s_bar)
721+data = generate_draws(firm, s_init=s_init, T=T, M=M)
711722```
712723713724Now we produce the rank-size plot:
@@ -716,7 +727,7 @@ Now we produce the rank-size plot:
716727fig, ax = plt.subplots()
717728718729rank_data, size_data = qe.rank_size(data, c=0.01)
719-ax.loglog(rank_data, size_data, 'o', markersize=3.0, alpha=0.5)
730+ax.loglog(rank_data, size_data, "o", markersize=3.0, alpha=0.5)
720731ax.set_xlabel("log rank")
721732ax.set_ylabel("log size")
722733