t = length(unique(time));
n = length(unique(agrimonia.IDStations));
locations = unique(agrimonia.IDStations);
for i = 1:length(locations)
idx = find(agrimonia.IDStations == locations(i));
coords(i, 2) = agrimonia.Latitude(idx(1));
coords(i, 3) = agrimonia.Longitude(idx(1));
ymat = reshape(agrimonia.AQ_pm10, t, n)';
ymat = ymat(:, (t1+1):t2);
n2ind = find(mean(isnan(ymat), 2) ~= 1);
ymat(isnan(ymat)) = nanmean(ymat, "all"); % na imputation
ymat(ymat == 0) = min(ymat(ymat > 0));
y = reshape(ymat, n2*(t2-t1), []); % reshape
covmat_blhmax = (reshape(agrimonia.WE_blh_layer_max, t, n)' - mean(agrimonia.WE_blh_layer_max, "all")) / sqrt(var(agrimonia.WE_blh_layer_max));
covmat_temp = (reshape(agrimonia.WE_temp_2m, t, n)' - mean(agrimonia.WE_temp_2m, "all")) / sqrt(var(agrimonia.WE_temp_2m));
covmat_humid = (reshape(agrimonia.WE_rh_mean, t, n)' - mean(agrimonia.WE_rh_mean, "all")) / sqrt(var(agrimonia.WE_rh_mean));
covmat_press = (reshape(agrimonia.WE_surface_pressure, t, n)' - mean(agrimonia.WE_surface_pressure, "all")) / sqrt(var(agrimonia.WE_surface_pressure));
x = [ reshape(covmat_blhmax, n*t, []) reshape(covmat_temp, n*t, []) reshape(covmat_humid, n*t, []) reshape(covmat_press, n*t, [])]; % nT x p-dimensional matrix of regressors
xmat = reshape(x, n, t, 4);
xmat = xmat(n2ind, (t1+1):t2, :);
x = reshape(xmat, n2*(t2-t1), 4);
distmatfull = distance_miles(coords(:, 2), coords(:, 3));
coords = coordsfull(n2ind, :);
distmat = distance_miles(coords(:, 2), coords(:, 3));
time_idx = string(unique(time));
ts1 = timeseries(median(ymat, 1), char(time_idx((t1+1):t2)));
ts2 = timeseries(quantile(ymat, 0.05, 1), char(time_idx((t1+1):t2)));
ts3 = timeseries(quantile(ymat, 0.95, 1), char(time_idx((t1+1):t2)));
ts1.Name = 'PM10 concentrations';
ts1.TimeInfo.Units = "days";
ts1.TimeInfo.StartDate = '2021-01-01';
ts1.TimeInfo.Format = 'yyyy-mm-dd';
ts2.TimeInfo.Units = "days";
ts2.TimeInfo.StartDate = '2021-01-01';
ts2.TimeInfo.Format = 'yyyy-mm-dd';
ts3.TimeInfo.Units = "days";
ts3.TimeInfo.StartDate = '2021-01-01';
ts3.TimeInfo.Format = 'yyyy-mm-dd';
p1 = plot(ts1, "-", 'LineWidth', 2, 'Color', [0,0,0.6]); hold on;
plot(ts2, "--", 'LineWidth', 1, 'Color', [0,0,0.6]); hold on;
plot(ts3, "--", 'LineWidth', 1, 'Color', [0,0,0.6]); hold on;
title('Median PM10 Concentrations','FontSize', 14, 'interpreter', 'latex');
p2 = geoscatter(coords(:,2)', coords(:,3)', 50, median(ymat, 2), "filled");
title('Median PM10 Concentrations','FontSize', 14, 'interpreter', 'latex');
p3 = geoscatter(coords(:,2)', coords(:,3)', 50, std(ymat'), "filled");
title('Standard Deviation of PM10 Concentrations','FontSize', 14, 'interpreter', 'latex');
distance_cuts = [5:5:10, 10:2:35, 35:5:80];
rsq = zeros(2, max(size(distance_cuts))); % 2d array (invdist/binary, sequence of distances)
for cut_off = 1:max(size(distance_cuts))
W = distmat < distance_cuts(cut_off) & distmat > 0;
nterm = nterm + (nterm==0);
info.model = 3; % 1 spatial fixed effects (x may not contain an intercept)
results = sdm_panel_FE(y, x, W, t2-t1, info);
rsq(1, cut_off) = results.sige;
W = distmat < distance_cuts(cut_off) & distmat > 0;
W(find(eye(size(W)))) = zeros(n,1);
nterm = nterm + (nterm==0);
info.model = 3; % 1 spatial fixed effects (x may not contain an intercept)
results = sdm_panel_FE(y, x, W, t2-t1, info);
rsq(2, cut_off) = results.sige;
weighting_scheme = plot(distance_cuts, rsq(1,:), "-o", col = "blue"); hold on
xline(distance_cuts(IA), "--", 'Color', "b")
plot(distance_cuts, rsq(2,:), "-o", 'Color', "r");
xline(distance_cuts(IB), "--", 'Color', "r")
legend("Row-standardised binary weights", "Optimum = " + string(distance_cuts(IA)) + "mi", "Row-standardised inverse distance weights", "Optimum = " + string(distance_cuts(IB)) + "mi")
xlabel("Cut-off distance in miles")
ylabel("Standard deviation of residuals")
IB = 14; % note that the optimal cut-off distance depends on the numerical optimization in the estimation and many cut-off distance around the optimum lead to very similar results;
% thus, the optimum may vary when repeatedly executing the code
W = distmat < distance_cuts(IB) & distmat > 0;
W(find(eye(size(W)))) = zeros(n,1);
nterm = nterm + (nterm==0);
1 - sum(W ~= 0, "all") / n^2
info.model = 3; % spatiotemporal fixed effects
results = sdm_panel_FE(y, x, W, t2-t1, info);
vnames = strvcat('PM10 concentrations', 'Boundary layer height (max)','Air temperature','Relative humidity','Air pressure');
prt_panel(results, vnames)
Homoscedastic model
MaxLike SDM model with both region and time period fixed effects
Dependent Variable = PM10 concentrations
R-squared = 0.8162
corr-squared = 0.0121
sigma^2 = 53.2209
Nobs,Nvar,#FE = 37595, 8, 468
log-likelihood = -130054.28
# of iterations = 18
min and max rho = -1.0000, 1.0000
total time in secs = 0.3696
time for lndet = 0.0047
Pace and Barry, 1999 MC lndet approximation used
order for MC appr = 50
iter for MC appr = 30
***************************************************************
Variable Coefficient Asymptot t-stat z-probability
Boundary layer height (max) -0.298873 -0.853461 0.393404
Air temperature -6.778210 -5.690340 0.000000
Relative humidity 1.837893 6.725601 0.000000
Air pressure 35.676692 8.554879 0.000000
W-Boundary layer height (max) 0.273217 0.643633 0.519814
W-Air temperature 6.919230 4.852370 0.000001
W-Relative humidity -1.655556 -4.970305 0.000001
W-Air pressure -30.825325 -6.107943 0.000000
rho 0.648946 134.221938 0.000000
Direct Coefficient t-stat t-prob lower 05 upper 95
Boundary layer height (max) -0.281777 -0.867070 0.387919 -0.942588 0.363230
Air temperature -6.342183 -5.755917 0.000000 -8.406223 -4.104213
Relative humidity 1.753628 6.979338 0.000000 1.261463 2.280891
Air pressure 34.508257 8.883559 0.000000 27.288481 42.173723
Indirect Coefficient t-stat t-prob lower 05 upper 95
Boundary layer height (max) 0.215383 0.363419 0.717037 -0.935051 1.328597
Air temperature 6.751836 3.478205 0.000741 2.864978 10.489767
Relative humidity -1.218508 -2.574665 0.011455 -2.154607 -0.305027
Air pressure -20.802138 -2.781209 0.006441 -36.369619 -6.926883
Total Coefficient t-stat t-prob lower 05 upper 95
Boundary layer height (max) -0.066393 -0.156456 0.875980 -0.935602 0.769222
Air temperature 0.409652 0.295854 0.767937 -2.463757 3.037626
Relative humidity 0.535120 1.459383 0.147503 -0.243859 1.234374
Air pressure 13.706118 2.356718 0.020328 1.654099 24.788980
residuals = results.resid;
time_idx = string(unique(time));
ts1 = timeseries(results.tfe, char(time_idx((t1+1):t2)));
ts1.Name = 'Time fixed effects';
ts1.TimeInfo.Units = "days";
ts1.TimeInfo.StartDate = '2021-01-01';
ts1.TimeInfo.Format = 'yyyy-mm-dd';
tfe = plot(ts1, "-", 'LineWidth', 2, 'Color', [0,0,0.6]); hold on;
title('Time fixed effects','FontSize', 14, 'interpreter', 'latex');
sfe = geoscatter(coords(:,2)', coords(:,3)', 50, results.sfe, "filled");hold on;
geobasemap 'streets-light';
title('Spatial fixed effects','FontSize', 14, 'interpreter', 'latex');
rhoini = [0.01; 0.2; 0.01];
muhini = (-1) .* ones([n 1]);
hhini = log(var(residuals)).*ones([N 1]);
% Note that the estimation of the results needs a reasonable amount of
% memory (10.5GB) and time (approx 10 hours)
%[Sdr, hdr, sigu2dr, muhdr, rhodr, count_rho] = PSSV_10MH_packg(residuals, W, R1, R2,...
% a0, b0, mh0, iVmh0, tau0, hhini, rhoini, sigu2ini, muhini);
% [ quantile(mean(muhdr, 1), 0.025) , quantile(mean(muhdr, 1), 0.975) ]
% [ quantile(rhodr(:,1), 0.025) , quantile(rhodr(:,1), 0.975) ]
% [ quantile(rhodr(:,2), 0.025) , quantile(rhodr(:,2), 0.975) ]
% [ quantile(rhodr(:,3), 0.025) , quantile(rhodr(:,3), 0.975) ]
% [ quantile(mean(hdr, 1), 0.025) , quantile(mean(hdr, 1), 0.975) ]
% [ quantile(sigu2dr, 0.025) , quantile(sigu2dr, 0.975) ]
[ quantile(mean(hdr, 1), 0.025) , quantile(mean(hdr, 1), 0.975) ]
hdr_median_mat = zeros(n, T);
hdr_median_mat(n_id, t_id) = median(hdr((t_id - 1)*n + n_id, :));
find(median(hdr_median_mat, 2) > 4.70) % 52
find(median(hdr_median_mat, 2) < 2) % 65
find(datetime(char(time_idx((t1+1):t2))) == datetime('2021-06-26')) % 177
find(datetime(char(time_idx((t1+1):t2))) == datetime('2021-12-06')) % 340
highlight_latitude1 = [coords(52,2)]; % Example highlighted latitudes
highlight_longitude1 = [coords(52,3)]; % Example highlighted longitudes
highlight_latitude2 = [coords(65,2)]; % Example highlighted latitudes
highlight_longitude2 = [coords(65,3)]; % Example highlighted longitudes
highlight_names1 = {'Station 52'}; % Example location names
highlight_names2 = {'Station 65'}; % Example location names
bottom = min(min(min(hdr_median_mat(:,177))),min(min(hdr_median_mat(:,340))));
top = max(max(max(hdr_median_mat(:,177))),max(max(hdr_median_mat(:,340))));
p_median = geoscatter(coords(:,2)', coords(:,3)', 50, median(hdr_median_mat, 2), "filled");
geobasemap 'streets-light';
title('Posterior median of h','FontSize', 14, 'interpreter', 'latex');
p_july = geoscatter(coords(:,2)', coords(:,3)', 50, hdr_median_mat(:,177), "filled");hold on;
highlight = geoscatter(highlight_latitude1, highlight_longitude1, 120, 'r', 'o', 'LineWidth', 2); % Use a larger size and a red color
text(highlight_latitude1 + 0.05, highlight_longitude1, highlight_names1, 'Color', 'r', 'FontSize', 12);
highlight = geoscatter(highlight_latitude2, highlight_longitude2, 120, 'r', 's', 'LineWidth', 2); % Use a larger size and a red color
text(highlight_latitude2 + 0.05, highlight_longitude2, highlight_names2, 'Color', 'r', 'FontSize', 12);
geobasemap 'streets-light';
title('Posterior median of h (2021-06-26)','FontSize', 14, 'interpreter', 'latex');
p_december = geoscatter(coords(:,2)', coords(:,3)', 50, hdr_median_mat(:,340), "filled");hold on;
highlight = geoscatter(highlight_latitude1, highlight_longitude1, 120, 'm', 'o', 'LineWidth', 2); % Use a larger size and a red color
text(highlight_latitude1 + 0.05, highlight_longitude1, highlight_names1, 'Color', 'm', 'FontSize', 12);
highlight = geoscatter(highlight_latitude2, highlight_longitude2, 120, 'm', 's', 'LineWidth', 2); % Use a larger size and a red color
text(highlight_latitude2 + 0.05, highlight_longitude2, highlight_names2, 'Color', 'm', 'FontSize', 12);
geobasemap 'streets-light';
title('Posterior median of h (2021-12-06)','FontSize', 14, 'interpreter', 'latex');
time_idx = string(unique(time));
ts1 = timeseries(median(hdr_median_mat, 1), char(time_idx((t1+1):t2)));
ts2 = timeseries(quantile(hdr_median_mat, 0.05, 1), char(time_idx((t1+1):t2)));
ts3 = timeseries(quantile(hdr_median_mat, 0.95, 1), char(time_idx((t1+1):t2)));
ts1.Name = 'Posterior median of h';
ts1.TimeInfo.Units = "days";
ts1.TimeInfo.StartDate = '2021-01-01';
ts1.TimeInfo.Format = 'yyyy-mm-dd';
ts2.TimeInfo.Units = "days";
ts2.TimeInfo.StartDate = '2021-01-01';
ts2.TimeInfo.Format = 'yyyy-mm-dd';
ts3.TimeInfo.Units = "days";
ts3.TimeInfo.StartDate = '2021-01-01';
ts3.TimeInfo.Format = 'yyyy-mm-dd';
p_median_h = plot(ts1, "-", 'LineWidth', 2, 'Color', [0,0,0.6]); hold on;
plot(ts2, "--", 'LineWidth', 1, 'Color', [0,0,0.6]); hold on;
plot(ts3, "--", 'LineWidth', 1, 'Color', [0,0,0.6]); hold on;
title('Posterior median of h','FontSize', 14, 'interpreter', 'latex');
time_idx = string(unique(time));
ts1 = timeseries(hdr_median_mat(52,:), char(time_idx((t1+1):t2)));
ts2 = timeseries(hdr_median_mat(65,:), char(time_idx((t1+1):t2)));
ts1.Name = 'Posterior median of h';
ts1.TimeInfo.Units = "days";
ts2.TimeInfo.Units = "days";
ts1.TimeInfo.StartDate = '2021-01-01';
ts2.TimeInfo.StartDate = '2021-01-01';
ts1.TimeInfo.Format = 'yyyy-mm-dd';
ts2.TimeInfo.Format = 'yyyy-mm-dd';
p_time = plot(ts1, "-", 'LineWidth', 2, 'Color', [0,0,0.6]); hold on;
plot(ts2, "-", 'LineWidth', 2, 'Color', [0,0,0.6]); hold on;
xline(datetime('2021-06-26'),'r',{'2021-06-26'}, 'LineWidth', 2);
xline(datetime('2021-12-06'),'m',{'2021-12-06'}, 'LineWidth', 2);
scatter(datetime('2021-06-26'), hdr_median_mat(43,177), 100, 'r', "o", 'LineWidth', 2);
scatter(datetime('2021-06-26'), hdr_median_mat(65,177), 100, 'r', "s", 'LineWidth', 2);
scatter(datetime('2021-12-06'), hdr_median_mat(43,340), 100, 'm', "o", 'LineWidth', 2);
scatter(datetime('2021-12-06'), hdr_median_mat(65,340), 100, 'm', "s", 'LineWidth', 2);
title('Posterior median of h (Stations 52 and 65)','FontSize', 14, 'interpreter', 'latex');
function [Sdr, hdr, sigu2dr, muhdr, psidr, count_psi] = PSSV_10MH_packg(y, ...
W, R1, R2, a0, b0, mh0, iVmh0, tau, h, psi, sigu2, mu_h)
%--------------------------------------------------------------------------
%--------------------------------------------------------------------------
% a0 : shape parameter for the IG
% b0 : scale parameter for the IG
% muh0: mean of the prior for mu_h
% iVh0: precision of the prior for mu_h
% tau : uniform(-1/tau, 1/tau) (spectral radius of W)
%--------------------------------------------------------------------------
% h : initial nT \times 1 vector of latent log-volatilities
% psi : initial value of spatial auto-regressive parameters
% sigu2 : initial variance of u_i's
% mu_h : initial n \times 1 vector of mean for h's
%--------------------------------------------------------------------------
% AMH initialization for psi
% -- normal mixture components
pj = [.00609 .04775 .13057 .20674 .22715 .18842 .12047 .05591 .01575 .00115];
mj = [1.92677 1.34744 .73504 .02266 -.85173 -1.97278 -3.46788 -5.55246 -8.68384 -14.65000];
sigj2 = [.11265 .17788 .26768 .40611 .62699 .98583 1.57469 2.54498 4.16591 7.33342];
% -- sample mixture component indicators
% (from a 10-component discrete distribution)
q = repmat(pj, [N 1]).*normpdf(repmat(ystar, [1 10]),...
repmat(h, [1 10]) + repmat(mj, [N 1]),...
q = q./repmat(sum(q, 2), [1 10]);
S = 10 - sum(repmat(tmprd, [1 10]) < cumsum(q, 2), 2) + 1;
iSig_s = sparse(1:N, 1:N, 1./sigj2(S));
[tmp_i, tmp_m, tmp_l, tmp_o] = OMi(psi, W, ki_rho, n);
tmp_a = blockdiag(tmp_i, kron(speye(T-2), tmp_m), tmp_l);
tmp_b = kron(sparse(1:(T-1), 2:T, 1, T, T), tmp_o);
Om = tmp_a + tmp_b + tmp_b';
Omi = Om./sigu2; % Omega^\star
Bmh = kron(ones([T 1]), mu_h);
bhat = Bh\(Omi*Bmh + iSig_s*(ystar - d_s));
h = bhat + chol(Bh, 'lower')'\randn([N 1]);
s_Oi = ((tmp_i + (T-2).*tmp_m + tmp_l) + (T-1).*tmp_o + (T-1).*tmp_o')./sigu2;
Muhhat = Vmhat\(iVmh0*mh0 + sum(reshape(Omi*h, [n T]), 2));
mu_h = Muhhat + chol(Vmhat,'lower')'\randn([n 1]);
hm = h - kron(ones([T 1]), mu_h);
J_rho = kron(speye(T), (speye(n) - psi(1).*W)) +...
kron(sparse(2:T,1:(T-1),1,T,T), -1.*(psi(2).*speye(n) + psi(3).*W));
Pi_rho = blockdiag(ki_rho, speye(n*(T-1)));
sigu2 = 1/gamrnd(a0 + N/2, 1/(b0 + ((hm'*(J_rho'*Pi_rho*J_rho)*hm)/2)));
% -- sample psi via AMH step (rho in the paper)
lpsi_i = llike_psi(psi, h, mu_h, sigu2, n, T, W);
psic = psi + sqrt(0.1^2/pk).*randn([pk 1]);
V_psi = cov(CovPsi(1:isim,:));
V_psi = Vps*(diag(dd))*Vps';
uf = double(rand() < .95);
psic = uf*(psi + chol(cc*(2.38^2/pk).*V_psi,'lower')*randn([pk 1])) +...
(1-uf)*(psi + sqrt(0.1^2/pk).*randn([pk 1]));
if (sum(abs(psic)) < (1/tau))
lpsi_c = llike_psi(psic, h, mu_h, sigu2, n, T, W);
if (lpsi_c - lpsi_i) > exp(1)
ratio = exp(lpsi_c - lpsi_i);
count_psi = count_psi + 1;
CovPsi(isim + 1, :) = psi';
if (count_psi/isim < 0.4), cc = cc/1.05; end
if (count_psi/isim > 0.6), cc = cc*1.05; end
sigu2dr(isim-R1) = sigu2;
muhdr(:, isim-R1) = mu_h;
psidr(isim-R1, :) = psi';
function Krho = K_rho(psi, W, n)
Sin = (speye(n) - rho1.*W)\speye(n);
An = rho2.*speye(n) + rho3.*W;
tmp2 = tmp2 + tmp1*tmp1';
function [tmp_i, tmp_m, tmp_l, tmp_o] = OMi(psi, W, ki_rho, n)
Sn = (speye(n) - rho1.*W);
An = rho2.*speye(n) + rho3.*W;
tmp_i = (Sn'*ki_rho*Sn + An'*An);
tmp_m = (Sn'*Sn + An'*An);
function llk = llike_psi(PSI, h, mu_h, sigu2, n, T, W)
Sn = (speye(n) - PSI(1).*W);
[tmp_i, tmp_m, tmp_l, tmp_o] = OMi(PSI, W, ki_rho, n);
tmp_a = blockdiag(tmp_i, kron(speye(T-2), tmp_m), tmp_l);
tmp_b = kron(sparse(1:(T-1), 2:T, 1, T, T), tmp_o);
Om = tmp_a + tmp_b + tmp_b';
Bmh = kron(ones([T 1]), mu_h);
llk = -(n*T)*log(sigu2)/2 + T*log(det(Sn)) - log(det(krho))/2 - ((h - Bmh)'*Omi*(h - Bmh))/2;