def describeMPCdstn(SomeTypes,percentiles): MPC_sim = np.concatenate([ThisType.MPCnow for ThisType in SomeTypes]) MPCpercentiles_quarterly = get_percentiles(MPC_sim,percentiles=percentiles) MPCpercentiles_annual = 1.0 - (1.0 - MPCpercentiles_quarterly)**4 for j in range(len(percentiles)): print('The ' + str(100*percentiles[j]) + 'th percentile of the MPC is ' + str(MPCpercentiles_annual[j]))
def update_pLvlGrid(self): """ Update the grid of persistent income levels. Currently only works for infinite horizon models (cycles=0) and lifecycle models (cycles=1). Not clear what to do about cycles>1 because the distribution of persistent income will be different within a period depending on how many cycles have elapsed. This method uses a simulation approach to generate the pLvlGrid at each period of the cycle, drawing on the initial distribution of persistent income, the pLvlNextFuncs, and the attribute pLvlPctiles. Parameters ---------- None Returns ------- None """ LivPrbAll = np.array(self.LivPrb) # Simulate the distribution of persistent income levels by t_cycle in a lifecycle model if self.cycles == 1: pLvlNow = Lognormal(self.pLvlInitMean, sigma=self.pLvlInitStd, seed=31382).draw(self.AgentCount) pLvlGrid = [] # empty list of time-varying persistent income grids # Calculate distribution of persistent income in each period of lifecycle for t in range(len(self.PermShkStd)): if t > 0: PermShkNow = self.PermShkDstn[t - 1].draw(N=self.AgentCount) pLvlNow = self.pLvlNextFunc[t - 1](pLvlNow) * PermShkNow pLvlGrid.append( get_percentiles(pLvlNow, percentiles=self.pLvlPctiles)) # Calculate "stationary" distribution in infinite horizon (might vary across periods of cycle) elif self.cycles == 0: T_long = 1000 # Number of periods to simulate to get to "stationary" distribution pLvlNow = Lognormal(mu=self.pLvlInitMean, sigma=self.pLvlInitStd, seed=31382).draw(self.AgentCount) t_cycle = np.zeros(self.AgentCount, dtype=int) for t in range(T_long): LivPrb = LivPrbAll[ t_cycle] # Determine who dies and replace them with newborns draws = Uniform(seed=t).draw(self.AgentCount) who_dies = draws > LivPrb pLvlNow[who_dies] = Lognormal(self.pLvlInitMean, self.pLvlInitStd, seed=t + 92615).draw( np.sum(who_dies)) t_cycle[who_dies] = 0 for j in range(self.T_cycle): # Update persistent income these = t_cycle == j PermShkTemp = self.PermShkDstn[j].draw(N=np.sum(these)) pLvlNow[these] = self.pLvlNextFunc[j]( pLvlNow[these]) * PermShkTemp t_cycle = t_cycle + 1 t_cycle[t_cycle == self.T_cycle] = 0 # We now have a "long run stationary distribution", extract percentiles pLvlGrid = [] # empty list of time-varying persistent income grids for t in range(self.T_cycle): these = t_cycle == t pLvlGrid.append( get_percentiles(pLvlNow[these], percentiles=self.pLvlPctiles)) # Throw an error if cycles>1 else: assert False, "Can only handle cycles=0 or cycles=1!" # Store the result and add attribute to time_vary self.pLvlGrid = pLvlGrid self.add_to_time_vary("pLvlGrid")
# ## Calculating the Lorenz Distance at Targets # # Now we want to construct a function that calculates the Euclidean distance between simulated and actual Lorenz curves at the four percentiles of interest: 20, 40, 60, and 80. # %% [markdown] # ## The Distribution Of the Marginal Propensity to Consume # # For many macroeconomic purposes, the distribution of the MPC $\kappa$ is more important than the distribution of wealth. Ours is a quarterly model, and MPC's are typically reported on an annual basis; we can compute a (very) approximate annual MPC from the quraterly ones as $\kappa_{Y} \approx 1.0 - (1.0 - \kappa_{Q})^4$ # # In the cell below, we retrieve the MPCs from our simulated consumers and show that the 10th percentile in the MPC distribution is only about 6 percent, while at the 90th percentile it is almost 0.5 # %% # Retrieve the MPC's percentiles = np.linspace(0.1, 0.9, 9) MPC_sim = np.concatenate([ThisType.MPCnow for ThisType in MyTypes]) MPCpercentiles_quarterly = get_percentiles(MPC_sim, percentiles=percentiles) MPCpercentiles_annual = 1.0 - (1.0 - MPCpercentiles_quarterly)**4 print('The MPC at the 10th percentile of the distribution is ' + str(decfmt2(MPCpercentiles_annual[0]))) print('The MPC at the 50th percentile of the distribution is ' + str(decfmt2(MPCpercentiles_annual[4]))) print('The MPC at the 90th percentile of the distribution is ' + str(decfmt2(MPCpercentiles_annual[-1]))) # %% [markdown] # ## Adding Very Impatient Households # # Now that we have some tools for examining both microeconomic (the MPC across the population) and macroeconomic (the distribution and overall level of wealth) outcomes from our model, we are all set to conduct our experiment. # # In this exercise, we are going to add very impatient households to the economy in a very direct way: by replacing the *most impatient consumer type* with an *even more impatient type*. Specifically, we will have these agents have a discount factor of $\beta = 0.80$ at a quarterly frequency, which corresponds to $\beta \approx 0.41$ annual.
def FagerengObjFunc(center,spread,verbose=False): ''' Objective function for the quick and dirty structural estimation to fit Fagereng, Holm, and Natvik's Table 9 results with a basic infinite horizon consumption-saving model (with permanent and transitory income shocks). Parameters ---------- center : float Center of the uniform distribution of discount factors. spread : float Width of the uniform distribution of discount factors. verbose : bool When True, print to screen MPC table for these parameters. When False, print (center, spread, distance). Returns ------- distance : float Euclidean distance between simulated MPCs and (adjusted) Table 9 MPCs. ''' # Give our consumer types the requested discount factor distribution beta_set = Uniform(bot=center-spread,top=center+spread).approx(N=TypeCount).X for j in range(TypeCount): EstTypeList[j].DiscFac = beta_set[j] # Solve and simulate all consumer types, then gather their wealth levels multi_thread_commands(EstTypeList,['solve()','initialize_sim()','simulate()','unpack_cFunc()']) WealthNow = np.concatenate([ThisType.state_now["aLvl"] for ThisType in EstTypeList]) # Get wealth quartile cutoffs and distribute them to each consumer type quartile_cuts = get_percentiles(WealthNow,percentiles=[0.25,0.50,0.75]) for ThisType in EstTypeList: WealthQ = np.zeros(ThisType.AgentCount,dtype=int) for n in range(3): WealthQ[ThisType.state_now["aLvl"] > quartile_cuts[n]] += 1 ThisType(WealthQ = WealthQ) # Keep track of MPC sets in lists of lists of arrays MPC_set_list = [ [[],[],[],[]], [[],[],[],[]], [[],[],[],[]], [[],[],[],[]] ] # Calculate the MPC for each of the four lottery sizes for all agents for ThisType in EstTypeList: ThisType.simulate(1) c_base = ThisType.controls["cNrm"] MPC_this_type = np.zeros((ThisType.AgentCount,4)) for k in range(4): # Get MPC for all agents of this type Llvl = lottery_size[k] Lnrm = Llvl/ThisType.state_now["pLvl"] if do_secant: SplurgeNrm = Splurge/ThisType.state_now["pLvl"] mAdj = ThisType.state_now["mNrmNow"] + Lnrm - SplurgeNrm cAdj = ThisType.cFunc[0](mAdj) + SplurgeNrm MPC_this_type[:,k] = (cAdj - c_base)/Lnrm else: mAdj = ThisType.state_now["mNrmNow"] + Lnrm MPC_this_type[:,k] = cAdj = ThisType.cFunc[0].derivative(mAdj) # Sort the MPCs into the proper MPC sets for q in range(4): these = ThisType.WealthQ == q for k in range(4): MPC_set_list[k][q].append(MPC_this_type[these,k]) # Calculate average within each MPC set simulated_MPC_means = np.zeros((4,4)) for k in range(4): for q in range(4): MPC_array = np.concatenate(MPC_set_list[k][q]) simulated_MPC_means[k,q] = np.mean(MPC_array) # Calculate Euclidean distance between simulated MPC averages and Table 9 targets diff = simulated_MPC_means - MPC_target if drop_corner: diff[0,0] = 0.0 distance = np.sqrt(np.sum((diff)**2)) if verbose: print(simulated_MPC_means) else: print (center, spread, distance) return distance
def calc_stats(self, aLvlNow, pLvlNow, MPCnow, TranShkNow, EmpNow, t_age, LorenzBool, ManyStatsBool): ''' Calculate various statistics about the current population in the economy. Parameters ---------- aLvlNow : [np.array] Arrays with end-of-period assets, listed by each ConsumerType in self.agents. pLvlNow : [np.array] Arrays with permanent income levels, listed by each ConsumerType in self.agents. MPCnow : [np.array] Arrays with marginal propensity to consume, listed by each ConsumerType in self.agents. TranShkNow : [np.array] Arrays with transitory income shocks, listed by each ConsumerType in self.agents. EmpNow : [np.array] Arrays with employment states: True if employed, False otherwise. t_age : [np.array] Arrays with periods elapsed since model entry, listed by each ConsumerType in self.agents. LorenzBool: bool Indicator for whether the Lorenz target points should be calculated. Usually False, only True when DiscFac has been identified for a particular nabla. ManyStatsBool: bool Indicator for whether a lot of statistics for tables should be calculated. Usually False, only True when parameters have been estimated and we want values for tables. Returns ------- None ''' # Combine inputs into single arrays aLvl = np.hstack(aLvlNow) pLvl = np.hstack(pLvlNow) age = np.hstack(t_age) TranShk = np.hstack(TranShkNow) EmpNow = np.hstack(EmpNow) # Calculate the capital to income ratio in the economy CohortWeight = self.PopGroFac**(-age) CapAgg = np.sum(aLvl * CohortWeight) IncAgg = np.sum(pLvl * TranShk * CohortWeight) KtoYnow = CapAgg / IncAgg self.KtoYnow = KtoYnow # Store Lorenz data if requested self.LorenzLong = np.nan if LorenzBool: order = np.argsort(aLvl) aLvl = aLvl[order] CohortWeight = CohortWeight[order] wealth_shares = get_lorenz_shares( aLvl, weights=CohortWeight, percentiles=self.LorenzPercentiles, presorted=True) self.Lorenz = wealth_shares if ManyStatsBool: self.LorenzLong = get_lorenz_shares(aLvl, weights=CohortWeight, percentiles=np.arange( 0.01, 1.0, 0.01), presorted=True) else: self.Lorenz = np.nan # Store nothing if we don't want Lorenz data # Calculate a whole bunch of statistics if requested if ManyStatsBool: # Reshape other inputs MPC = np.hstack(MPCnow) # Sort other data items if aLvl and CohortWeight were sorted if LorenzBool: pLvl = pLvl[order] MPC = MPC[order] TranShk = TranShk[order] age = age[order] EmpNow = EmpNow[order] aNrm = aLvl / pLvl # Normalized assets (wealth ratio) IncLvl = TranShk * pLvl # Labor income this period # Calculate overall population MPC and by subpopulations MPCannual = 1.0 - (1.0 - MPC)**4 self.MPCall = np.sum( MPCannual * CohortWeight) / np.sum(CohortWeight) employed = EmpNow unemployed = np.logical_not(employed) if self.T_retire > 0: # Adjust for the lifecycle model, where agents might be retired instead unemployed = np.logical_and(unemployed, age < self.T_retire) employed = np.logical_and(employed, age < self.T_retire) retired = age >= self.T_retire else: retired = np.zeros_like(unemployed, dtype=bool) self.MPCunemployed = np.sum( MPCannual[unemployed] * CohortWeight[unemployed]) / np.sum( CohortWeight[unemployed]) self.MPCemployed = np.sum( MPCannual[employed] * CohortWeight[employed]) / np.sum( CohortWeight[employed]) self.MPCretired = np.sum( MPCannual[retired] * CohortWeight[retired]) / np.sum( CohortWeight[retired]) self.MPCbyWealthRatio = calc_subpop_avg(MPCannual, aNrm, self.cutoffs, CohortWeight) self.MPCbyIncome = calc_subpop_avg(MPCannual, IncLvl, self.cutoffs, CohortWeight) # Calculate the wealth quintile distribution of "hand to mouth" consumers quintile_cuts = get_percentiles(aLvl, weights=CohortWeight, percentiles=[0.2, 0.4, 0.6, 0.8]) wealth_quintiles = np.ones(aLvl.size, dtype=int) wealth_quintiles[aLvl > quintile_cuts[0]] = 2 wealth_quintiles[aLvl > quintile_cuts[1]] = 3 wealth_quintiles[aLvl > quintile_cuts[2]] = 4 wealth_quintiles[aLvl > quintile_cuts[3]] = 5 MPC_cutoff = get_percentiles( MPCannual, weights=CohortWeight, percentiles=[ 2.0 / 3.0 ]) # Looking at consumers with MPCs in the top 1/3 these = MPCannual > MPC_cutoff in_top_third_MPC = wealth_quintiles[these] temp_weights = CohortWeight[these] hand_to_mouth_total = np.sum(temp_weights) hand_to_mouth_pct = [] for q in range(1, 6): hand_to_mouth_pct.append( np.sum(temp_weights[in_top_third_MPC == q]) / hand_to_mouth_total) self.HandToMouthPct = np.array(hand_to_mouth_pct) else: # If we don't want these stats, just put empty values in history self.MPCall = np.nan self.MPCunemployed = np.nan self.MPCemployed = np.nan self.MPCretired = np.nan self.MPCbyWealthRatio = np.nan self.MPCbyIncome = np.nan self.HandToMouthPct = np.nan