From 0475f478ef2ce2399d7d6580bfc42875c82f43a9 Mon Sep 17 00:00:00 2001 From: Humphrey Yang Date: Mon, 5 Feb 2024 20:12:35 +1100 Subject: [PATCH 1/3] consumption smoothing experiments --- lectures/cons_smooth.md | 142 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 5 deletions(-) diff --git a/lectures/cons_smooth.md b/lectures/cons_smooth.md index ea52430a..bea3c268 100644 --- a/lectures/cons_smooth.md +++ b/lectures/cons_smooth.md @@ -4,7 +4,7 @@ jupytext: extension: .md format_name: myst format_version: 0.13 - jupytext_version: 1.14.5 + jupytext_version: 1.16.1 kernelspec: display_name: Python 3 (ipykernel) language: python @@ -158,7 +158,6 @@ def creat_cs_model(R=1.05, g1=1, g2=1/2, T=65): +++ {"user_expressions": []} - ## Friedman-Hall consumption-smoothing model A key object is what Milton Friedman called "human" or "non-financial" wealth at time $0$: @@ -301,7 +300,7 @@ This can be interpreted as a student debt. The non-financial process $\{y_t\}_{t=0}^{T}$ is constant and positive up to $t=45$ and then becomes zero afterward. -The drop in non-financial income late in life reflects retirement from work. +The drop in non-financial income late in life reflects retirement from work. ```{code-cell} ipython3 # Financial wealth @@ -311,7 +310,7 @@ a0 = -2 # such as "student debt" y_seq = np.concatenate([np.ones(46), np.zeros(20)]) cs_model = creat_cs_model() -c_seq, a_seq = compute_optimal(cs_model, a0, y_seq) +c_seq, a_seq, h0 = compute_optimal(cs_model, a0, y_seq) print('check a_T+1=0:', np.abs(a_seq[-1] - 0) <= 1e-8) @@ -348,6 +347,139 @@ def welfare(model, c_seq): print('Welfare:', welfare(cs_model, c_seq)) ``` +### Experiments + +In this section we experiment consumption smoothing behavior under different setups. + +First we write a function `plot_cs` that generate graphs above based on a consumption smoothing model `cs_model`. + +This helps us repeat the steps shown above + +```{code-cell} ipython3 +def plot_cs(model, # consumption smoothing model + a0, # initial financial wealth + y_seq # non-financial income process + ): + + # Compute optimal consumption + c_seq, a_seq, h0 = compute_optimal(model, a0, y_seq) + print('average consumption:', np.mean(c_seq)) + + # Sequence length + T = cs_model.T + + # Generate plot + plt.plot(range(T+1), y_seq, label='non-financial income') + plt.plot(range(T+1), c_seq, label='consumption') + plt.plot(range(T+2), a_seq, label='financial wealth') + plt.plot(range(T+2), np.zeros(T+2), '--') + + plt.legend() + plt.xlabel(r'$t$') + plt.ylabel(r'$c_t,y_t,a_t$') + plt.show() +``` + +#### Experiment 1: one-time gain/loss + +We first assume a one-time windfall of $W_0$ in year 21 of the income sequence $y$. + +We'll make $W_0$ big - positive to indicate a one-time windfall, negative to indicate a one-time "disaster". + +```{code-cell} ipython3 +# Windfall W_0 = 5 +y_seq_pos = np.concatenate( + [np.zeros(21), np.array([5]), np.zeros(44)]) + +plot_cs(cs_model, a0, y_seq_pos) +``` + +```{code-cell} ipython3 +# Disaster W_0 = -5 +y_seq_neg = np.concatenate( + [np.zeros(21), np.array([-5]), np.zeros(44)]) + +plot_cs(cs_model, a0, y_seq_neg) +``` + +#### Experiment 2: permanent wage gain/loss + +Now we a permanent increase in income of $W$ in year 21 of the $y$-sequence. + +Again we can study positive and negative cases + +```{code-cell} ipython3 +# Positive permenant income W_t = 1 when t >= 21 +y_seq_pos = np.concatenate( + [np.zeros(21), np.ones(45)]) + +plot_cs(cs_model, a0, y_seq_pos) +``` + +```{code-cell} ipython3 +# Negative permenant income W_t = -1 when t >= 21 +y_seq_pos = np.concatenate( + [np.zeros(21), -np.ones(45)]) + +plot_cs(cs_model, a0, y_seq_pos) +``` + +#### Experiment 3: a late starter + +Now we simulate a $y$ sequence in which a person gets zero for 46 years, and then works and gets 1 the last 20 years of life (a "late starter") + +```{code-cell} ipython3 +# Late starter +y_seq_late = np.concatenate( + [np.zeros(46), np.ones(20)]) + +plot_cs(cs_model, a0, y_seq_late) +``` + +#### Experiment 4: geometric earner + +Now we simulate a geometric $y$ sequence in which a person gets $y_t = \lambda^t y_0$ in first 46 years. + +We first experiemnt with $\lambda = 1.05$ + +```{code-cell} ipython3 +# Geometric earner parameters where λ = 1.05 +λ = 1.05 +y_0 = 1 +t_max = 46 + +# Generate geometric y sequence +y_seq_geo = λ ** np.arange(t_max) * y_0 +y_seq = np.concatenate( + [y_seq_geo, np.zeros(20)]) + +plot_cs(cs_model, a0, y_seq) +``` + +Now we show the behavior when $\lambda = 0.95$ + +```{code-cell} ipython3 +λ = 0.95 + +y_seq_geo = λ ** np.arange(t_max) * y_0 +y_seq = np.concatenate( + [y_seq_geo, np.zeros(20)]) + +plot_cs(cs_model, a0, y_seq) +``` + +What happens when $\lambda$ is negative + +```{code-cell} ipython3 +λ = -0.05 + +y_seq_geo = λ ** np.arange(t_max) * y_0 +y_seq = np.concatenate( + [y_seq_geo, np.zeros(20)]) + +plot_cs(cs_model, a0, y_seq) +``` + +++ {"user_expressions": []} ### Feasible consumption variations @@ -429,7 +561,7 @@ def compute_variation(model, ξ1, ϕ, a0, y_seq, verbose=1): if verbose == 1: print('check feasible:', np.isclose(β_seq @ v_seq, 0)) # since β = 1/R - c_opt, _ = compute_optimal(model, a0, y_seq) + c_opt, _, _ = compute_optimal(model, a0, y_seq) cvar_seq = c_opt + v_seq return cvar_seq From 9a05a0173f8034cae87804bc36e62bafc7aed537 Mon Sep 17 00:00:00 2001 From: Humphrey Yang Date: Mon, 5 Feb 2024 21:04:32 +1100 Subject: [PATCH 2/3] update md --- lectures/cons_smooth.md | 53 ++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/lectures/cons_smooth.md b/lectures/cons_smooth.md index bea3c268..8d8bbe11 100644 --- a/lectures/cons_smooth.md +++ b/lectures/cons_smooth.md @@ -363,7 +363,6 @@ def plot_cs(model, # consumption smoothing model # Compute optimal consumption c_seq, a_seq, h0 = compute_optimal(model, a0, y_seq) - print('average consumption:', np.mean(c_seq)) # Sequence length T = cs_model.T @@ -384,49 +383,49 @@ def plot_cs(model, # consumption smoothing model We first assume a one-time windfall of $W_0$ in year 21 of the income sequence $y$. -We'll make $W_0$ big - positive to indicate a one-time windfall, negative to indicate a one-time "disaster". +We'll make $W_0$ big - positive to indicate a one-time windfall, and negative to indicate a one-time "disaster". ```{code-cell} ipython3 -# Windfall W_0 = 5 +# Windfall W_0 = 20 y_seq_pos = np.concatenate( - [np.zeros(21), np.array([5]), np.zeros(44)]) + [np.ones(21), np.array([20]), np.ones(44)]) plot_cs(cs_model, a0, y_seq_pos) ``` ```{code-cell} ipython3 -# Disaster W_0 = -5 +# Disaster W_0 = -20 y_seq_neg = np.concatenate( - [np.zeros(21), np.array([-5]), np.zeros(44)]) + [np.ones(21), np.array([-20]), np.ones(44)]) plot_cs(cs_model, a0, y_seq_neg) ``` #### Experiment 2: permanent wage gain/loss -Now we a permanent increase in income of $W$ in year 21 of the $y$-sequence. +Now we assume a permanent increase in income of $W$ in year 21 of the $y$-sequence. Again we can study positive and negative cases ```{code-cell} ipython3 -# Positive permenant income W_t = 1 when t >= 21 +# Positive permanent income change W = 0.5 when t >= 21 y_seq_pos = np.concatenate( - [np.zeros(21), np.ones(45)]) + [np.ones(21), np.repeat(1.5, 45)]) plot_cs(cs_model, a0, y_seq_pos) ``` ```{code-cell} ipython3 -# Negative permenant income W_t = -1 when t >= 21 -y_seq_pos = np.concatenate( - [np.zeros(21), -np.ones(45)]) +# Negative permanent income change W = -0.5 when t >= 21 +y_seq_neg = np.concatenate( + [np.ones(21), np.repeat(0.5, 45)]) -plot_cs(cs_model, a0, y_seq_pos) +plot_cs(cs_model, a0, y_seq_neg) ``` #### Experiment 3: a late starter -Now we simulate a $y$ sequence in which a person gets zero for 46 years, and then works and gets 1 the last 20 years of life (a "late starter") +Now we simulate a $y$ sequence in which a person gets zero for 46 years, and then works and gets 1 for the last 20 years of life (a "late starter") ```{code-cell} ipython3 # Late starter @@ -440,7 +439,7 @@ plot_cs(cs_model, a0, y_seq_late) Now we simulate a geometric $y$ sequence in which a person gets $y_t = \lambda^t y_0$ in first 46 years. -We first experiemnt with $\lambda = 1.05$ +We first experiment with $\lambda = 1.05$ ```{code-cell} ipython3 # Geometric earner parameters where λ = 1.05 @@ -449,11 +448,11 @@ y_0 = 1 t_max = 46 # Generate geometric y sequence -y_seq_geo = λ ** np.arange(t_max) * y_0 -y_seq = np.concatenate( - [y_seq_geo, np.zeros(20)]) +geo_seq = λ ** np.arange(t_max) * y_0 +y_seq_geo = np.concatenate( + [geo_seq, np.zeros(20)]) -plot_cs(cs_model, a0, y_seq) +plot_cs(cs_model, a0, y_seq_geo) ``` Now we show the behavior when $\lambda = 0.95$ @@ -461,11 +460,11 @@ Now we show the behavior when $\lambda = 0.95$ ```{code-cell} ipython3 λ = 0.95 -y_seq_geo = λ ** np.arange(t_max) * y_0 -y_seq = np.concatenate( - [y_seq_geo, np.zeros(20)]) +geo_seq = λ ** np.arange(t_max) * y_0 +y_seq_geo = np.concatenate( + [geo_seq, np.zeros(20)]) -plot_cs(cs_model, a0, y_seq) +plot_cs(cs_model, a0, y_seq_geo) ``` What happens when $\lambda$ is negative @@ -473,11 +472,11 @@ What happens when $\lambda$ is negative ```{code-cell} ipython3 λ = -0.05 -y_seq_geo = λ ** np.arange(t_max) * y_0 -y_seq = np.concatenate( - [y_seq_geo, np.zeros(20)]) +geo_seq = λ ** np.arange(t_max) * y_0 +y_seq_geo = np.concatenate( + [geo_seq, np.zeros(20)]) -plot_cs(cs_model, a0, y_seq) +plot_cs(cs_model, a0, y_seq_geo) ``` +++ {"user_expressions": []} From ce1ca1219a93958c0fb031548097bfeabfae6d7c Mon Sep 17 00:00:00 2001 From: Humphrey Yang Date: Mon, 5 Feb 2024 21:42:52 +1100 Subject: [PATCH 3/3] Edits on cons_smoothing requested by Tom --- lectures/cons_smooth.md | 146 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 134 insertions(+), 12 deletions(-) diff --git a/lectures/cons_smooth.md b/lectures/cons_smooth.md index 8d10471f..0c92a27e 100644 --- a/lectures/cons_smooth.md +++ b/lectures/cons_smooth.md @@ -4,14 +4,13 @@ jupytext: extension: .md format_name: myst format_version: 0.13 - jupytext_version: 1.14.5 + jupytext_version: 1.16.1 kernelspec: display_name: Python 3 (ipykernel) language: python name: python3 --- -+++ {"user_expressions": []} # Consumption Smoothing @@ -48,7 +47,6 @@ import matplotlib.pyplot as plt from collections import namedtuple ``` -+++ {"user_expressions": []} The model describes a consumer who lives from time $t=0, 1, \ldots, T$, receives a stream $\{y_t\}_{t=0}^T$ of non-financial income and chooses a consumption stream $\{c_t\}_{t=0}^T$. @@ -156,8 +154,6 @@ def creat_cs_model(R=1.05, g1=1, g2=1/2, T=65): β_seq=β_seq, T=65) ``` -+++ {"user_expressions": []} - ## Friedman-Hall consumption-smoothing model @@ -206,7 +202,6 @@ $$ (eq:conssmoothing) Equation {eq}`eq:conssmoothing` is the consumption-smoothing model in a nutshell. -+++ {"user_expressions": []} ## Mechanics of Consumption smoothing model @@ -301,7 +296,7 @@ This can be interpreted as a student debt. The non-financial process $\{y_t\}_{t=0}^{T}$ is constant and positive up to $t=45$ and then becomes zero afterward. -The drop in non-financial income late in life reflects retirement from work. +The drop in non-financial income late in life reflects retirement from work. ```{code-cell} ipython3 # Financial wealth @@ -348,7 +343,138 @@ def welfare(model, c_seq): print('Welfare:', welfare(cs_model, c_seq)) ``` -+++ {"user_expressions": []} +### Experiments + +In this section we experiment consumption smoothing behavior under different setups. + +First we write a function `plot_cs` that generate graphs above based on a consumption smoothing model `cs_model`. + +This helps us repeat the steps shown above + +```{code-cell} ipython3 +def plot_cs(model, # consumption smoothing model + a0, # initial financial wealth + y_seq # non-financial income process + ): + + # Compute optimal consumption + c_seq, a_seq, h0 = compute_optimal(model, a0, y_seq) + + # Sequence length + T = cs_model.T + + # Generate plot + plt.plot(range(T+1), y_seq, label='non-financial income') + plt.plot(range(T+1), c_seq, label='consumption') + plt.plot(range(T+2), a_seq, label='financial wealth') + plt.plot(range(T+2), np.zeros(T+2), '--') + + plt.legend() + plt.xlabel(r'$t$') + plt.ylabel(r'$c_t,y_t,a_t$') + plt.show() +``` + +#### Experiment 1: one-time gain/loss + +We first assume a one-time windfall of $W_0$ in year 21 of the income sequence $y$. + +We'll make $W_0$ big - positive to indicate a one-time windfall, and negative to indicate a one-time "disaster". + +```{code-cell} ipython3 +# Windfall W_0 = 20 +y_seq_pos = np.concatenate( + [np.ones(21), np.array([20]), np.ones(44)]) + +plot_cs(cs_model, a0, y_seq_pos) +``` + +```{code-cell} ipython3 +# Disaster W_0 = -20 +y_seq_neg = np.concatenate( + [np.ones(21), np.array([-20]), np.ones(44)]) + +plot_cs(cs_model, a0, y_seq_neg) +``` + +#### Experiment 2: permanent wage gain/loss + +Now we assume a permanent increase in income of $W$ in year 21 of the $y$-sequence. + +Again we can study positive and negative cases + +```{code-cell} ipython3 +# Positive permanent income change W = 0.5 when t >= 21 +y_seq_pos = np.concatenate( + [np.ones(21), np.repeat(1.5, 45)]) + +plot_cs(cs_model, a0, y_seq_pos) +``` + +```{code-cell} ipython3 +# Negative permanent income change W = -0.5 when t >= 21 +y_seq_neg = np.concatenate( + [np.ones(21), np.repeat(0.5, 45)]) + +plot_cs(cs_model, a0, y_seq_neg) +``` + +#### Experiment 3: a late starter + +Now we simulate a $y$ sequence in which a person gets zero for 46 years, and then works and gets 1 for the last 20 years of life (a "late starter") + +```{code-cell} ipython3 +# Late starter +y_seq_late = np.concatenate( + [np.zeros(46), np.ones(20)]) + +plot_cs(cs_model, a0, y_seq_late) +``` + +#### Experiment 4: geometric earner + +Now we simulate a geometric $y$ sequence in which a person gets $y_t = \lambda^t y_0$ in first 46 years. + +We first experiment with $\lambda = 1.05$ + +```{code-cell} ipython3 +# Geometric earner parameters where λ = 1.05 +λ = 1.05 +y_0 = 1 +t_max = 46 + +# Generate geometric y sequence +geo_seq = λ ** np.arange(t_max) * y_0 +y_seq_geo = np.concatenate( + [geo_seq, np.zeros(20)]) + +plot_cs(cs_model, a0, y_seq_geo) +``` + +Now we show the behavior when $\lambda = 0.95$ + +```{code-cell} ipython3 +λ = 0.95 + +geo_seq = λ ** np.arange(t_max) * y_0 +y_seq_geo = np.concatenate( + [geo_seq, np.zeros(20)]) + +plot_cs(cs_model, a0, y_seq_geo) +``` + +What happens when $\lambda$ is negative + +```{code-cell} ipython3 +λ = -0.05 + +geo_seq = λ ** np.arange(t_max) * y_0 +y_seq_geo = np.concatenate( + [geo_seq, np.zeros(20)]) + +plot_cs(cs_model, a0, y_seq_geo) +``` + ### Feasible consumption variations @@ -435,7 +561,6 @@ def compute_variation(model, ξ1, ϕ, a0, y_seq, verbose=1): return cvar_seq ``` -+++ {"user_expressions": []} We visualize variations for $\xi_1 \in \{.01, .05\}$ and $\phi \in \{.95, 1.02\}$ @@ -473,7 +598,6 @@ plt.ylabel(r'$c_t$') plt.show() ``` -+++ {"user_expressions": []} We can even use the Python `np.gradient` command to compute derivatives of welfare with respect to our two parameters. @@ -498,7 +622,6 @@ def welfare_rel(ξ1, ϕ): welfare_vec = np.vectorize(welfare_rel) ``` -+++ {"user_expressions": []} Then we can visualize the relationship between welfare and $\xi_1$ and compute its derivatives @@ -518,7 +641,6 @@ plt.xlabel(r'$\xi_1$') plt.show() ``` -+++ {"user_expressions": []} The same can be done on $\phi$