<p><div><div><div><div data-show-faces="false" data-height="21" data-width="100" data-layout="button_count" data-send="true" data-href="https://www.r-bloggers.com/leading-indicators-of-economic-growth-by-ellis2013nz/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="http://freerangestats.info/blog/2018/08/10/leading-indicators"> free range statistics - R</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p>The debate about business confidence as a possible leading indicator of economic growth reminded me of a question that has long been on my back-burner – what data make the best leading indicators (say, a quarter of a year ahead) of economic growth? There are a number of indicators that get a lot of attention in the media and from economic pundits of course, but I wanted to take a data-driven approach and see what actual evidence there is for the various candidate variables.</p>
<p>To cut to the chase, this graphic summarises my results:</p>
<p><img width="100%" src="http://freerangestats.io/img/0128-ridge-boot-results.svg"></p>
<p>The previous quarter’s GDP growth is, unsurprisingly, a strong indicator of the coming quarter’s. Food prices are strongly negatively related to economic growth (in New Zealand anyway – with the strong agriculture sector here food prices may be serving as a proxy for something else, but exactly what I’m not sure although I’ve got some ideas). Other indicators with fair evidence of value include visitor arrivals, car registrations, business confidence (the OECD measure, which David Hood points out is <a rel="nofollow" href="https://twitter.com/Thoughtfulnz/status/1027339431638749185" target="_blank">apparently derived from the NZIER’s survey, not the ANZ’s</a>), and electronic card transactions.</p>
<p>One surprise for me is the complete absence of predictive power of merchandise goods exports for growth in GDP. Any suggestions why welcomed in the comments!</p>
<p>These confidence intervals come from what I believe to be a pretty robust frequentist modelling approach that combines multiple imputation, bootstrap and elastic net regularization. I have in the back of my head the idea of a follow-up project that uses Bayesian methods to provide actual forecasts of the coming GDP, but I may not get around to it (or if I do, it will probably be for Victorian or Australian data seeing as I’ll be living there from October and more motivated for forecasts in that space).</p>
<h2>Data</h2>
<p>To address the question, I started with the <a rel="nofollow" href="https://www.stats.govt.nz/release-calendar/" target="_blank">Stats NZ data release calendar</a> to identify official statistics that are available on a monthly basis and published within a month of the reference period. I counted seven such collections:</p>
<ul>
<li>Electronic card transactions</li>
<li>Food price index</li>
<li>Transport vehicle registrations</li>
<li>International travel and migration</li>
<li>Overseas merchandise trade</li>
<li>Building consents issued</li>
<li>Livestock slaughtering statistics</li>
</ul>
<p>To these seven, I also added the:</p>
<ul>
<li>Reserve Bank’s Trade Weighted Index of the New Zealand dollar compared to a basket of currencies</li>
<li>OECD business confidence series (I used this rather than the ANZ or NZIER original series as the OECD version is available freely for reproducibility purposes)</li>
</ul>
<p>Some of these datasets are available programmatically, and some required manual steps to download from Infoshare. Slightly more detail on this is <a rel="nofollow" href="https://github.com/ellisp/nz-lead-indicators/blob/master/data/data-provenance.md" target="_blank">documented in the GitHub repo</a> I set up for the project, as are all the scripts that import and combine the data. If you’re interested in that, fork the entire repository, as its a combined whole and needs to be run as such, via the <code>integrate.R</code> script in the root directory of the project.</p>
<p>Here’s the original time series, plus seasonally adjusted versions of all of them except for the trade weighted index (currencies are so random I thought it made little sense to do a seasonally adjusted version of this; possible subject of future investigation):</p>
<p><img width="100%" src="http://freerangestats.io/img/0128-line-charts.svg"></p>
<p>I did my own seasonal adjustment because even when seasonally adjusted series are available in some of the Stats NZ collections, typically it goes back only to the 1990s or 2000s. I wanted data at least all the way back to 1987 when the quarterly GDP series starts; time series are difficult enough to analyse as it is without restricting ourselves to a decade or so of data (this is also why I went back to original data series rather than using the Treasury “leading economic indicator” Excel collections, as they cover only a relatively recent period).</p>
<p>Apart from business confidence (which by its nature is restricted to within a constant range), the original data are all clearly non-stationary, so I converted them to growth rates, which gives us the following chart summarising my candidate leading economic indicators:</p>
<p><img width="100%" src="http://freerangestats.io/img/0128-stationary-line-charts.svg"></p>
<p>Both seasonal adjustment and stationarity are important with time series so we can focus on the underlying relationships and avoid spurious correlations from the structural (growth and episodicity) elements of the series. An alternative approach would be to model the seasonality and growth directly in a full blown Bayesian model, but even in that case I’d want to look at de-seasoned, stationary versions for the exploratory stage.</p>
<p>Now that we have those de-seasoned, stationary versions of the data, we can look at them and see which ones are correlated one-on-one with economic growth for the quarter. Remember, the idea here is to use data that are available fairly promptly as leading indicators of economic growth in the same quarter, because economic growth is only reported quarterly and is hard to estimate, and hence is reported at a material lag. Here are the bivariate correlations:</p>
<p><img width="100%" src="http://freerangestats.io/img/0128-pairs.svg"></p>
<p>I’ve sequenced the variables in order of strength of relationship with GDP growth in the quarter (<a rel="nofollow" href="https://github.com/ellisp/nz-lead-indicators/blob/master/grooming/80-combine.R" target="_blank">code</a>). We can see that lagged GDP growth, electronic card transactions growth, and OECD business confidence have the three strongest positive correlations with GDP growth. Food price growth has a strong negative correlation (ie high growth in food prices is a predictor of low growth in GDP).</p>
<p>The lower diagonal in that pairs plot shows connected scatter plots with a single-predictor linear model. This turned out to be pleasantly easy to do with <code>GGally::ggpairs</code> and a simple user-defined function. Here’s the <a rel="nofollow" href="https://github.com/ellisp/nz-lead-indicators/blob/master/analysis/exploratory.R" target="_blank">code for all three of the exploratory charts</a>.</p>
<h2>Minimalist regression (poor)</h2>
<p>My starting point was a fairly naive out-of-the-box linear regression, just chucking all the data into <code>lm()</code>.</p>
<figure>
<pre><code data-lang="r"><span>load</span><span>(</span><span>&quot;data/ind_data.rda&quot;</span><span>)</span><span>
</span><span>summary</span><span>(</span><span>ind_data_wide</span><span>)</span><span> </span><span># 109 cases where even gdp_growth is missing. No point in using them
</span><span>ind_data_wide2</span><span> </span><span>&lt;-</span><span> </span><span>subset</span><span>(</span><span>ind_data_wide</span><span>,</span><span> </span><span>!</span><span>is.na</span><span>(</span><span>gdp_growth_lag</span><span>))</span><span>
</span><span># naive approach *will* fit a plausible model, but the NA is down to the lowest common denominator, when data available on all variables
</span><span>mod</span><span> </span><span>&lt;-</span><span> </span><span>lm</span><span>(</span><span>gdp_growth</span><span> </span><span>~</span><span> </span><span>.</span><span>,</span><span> </span><span>data</span><span> </span><span>=</span><span> </span><span>ind_data_wide2</span><span>)</span><span>
</span><span>stargazer</span><span>(</span><span>mod</span><span>,</span><span> </span><span>type</span><span> </span><span>=</span><span>&quot;html&quot;</span><span>)</span></code></pre>
</figure>
<p>This gives me this result:</p>
<table>
<tr>
<td colspan="2"></td>
</tr>
<tr>
<td></td>
<td><em>Dependent variable:</em></td>
</tr>
<tr>
<td></td>
<td colspan="1"></td>
</tr>
<tr>
<td></td>
<td>gdp_growth</td>
</tr>
<tr>
<td colspan="2"></td>
</tr>
<tr>
<td>gdp_growth_lag</td>
<td>0.009</td>
</tr>
<tr>
<td></td>
<td>(0.121)</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>ect_growth</td>
<td>0.335<sup>***</sup></td>
</tr>
<tr>
<td></td>
<td>(0.114)</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>bc_sa</td>
<td>0.0001</td>
</tr>
<tr>
<td></td>
<td>(0.001)</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>cars_growth</td>
<td>0.022</td>
</tr>
<tr>
<td></td>
<td>(0.016)</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>com_vcl_growth</td>
<td>0.003</td>
</tr>
<tr>
<td></td>
<td>(0.009)</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>iva_growth</td>
<td>0.046<sup>**</sup></td>
</tr>
<tr>
<td></td>
<td>(0.020)</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>bci_growth</td>
<td>0.001</td>
</tr>
<tr>
<td></td>
<td>(0.008)</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>twi_growth</td>
<td>-0.004</td>
</tr>
<tr>
<td></td>
<td>(0.020)</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>lst_growth</td>
<td>-0.002</td>
</tr>
<tr>
<td></td>
<td>(0.008)</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>goods_growth</td>
<td>0.005</td>
</tr>
<tr>
<td></td>
<td>(0.012)</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>fpi_growth</td>
<td>-0.236<sup>***</sup></td>
</tr>
<tr>
<td></td>
<td>(0.080)</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>yr_num</td>
<td>0.0001</td>
</tr>
<tr>
<td></td>
<td>(0.0002)</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>Constant</td>
<td>-0.177</td>
</tr>
<tr>
<td></td>
<td>(0.354)</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td colspan="2"></td>
</tr>
<tr>
<td>Observations</td>
<td>61</td>
</tr>
<tr>
<td>R<sup>2</sup></td>
<td>0.566</td>
</tr>
<tr>
<td>Adjusted R<sup>2</sup></td>
<td>0.457</td>
</tr>
<tr>
<td>Residual Std. Error</td>
<td>0.004 (df = 48)</td>
</tr>
<tr>
<td>F Statistic</td>
<td>5.215<sup>***</sup> (df = 12; 48)</td>
</tr>
<tr>
<td colspan="2"></td>
</tr>
<tr>
<td><em>Note:</em></td>
<td><sup>*</sup>p&lt;0.1; <sup>**</sup>p&lt;0.05; <sup>***</sup>p&lt;0.01</td>
</tr>
</table>
<p>There’s two big problems with this approach:</p>
<ul>
<li>By default, the model is fit to the minimal set of the data that is complete for all variables. Electronic card transactions is the shortest series, going back only to 2003 when it is treated as a growth rate. So all the information in the other variables before that time has been thrown out.</li>
<li>A big amount of collinearity in the data is inevitable with this sort of collection. While it doesn’t bias the estimated coefficients, it can make a big increase to the variance, and basically adds a lot of noise to our estimated effect sizes. I think this is why we have the implausible result in this naive model of lagged GDP growth not being useful as an explanatory variable.</li>
</ul>
<p>… and one small problem, which is that other than including the lagged value of GDP growth as an explanatory variable, I’m ignoring the time series nature of the data. More on that later.</p>
<h2>Multiple imputation (better)</h2>
<p>We could address the problem of the short electronic card transactions series either by eliminating that series from the data altogether (which would be a shame as it’s obviously useful), or by imputing it’s previous values. Conceptually this is a little dubious – what exactly are we saying growth in electronic card transactions in 1988 would mean? – but statistically it’s fine. The imputed values will capture the relationships between the various variables from 2003 onwards and avoid creating new artefacts, so in effect we will find the relationship between electronic cards and the others for the years we have the data, while still using the full 1987+ data for all other variables.</p>
<p>The appropriate way to do this is to create multiple complete data sets with different imputed values, fit our model to each, and pool the results. The <code>mice</code> package makes this spectacularly easy:</p>
<figure>
<pre><code data-lang="r"><span>ind_mi</span><span> </span><span>&lt;-</span><span> </span><span>mice</span><span>(</span><span>ind_data_wide2</span><span>,</span><span> </span><span>print</span><span> </span><span>=</span><span> </span><span>FALSE</span><span>)</span><span>
</span><span>mod_mi</span><span> </span><span>&lt;-</span><span> </span><span>with</span><span>(</span><span>ind_mi</span><span>,</span><span> </span><span>lm</span><span>(</span><span>gdp_growth</span><span> </span><span>~</span><span> </span><span>yr_num</span><span> </span><span>+</span><span> </span><span>gdp_growth_lag</span><span> </span><span>+</span><span> </span><span>bc_sa</span><span> </span><span>+</span><span> </span><span>bci_growth</span><span> </span><span>+</span><span>
</span><span>ect_growth</span><span> </span><span>+</span><span> </span><span>fpi_growth</span><span> </span><span>+</span><span> </span><span>iva_growth</span><span> </span><span>+</span><span> </span><span>goods_growth</span><span> </span><span>+</span><span> </span><span>cars_growth</span><span> </span><span>+</span><span> </span><span>com_vcl_growth</span><span> </span><span>+</span><span> </span><span>twi_growth</span><span> </span><span>+</span><span> </span><span>lst_growth</span><span>))</span><span>
</span><span>summary</span><span>(</span><span>pool</span><span>(</span><span>mod_mi</span><span>))</span><span> </span><span>%>%</span><span> </span><span>kable</span><span>(</span><span>digits</span><span> </span><span>=</span><span> </span><span>4</span><span>)</span></code></pre>
</figure>
<table>
<thead>
<tr>
<th> </th>
<th>estimate</th>
<th>std.error</th>
<th>statistic</th>
<th>df</th>
<th>p.value</th>
</tr>
</thead>
<tbody>
<tr>
<td>(Intercept)</td>
<td>-0.2889</td>
<td>0.1712</td>
<td>-1.6878</td>
<td>54.8951</td>
<td>0.0944</td>
</tr>
<tr>
<td>yr_num</td>
<td>0.0001</td>
<td>0.0001</td>
<td>1.5128</td>
<td>59.0142</td>
<td>0.1333</td>
</tr>
<tr>
<td>gdp_growth_lag</td>
<td>0.2182</td>
<td>0.0846</td>
<td>2.5785</td>
<td>79.8727</td>
<td>0.0113</td>
</tr>
<tr>
<td>bc_sa</td>
<td>0.0005</td>
<td>0.0004</td>
<td>1.3342</td>
<td>100.3535</td>
<td>0.1850</td>
</tr>
<tr>
<td>bci_growth</td>
<td>0.0061</td>
<td>0.0069</td>
<td>0.8820</td>
<td>106.1382</td>
<td>0.3798</td>
</tr>
<tr>
<td>ect_growth</td>
<td>0.2059</td>
<td>0.1308</td>
<td>1.5741</td>
<td>15.6965</td>
<td>0.1184</td>
</tr>
<tr>
<td>fpi_growth</td>
<td>-0.1900</td>
<td>0.0566</td>
<td>-3.3599</td>
<td>105.0452</td>
<td>0.0011</td>
</tr>
<tr>
<td>iva_growth</td>
<td>0.0257</td>
<td>0.0193</td>
<td>1.3335</td>
<td>82.9585</td>
<td>0.1852</td>
</tr>
<tr>
<td>goods_growth</td>
<td>0.0008</td>
<td>0.0125</td>
<td>0.0628</td>
<td>97.7269</td>
<td>0.9500</td>
</tr>
<tr>
<td>cars_growth</td>
<td>0.0172</td>
<td>0.0071</td>
<td>2.4403</td>
<td>104.3330</td>
<td>0.0163</td>
</tr>
<tr>
<td>com_vcl_growth</td>
<td>0.0050</td>
<td>0.0077</td>
<td>0.6501</td>
<td>102.0428</td>
<td>0.5171</td>
</tr>
<tr>
<td>twi_growth</td>
<td>0.0150</td>
<td>0.0192</td>
<td>0.7807</td>
<td>103.6387</td>
<td>0.4367</td>
</tr>
<tr>
<td>lst_growth</td>
<td>0.0024</td>
<td>0.0061</td>
<td>0.3999</td>
<td>104.7962</td>
<td>0.6900</td>
</tr>
</tbody>
</table>
<p>Compared to the naive model on the cut-down dataset, we now have a much more plausible strong relationship with lagged GDP growth (which didn’t show up at all as significant in the first model). Food prices and car/stationwagon registrations the other two variables with “significant” evidence at this stage. The electronic card transactions (<code>ect_growth</code>) effect is much smaller now, an unsurprising result from the “blurring” of the data introduced by the imputation approach and the fact that the other variables have long series of actual values to assert their relationships with the response of GDP growth.</p>
<p>So, we’ve dealt with the missing data, but not yet the multi-collinearity.</p>
<h2>Multiple imputation, elastic net regularization, and bootstrap (best)</h2>
<p>My preferred approach to reducing the random noise in effect sizes from having collinear explanatory variables is elastic net regularization, which I’ve written about a number of times in this blog.</p>
<p>Regularization can be combined with multiple imputation if we use a bootstrap. For each bootstrap re-sample, we perform a single imputation of missing variables, using just the data in the re-sample. This adds a little to the computational complexity, but this dataset isn’t large, and the more valid inferences possible make it worthwhile. In general, as much of the data processing stage as possible should be repeated in each bootstrap re-sample, for the re-sampling process to work as intended, ie as a mimic of “what would happen if we could repeat this experiment 999 times”, the fundamental principle of frequentist inference.</p>
<p>Bootstrap of a time series normally needs special methods. I’m not necessarily happy with the shortcut I’ve taken here, which is to say that I can treat the data as cross-sectional other than by including the lagged GDP growth variable. However, with all the variables in their stationary state, and with the residual series not showing signs of autocorrelation (more on this later), I think it’s fair enough in a pragmatic way.</p>
<p>I was able to cannibalise code from my previous posts with minimal amendments.</p>
<p>My first step is to estimate good values for the <code>alpha</code> hyper-parameter in generalised linear regression. This is the value between 0 and 1 that lets you choose a combination of the <em>type</em> of penalty used to determine the amount that coefficients are shrunk towards zero, to counter the way collinearity adds random noise to their estimates. I do this by fitting models with a range of different values of <code>alpha</code>, while using cross-validation (built into the <code>glmnet</code> package) to get good values of <code>lambda</code>. <code>lambda</code> is the hyper-parameter that indicates how <em>much</em> shrinkage there is.</p>
<figure>
<pre><code data-lang="r"><span># I like Michael's answer at https://stats.stackexchange.com/questions/46719/multiple-imputation-and-model-selection:
# use in combination with bootstrap.
</span><span>cv_results</span><span> </span><span>&lt;-</span><span> </span><span>data_frame</span><span>(</span><span>lambda</span><span> </span><span>=</span><span> </span><span>numeric</span><span>(),</span><span> </span><span>alpha</span><span> </span><span>=</span><span> </span><span>numeric</span><span>(),</span><span> </span><span>mcve</span><span> </span><span>=</span><span> </span><span>numeric</span><span>(),</span><span> </span><span>imputation</span><span> </span><span>=</span><span> </span><span>numeric</span><span>())</span><span>
</span><span>set.seed</span><span>(</span><span>765</span><span>)</span><span>
</span><span>for</span><span>(</span><span>imp</span><span> </span><span>in</span><span> </span><span>1</span><span>:</span><span>5</span><span>){</span><span>
</span><span>the_data</span><span> </span><span>&lt;-</span><span> </span><span>mice</span><span>::</span><span>complete</span><span>(</span><span>ind_mi</span><span>,</span><span> </span><span>imp</span><span>)</span><span>
</span><span>X</span><span> </span><span>&lt;-</span><span>as.matrix</span><span>(</span><span>select</span><span>(</span><span>the_data</span><span>,</span><span> </span><span>-</span><span>gdp_growth</span><span>))</span><span>
</span><span>Y</span><span> </span><span>&lt;-</span><span> </span><span>the_data</span><span>$</span><span>gdp_growth</span><span>
</span><span>alphas</span><span> </span><span>&lt;-</span><span> </span><span>seq</span><span>(</span><span>from</span><span> </span><span>=</span><span> </span><span>0</span><span>,</span><span> </span><span>to</span><span> </span><span>=</span><span> </span><span>1</span><span>,</span><span> </span><span>length.out</span><span> </span><span>=</span><span> </span><span>21</span><span>)</span><span>
</span><span>for</span><span>(</span><span>i</span><span> </span><span>in</span><span> </span><span>alphas</span><span>){</span><span>
</span><span>cvfit</span><span> </span><span>&lt;-</span><span> </span><span>cv.glmnet</span><span>(</span><span>as.matrix</span><span>(</span><span>X</span><span>),</span><span> </span><span>Y</span><span>,</span><span> </span><span>alpha</span><span> </span><span>=</span><span> </span><span>i</span><span>)</span><span>
</span><span>tmp</span><span> </span><span>&lt;-</span><span> </span><span>data_frame</span><span>(</span><span>lambda</span><span> </span><span>=</span><span> </span><span>cvfit</span><span>$</span><span>lambda</span><span>,</span><span> </span><span>alpha</span><span> </span><span>=</span><span> </span><span>i</span><span>,</span><span> </span><span>mcve</span><span> </span><span>=</span><span> </span><span>cvfit</span><span>$</span><span>cvm</span><span>,</span><span> </span><span>imputation</span><span> </span><span>=</span><span> </span><span>imp</span><span>)</span><span> </span><span>cv_results</span><span> </span><span>&lt;-</span><span> </span><span>rbind</span><span>(</span><span>cv_results</span><span>,</span><span> </span><span>tmp</span><span>)</span><span>
</span><span>}</span><span>
</span><span>}</span><span>
</span><span>cv_res_grp</span><span> </span><span>&lt;-</span><span> </span><span>cv_results</span><span> </span><span>%>%</span><span>
</span><span>group_by</span><span>(</span><span>lambda</span><span>,</span><span> </span><span>alpha</span><span>)</span><span> </span><span>%>%</span><span>
</span><span>summarise</span><span>(</span><span>mcve</span><span> </span><span>=</span><span> </span><span>mean</span><span>(</span><span>mcve</span><span>))</span><span> </span><span>%>%</span><span>
</span><span>ungroup</span><span>()</span><span> </span><span>%>%</span><span>
</span><span>arrange</span><span>(</span><span>mcve</span><span>)</span><span>
</span><span>cv_res_grp</span><span> </span><span>%>%</span><span>
</span><span>mutate</span><span>(</span><span>ordered_alpha</span><span> </span><span>=</span><span> </span><span>as.factor</span><span>(</span><span>round</span><span>(</span><span>alpha</span><span>,</span><span> </span><span>3</span><span>)),</span><span>
</span><span>ordered_alpha</span><span> </span><span>=</span><span> </span><span>fct_reorder</span><span>(</span><span>ordered_alpha</span><span>,</span><span> </span><span>mcve</span><span>,</span><span> </span><span>.fun</span><span> </span><span>=</span><span> </span><span>min</span><span>))</span><span> </span><span>%>%</span><span>
</span><span>ggplot</span><span>(</span><span>aes</span><span>(</span><span>x</span><span> </span><span>=</span><span> </span><span>lambda</span><span>,</span><span> </span><span>y</span><span> </span><span>=</span><span> </span><span>sqrt</span><span>(</span><span>mcve</span><span>),</span><span> </span><span>colour</span><span> </span><span>=</span><span> </span><span>ordered_alpha</span><span>))</span><span> </span><span>+</span><span>
</span><span>geom_line</span><span>(</span><span>size</span><span> </span><span>=</span><span> </span><span>2</span><span>)</span><span> </span><span>+</span><span>
</span><span>scale_x_log10</span><span>(</span><span>label</span><span> </span><span>=</span><span> </span><span>comma</span><span>)</span><span> </span><span>+</span><span>
</span><span>scale_colour_viridis</span><span>(</span><span>&quot;alpha&quot;</span><span>,</span><span> </span><span>guide</span><span> </span><span>=</span><span> </span><span>guide_legend</span><span>(</span><span>reverse</span><span> </span><span>=</span><span> </span><span>TRUE</span><span>),</span><span> </span><span>discrete</span><span> </span><span>=</span><span> </span><span>TRUE</span><span>)</span><span> </span><span>+</span><span>
</span><span>ggtitle</span><span>(</span><span>&quot;Cross-validation to select hyper parameters in elastic net regression&quot;</span><span>)</span><span> </span><span>+</span><span>
</span><span>scale_y_continuous</span><span>(</span><span>&quot;Square root of mean cross validation error&quot;</span><span>,</span><span> </span><span>label</span><span> </span><span>=</span><span> </span><span>comma</span><span>)</span><span> </span><span>+</span><span>
</span><span>theme</span><span>(</span><span>legend.position</span><span> </span><span>=</span><span> </span><span>&quot;right&quot;</span><span>)</span><span>
</span><span>chosen_alpha</span><span> </span><span>&lt;-</span><span> </span><span>arrange</span><span>(</span><span>cv_res_grp</span><span>,</span><span> </span><span>mcve</span><span>)[</span><span>1</span><span>,</span><span> </span><span>]</span><span>$</span><span>alpha</span></code></pre>
</figure>
<p>This gives me a graphic like the following:</p>
<p><img width="100%" src="http://freerangestats.io/img/0128-hyper-params-glmnet.svg"></p>
<p>We can see that we can get low values of mean error with many combinations of <code>alpha</code> and <code>lambda</code>, which is reassuring in suggesting that the choices aren’t critical. But I store the best value of alpha for use in the actual bootstrapped model fitting to follow.</p>
<p>Here’s the code that does the actual bootstrapped, mulitply-imputed, elastic net regularized fitting of my model. It has four steps:</p>
<ul>
<li>define a function <code>elastic()</code> that takes a resampled set of data, performs a single imputation on it, determines a good value of lambda, fits the model and returns the values of the coefficients</li>
<li>use the <code>boot::boot</code> function to run that function on 999 different samples with replacement of the original data</li>
<li>tidy up the resulting percentile confidence intervals into a neat data frame</li>
<li>draw a nice graphic showing the confidence intervals</li>
</ul>
<p>Most of the code below is actually in the third and fourth of those steps:</p>
<figure>
<pre><code data-lang="r"><span># Define a function that creates a single imputed set of data from a bootstrapped resample, then fits ridge regression to it
</span><span>elastic</span><span> </span><span>&lt;-</span><span> </span><span>function</span><span>(</span><span>data</span><span>,</span><span> </span><span>i</span><span>){</span><span>
</span><span># create a single complete imputed set of data:
</span><span> </span><span>the_data</span><span> </span><span>=</span><span> </span><span>mice</span><span>::</span><span>complete</span><span>(</span><span>mice</span><span>(</span><span>data</span><span>[</span><span>i</span><span>,</span><span> </span><span>],</span><span> </span><span>m</span><span> </span><span>=</span><span> </span><span>1</span><span>,</span><span> </span><span>print</span><span> </span><span>=</span><span> </span><span>FALSE</span><span>),</span><span> </span><span>1</span><span>)</span><span>
</span><span>X</span><span> </span><span>&lt;-</span><span> </span><span>as.matrix</span><span>(</span><span>dplyr</span><span>::</span><span>select</span><span>(</span><span>the_data</span><span>,</span><span> </span><span>-</span><span>gdp_growth</span><span>))</span><span>
</span><span>Y</span><span> </span><span>&lt;-</span><span> </span><span>the_data</span><span>$</span><span>gdp_growth</span><span>
</span><span># we're going to scale the data so the results are in terms of standard deviations of the </span><span> </span><span># variable in question, to make coefficients more comparable. Note that this scaling takes
</span><span> </span><span># place *within* the bootstrap as it is part of the analytical process we are simulating
</span><span> </span><span># by resampling. </span><span> </span><span>X</span><span> </span><span>&lt;-</span><span> </span><span>scale</span><span>(</span><span>X</span><span>)</span><span>
</span><span># work out the best lambda for this dataset using cross validation&quot;
</span><span> </span><span>lambda</span><span> </span><span>&lt;-</span><span> </span><span>cv.glmnet</span><span>(</span><span>as.matrix</span><span>(</span><span>X</span><span>),</span><span> </span><span>Y</span><span>,</span><span> </span><span>alpha</span><span> </span><span>=</span><span> </span><span>chosen_alpha</span><span>)</span><span>$</span><span>lambda.min</span><span>
</span><span># fit the model:
</span><span> </span><span>mod_glmnet</span><span> </span><span>&lt;-</span><span> </span><span>glmnet</span><span>(</span><span>X</span><span>,</span><span> </span><span>Y</span><span>,</span><span> </span><span>alpha</span><span> </span><span>=</span><span> </span><span>chosen_alpha</span><span>,</span><span> </span><span>lambda</span><span> </span><span>=</span><span> </span><span>lambda</span><span>)</span><span>
</span><span># return the results:
</span><span> </span><span>as.vector</span><span>(</span><span>coef</span><span>(</span><span>mod_glmnet</span><span>))</span><span>
</span><span>}</span><span>
</span><span># Now we run the bootstrap, using the function above.
</span><span>set.seed</span><span>(</span><span>321</span><span>)</span><span>
</span><span>elastic_bt</span><span> </span><span>&lt;-</span><span> </span><span>boot</span><span>(</span><span>data</span><span> </span><span>=</span><span> </span><span>ind_data_wide2</span><span>,</span><span> </span><span>statistic</span><span> </span><span>=</span><span> </span><span>elastic</span><span>,</span><span> </span><span>R</span><span> </span><span>=</span><span> </span><span>999</span><span>)</span><span>
</span><span># process results into a tidy data frame
</span><span>boot_coefs</span><span> </span><span>&lt;-</span><span> </span><span>data_frame</span><span>(</span><span>
</span><span>variable</span><span> </span><span>=</span><span> </span><span>character</span><span>(),</span><span>
</span><span>lower</span><span> </span><span>=</span><span> </span><span>numeric</span><span>(),</span><span>
</span><span>upper</span><span> </span><span>=</span><span> </span><span>numeric</span><span>(),</span><span>
</span><span>point</span><span> </span><span>=</span><span> </span><span>numeric</span><span>()</span><span>
</span><span>)</span><span>
</span><span>var_names</span><span> </span><span>&lt;-</span><span> </span><span>c</span><span>(</span><span>&quot;Intercept&quot;</span><span>,</span><span> </span><span>names</span><span>(</span><span>ind_data_wide_names</span><span>)[</span><span>-2</span><span>])</span><span>
</span><span>for</span><span>(</span><span>i</span><span> </span><span>in</span><span> </span><span>1</span><span>:</span><span>length</span><span>(</span><span>var_names</span><span>)){</span><span>
</span><span>x</span><span> </span><span>&lt;-</span><span> </span><span>boot.ci</span><span>(</span><span>elastic_bt</span><span>,</span><span> </span><span>type</span><span> </span><span>=</span><span> </span><span>&quot;perc&quot;</span><span>,</span><span> </span><span>index</span><span> </span><span>=</span><span> </span><span>i</span><span>)</span><span>
</span><span>boot_coefs</span><span> </span><span>&lt;-</span><span> </span><span>rbind</span><span>(</span><span>boot_coefs</span><span>,</span><span>
</span><span>data_frame</span><span>(</span><span>variable</span><span> </span><span>=</span><span> </span><span>var_names</span><span>[</span><span>i</span><span>],</span><span>
</span><span>lower</span><span> </span><span>=</span><span> </span><span>x</span><span>$</span><span>percent</span><span>[</span><span>4</span><span>],</span><span>
</span><span>upper</span><span> </span><span>=</span><span> </span><span>x</span><span>$</span><span>percent</span><span>[</span><span>5</span><span>],</span><span>
</span><span>point</span><span> </span><span>=</span><span> </span><span>x</span><span>$</span><span>t</span><span>0</span><span>))</span><span>
</span><span>}</span><span>
</span><span># draw graphic
</span><span>boot_coefs</span><span> </span><span>%>%</span><span>
</span><span>filter</span><span>(</span><span>!</span><span>variable</span><span> </span><span>%in%</span><span> </span><span>c</span><span>(</span><span>&quot;yr_num&quot;</span><span>,</span><span> </span><span>&quot;Intercept&quot;</span><span>))</span><span> </span><span>%>%</span><span>
</span><span># next line is in there in case we do still want the time trend in the graphic. But it's never significant,
</span><span> </span><span># and it's hard to explian.
</span><span> </span><span>mutate</span><span>(</span><span>variable</span><span> </span><span>=</span><span> </span><span>ifelse</span><span>(</span><span>variable</span><span> </span><span>==</span><span> </span><span>&quot;yr_num&quot;</span><span>,</span><span> </span><span>&quot;Linear\ntime trend&quot;</span><span>,</span><span> </span><span>variable</span><span>))</span><span> </span><span>%>%</span><span>
</span><span>mutate</span><span>(</span><span>variable</span><span> </span><span>=</span><span> </span><span>fct_reorder</span><span>(</span><span>variable</span><span>,</span><span> </span><span>lower</span><span>))</span><span> </span><span>%>%</span><span>
</span><span>ggplot</span><span>(</span><span>aes</span><span>(</span><span>y</span><span> </span><span>=</span><span> </span><span>variable</span><span>))</span><span> </span><span>+</span><span>
</span><span>geom_vline</span><span>(</span><span>xintercept</span><span> </span><span>=</span><span> </span><span>0</span><span>,</span><span> </span><span>colour</span><span> </span><span>=</span><span> </span><span>&quot;black&quot;</span><span>)</span><span> </span><span>+</span><span>
</span><span>geom_segment</span><span>(</span><span>aes</span><span>(</span><span>yend</span><span> </span><span>=</span><span> </span><span>variable</span><span>,</span><span> </span><span>x</span><span> </span><span>=</span><span> </span><span>lower</span><span>,</span><span> </span><span>xend</span><span> </span><span>=</span><span> </span><span>upper</span><span>),</span><span> </span><span>size</span><span> </span><span>=</span><span> </span><span>3</span><span>,</span><span> </span><span>colour</span><span> </span><span>=</span><span> </span><span>&quot;steelblue&quot;</span><span>,</span><span> </span><span>alpha</span><span> </span><span>=</span><span> </span><span>0.5</span><span>)</span><span> </span><span>+</span><span>
</span><span>geom_point</span><span>(</span><span>aes</span><span>(</span><span>x</span><span> </span><span>=</span><span> </span><span>point</span><span>))</span><span> </span><span>+</span><span>
</span><span>scale_x_continuous</span><span>(</span><span>label</span><span> </span><span>=</span><span> </span><span>percent</span><span>)</span><span> </span><span>+</span><span>
</span><span>labs</span><span>(</span><span>x</span><span> </span><span>=</span><span> </span><span>&quot;Estimated impact in percentage points on coming quarter's GDP growth,
of change in one standard deviation in the explanatory variable&quot;</span><span>,</span><span>
</span><span>y</span><span> </span><span>=</span><span> </span><span>&quot;&quot;</span><span>,</span><span>
</span><span>caption</span><span> </span><span>=</span><span> </span><span>&quot;95% confidence intervals based on bootstrapped elastic net regularized regression, with
electronic card transactions imputed differently for each bootstrap resample.
Analysis by http://freerangestats.info&quot;</span><span>)</span><span> </span><span>+</span><span>
</span><span>ggtitle</span><span>(</span><span>&quot;Previous quarter's economic growth (+ve) and food prices (-ve)
most useful as leading indicators of this quarter's New Zealand economic growth&quot;</span><span>,</span><span>
</span><span>str_wrap</span><span>(</span><span>&quot;Variables considered are official statistics available from Stats NZ every month, within a month; plus the OECD business confidence measure (which is based on NZIER's Quarterly Survey of Business Opinion); and the trade weighted index for currency published by RBNZ. Data goes back to 1987.&quot;</span><span>,</span><span> </span><span>80</span><span>))</span></code></pre>
</figure>
<p><img width="100%" src="http://freerangestats.io/img/0128-ridge-boot-results.svg"></p>
<p>And I’m sticking to that (same graphic as I started the post with) as my best results.</p>
<h2>Timeseries method</h2>
<p>I’ve mentioned a couple of times my nervousness about how I’ve inadequately treated the timeseries nature of these data. Here’s a quick look at the residuals of one version of my model (ie one set of imputed values). It assures me that there is no evidence of the residuals being autocorrelated, but I still think I’m cutting corners:</p>
<figure>
<pre><code data-lang="r"><span>lambda</span><span> </span><span>&lt;-</span><span> </span><span>cv.glmnet</span><span>(</span><span>as.matrix</span><span>(</span><span>X</span><span>),</span><span> </span><span>Y</span><span>,</span><span> </span><span>alpha</span><span> </span><span>=</span><span> </span><span>chosen_alpha</span><span>)</span><span>$</span><span>lambda.min</span><span>
</span><span>mod_glmnet</span><span> </span><span>&lt;-</span><span> </span><span>glmnet</span><span>(</span><span>X</span><span>,</span><span> </span><span>Y</span><span>,</span><span> </span><span>alpha</span><span> </span><span>=</span><span> </span><span>chosen_alpha</span><span>,</span><span> </span><span>lambda</span><span> </span><span>=</span><span> </span><span>lambda</span><span>)</span><span>
</span><span>residuals</span><span> </span><span>&lt;-</span><span> </span><span>ts</span><span>(</span><span>Y</span><span> </span><span>-</span><span> </span><span>predict</span><span>(</span><span>mod_glmnet</span><span>,</span><span> </span><span>newx</span><span> </span><span>=</span><span> </span><span>X</span><span>),</span><span> </span><span>start</span><span> </span><span>=</span><span> </span><span>c</span><span>(</span><span>1987</span><span>,</span><span> </span><span>4</span><span>),</span><span> </span><span>frequency</span><span> </span><span>=</span><span> </span><span>4</span><span>)</span><span>
</span><span># Fail to reject null hypothesis of no autocorrelation (which means that it is probably ok to have used the
# above method, pretending it is cross-sectional other than by including the lagged GDP)
</span><span>Box.test</span><span>(</span><span>residuals</span><span>,</span><span> </span><span>lag</span><span> </span><span>=</span><span> </span><span>4</span><span>,</span><span> </span><span>type</span><span> </span><span>=</span><span> </span><span>&quot;Ljung-Box&quot;</span><span>)</span><span>
</span><span>par</span><span>(</span><span>family</span><span> </span><span>=</span><span> </span><span>&quot;myfont&quot;</span><span>,</span><span> </span><span>font.main</span><span> </span><span>=</span><span> </span><span>1</span><span>,</span><span> </span><span>bty</span><span> </span><span>=</span><span> </span><span>&quot;l&quot;</span><span>)</span><span>
</span><span>tsdisplay</span><span>(</span><span>residuals</span><span>,</span><span> </span><span>main</span><span> </span><span>=</span><span> </span><span>&quot;Time series of residuals from regression with imputation and regularization&quot;</span><span>)</span></code></pre>
</figure>
<p><img width="100%" src="http://freerangestats.io/img/0128-residuals.ts.svg"></p>
<p>As an alternative approach, I used the <code>forecast::auto.arima()</code> approach to model an imputed set of the data explicitly as a timeseries. This gets me some results that differ slightly from the above, but perhaps are broadly compatible:</p>
<table>
<thead>
<tr>
<th>.rownames</th>
<th>X2.5..</th>
<th>X97.5..</th>
</tr>
</thead>
<tbody>
<tr>
<td>ar1</td>
<td>0.7812</td>
<td>1.0434</td>
</tr>
<tr>
<td>ma1</td>
<td>-1.8363</td>
<td>-1.5284</td>
</tr>
<tr>
<td>ma2</td>
<td>0.6152</td>
<td>0.9077</td>
</tr>
<tr>
<td>sar1</td>
<td>0.0547</td>
<td>0.5393</td>
</tr>
<tr>
<td>sar2</td>
<td>-0.4780</td>
<td>-0.0796</td>
</tr>
<tr>
<td>sma1</td>
<td>-1.0269</td>
<td>-0.7013</td>
</tr>
<tr>
<td>ect_growth</td>
<td>0.0003</td>
<td>0.0018</td>
</tr>
<tr>
<td>bc_sa</td>
<td>0.0009</td>
<td>0.0036</td>
</tr>
<tr>
<td>cars_growth</td>
<td>-0.0001</td>
<td>0.0017</td>
</tr>
<tr>
<td>com_vcl_growth</td>
<td>-0.0007</td>
<td>0.0010</td>
</tr>
<tr>
<td>iva_growth</td>
<td>-0.0003</td>
<td>0.0017</td>
</tr>
<tr>
<td>bci_growth</td>
<td>-0.0010</td>
<td>0.0004</td>
</tr>
<tr>
<td>twi_growth</td>
<td>-0.0002</td>
<td>0.0013</td>
</tr>
<tr>
<td>lst_growth</td>
<td>-0.0001</td>
<td>0.0016</td>
</tr>
<tr>
<td>goods_growth</td>
<td>-0.0011</td>
<td>0.0008</td>
</tr>
<tr>
<td>fpi_growth</td>
<td>-0.0023</td>
<td>-0.0003</td>
</tr>
</tbody>
</table>
<p>The key difference is that business confidence (<code>bc_sa</code>) and, to a lesser extent, electronic card transactions (<code>ect_growth</code>) are more strongly significant in this version. Food price (<code>fpi_growth</code>) is still strongly negative, and other confidence intervals all encompass zero. Note that lagged GDP growth doesn’t appear in this model explicitly – it comes in automatically as the <code>ar1</code> (auto-regression at lag 1) term of the algorithmically-chosen ARIMA model.</p>
<p>The results are similar enough that I decide I’m ok with the approach I’ve taken, so I don’t invest in what would be quite an effort (but eminently possible with time) to introduce multiple imputation and regularization into the time series model.</p>
<p>Here’s the code for that bit of time series modelling:</p>
<figure>
<pre><code data-lang="r"><span>set.seed</span><span>(</span><span>234</span><span>)</span><span>
</span><span>ind_imp</span><span> </span><span>&lt;-</span><span> </span><span>mice</span><span>::</span><span>complete</span><span>(</span><span>mice</span><span>(</span><span>ind_data_wide2</span><span>,</span><span> </span><span>m</span><span> </span><span>=</span><span> </span><span>1</span><span>,</span><span> </span><span>print</span><span> </span><span>=</span><span> </span><span>FALSE</span><span>),</span><span> </span><span>1</span><span>)</span><span>
</span><span>Y</span><span> </span><span>&lt;-</span><span> </span><span>ts</span><span>(</span><span>ind_imp</span><span>$</span><span>gdp_growth</span><span>,</span><span> </span><span>start</span><span> </span><span>=</span><span> </span><span>c</span><span>(</span><span>1987</span><span>,</span><span> </span><span>4</span><span>),</span><span> </span><span>frequency</span><span> </span><span>=</span><span> </span><span>4</span><span>)</span><span>
</span><span>X</span><span> </span><span>&lt;-</span><span> </span><span>scale</span><span>(</span><span>select</span><span>(</span><span>ind_imp</span><span>,</span><span> </span><span>-</span><span>yr_num</span><span>,</span><span> </span><span>-</span><span>gdp_growth</span><span>,</span><span> </span><span>-</span><span>gdp_growth_lag</span><span>))</span><span>
</span><span>mod</span><span> </span><span>&lt;-</span><span> </span><span>auto.arima</span><span>(</span><span>Y</span><span>,</span><span> </span><span>xreg</span><span> </span><span>=</span><span> </span><span>X</span><span>)</span><span>
</span><span>confint</span><span>(</span><span>mod</span><span>)</span><span> </span><span>%>%</span><span> </span><span>tidy</span><span> </span><span>%>%</span><span> </span><span>kable</span><span>(</span><span>digits</span><span> </span><span>=</span><span> </span><span>4</span><span>)</span></code></pre>
</figure>
<p>All the analysis code, as well as the data downloading and grooming code (which is the bulk of it…), is available in the <a rel="nofollow" href="https://github.com/ellisp/nz-lead-indicators/" target="_blank">GitHub repository for this project</a>. The code excerpts above aren’t self-sufficient without the rest of the project, which has a folder system that doesn’t lend itself to representing in a simple blog post; you need to clone the project to get it to work. For the record, here are the R packages used:</p>
<figure>
<pre><code data-lang="r"><span>library</span><span>(</span><span>tidyverse</span><span>)</span><span>
</span><span>library</span><span>(</span><span>lubridate</span><span>)</span><span>
</span><span>library</span><span>(</span><span>scales</span><span>)</span><span>
</span><span>library</span><span>(</span><span>seasonal</span><span>)</span><span>
</span><span>library</span><span>(</span><span>GGally</span><span>)</span><span>
</span><span>library</span><span>(</span><span>openxlsx</span><span>)</span><span>
</span><span>library</span><span>(</span><span>mice</span><span>)</span><span>
</span><span>library</span><span>(</span><span>forecast</span><span>)</span><span>
</span><span>library</span><span>(</span><span>glmnet</span><span>)</span><span>
</span><span>library</span><span>(</span><span>viridis</span><span>)</span><span>
</span><span>library</span><span>(</span><span>boot</span><span>)</span><span>
</span><span>library</span><span>(</span><span>broom</span><span>)</span><span>
</span><span>library</span><span>(</span><span>stargazer</span><span>)</span><span>
</span><span>library</span><span>(</span><span>knitr</span><span>)</span><span> </span></code></pre>
</figure>
	<p>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="http://freerangestats.info/blog/2018/08/10/leading-indicators"> free range statistics - R</a></strong>.</div>
<hr>
<a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers.com</a> offers <strong><a rel="nofollow" href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers">daily e-mail updates</a></strong> about <a rel="nofollow" href="https://www.r-project.org/" title="The R Project for Statistical Computing">R</a> news and <a rel="nofollow" href="https://www.r-bloggers.com/search/tutorial" title="R tutorials">tutorials</a> on topics such as: <a rel="nofollow" href="https://www.r-bloggers.com/search/data%20science" title="Data science">Data science</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Big%20Data" title="Big Data">Big Data, <a rel="nofollow" href="https://www.r-users.com/" title="R jobs">R jobs</a>, visualization (<a rel="nofollow" href="https://www.r-bloggers.com/search/ggplot2" title="ggplot and ggplot2 tutorials">ggplot2</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/boxplot" title="Boxplots using lattice and ggplot2 tutorials">Boxplots</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/map" title="Maps and gis">maps</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/animation" title="Animation in R">animation</a>), programming (<a rel="nofollow" href="https://www.r-bloggers.com/search/RStudio" title="RStudio IDE for R">RStudio</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/sweave" title="Sweave and literate programming">Sweave</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/LaTeX" title="LaTeX in R">LaTeX</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/SQL" title="SQL and databases">SQL</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/eclipse" title="Eclipse IDE for R">Eclipse</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/git" title="git and github, Version Control System">git</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/hadoop" title="Large data in R using Hadoop">hadoop</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Web+Scraping" title="Web Scraping of google, facebook, yahoo, twitter and more using R">Web Scraping</a>) statistics (<a rel="nofollow" href="https://www.r-bloggers.com/search/regression" title="Regressions and ANOVA analysis tutorials">regression</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/PCA" title="principal component analysis tutorial">PCA</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/time+series" title="Time series">time series</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/trading" title="finance trading">trading</a>) and more...
<img width="1" alt="" src="http://feeds.feedburner.com/~r/RBloggers/~4/GunhLxX-5bI" height="1"></a></div>