{ "cells": [ { "cell_type": "markdown", "id": "e59f87b0", "metadata": {}, "source": [ "\n", "" ] }, { "cell_type": "markdown", "id": "95e17e04", "metadata": {}, "source": [ "# Job Search II: Search and Separation\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "id": "6425cd3f", "metadata": {}, "source": [ "## Contents\n", "\n", "- [Job Search II: Search and Separation](#Job-Search-II:-Search-and-Separation) \n", " - [Overview](#Overview) \n", " - [The Model](#The-Model) \n", " - [Solving the Model](#Solving-the-Model) \n", " - [Implementation](#Implementation) \n", " - [Impact of Parameters](#Impact-of-Parameters) \n", " - [Exercises](#Exercises) " ] }, { "cell_type": "markdown", "id": "54fdd0c4", "metadata": {}, "source": [ "In addition to what’s in Anaconda, this lecture will need the following libraries:" ] }, { "cell_type": "code", "execution_count": null, "id": "31b7719a", "metadata": { "hide-output": false }, "outputs": [], "source": [ "!pip install quantecon" ] }, { "cell_type": "markdown", "id": "c5fe5ded", "metadata": {}, "source": [ "## Overview\n", "\n", "Previously [we looked](https://python.quantecon.org/mccall_model.html) at the McCall job search model [[McCall, 1970](https://python.quantecon.org/zreferences.html#id193)] as a way of understanding unemployment and worker decisions.\n", "\n", "One unrealistic feature of the model is that every job is permanent.\n", "\n", "In this lecture, we extend the McCall model by introducing job separation.\n", "\n", "Once separation enters the picture, the agent comes to view\n", "\n", "- the loss of a job as a capital loss, and \n", "- a spell of unemployment as an *investment* in searching for an acceptable job \n", "\n", "\n", "The other minor addition is that a utility function will be included to make\n", "worker preferences slightly more sophisticated.\n", "\n", "We’ll need the following imports" ] }, { "cell_type": "code", "execution_count": null, "id": "8cd910a9", "metadata": { "hide-output": false }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "plt.rcParams[\"figure.figsize\"] = (11, 5) #set default figure size\n", "import numpy as np\n", "from numba import njit, float64\n", "from numba.experimental import jitclass\n", "from quantecon.distributions import BetaBinomial" ] }, { "cell_type": "markdown", "id": "b72efee7", "metadata": {}, "source": [ "## The Model\n", "\n", "The model is similar to the [baseline McCall job search model](https://python.quantecon.org/mccall_model.html).\n", "\n", "It concerns the life of an infinitely lived worker and\n", "\n", "- the opportunities he or she (let’s say he to save one character) has to work at different wages \n", "- exogenous events that destroy his current job \n", "- his decision making process while unemployed \n", "\n", "\n", "The worker can be in one of two states: employed or unemployed.\n", "\n", "He wants to maximize\n", "\n", "\n", "\n", "$$\n", "{\\mathbb E} \\sum_{t=0}^\\infty \\beta^t u(y_t) \\tag{28.1}\n", "$$\n", "\n", "At this stage the only difference from the [baseline model](https://python.quantecon.org/mccall_model.html) is that we’ve added some flexibility to preferences by\n", "introducing a utility function $ u $.\n", "\n", "It satisfies $ u'> 0 $ and $ u'' < 0 $." ] }, { "cell_type": "markdown", "id": "9886a1ea", "metadata": {}, "source": [ "### The Wage Process\n", "\n", "For now we will drop the separation of state process and wage process that we\n", "maintained for the [baseline model](https://python.quantecon.org/mccall_model.html).\n", "\n", "In particular, we simply suppose that wage offers $ \\{ w_t \\} $ are IID with common distribution $ q $.\n", "\n", "The set of possible wage values is denoted by $ \\mathbb W $.\n", "\n", "(Later we will go back to having a separate state process $ \\{s_t\\} $\n", "driving random outcomes, since this formulation is usually convenient in more sophisticated\n", "models.)" ] }, { "cell_type": "markdown", "id": "55a5097d", "metadata": {}, "source": [ "### Timing and Decisions\n", "\n", "At the start of each period, the agent can be either\n", "\n", "- unemployed or \n", "- employed at some existing wage level $ w_e $. \n", "\n", "\n", "At the start of a given period, the current wage offer $ w_t $ is observed.\n", "\n", "If currently *employed*, the worker\n", "\n", "1. receives utility $ u(w_e) $ and \n", "1. is fired with some (small) probability $ \\alpha $. \n", "\n", "\n", "If currently *unemployed*, the worker either accepts or rejects the current offer $ w_t $.\n", "\n", "If he accepts, then he begins work immediately at wage $ w_t $.\n", "\n", "If he rejects, then he receives unemployment compensation $ c $.\n", "\n", "The process then repeats.\n", "\n", ">**Note**\n", ">\n", ">We do not allow for job search while employed—this topic is taken up in a [later lecture](https://python.quantecon.org/jv.html)." ] }, { "cell_type": "markdown", "id": "0fcb4866", "metadata": {}, "source": [ "## Solving the Model\n", "\n", "We drop time subscripts in what follows and primes denote next period values.\n", "\n", "Let\n", "\n", "- $ v(w_e) $ be total lifetime value accruing to a worker who enters the current period *employed* with existing wage $ w_e $ \n", "- $ h(w) $ be total lifetime value accruing to a worker who who enters the current period *unemployed* and receives\n", " wage offer $ w $. \n", "\n", "\n", "Here *value* means the value of the objective function [(28.1)](#equation-objective) when the worker makes optimal decisions at all future points in time.\n", "\n", "Our first aim is to obtain these functions." ] }, { "cell_type": "markdown", "id": "baaf6a6e", "metadata": {}, "source": [ "### The Bellman Equations\n", "\n", "Suppose for now that the worker can calculate the functions $ v $ and $ h $ and use them in his decision making.\n", "\n", "Then $ v $ and $ h $ should satisfy\n", "\n", "\n", "\n", "$$\n", "v(w_e) = u(w_e) + \\beta\n", " \\left[\n", " (1-\\alpha)v(w_e) + \\alpha \\sum_{w' \\in \\mathbb W} h(w') q(w')\n", " \\right] \\tag{28.2}\n", "$$\n", "\n", "and\n", "\n", "\n", "\n", "$$\n", "h(w) = \\max \\left\\{ v(w), \\, u(c) + \\beta \\sum_{w' \\in \\mathbb W} h(w') q(w') \\right\\} \\tag{28.3}\n", "$$\n", "\n", "Equation [(28.2)](#equation-bell1-mccall) expresses the value of being employed at wage $ w_e $ in terms of\n", "\n", "- current reward $ u(w_e) $ plus \n", "- discounted expected reward tomorrow, given the $ \\alpha $ probability of being fired \n", "\n", "\n", "Equation [(28.3)](#equation-bell2-mccall) expresses the value of being unemployed with offer\n", "$ w $ in hand as a maximum over the value of two options: accept or reject\n", "the current offer.\n", "\n", "Accepting transitions the worker to employment and hence yields reward $ v(w) $.\n", "\n", "Rejecting leads to unemployment compensation and unemployment tomorrow.\n", "\n", "Equations [(28.2)](#equation-bell1-mccall) and [(28.3)](#equation-bell2-mccall) are the Bellman equations for this model.\n", "\n", "They provide enough information to solve for both $ v $ and $ h $.\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "id": "7012e15c", "metadata": {}, "source": [ "### A Simplifying Transformation\n", "\n", "Rather than jumping straight into solving these equations, let’s see if we can\n", "simplify them somewhat.\n", "\n", "(This process will be analogous to our [second pass](https://python.quantecon.org/mccall_model.html#mm-op2) at the plain vanilla\n", "McCall model, where we simplified the Bellman equation.)\n", "\n", "First, let\n", "\n", "\n", "\n", "$$\n", "d := \\sum_{w' \\in \\mathbb W} h(w') q(w') \\tag{28.4}\n", "$$\n", "\n", "be the expected value of unemployment tomorrow.\n", "\n", "We can now write [(28.3)](#equation-bell2-mccall) as\n", "\n", "$$\n", "h(w) = \\max \\left\\{ v(w), \\, u(c) + \\beta d \\right\\}\n", "$$\n", "\n", "or, shifting time forward one period\n", "\n", "$$\n", "\\sum_{w' \\in \\mathbb W} h(w') q(w')\n", " = \\sum_{w' \\in \\mathbb W} \\max \\left\\{ v(w'), \\, u(c) + \\beta d \\right\\} q(w')\n", "$$\n", "\n", "Using [(28.4)](#equation-defd-mm) again now gives\n", "\n", "\n", "\n", "$$\n", "d = \\sum_{w' \\in \\mathbb W} \\max \\left\\{ v(w'), \\, u(c) + \\beta d \\right\\} q(w') \\tag{28.5}\n", "$$\n", "\n", "Finally, [(28.2)](#equation-bell1-mccall) can now be rewritten as\n", "\n", "\n", "\n", "$$\n", "v(w) = u(w) + \\beta\n", " \\left[\n", " (1-\\alpha)v(w) + \\alpha d\n", " \\right] \\tag{28.6}\n", "$$\n", "\n", "In the last expression, we wrote $ w_e $ as $ w $ to make the notation\n", "simpler." ] }, { "cell_type": "markdown", "id": "1a4a75f6", "metadata": {}, "source": [ "### The Reservation Wage\n", "\n", "Suppose we can use [(28.5)](#equation-bell02-mccall) and [(28.6)](#equation-bell01-mccall) to solve for\n", "$ d $ and $ v $.\n", "\n", "(We will do this soon.)\n", "\n", "We can then determine optimal behavior for the worker.\n", "\n", "From [(28.3)](#equation-bell2-mccall), we see that an unemployed agent accepts current offer\n", "$ w $ if $ v(w) \\geq u(c) + \\beta d $.\n", "\n", "This means precisely that the value of accepting is higher than the expected value of rejecting.\n", "\n", "It is clear that $ v $ is (at least weakly) increasing in $ w $, since the agent is never made worse off by a higher wage offer.\n", "\n", "Hence, we can express the optimal choice as accepting wage offer $ w $ if and only if\n", "\n", "$$\n", "w \\geq \\bar w\n", "\\quad \\text{where} \\quad\n", "\\bar w \\text{ solves } v(\\bar w) = u(c) + \\beta d\n", "$$" ] }, { "cell_type": "markdown", "id": "4915f3f3", "metadata": {}, "source": [ "### Solving the Bellman Equations\n", "\n", "We’ll use the same iterative approach to solving the Bellman equations that we\n", "adopted in the [first job search lecture](https://python.quantecon.org/mccall_model.html).\n", "\n", "Here this amounts to\n", "\n", "1. make guesses for $ d $ and $ v $ \n", "1. plug these guesses into the right-hand sides of [(28.5)](#equation-bell02-mccall) and [(28.6)](#equation-bell01-mccall) \n", "1. update the left-hand sides from this rule and then repeat \n", "\n", "\n", "In other words, we are iterating using the rules\n", "\n", "\n", "\n", "$$\n", "d_{n+1} = \\sum_{w' \\in \\mathbb W}\n", " \\max \\left\\{ v_n(w'), \\, u(c) + \\beta d_n \\right\\} q(w') \\tag{28.7}\n", "$$\n", "\n", "\n", "\n", "$$\n", "v_{n+1}(w) = u(w) + \\beta\n", " \\left[\n", " (1-\\alpha)v_n(w) + \\alpha d_n\n", " \\right] \\tag{28.8}\n", "$$\n", "\n", "starting from some initial conditions $ d_0, v_0 $.\n", "\n", "As before, the system always converges to the true solutions—in this case,\n", "the $ v $ and $ d $ that solve [(28.5)](#equation-bell02-mccall) and [(28.6)](#equation-bell01-mccall).\n", "\n", "(A proof can be obtained via the Banach contraction mapping theorem.)" ] }, { "cell_type": "markdown", "id": "01747ba1", "metadata": {}, "source": [ "## Implementation\n", "\n", "Let’s implement this iterative process.\n", "\n", "In the code, you’ll see that we use a class to store the various parameters and other\n", "objects associated with a given model.\n", "\n", "This helps to tidy up the code and provides an object that’s easy to pass to functions.\n", "\n", "The default utility function is a CRRA utility function" ] }, { "cell_type": "code", "execution_count": null, "id": "5f44defa", "metadata": { "hide-output": false }, "outputs": [], "source": [ "@njit\n", "def u(c, σ=2.0):\n", " return (c**(1 - σ) - 1) / (1 - σ)" ] }, { "cell_type": "markdown", "id": "58440ea7", "metadata": {}, "source": [ "Also, here’s a default wage distribution, based around the BetaBinomial\n", "distribution:" ] }, { "cell_type": "code", "execution_count": null, "id": "22efbd75", "metadata": { "hide-output": false }, "outputs": [], "source": [ "n = 60 # n possible outcomes for w\n", "w_default = np.linspace(10, 20, n) # wages between 10 and 20\n", "a, b = 600, 400 # shape parameters\n", "dist = BetaBinomial(n-1, a, b)\n", "q_default = dist.pdf()" ] }, { "cell_type": "markdown", "id": "d6937bfc", "metadata": {}, "source": [ "Here’s our jitted class for the McCall model with separation." ] }, { "cell_type": "code", "execution_count": null, "id": "e5831164", "metadata": { "hide-output": false }, "outputs": [], "source": [ "mccall_data = [\n", " ('α', float64), # job separation rate\n", " ('β', float64), # discount factor\n", " ('c', float64), # unemployment compensation\n", " ('w', float64[:]), # list of wage values\n", " ('q', float64[:]) # pmf of random variable w\n", "]\n", "\n", "@jitclass(mccall_data)\n", "class McCallModel:\n", " \"\"\"\n", " Stores the parameters and functions associated with a given model.\n", " \"\"\"\n", "\n", " def __init__(self, α=0.2, β=0.98, c=6.0, w=w_default, q=q_default):\n", "\n", " self.α, self.β, self.c, self.w, self.q = α, β, c, w, q\n", "\n", "\n", " def update(self, v, d):\n", "\n", " α, β, c, w, q = self.α, self.β, self.c, self.w, self.q\n", "\n", " v_new = np.empty_like(v)\n", "\n", " for i in range(len(w)):\n", " v_new[i] = u(w[i]) + β * ((1 - α) * v[i] + α * d)\n", "\n", " d_new = np.sum(np.maximum(v, u(c) + β * d) * q)\n", "\n", " return v_new, d_new" ] }, { "cell_type": "markdown", "id": "1eb7386a", "metadata": {}, "source": [ "Now we iterate until successive realizations are closer together than some small tolerance level.\n", "\n", "We then return the current iterate as an approximate solution." ] }, { "cell_type": "code", "execution_count": null, "id": "f9284a09", "metadata": { "hide-output": false }, "outputs": [], "source": [ "@njit\n", "def solve_model(mcm, tol=1e-5, max_iter=2000):\n", " \"\"\"\n", " Iterates to convergence on the Bellman equations\n", "\n", " * mcm is an instance of McCallModel\n", " \"\"\"\n", "\n", " v = np.ones_like(mcm.w) # Initial guess of v\n", " d = 1 # Initial guess of d\n", " i = 0\n", " error = tol + 1\n", "\n", " while error > tol and i < max_iter:\n", " v_new, d_new = mcm.update(v, d)\n", " error_1 = np.max(np.abs(v_new - v))\n", " error_2 = np.abs(d_new - d)\n", " error = max(error_1, error_2)\n", " v = v_new\n", " d = d_new\n", " i += 1\n", "\n", " return v, d" ] }, { "cell_type": "markdown", "id": "ccd83a40", "metadata": {}, "source": [ "### The Reservation Wage: First Pass\n", "\n", "The optimal choice of the agent is summarized by the reservation wage.\n", "\n", "As discussed above, the reservation wage is the $ \\bar w $ that solves\n", "$ v(\\bar w) = h $ where $ h := u(c) + \\beta d $ is the continuation\n", "value.\n", "\n", "Let’s compare $ v $ and $ h $ to see what they look like.\n", "\n", "We’ll use the default parameterizations found in the code above." ] }, { "cell_type": "code", "execution_count": null, "id": "7133f589", "metadata": { "hide-output": false }, "outputs": [], "source": [ "mcm = McCallModel()\n", "v, d = solve_model(mcm)\n", "h = u(mcm.c) + mcm.β * d\n", "\n", "fig, ax = plt.subplots()\n", "\n", "ax.plot(mcm.w, v, 'b-', lw=2, alpha=0.7, label='$v$')\n", "ax.plot(mcm.w, [h] * len(mcm.w),\n", " 'g-', lw=2, alpha=0.7, label='$h$')\n", "ax.set_xlim(min(mcm.w), max(mcm.w))\n", "ax.legend()\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "8b1f1daf", "metadata": {}, "source": [ "The value $ v $ is increasing because higher $ w $ generates a higher wage flow conditional on staying employed." ] }, { "cell_type": "markdown", "id": "7c7c08ef", "metadata": {}, "source": [ "### The Reservation Wage: Computation\n", "\n", "Here’s a function `compute_reservation_wage` that takes an instance of `McCallModel`\n", "and returns the associated reservation wage." ] }, { "cell_type": "code", "execution_count": null, "id": "a5bf1048", "metadata": { "hide-output": false }, "outputs": [], "source": [ "@njit\n", "def compute_reservation_wage(mcm):\n", " \"\"\"\n", " Computes the reservation wage of an instance of the McCall model\n", " by finding the smallest w such that v(w) >= h.\n", "\n", " If no such w exists, then w_bar is set to np.inf.\n", " \"\"\"\n", "\n", " v, d = solve_model(mcm)\n", " h = u(mcm.c) + mcm.β * d\n", "\n", " i = np.searchsorted(v, h, side='right')\n", " w_bar = mcm.w[i]\n", "\n", " return w_bar" ] }, { "cell_type": "markdown", "id": "4199e682", "metadata": {}, "source": [ "Next we will investigate how the reservation wage varies with parameters." ] }, { "cell_type": "markdown", "id": "7cf4526b", "metadata": {}, "source": [ "## Impact of Parameters\n", "\n", "In each instance below, we’ll show you a figure and then ask you to reproduce it in the exercises." ] }, { "cell_type": "markdown", "id": "c4ac83af", "metadata": {}, "source": [ "### The Reservation Wage and Unemployment Compensation\n", "\n", "First, let’s look at how $ \\bar w $ varies with unemployment compensation.\n", "\n", "In the figure below, we use the default parameters in the `McCallModel` class, apart from\n", "c (which takes the values given on the horizontal axis)\n", "\n", "![https://python.quantecon.org/_static/lecture_specific/mccall_model_with_separation/mccall_resw_c.png](https://python.quantecon.org/_static/lecture_specific/mccall_model_with_separation/mccall_resw_c.png)\n", "\n", " \n", "As expected, higher unemployment compensation causes the worker to hold out for higher wages.\n", "\n", "In effect, the cost of continuing job search is reduced." ] }, { "cell_type": "markdown", "id": "ee0fe08d", "metadata": {}, "source": [ "### The Reservation Wage and Discounting\n", "\n", "Next, let’s investigate how $ \\bar w $ varies with the discount factor.\n", "\n", "The next figure plots the reservation wage associated with different values of\n", "$ \\beta $\n", "\n", "![https://python.quantecon.org/_static/lecture_specific/mccall_model_with_separation/mccall_resw_beta.png](https://python.quantecon.org/_static/lecture_specific/mccall_model_with_separation/mccall_resw_beta.png)\n", "\n", " \n", "Again, the results are intuitive: More patient workers will hold out for higher wages." ] }, { "cell_type": "markdown", "id": "f409a294", "metadata": {}, "source": [ "### The Reservation Wage and Job Destruction\n", "\n", "Finally, let’s look at how $ \\bar w $ varies with the job separation rate $ \\alpha $.\n", "\n", "Higher $ \\alpha $ translates to a greater chance that a worker will face termination in each period once employed.\n", "\n", "![https://python.quantecon.org/_static/lecture_specific/mccall_model_with_separation/mccall_resw_alpha.png](https://python.quantecon.org/_static/lecture_specific/mccall_model_with_separation/mccall_resw_alpha.png)\n", "\n", " \n", "Once more, the results are in line with our intuition.\n", "\n", "If the separation rate is high, then the benefit of holding out for a higher wage falls.\n", "\n", "Hence the reservation wage is lower." ] }, { "cell_type": "markdown", "id": "337114eb", "metadata": {}, "source": [ "## Exercises" ] }, { "cell_type": "markdown", "id": "efbeb98a", "metadata": {}, "source": [ "## Exercise 28.1\n", "\n", "Reproduce all the reservation wage figures shown above.\n", "\n", "Regarding the values on the horizontal axis, use" ] }, { "cell_type": "code", "execution_count": null, "id": "97efef25", "metadata": { "hide-output": false }, "outputs": [], "source": [ "grid_size = 25\n", "c_vals = np.linspace(2, 12, grid_size) # unemployment compensation\n", "beta_vals = np.linspace(0.8, 0.99, grid_size) # discount factors\n", "alpha_vals = np.linspace(0.05, 0.5, grid_size) # separation rate" ] }, { "cell_type": "markdown", "id": "ef3f7968", "metadata": {}, "source": [ "## Solution to[ Exercise 28.1](https://python.quantecon.org/#mmws_ex1)\n", "\n", "Here’s the first figure." ] }, { "cell_type": "code", "execution_count": null, "id": "27230e8e", "metadata": { "hide-output": false }, "outputs": [], "source": [ "mcm = McCallModel()\n", "\n", "w_bar_vals = np.empty_like(c_vals)\n", "\n", "fig, ax = plt.subplots()\n", "\n", "for i, c in enumerate(c_vals):\n", " mcm.c = c\n", " w_bar = compute_reservation_wage(mcm)\n", " w_bar_vals[i] = w_bar\n", "\n", "ax.set(xlabel='unemployment compensation',\n", " ylabel='reservation wage')\n", "ax.plot(c_vals, w_bar_vals, label=r'$\\bar w$ as a function of $c$')\n", "ax.legend()\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "2d2e2ae9", "metadata": {}, "source": [ "Here’s the second one." ] }, { "cell_type": "code", "execution_count": null, "id": "3ce215c1", "metadata": { "hide-output": false }, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "\n", "for i, β in enumerate(beta_vals):\n", " mcm.β = β\n", " w_bar = compute_reservation_wage(mcm)\n", " w_bar_vals[i] = w_bar\n", "\n", "ax.set(xlabel='discount factor', ylabel='reservation wage')\n", "ax.plot(beta_vals, w_bar_vals, label=r'$\\bar w$ as a function of $\\beta$')\n", "ax.legend()\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "394b886c", "metadata": {}, "source": [ "Here’s the third." ] }, { "cell_type": "code", "execution_count": null, "id": "1d8cce6b", "metadata": { "hide-output": false }, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "\n", "for i, α in enumerate(alpha_vals):\n", " mcm.α = α\n", " w_bar = compute_reservation_wage(mcm)\n", " w_bar_vals[i] = w_bar\n", "\n", "ax.set(xlabel='separation rate', ylabel='reservation wage')\n", "ax.plot(alpha_vals, w_bar_vals, label=r'$\\bar w$ as a function of $\\alpha$')\n", "ax.legend()\n", "\n", "plt.show()" ] } ], "metadata": { "date": 1714442507.7272375, "filename": "mccall_model_with_separation.md", "kernelspec": { "display_name": "Python", "language": "python3", "name": "python3" }, "title": "Job Search II: Search and Separation" }, "nbformat": 4, "nbformat_minor": 5 }