{ "cells": [ { "cell_type": "markdown", "id": "c5125e26", "metadata": {}, "source": [ "$$\n", "\\newcommand{\\argmax}{arg\\,max}\n", "\\newcommand{\\argmin}{arg\\,min}\n", "$$" ] }, { "cell_type": "markdown", "id": "7c72a0ec", "metadata": {}, "source": [ "\n", "\n", "
\n", " \n", " \"QuantEcon\"\n", " \n", "
" ] }, { "cell_type": "markdown", "id": "6d751a4f", "metadata": {}, "source": [ "# A Lake Model of Employment and Unemployment" ] }, { "cell_type": "markdown", "id": "9a6d716d", "metadata": {}, "source": [ "# GPU\n", "\n", "This lecture was built using a machine with access to a GPU — although it will also run without one.\n", "\n", "[Google Colab](https://colab.research.google.com/) has a free tier with GPUs\n", "that you can access as follows:\n", "\n", "1. Click on the “play” icon top right \n", "1. Select Colab \n", "1. Set the runtime environment to include a GPU \n", "\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "id": "dddd8da0", "metadata": {}, "source": [ "## Contents\n", "\n", "- [A Lake Model of Employment and Unemployment](#A-Lake-Model-of-Employment-and-Unemployment) \n", " - [Overview](#Overview) \n", " - [The model](#The-model) \n", " - [Implementation](#Implementation) \n", " - [Dynamics of an individual worker](#Dynamics-of-an-individual-worker) \n", " - [Exercises](#Exercises) " ] }, { "cell_type": "markdown", "id": "abc4d144", "metadata": {}, "source": [ "In addition to what’s in Anaconda, this lecture will need the following libraries:" ] }, { "cell_type": "code", "execution_count": null, "id": "5733c407", "metadata": { "hide-output": false }, "outputs": [], "source": [ "!pip install quantecon jax" ] }, { "cell_type": "markdown", "id": "443fd76a", "metadata": {}, "source": [ "## Overview\n", "\n", "This lecture describes what has come to be called a *lake model*.\n", "\n", "The lake model is a basic tool for modeling unemployment.\n", "\n", "It allows us to analyze\n", "\n", "- flows between unemployment and employment \n", "- how these flows influence steady state employment and unemployment rates \n", "\n", "\n", "It is a good model for interpreting monthly labor department reports on gross and net jobs created and destroyed.\n", "\n", "The “lakes” in the model are the pools of employed and unemployed.\n", "\n", "The “flows” between the lakes are caused by\n", "\n", "- firing and hiring \n", "- entry and exit from the labor force \n", "\n", "\n", "For the first part of this lecture, the parameters governing transitions into\n", "and out of unemployment and employment are exogenous.\n", "\n", "Later, we’ll determine some of these transition rates endogenously using the [McCall search model](https://python.quantecon.org/mccall_model.html).\n", "\n", "We’ll also use some nifty concepts like ergodicity, which provides a fundamental link between *cross-sectional* and *long run time series* distributions.\n", "\n", "These concepts will help us build an equilibrium model of ex-ante homogeneous workers whose different luck generates variations in their ex post experiences.\n", "\n", "Let’s start with some imports:" ] }, { "cell_type": "code", "execution_count": null, "id": "363a357b", "metadata": { "hide-output": false }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import jax\n", "import jax.numpy as jnp\n", "from typing import NamedTuple\n", "from quantecon.distributions import BetaBinomial\n", "from functools import partial\n", "import jax.scipy.stats as stats" ] }, { "cell_type": "markdown", "id": "a09ac1ea", "metadata": {}, "source": [ "### Prerequisites\n", "\n", "Before working through what follows, we recommend you read the\n", "[lecture on finite Markov chains](https://python.quantecon.org/finite_markov.html).\n", "\n", "You will also need some basic [linear algebra](https://python.quantecon.org/linear_algebra.html) and probability." ] }, { "cell_type": "markdown", "id": "43b66876", "metadata": {}, "source": [ "## The model\n", "\n", "The economy is inhabited by a very large number of ex-ante identical workers.\n", "\n", "The workers live forever, spending their lives moving between unemployment and employment.\n", "\n", "Their rates of transition between employment and unemployment are governed by the following parameters:\n", "\n", "- $ \\lambda $, the job finding rate for currently unemployed workers \n", "- $ \\alpha $, the dismissal rate for currently employed workers \n", "- $ b $, the entry rate into the labor force \n", "- $ d $, the exit rate from the labor force \n", "\n", "\n", "The growth rate of the labor force evidently equals $ g=b-d $." ] }, { "cell_type": "markdown", "id": "edda4e5c", "metadata": {}, "source": [ "### Aggregate variables\n", "\n", "We want to derive the dynamics of the following aggregates:\n", "\n", "- $ E_t $, the total number of employed workers at date $ t $ \n", "- $ U_t $, the total number of unemployed workers at $ t $ \n", "- $ N_t $, the number of workers in the labor force at $ t $ " ] }, { "cell_type": "markdown", "id": "73ebeed0", "metadata": {}, "source": [ "### Laws of motion for stock variables\n", "\n", "We begin by constructing laws of motion for the aggregate variables $ E_t,U_t, N_t $.\n", "\n", "Of the mass of workers $ E_t $ who are employed at date $ t $,\n", "\n", "- $ (1-d)E_t $ will remain in the labor force \n", "- of these, $ (1-\\alpha)(1-d)E_t $ will remain employed \n", "\n", "\n", "Of the mass of workers $ U_t $ workers who are currently unemployed,\n", "\n", "- $ (1-d)U_t $ will remain in the labor force \n", "- of these, $ (1-d) \\lambda U_t $ will become employed \n", "\n", "\n", "Therefore, the number of workers who will be employed at date $ t+1 $ will be\n", "\n", "$$\n", "E_{t+1} = (1-d)(1-\\alpha)E_t + (1-d)\\lambda U_t\n", "$$\n", "\n", "A similar analysis implies\n", "\n", "$$\n", "U_{t+1} = (1-d)\\alpha E_t + (1-d)(1-\\lambda)U_t + b (E_t+U_t)\n", "$$\n", "\n", "The value $ b(E_t+U_t) $ is the mass of new workers entering the labor force unemployed.\n", "\n", "The total stock of workers $ N_t=E_t+U_t $ evolves as\n", "\n", "$$\n", "N_{t+1} = (1+b-d)N_t = (1+g)N_t\n", "$$\n", "\n", "Letting $ X_t := \\left(\\begin{matrix}U_t\\\\E_t\\end{matrix}\\right) $, the law of motion for $ X $ is\n", "\n", "$$\n", "X_{t+1} = A X_t\n", "\\quad \\text{where} \\quad\n", "A :=\n", "\\begin{bmatrix}\n", " (1-d)(1-\\lambda) + b & (1-d)\\alpha + b \\\\\n", " (1-d)\\lambda & (1-d)(1-\\alpha)\n", "\\end{bmatrix}\n", "$$\n", "\n", "This law tells us how total employment and unemployment evolve over time." ] }, { "cell_type": "markdown", "id": "71ba862b", "metadata": {}, "source": [ "### Laws of motion for rates\n", "\n", "Now let’s derive the law of motion for rates.\n", "\n", "We want to track the values of the following objects:\n", "\n", "- The employment rate $ e_t := E_t/N_t $. \n", "- The unemployment rate $ u_t := U_t/N_t $. \n", "\n", "\n", "(Here and below, capital letters represent aggregates and lowercase letters represent rates)\n", "\n", "To get these we can divide both sides of $ X_{t+1} = A X_t $ by $ N_{t+1} $ to get\n", "\n", "$$\n", "\\begin{bmatrix}\n", " U_{t+1}/N_{t+1} \\\\\n", " E_{t+1}/N_{t+1}\n", "\\end{bmatrix} =\n", "\\frac1{1+g} A\n", "\\begin{bmatrix}\n", " U_{t}/N_{t}\n", " \\\\\n", " E_{t}/N_{t}\n", "\\end{bmatrix}\n", "$$\n", "\n", "Letting\n", "\n", "$$\n", "x_t :=\n", "\\left(\\begin{matrix}\n", " u_t\\\\ e_t\n", "\\end{matrix}\\right) =\n", "\\left(\\begin{matrix}\n", " U_t/N_t\\\\ E_t/N_t\n", "\\end{matrix}\\right)\n", "$$\n", "\n", "we can also write this as\n", "\n", "$$\n", "x_{t+1} = R x_t\n", "\\quad \\text{where} \\quad\n", "R := \\frac{1}{1 + g} A\n", "$$\n", "\n", "You can check that $ e_t + u_t = 1 $ implies that $ e_{t+1}+u_{t+1} = 1 $.\n", "\n", "This follows from the fact that the columns of $ R $ sum to 1." ] }, { "cell_type": "markdown", "id": "f44bbdd5", "metadata": {}, "source": [ "## Implementation\n", "\n", "Let’s code up these equations." ] }, { "cell_type": "markdown", "id": "cefae3e9", "metadata": {}, "source": [ "### Model\n", "\n", "To begin, we set up a class called `LakeModel` that stores the primitives $ \\alpha, \\lambda, b, d $." ] }, { "cell_type": "code", "execution_count": null, "id": "af2609c2", "metadata": { "hide-output": false }, "outputs": [], "source": [ "class LakeModel(NamedTuple):\n", " \"\"\"\n", " Parameters for the lake model\n", " \"\"\"\n", " λ: float\n", " α: float\n", " b: float\n", " d: float\n", " A: jnp.ndarray \n", " R: jnp.ndarray\n", " g: float\n", "\n", "\n", "def create_lake_model(\n", " λ: float = 0.283, # job finding rate\n", " α: float = 0.013, # separation rate\n", " b: float = 0.0124, # birth rate\n", " d: float = 0.00822 # death rate\n", " ) -> LakeModel:\n", " \"\"\"\n", " Create a LakeModel instance with default parameters.\n", "\n", " Computes and stores the transition matrices A and R,\n", " and the labor force growth rate g.\n", "\n", " \"\"\"\n", " # Compute growth rate\n", " g = b - d\n", "\n", " # Compute transition matrix A\n", " A = jnp.array([\n", " [(1-d) * (1-λ) + b, (1-d) * α + b],\n", " [(1-d) * λ, (1-d) * (1-α)]\n", " ])\n", "\n", " # Compute normalized transition matrix R\n", " R = A / (1 + g)\n", "\n", " return LakeModel(λ=λ, α=α, b=b, d=d, A=A, R=R, g=g)" ] }, { "cell_type": "markdown", "id": "c6cf3741", "metadata": {}, "source": [ "The default parameter values are:\n", "\n", "- $ \\alpha = 0.013 $ and $ \\lambda = 0.283 $ are based on [[Davis *et al.*, 2006](https://python.quantecon.org/zreferences.html#id154)] \n", "- $ b = 0.0124 $ and $ d = 0.00822 $ are set to match monthly [birth](https://www.cdc.gov/nchs/fastats/births.htm) and [death rates](https://www.cdc.gov/nchs/fastats/deaths.htm), respectively, in the U.S. population \n", "\n", "\n", "As an experiment, let’s create two instances, one with $ α=0.013 $ and another with $ α=0.03 $" ] }, { "cell_type": "code", "execution_count": null, "id": "e39d3dfb", "metadata": { "hide-output": false }, "outputs": [], "source": [ "model = create_lake_model()\n", "print(f\"Default α: {model.α}\")\n", "print(f\"A matrix:\\n{model.A}\")\n", "print(f\"R matrix:\\n{model.R}\")" ] }, { "cell_type": "code", "execution_count": null, "id": "57c67ed4", "metadata": { "hide-output": false }, "outputs": [], "source": [ "model_new = create_lake_model(α=0.03)\n", "print(f\"New α: {model_new.α}\")\n", "print(f\"New A matrix:\\n{model_new.A}\")\n", "print(f\"New R matrix:\\n{model_new.R}\")" ] }, { "cell_type": "markdown", "id": "e801d83f", "metadata": {}, "source": [ "### Code for dynamics\n", "\n", "We will also use a specialized function to generate time series in an efficient\n", "JAX-compatible manner.\n", "\n", "Iteratively generating time series is somewhat nontrivial in JAX because arrays\n", "are immutable.\n", "\n", "Here we use `lax.scan`, which allows the function to be jit-compiled.\n", "\n", "Readers who prefer to skip the details can safely continue reading after the function definition." ] }, { "cell_type": "code", "execution_count": null, "id": "94dd4419", "metadata": { "hide-output": false }, "outputs": [], "source": [ "@partial(jax.jit, static_argnames=['f', 'num_steps'])\n", "def generate_path(f, initial_state, num_steps, **kwargs):\n", " \"\"\"\n", " Generate a time series by repeatedly applying an update rule.\n", "\n", " Given a map f, initial state x_0, and model parameters, this\n", " function computes and returns the sequence {x_t}_{t=0}^{T-1} when\n", "\n", " x_{t+1} = f(x_t, **kwargs)\n", "\n", " Args:\n", " f: Update function mapping (x_t, **kwargs) -> x_{t+1}\n", " initial_state: Initial state x_0\n", " num_steps: Number of time steps T to simulate\n", " **kwargs: Optional extra arguments passed to f\n", "\n", " Returns:\n", " Array of shape (dim(x), T) containing the time series path\n", " [x_0, x_1, x_2, ..., x_{T-1}]\n", " \"\"\"\n", "\n", " def update_wrapper(state, t):\n", " \"\"\"\n", " Wrapper function that adapts f for use with JAX scan.\n", " \"\"\"\n", " next_state = f(state, **kwargs)\n", " return next_state, state\n", "\n", " _, path = jax.lax.scan(update_wrapper,\n", " initial_state, jnp.arange(num_steps))\n", " return path.T" ] }, { "cell_type": "markdown", "id": "09e7b0e2", "metadata": {}, "source": [ "Here are functions to update $ X_t $ and $ x_t $." ] }, { "cell_type": "code", "execution_count": null, "id": "bc6c277e", "metadata": { "hide-output": false }, "outputs": [], "source": [ "def stock_update(X: jnp.ndarray, model: LakeModel) -> jnp.ndarray:\n", " \"\"\"Apply transition matrix to get next period's stocks.\"\"\"\n", " λ, α, b, d, A, R, g = model\n", " return A @ X\n", "\n", "def rate_update(x: jnp.ndarray, model: LakeModel) -> jnp.ndarray:\n", " \"\"\"Apply normalized transition matrix for next period's rates.\"\"\"\n", " λ, α, b, d, A, R, g = model\n", " return R @ x" ] }, { "cell_type": "markdown", "id": "5f5a459d", "metadata": {}, "source": [ "### Aggregate dynamics\n", "\n", "Let’s run a simulation under the default parameters starting from $ X_0 = (12, 138) $.\n", "\n", "We will plot the sequences $ \\{E_t\\} $, $ \\{U_t\\} $ and $ \\{N_t\\} $." ] }, { "cell_type": "code", "execution_count": null, "id": "242ddda7", "metadata": { "hide-output": false }, "outputs": [], "source": [ "N_0 = 150 # Population\n", "e_0 = 0.92 # Initial employment rate\n", "u_0 = 1 - e_0 # Initial unemployment rate\n", "T = 50 # Simulation length\n", "\n", "U_0 = u_0 * N_0\n", "E_0 = e_0 * N_0\n", "\n", "# Generate X path\n", "X_0 = jnp.array([U_0, E_0])\n", "X_path = generate_path(stock_update, X_0, T, model=model)\n", "\n", "# Plot\n", "fig, axes = plt.subplots(3, 1, figsize=(10, 8))\n", "titles = ['unemployment', 'employment', 'labor force']\n", "data = [X_path[0, :], X_path[1, :], X_path.sum(0)]\n", "for ax, title, series in zip(axes, titles, data):\n", " ax.plot(series, lw=2)\n", " ax.set_title(title)\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "1cb1f3f9", "metadata": {}, "source": [ "The aggregates $ E_t $ and $ U_t $ don’t converge because their sum $ E_t + U_t $ grows at rate $ g $." ] }, { "cell_type": "markdown", "id": "88eca82c", "metadata": {}, "source": [ "### Rate dynamics\n", "\n", "On the other hand, the vector of employment and unemployment rates $ x_t $ can be in a steady state $ \\bar x $ if\n", "there exists an $ \\bar x $ such that\n", "\n", "- $ \\bar x = R \\bar x $ \n", "- the components satisfy $ \\bar e + \\bar u = 1 $ \n", "\n", "\n", "This equation tells us that a steady state level $ \\bar x $ is an eigenvector of $ R $ associated with a unit eigenvalue.\n", "\n", "The following function can be used to compute the steady state." ] }, { "cell_type": "code", "execution_count": null, "id": "a7ce221b", "metadata": { "hide-output": false }, "outputs": [], "source": [ "@jax.jit\n", "def rate_steady_state(model: LakeModel) -> jnp.ndarray:\n", " r\"\"\"\n", " Finds the steady state of the system :math:`x_{t+1} = R x_{t}`\n", " by computing the eigenvector corresponding to the largest eigenvalue.\n", "\n", " By the Perron-Frobenius theorem, since :math:`R` is a non-negative\n", " matrix with columns summing to 1 (a stochastic matrix), the largest\n", " eigenvalue equals 1 and the corresponding eigenvector gives the steady state.\n", " \"\"\"\n", " λ, α, b, d, A, R, g = model\n", " eigenvals, eigenvec = jnp.linalg.eig(R)\n", "\n", " # Find the eigenvector corresponding to the largest eigenvalue\n", " # (which is 1 for a stochastic matrix by Perron-Frobenius theorem)\n", " max_idx = jnp.argmax(jnp.abs(eigenvals))\n", "\n", " # Get the corresponding eigenvector\n", " steady_state = jnp.real(eigenvec[:, max_idx])\n", "\n", " # Normalize to ensure positive values and sum to 1\n", " steady_state = jnp.abs(steady_state)\n", " steady_state = steady_state / jnp.sum(steady_state)\n", "\n", " return steady_state" ] }, { "cell_type": "markdown", "id": "44701cb7", "metadata": {}, "source": [ "We also have $ x_t \\to \\bar x $ as $ t \\to \\infty $ provided that the remaining\n", "eigenvalue of $ R $ has modulus less than 1.\n", "\n", "This is the case for our default parameters:" ] }, { "cell_type": "code", "execution_count": null, "id": "44c045c4", "metadata": { "hide-output": false }, "outputs": [], "source": [ "model = create_lake_model()\n", "e, f = jnp.linalg.eigvals(model.R)\n", "print(f\"Eigenvalue magnitudes: {abs(e):.2f}, {abs(f):.2f}\")" ] }, { "cell_type": "markdown", "id": "5c6029db", "metadata": {}, "source": [ "Let’s look at the convergence of the unemployment and employment rates to steady state levels (dashed line)" ] }, { "cell_type": "code", "execution_count": null, "id": "0292d27b", "metadata": { "hide-output": false }, "outputs": [], "source": [ "xbar = rate_steady_state(model)\n", "\n", "fig, axes = plt.subplots(2, 1, figsize=(10, 8))\n", "x_0 = jnp.array([u_0, e_0])\n", "x_path = generate_path(rate_update, x_0, T, model=model)\n", "\n", "titles = ['unemployment rate', 'employment rate']\n", "\n", "for i, title in enumerate(titles):\n", " axes[i].plot(x_path[i, :], lw=2, alpha=0.5)\n", " axes[i].hlines(xbar[i], 0, T, color='C1', linestyle='--')\n", " axes[i].set_title(title)\n", "\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "9c28c494", "metadata": {}, "source": [ "### Exercise 73.1\n", "\n", "Use JAX’s `vmap` to compute steady-state unemployment rates for a range of job finding rates $ \\lambda $ (from 0.1 to 0.5), and plot the relationship." ] }, { "cell_type": "markdown", "id": "a713fa32", "metadata": {}, "source": [ "### Solution\n", "\n", "Here is one solution" ] }, { "cell_type": "code", "execution_count": null, "id": "eaccbf09", "metadata": { "hide-output": false }, "outputs": [], "source": [ "@jax.jit\n", "def compute_unemployment_rate(λ_val):\n", " \"\"\"Computes steady-state unemployment for a given λ\"\"\"\n", " model = create_lake_model(λ=λ_val)\n", " steady_state = rate_steady_state(model)\n", " return steady_state[0]\n", "\n", "# Use vmap to compute for multiple λ values\n", "λ_values = jnp.linspace(0.1, 0.5, 50)\n", "unemployment_rates = jax.vmap(compute_unemployment_rate)(λ_values)\n", "\n", "# Plot the results\n", "fig, ax = plt.subplots(figsize=(10, 6))\n", "ax.plot(λ_values, unemployment_rates, lw=2)\n", "ax.set_xlabel(r'$\\lambda$')\n", "ax.set_ylabel('steady-state unemployment rate')\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "969fc6e1", "metadata": {}, "source": [ "\n", "" ] }, { "cell_type": "markdown", "id": "113613da", "metadata": {}, "source": [ "## Dynamics of an individual worker\n", "\n", "An individual worker’s employment dynamics are governed by a [finite state Markov process](https://python.quantecon.org/finite_markov.html).\n", "\n", "The worker can be in one of two states:\n", "\n", "- $ s_t=0 $ means unemployed \n", "- $ s_t=1 $ means employed \n", "\n", "\n", "Let’s start off under the assumption that $ b = d = 0 $.\n", "\n", "The associated transition matrix is then\n", "\n", "$$\n", "P = \\left(\n", " \\begin{matrix}\n", " 1 - \\lambda & \\lambda \\\\\n", " \\alpha & 1 - \\alpha\n", " \\end{matrix}\n", " \\right)\n", "$$\n", "\n", "Let $ \\psi_t $ denote the [marginal distribution](https://python.quantecon.org/finite_markov.html#mc-md) over employment/unemployment states for the worker at time $ t $.\n", "\n", "As usual, we regard it as a row vector.\n", "\n", "We know [from an earlier discussion](https://python.quantecon.org/finite_markov.html#mc-md) that $ \\psi_t $ follows the law of motion\n", "\n", "$$\n", "\\psi_{t+1} = \\psi_t P\n", "$$\n", "\n", "We also know from the [lecture on finite Markov chains](https://python.quantecon.org/finite_markov.html)\n", "that if $ \\alpha \\in (0, 1) $ and $ \\lambda \\in (0, 1) $, then\n", "$ P $ has a unique stationary distribution, denoted here by $ \\psi^* $.\n", "\n", "The unique stationary distribution satisfies\n", "\n", "$$\n", "\\psi^*[0] = \\frac{\\alpha}{\\alpha + \\lambda}\n", "$$\n", "\n", "Not surprisingly, probability mass on the unemployment state increases with\n", "the dismissal rate and falls with the job finding rate." ] }, { "cell_type": "markdown", "id": "c57764bc", "metadata": {}, "source": [ "### Ergodicity\n", "\n", "Let’s look at a typical lifetime of employment-unemployment spells.\n", "\n", "We want to compute the average amounts of time an infinitely lived worker would spend employed and unemployed.\n", "\n", "Let\n", "\n", "$$\n", "\\bar s_{u,T} := \\frac1{T} \\sum_{t=1}^T \\mathbb 1\\{s_t = 0\\}\n", "$$\n", "\n", "and\n", "\n", "$$\n", "\\bar s_{e,T} := \\frac1{T} \\sum_{t=1}^T \\mathbb 1\\{s_t = 1\\}\n", "$$\n", "\n", "(As usual, $ \\mathbb 1\\{Q\\} = 1 $ if statement $ Q $ is true and 0 otherwise)\n", "\n", "These are the fraction of time a worker spends unemployed and employed, respectively, up until period $ T $.\n", "\n", "If $ \\alpha \\in (0, 1) $ and $ \\lambda \\in (0, 1) $, then $ P $ is [ergodic](https://python.quantecon.org/finite_markov.html#ergodicity), and hence we have\n", "\n", "$$\n", "\\lim_{T \\to \\infty} \\bar s_{u, T} = \\psi^*[0]\n", "\\quad \\text{and} \\quad\n", "\\lim_{T \\to \\infty} \\bar s_{e, T} = \\psi^*[1]\n", "$$\n", "\n", "with probability one.\n", "\n", "Inspection tells us that $ P $ is exactly the transpose of $ R $ under the assumption $ b=d=0 $.\n", "\n", "Thus, the percentages of time that an infinitely lived worker spends employed and unemployed equal the fractions of workers employed and unemployed in the steady state distribution." ] }, { "cell_type": "markdown", "id": "db32ffae", "metadata": {}, "source": [ "### Convergence rate\n", "\n", "How long does it take for time series sample averages to converge to cross-sectional averages?\n", "\n", "We can investigate this by simulating the Markov chain.\n", "\n", "Let’s plot the path of the sample averages over 5,000 periods" ] }, { "cell_type": "code", "execution_count": null, "id": "622ad997", "metadata": { "hide-output": false }, "outputs": [], "source": [ "def markov_update(state, P, key):\n", " \"\"\"\n", " Sample next state from transition probabilities.\n", " \"\"\"\n", " probs = P[state]\n", " state_new = jax.random.choice(key,\n", " a=jnp.arange(len(probs)),\n", " p=probs)\n", " return state_new\n", "\n", "model_markov = create_lake_model(d=0, b=0)\n", "T = 5000 # Simulation length\n", "\n", "α, λ = model_markov.α, model_markov.λ\n", "\n", "P = jnp.array([[1 - λ, λ],\n", " [ α, 1 - α]])\n", "\n", "xbar = rate_steady_state(model_markov)\n", "\n", "# Simulate the Markov chain - we need a different approach for random updates\n", "key = jax.random.PRNGKey(0)\n", "\n", "def simulate_markov(P, initial_state, T, key):\n", " \"\"\"Simulate Markov chain for T periods\"\"\"\n", " keys = jax.random.split(key, T)\n", "\n", " def scan_fn(state, key):\n", " next_state = markov_update(state, P, key)\n", " return next_state, state\n", "\n", " _, path = jax.lax.scan(scan_fn, initial_state, keys)\n", " return path\n", "\n", "s_path = simulate_markov(P, 1, T, key)\n", "\n", "fig, axes = plt.subplots(2, 1, figsize=(10, 8))\n", "s_bar_e = jnp.cumsum(s_path) / jnp.arange(1, T+1)\n", "s_bar_u = 1 - s_bar_e\n", "\n", "to_plot = [s_bar_u, s_bar_e]\n", "titles = ['percent of time unemployed', 'percent of time employed']\n", "\n", "for i, plot in enumerate(to_plot):\n", " axes[i].plot(plot, lw=2, alpha=0.5)\n", " axes[i].hlines(xbar[i], 0, T, color='C1', linestyle='--')\n", " axes[i].set_title(titles[i])\n", "\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "f3ff152e", "metadata": {}, "source": [ "The stationary probabilities are given by the dashed line.\n", "\n", "In this case it takes much of the sample for these two objects to converge.\n", "\n", "This is largely due to the high persistence in the Markov chain." ] }, { "cell_type": "markdown", "id": "849f0778", "metadata": {}, "source": [ "## Exercises" ] }, { "cell_type": "markdown", "id": "87e3a1cd", "metadata": {}, "source": [ "## Exercise 73.2\n", "\n", "Consider an economy with an initial stock of workers $ N_0 = 100 $ at the\n", "steady state level of employment in the baseline parameterization.\n", "\n", "Suppose that in response to new legislation the hiring rate reduces to $ \\lambda = 0.2 $.\n", "\n", "Plot the transition dynamics of the unemployment and employment stocks for 50 periods.\n", "\n", "Plot the transition dynamics for the rates.\n", "\n", "How long does the economy take to converge to its new steady state?\n", "\n", "What is the new steady state level of employment?" ] }, { "cell_type": "markdown", "id": "755449e8", "metadata": {}, "source": [ "## Solution\n", "\n", "We begin by constructing the model with default parameters and finding the\n", "initial steady state" ] }, { "cell_type": "code", "execution_count": null, "id": "e2aca9f3", "metadata": { "hide-output": false }, "outputs": [], "source": [ "model_initial = create_lake_model()\n", "x0 = rate_steady_state(model_initial)\n", "print(f\"Initial Steady State: {x0}\")" ] }, { "cell_type": "markdown", "id": "d20afaaa", "metadata": {}, "source": [ "Initialize the simulation values" ] }, { "cell_type": "code", "execution_count": null, "id": "a49354d1", "metadata": { "hide-output": false }, "outputs": [], "source": [ "N0 = 100\n", "T = 50" ] }, { "cell_type": "markdown", "id": "7e9cead7", "metadata": {}, "source": [ "New legislation changes $ \\lambda $ to $ 0.2 $" ] }, { "cell_type": "code", "execution_count": null, "id": "068d0f34", "metadata": { "hide-output": false }, "outputs": [], "source": [ "model_ex2 = create_lake_model(λ=0.2)\n", "xbar = rate_steady_state(model_ex2) # new steady state\n", "\n", "# Simulate paths\n", "X_path = generate_path(stock_update, x0 * N0, T, model=model_ex2)\n", "x_path = generate_path(rate_update, x0, T, model=model_ex2)\n", "print(f\"New Steady State: {xbar}\")" ] }, { "cell_type": "markdown", "id": "95e34f6b", "metadata": {}, "source": [ "Now plot stocks" ] }, { "cell_type": "code", "execution_count": null, "id": "01e2c283", "metadata": { "hide-output": false }, "outputs": [], "source": [ "fig, axes = plt.subplots(3, 1, figsize=[10, 9])\n", "\n", "axes[0].plot(X_path[0, :])\n", "axes[0].set_title('unemployment')\n", "\n", "axes[1].plot(X_path[1, :])\n", "axes[1].set_title('employment')\n", "\n", "axes[2].plot(X_path.sum(0))\n", "axes[2].set_title('labor force')\n", "\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "fb301cc4", "metadata": {}, "source": [ "And how the rates evolve" ] }, { "cell_type": "code", "execution_count": null, "id": "19b689b7", "metadata": { "hide-output": false }, "outputs": [], "source": [ "fig, axes = plt.subplots(2, 1, figsize=(10, 8))\n", "\n", "titles = ['unemployment rate', 'employment rate']\n", "\n", "for i, title in enumerate(titles):\n", " axes[i].plot(x_path[i, :])\n", " axes[i].hlines(xbar[i], 0, T, color='C1', linestyle='--')\n", " axes[i].set_title(title)\n", "\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "f0480d2b", "metadata": {}, "source": [ "We see that it takes 20 periods for the economy to converge to its new\n", "steady state levels." ] }, { "cell_type": "markdown", "id": "a9fcdc11", "metadata": {}, "source": [ "## Exercise 73.3\n", "\n", "Consider an economy with an initial stock of workers $ N_0 = 100 $ at the\n", "steady state level of employment in the baseline parameterization.\n", "\n", "Suppose that for 20 periods the birth rate was temporarily high ($ b = 0.025 $) and then returned to its original level.\n", "\n", "Plot the transition dynamics of the unemployment and employment stocks for 50 periods.\n", "\n", "Plot the transition dynamics for the rates.\n", "\n", "How long does the economy take to return to its original steady state?" ] }, { "cell_type": "markdown", "id": "3747d8a2", "metadata": {}, "source": [ "## Solution\n", "\n", "This exercise has the economy experiencing a boom in entrances to\n", "the labor market and then later returning to the original levels.\n", "\n", "For 20 periods the economy has a new entry rate into the labor market.\n", "\n", "Let’s start off at the baseline parameterization and record the steady\n", "state" ] }, { "cell_type": "code", "execution_count": null, "id": "e5c2b265", "metadata": { "hide-output": false }, "outputs": [], "source": [ "model_baseline = create_lake_model()\n", "x0 = rate_steady_state(model_baseline)\n", "N0 = 100\n", "T = 50" ] }, { "cell_type": "markdown", "id": "72d6c9f4", "metadata": {}, "source": [ "Here are the other parameters:" ] }, { "cell_type": "code", "execution_count": null, "id": "f7ec812f", "metadata": { "hide-output": false }, "outputs": [], "source": [ "b_hat = 0.025\n", "T_hat = 20" ] }, { "cell_type": "markdown", "id": "5a9d4694", "metadata": {}, "source": [ "Let’s increase $ b $ to the new value and simulate for 20 periods" ] }, { "cell_type": "code", "execution_count": null, "id": "2eac6ebd", "metadata": { "hide-output": false }, "outputs": [], "source": [ "model_high_b = create_lake_model(b=b_hat)\n", "\n", "# Simulate stocks and rates for first 20 periods\n", "X_path1 = generate_path(stock_update, x0 * N0, T_hat, model=model_high_b)\n", "x_path1 = generate_path(rate_update, x0, T_hat, model=model_high_b)" ] }, { "cell_type": "markdown", "id": "7316dc16", "metadata": {}, "source": [ "Now we reset $ b $ to the original value and then, using the state\n", "after 20 periods for the new initial conditions, we simulate for the\n", "additional 30 periods" ] }, { "cell_type": "code", "execution_count": null, "id": "e21bc653", "metadata": { "hide-output": false }, "outputs": [], "source": [ "# Use final state from period 20 as initial condition\n", "X_path2 = generate_path(stock_update, X_path1[:, -1], T-T_hat,\n", " model=model_baseline)\n", "x_path2 = generate_path(rate_update, x_path1[:, -1], T-T_hat,\n", " model=model_baseline)" ] }, { "cell_type": "markdown", "id": "fbff930d", "metadata": {}, "source": [ "Finally, we combine these two paths and plot" ] }, { "cell_type": "code", "execution_count": null, "id": "48fd573b", "metadata": { "hide-output": false }, "outputs": [], "source": [ "# Combine paths\n", "X_path = jnp.hstack([X_path1, X_path2[:, 1:]])\n", "x_path = jnp.hstack([x_path1, x_path2[:, 1:]])\n", "\n", "fig, axes = plt.subplots(3, 1, figsize=[10, 9])\n", "\n", "axes[0].plot(X_path[0, :])\n", "axes[0].set_title('unemployment')\n", "\n", "axes[1].plot(X_path[1, :])\n", "axes[1].set_title('employment')\n", "\n", "axes[2].plot(X_path.sum(0))\n", "axes[2].set_title('labor force')\n", "\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "26e8dca6", "metadata": {}, "source": [ "And the rates" ] }, { "cell_type": "code", "execution_count": null, "id": "506b0857", "metadata": { "hide-output": false }, "outputs": [], "source": [ "fig, axes = plt.subplots(2, 1, figsize=[10, 6])\n", "\n", "titles = ['unemployment rate', 'employment rate']\n", "\n", "for i, title in enumerate(titles):\n", " axes[i].plot(x_path[i, :])\n", " axes[i].hlines(x0[i], 0, T, color='C1', linestyle='--')\n", " axes[i].set_title(title)\n", "\n", "plt.tight_layout()\n", "plt.show()" ] } ], "metadata": { "date": 1770028419.117745, "filename": "lake_model.md", "kernelspec": { "display_name": "Python", "language": "python3", "name": "python3" }, "title": "A Lake Model of Employment and Unemployment" }, "nbformat": 4, "nbformat_minor": 5 }