AgeConditionalStats: tolerances and averages

I have a very basic question: I’m trying to understand what is the Mean in AgeConditionalStats.

I’m looking at my Life-Cycle graphs (based on my highly modified OLGModel14), and I’m imagining that I’m looking at the Life-Cycle profile of my agent or agents. When the model has Z and E shocks, I can imagine that the mean is averaging the fates of the different shock values across the lifecycle–no issues there. But when I narrow the Life Cycle down to a single, zero-valued Z, and I have no E, I would expect there to be one singular agent whose life’s detail I see as the only data. In that case, I’d expect Minimum, Maximum, and Mean to all be the same as the agent travels through time. But that’s not the case.

Setting a breakpoint in StatsFromWeightedGirds to catch when there are more than one Values passed in, it seems that when my agent turns 29, StationaryDistVec_jj comes back with 3 non-zero values (two of them distinct), not just one. When I change the tolerance value from its default to 10^(-4)), the divergence comes at age 34. What does it mean for an agent to produce multiple results at a point in time in the Stationary distribution?

Note, in my case, some of the functions return continuous values that might differ by 25% from least to most. In other cases, discrete functions can return very different numbers, like 0 vs. 4. I want to understand why an agent, at a given age, might have function evaluating to a minimum value of 0, a maximum value of 4, and an average value of 2.

There are a few reasons agents can end up in different places (and hence the mean will differ from min/max/etc). One is, as you say, they get hit with different shocks (but in a model without shocks this won’t happen). A second is they start in different places (in the distribution of agents at j=1). The third is just that they are different permanent types of agent and so make different decisions.

Theoretically, these are the only three reasons.

If however you are using grid interpolation layer there is a fourth reason that can give minor variations in the outcome of agents. Specifically, agents can now choose ‘aprime’ to be a value that is between two consecutive points on ‘a_grid’. Because the agent distribution is only stored on ‘a_grid’ we need to linearly interpolate these people back onto the grid. The way this is done is that if they choose an ‘aprime’ that is 2/3rds of the way from ‘a_grid lower point’ to ‘a_grid upper point’ (lower and upper being the two grid points just below and above ‘aprime’) then we put 1/3 of the mass on the lower point and 2/3rds on the upper point (so most of the mass goes to the point you are nearest too). This results in mass on two different points. You can think of this as a form of numerical error (in practice it is more accurate than not using grid interpolation, given the same a_grid).
Update: Alternative endogenous state types like experienceasset also do this ‘interpolate back onto grid’ and so will have the same issue.

From the sounds of things this ‘interpolate back onto grid’ is what is happening. (I’m not certain, but sounds about right.)

I think you are correct about gridinterp. I have disabled it within my program, but I don’t doubt my agents are requesting values between two gridpoints (I have intentionally made the price distance between shares a large amount, to force them to either use their bank accounts for incremental accumulation, or to swing big either buying a house or a large chunk of shares). Where should I find such organic interpolation?

If you do not have grid interpolation then your agents cannot consider anything that is not on the grid. Except that from memory you have experienceasset, which has the same issue, so maybe it is still this ‘interpolate back onto grid’ issue.

As I understand, your model has three endo states (a1,a2,a3). You have turned off grid interpolation for a1 which is standard endo, a2 is standard endo, and a3 is experienceasset. The ‘interpolate back onto grid’ is only capable of affecting the state that is being interpolated, it cannot contaminate the other states. So if it is this issue you should see it only in a3. You can probably easy check this by looking at mean vs min/max for each of
FnsToEvaluate.a1=@(…) a1;
FnsToEvaluate.a3=@(…) a2;
FnsToEvaluate.a3=@(…) a3;
and it should only be a3 that shows this “agents end up in different places despite starting in same j=1 position and having no shocks and no ptypes”.
(obviously, replace … with your action space)

Lines 123-143 of CreateaprimePolicyExperienceAsset_Case1.m:

a2_griddiff=a2_grid(2:end)-a2_grid(1:end-1); % Distance between point and the next point

a2primeIndexes=discretize(a2primeVals,a2_grid); % Finds the lower grid point
% Have to have special treatment for trying to leave the ends of the grid

% Those points which tried to leave the bottom of the grid have probability 0 of the 'upper' point (1 of lower point)
offBottomOfGrid=(a2primeVals<=a2_grid(1));
a2primeIndexes(offBottomOfGrid)=1; % Has already been handled
% Those points which tried to leave the top of the grid have probability 1 of the 'upper' point (0 of lower point)
offTopOfGrid=(a2primeVals>=a2_grid(end));
a2primeIndexes(offTopOfGrid)=n_a2-1; % lower grid point is the one before the end point
if N_z<2
    a2primeIndexes=reshape(a2primeIndexes,[N_a,1]);
else
    a2primeIndexes=reshape(a2primeIndexes,[N_a*N_z,1]);
end

% Now, find the probabilities
aprime_residual=a2primeVals'-a2_grid(a2primeIndexes);
% Probability of the 'lower' points
a2primeProbs=1-aprime_residual./a2_griddiff(a2primeIndexes);

The creation of those probabilities leads to StationaryDist_FHorz_Iteration_nProbs_raw

which I believe is doing that actual state blending.

1 Like

Interestingly, in the normal FHorz case, the probabilities are only calculated when asking for gridinterp (unindented for legibility):

if simoptions.gridinterplayer==0
    StationaryDist=StationaryDist_FHorz_Iteration_noz_raw(jequaloneDist,AgeWeightParamNames,Policy_aprime,N_a,N_j,Parameters);
elseif simoptions.gridinterplayer==1
    % (a,1,j)
    Policy_aprime=reshape(Policy_aprime,[N_a,1,N_j]);
    Policy_aprime=repmat(Policy_aprime,1,2,1);
    PolicyProbs=ones([N_a,2,N_j],'gpuArray');
    % Policy_aprime(:,1,:) lower grid point for a1 is unchanged
    Policy_aprime(:,2,:)=Policy_aprime(:,2,:)+1; % add one to a1, to get upper grid point

    aprimeProbs_upper=reshape(shiftdim((Policy(end,:,:)-1)/(simoptions.ngridinterp+1),1),[N_a,1,N_j]); % probability of upper grid point (from L2 index)
    PolicyProbs(:,1,:)=PolicyProbs(:,1,:).*(1-aprimeProbs_upper); % lower a1
    PolicyProbs(:,2,:)=PolicyProbs(:,2,:).*aprimeProbs_upper; % upper a1

    StationaryDist=StationaryDist_FHorz_Iteration_nProbs_noz_raw(jequaloneDist,AgeWeightParamNames,Policy_aprime,PolicyProbs,2,N_a,N_j,Parameters);
end

Interestingly, in the normal FHorz case, the probabilities are only calculated when asking for gridinterp (unindented for legibility)

Because if you only have standard endogneous states, and you don’t use gridinterp then all next period endogenous state choices are already on the grid, and so no need to linearly interpolate them back onto the grid.

PS. The code is called “nProbs”. If you have all standard endo states and use gridinterp on one, there are ‘two probs’. If you use an experience asset, there are ‘two probs’. If you have a standard endo state with gridinterp alonside an experience asset, there are ‘four probs’ (two times two). The “nProbs” thus handles all the different cases [I rewrote all the old codes about a year ago so that there is only nProbs, there used to be different codes for twoProbs, nProbs, etc., and I realised these could all just be done as one general nProbs and that I could then focus on just writing that one command as nicely as possible.]

1 Like