<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Variable Selection | Carlos Mendez</title><link>https://carlos-mendez.org/category/variable-selection/</link><atom:link href="https://carlos-mendez.org/category/variable-selection/index.xml" rel="self" type="application/rss+xml"/><description>Variable Selection</description><generator>Wowchemy (https://wowchemy.com)</generator><language>en-us</language><copyright>Carlos Mendez</copyright><lastBuildDate>Sat, 04 Apr 2026 00:00:00 +0000</lastBuildDate><image><url>https://carlos-mendez.org/media/icon_huedfae549300b4ca5d201a9bd09a3ecd5_79625_512x512_fill_lanczos_center_3.png</url><title>Variable Selection</title><link>https://carlos-mendez.org/category/variable-selection/</link></image><item><title>Identifying Latent Group Structures in Panel Data: The classifylasso Command in Stata</title><link>https://carlos-mendez.org/post/stata_panel_lasso_cluster/</link><pubDate>Sat, 04 Apr 2026 00:00:00 +0000</pubDate><guid>https://carlos-mendez.org/post/stata_panel_lasso_cluster/</guid><description>&lt;h2 id="1-overview">1. Overview&lt;/h2>
&lt;p>Do all countries respond the same way to inflation? To interest rates? To democratic transitions? Most panel data models assume yes. They force every country to share the same slope coefficients. That is a strong assumption &amp;mdash; and often a wrong one.&lt;/p>
&lt;p>Here is a preview of what we will discover. When we estimate the effect of inflation on savings across 56 countries, the pooled model says: &amp;ldquo;no significant effect.&amp;rdquo; But that average is a lie. One group of countries saves &lt;em>less&lt;/em> when inflation rises. Another group saves &lt;em>more&lt;/em>. The pooled estimate averages a negative and a positive effect, producing a misleading zero.&lt;/p>
&lt;p>The &lt;strong>Classifier-LASSO&lt;/strong> (C-LASSO) method solves this problem. Developed by Su, Shi, and Phillips (2016), it discovers &lt;strong>latent groups&lt;/strong> in your panel data. Countries within each group share the same coefficients. Countries across groups can differ. Think of it like a sorting hat: rather than treating all countries as identical or all as unique, C-LASSO sorts them into a small number of groups with shared behavioral patterns.&lt;/p>
&lt;p>This tutorial demonstrates the &lt;code>classifylasso&lt;/code> Stata command (Huang, Wang, and Zhou 2024) with two applications:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Savings behavior&lt;/strong> across 56 countries (1995&amp;ndash;2010) &amp;mdash; where inflation affects savings in &lt;em>opposite directions&lt;/em> depending on the country group&lt;/li>
&lt;li>&lt;strong>Democracy and economic growth&lt;/strong> across 98 countries (1970&amp;ndash;2010) &amp;mdash; where the pooled estimate of +1.05 masks a split of +2.15 in one group and -0.94 in another&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Learning objectives:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Understand why assuming homogeneous slopes can be misleading in panel data&lt;/li>
&lt;li>Learn the Classifier-LASSO method for identifying latent group structures&lt;/li>
&lt;li>Implement &lt;code>classifylasso&lt;/code> in Stata with both static and dynamic specifications&lt;/li>
&lt;li>Use postestimation commands (&lt;code>classogroup&lt;/code>, &lt;code>classocoef&lt;/code>, &lt;code>predict gid&lt;/code>) to visualize and interpret results&lt;/li>
&lt;li>Compare pooled fixed-effects estimates with group-specific C-LASSO estimates&lt;/li>
&lt;/ul>
&lt;p>The diagram below maps the tutorial&amp;rsquo;s progression. We start simple and build complexity step by step.&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph LR
A[&amp;quot;&amp;lt;b&amp;gt;EDA&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Savings data&amp;quot;] --&amp;gt; B[&amp;quot;&amp;lt;b&amp;gt;Baseline FE&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Pooled &amp;amp;&amp;lt;br/&amp;gt;fixed effects&amp;quot;]
B --&amp;gt; C[&amp;quot;&amp;lt;b&amp;gt;C-LASSO&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Static model&amp;lt;br/&amp;gt;(no lagged DV)&amp;quot;]
C --&amp;gt; D[&amp;quot;&amp;lt;b&amp;gt;C-LASSO&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Dynamic model&amp;lt;br/&amp;gt;(jackknife)&amp;quot;]
D --&amp;gt; E[&amp;quot;&amp;lt;b&amp;gt;Democracy&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Application&amp;lt;br/&amp;gt;(two-way FE)&amp;quot;]
E --&amp;gt; F[&amp;quot;&amp;lt;b&amp;gt;Comparison&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Pooled vs&amp;lt;br/&amp;gt;group-specific&amp;quot;]
style A fill:#141413,stroke:#141413,color:#fff
style B fill:#6a9bcc,stroke:#141413,color:#fff
style C fill:#d97757,stroke:#141413,color:#fff
style D fill:#d97757,stroke:#141413,color:#fff
style E fill:#00d4c8,stroke:#141413,color:#141413
style F fill:#1a3a8a,stroke:#141413,color:#fff
&lt;/code>&lt;/pre>
&lt;hr>
&lt;h2 id="2-the-problem-homogeneous-vs-heterogeneous-slopes">2. The Problem: Homogeneous vs Heterogeneous Slopes&lt;/h2>
&lt;h3 id="21-three-approaches-to-slope-heterogeneity">2.1 Three approaches to slope heterogeneity&lt;/h3>
&lt;p>Imagine 56 students taking the same exam. &lt;strong>Approach 1&lt;/strong> assumes they all studied the same way &amp;mdash; one average study strategy explains everyone&amp;rsquo;s score. &lt;strong>Approach 2&lt;/strong> gives each student a unique strategy &amp;mdash; but with only a few data points per student, the estimates are noisy. &lt;strong>Approach 3&lt;/strong> (C-LASSO) discovers that students naturally fall into 2&amp;ndash;3 study groups. Students within a group share the same strategy. Students across groups differ.&lt;/p>
&lt;p>The same logic applies to panel data. The standard fixed-effects model is:&lt;/p>
&lt;p>$$y_{it} = \mu_i + \boldsymbol{\beta}' \mathbf{x}_{it} + u_{it}$$&lt;/p>
&lt;p>Here, $y_{it}$ is the outcome for country $i$ at time $t$. The term $\mu_i$ captures country-specific intercepts (fixed effects). The slope vector $\boldsymbol{\beta}$ links the regressors $\mathbf{x}_{it}$ to the outcome. The critical assumption: $\boldsymbol{\beta}$ is the &lt;strong>same for all countries&lt;/strong>. Japan and Nigeria get the same coefficient on inflation. That may be wrong.&lt;/p>
&lt;p>At the other extreme, we could run separate regressions for each country. But with only $T = 15$ time periods per country, individual estimates are noisy. We lose statistical power.&lt;/p>
&lt;p>C-LASSO introduces a middle ground. It assumes countries belong to $K$ latent groups:&lt;/p>
&lt;p>$$\boldsymbol{\beta}_i = \boldsymbol{\alpha}_k \quad \text{if} \quad i \in G_k, \quad k = 1, \ldots, K$$&lt;/p>
&lt;p>In words, country $i$ gets the slope coefficients of its group $G_k$. The method estimates three things simultaneously: the number of groups $K$, which countries belong to which group, and each group&amp;rsquo;s coefficients $\boldsymbol{\alpha}_k$. You do not need to specify the groups in advance. The data reveals them.&lt;/p>
&lt;h3 id="22-why-not-just-use-k-means">2.2 Why not just use K-means?&lt;/h3>
&lt;p>A natural question: why not run individual regressions first and then cluster the coefficients with K-means? C-LASSO has two advantages. First, it estimates group membership and coefficients &lt;strong>jointly&lt;/strong>. A two-step approach (estimate, then cluster) propagates first-stage errors into the grouping. Second, C-LASSO&amp;rsquo;s penalty structure naturally pulls similar countries toward the same group. It is a statistically principled sorting mechanism, not an ad-hoc post-processing step.&lt;/p>
&lt;hr>
&lt;h2 id="3-the-classifier-lasso-method">3. The Classifier-LASSO Method&lt;/h2>
&lt;h3 id="31-the-c-lasso-objective-function">3.1 The C-LASSO objective function&lt;/h3>
&lt;p>C-LASSO minimizes a penalized least-squares objective:&lt;/p>
&lt;p>$$Q_{NT,\lambda}^{(K)} = \frac{1}{NT} \sum_{i=1}^{N} \sum_{t=1}^{T} (y_{it} - \boldsymbol{\beta}_i' \mathbf{x}_{it})^2 + \frac{\lambda_{NT}}{N} \sum_{i=1}^{N} \prod_{k=1}^{K} |\boldsymbol{\beta}_i - \boldsymbol{\alpha}_k|$$&lt;/p>
&lt;p>The first term is the standard sum of squared residuals. It measures how well the model fits the data. The second term is the &lt;strong>penalty&lt;/strong>. It encourages each country&amp;rsquo;s coefficients $\boldsymbol{\beta}_i$ to be close to one of the group centers $\boldsymbol{\alpha}_k$.&lt;/p>
&lt;p>Think of each group center as a &lt;strong>planet with gravitational pull&lt;/strong>. If a country&amp;rsquo;s coefficients are close to &lt;em>any&lt;/em> planet, the product $\prod_k |\boldsymbol{\beta}_i - \boldsymbol{\alpha}_k|$ shrinks toward zero. The penalty becomes small. The country gets pulled into that group. If the coefficients are far from all planets, the penalty stays large. The tuning parameter $\lambda_{NT} = c_\lambda T^{-1/3}$ controls how strong this gravitational pull is.&lt;/p>
&lt;h3 id="32-three-step-estimation-procedure">3.2 Three-step estimation procedure&lt;/h3>
&lt;p>The &lt;code>classifylasso&lt;/code> command works in three steps:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Sort countries into groups.&lt;/strong> For each candidate number of groups $K$, the algorithm iteratively updates group centers and reassigns countries until convergence. Starting values come from unit-by-unit regressions.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Re-estimate within groups (postlasso).&lt;/strong> The LASSO penalty biases the coefficient estimates. So after sorting, we discard the penalized estimates and re-run plain OLS within each group. Think of it like a talent show: LASSO is the audition that selects who is in which group, but the final performance (the coefficient estimates) is unpenalized. This postlasso step gives us valid standard errors and confidence intervals.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Pick the best $K$ (information criterion).&lt;/strong> How many groups are there? The command tests $K = 1, 2, \ldots, K_{\max}$ and picks the $K$ that minimizes an information criterion. The IC acts like a &lt;strong>referee&lt;/strong> balancing two concerns: fit (more groups fit better) and complexity (more groups risk overfitting). It works like AIC or BIC. The tuning parameter $\rho_{NT} = c_\rho (NT)^{-1/2}$ controls how harshly the referee penalizes extra groups.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h3 id="33-dynamic-panels-and-nickell-bias">3.3 Dynamic panels and Nickell bias&lt;/h3>
&lt;p>What if your model includes a lagged dependent variable, like $y_{i,t-1}$? This creates a problem called &lt;strong>Nickell bias&lt;/strong>. When you demean the data to remove fixed effects, the demeaned lagged outcome becomes correlated with the demeaned error. The result: biased coefficients.&lt;/p>
&lt;p>The &lt;code>classifylasso&lt;/code> command offers a &lt;code>dynamic&lt;/code> option to fix this. It uses the &lt;strong>half-panel jackknife&lt;/strong> (Dhaene and Jochmans 2015). The idea is simple: split the time series in half. Estimate the model on each half. Combine the two estimates in a way that cancels the bias. Problem solved.&lt;/p>
&lt;p>Now that we understand the method, let&amp;rsquo;s apply it to real data.&lt;/p>
&lt;hr>
&lt;h2 id="4-data-exploration-savings">4. Data Exploration: Savings&lt;/h2>
&lt;h3 id="41-load-and-describe-the-data">4.1 Load and describe the data&lt;/h3>
&lt;p>Our first application uses a panel of 56 countries over 15 years, from Su, Shi, and Phillips (2016). The outcome is the savings-to-GDP ratio. The regressors are lagged savings, CPI inflation, real interest rates, and GDP growth.&lt;/p>
&lt;pre>&lt;code class="language-stata">use &amp;quot;https://github.com/cmg777/starter-academic-v501/raw/master/content/post/stata_panel_lasso_cluster/refMaterials/saving.dta&amp;quot;, clear
xtset code year
summarize savings lagsavings cpi interest gdp
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> Variable | Obs Mean Std. dev. Min Max
-------------+---------------------------------------------------------
savings | 840 -2.87e-08 1.000596 -2.495871 2.893858
lagsavings | 840 5.81e-08 1.000596 -2.832278 2.91508
cpi | 840 3.56e-09 1.000596 -2.773791 3.548945
interest | 840 -7.17e-09 1.000596 -3.600348 3.277582
gdp | 840 1.06e-08 1.000596 -3.554419 2.461317
&lt;/code>&lt;/pre>
&lt;p>The panel is strongly balanced: 56 countries $\times$ 15 years = 840 observations. All variables are standardized to mean zero and standard deviation one. This means coefficients are in standard-deviation units. A coefficient of 0.18 means &amp;ldquo;a one-SD increase in CPI is associated with a 0.18-SD change in savings.&amp;rdquo; The balanced structure matters: C-LASSO requires all countries to be observed in all time periods.&lt;/p>
&lt;h3 id="42-visualize-cross-country-heterogeneity">4.2 Visualize cross-country heterogeneity&lt;/h3>
&lt;p>Before running any regressions, it helps to visualize how savings trajectories differ across countries. The &lt;code>xtline&lt;/code> command overlays all 56 country lines on a single plot:&lt;/p>
&lt;pre>&lt;code class="language-stata">xtline savings, overlay ///
title(&amp;quot;Savings-to-GDP Ratio Across 56 Countries&amp;quot;, size(medium)) ///
subtitle(&amp;quot;Each line represents one country&amp;quot;, size(small)) ///
ytitle(&amp;quot;Savings / GDP&amp;quot;) xtitle(&amp;quot;Year&amp;quot;) legend(off)
graph export &amp;quot;stata_panel_lasso_cluster_fig1_savings_scatter.png&amp;quot;, replace width(2400)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_panel_lasso_cluster_fig1_savings_scatter.png" alt="Spaghetti plot of savings-to-GDP ratio across 56 countries, showing wide dispersion in trajectories.">
&lt;em>Figure 1: Savings-to-GDP ratio across 56 countries (1995&amp;ndash;2010). Each line represents one country, revealing substantial heterogeneity in savings dynamics.&lt;/em>&lt;/p>
&lt;p>The spaghetti plot tells a clear story: countries do not move in lockstep. Some maintain positive savings ratios throughout. Others swing below zero. The lines diverge, cross, and cluster &amp;mdash; suggesting that different countries follow fundamentally different savings dynamics. This is exactly the kind of heterogeneity that C-LASSO is designed to detect. Perhaps subsets of countries share similar responses, even if the full panel does not.&lt;/p>
&lt;p>But first, let&amp;rsquo;s see what the standard models say.&lt;/p>
&lt;hr>
&lt;h2 id="5-baseline-pooled-and-fixed-effects-regressions">5. Baseline: Pooled and Fixed Effects Regressions&lt;/h2>
&lt;p>Before applying C-LASSO, we establish a benchmark by estimating the standard pooled OLS and fixed-effects models. These models assume that all 56 countries share the same slope coefficients.&lt;/p>
&lt;pre>&lt;code class="language-stata">* Pooled OLS
regress savings lagsavings cpi interest gdp
* Standard Fixed Effects
xtreg savings lagsavings cpi interest gdp, fe
* Robust Fixed Effects (reghdfe)
reghdfe savings lagsavings cpi interest gdp, absorb(code) vce(robust)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> Pooled OLS FE (robust)
lagsavings 0.6051 0.6051
cpi 0.0301 0.0301
interest 0.0059 0.0059
gdp 0.1882 0.1882
&lt;/code>&lt;/pre>
&lt;p>The pooled OLS and fixed-effects estimates are virtually identical. R-squared is 0.438. Lagged savings dominates (coefficient 0.605, $p &amp;lt; 0.001$). GDP growth matters too (0.188, $p &amp;lt; 0.001$).&lt;/p>
&lt;p>Now look at the two remaining variables. CPI: 0.030. Interest rate: 0.006. Both statistically insignificant. A textbook conclusion would be: &amp;ldquo;Inflation and interest rates do not affect savings.&amp;rdquo;&lt;/p>
&lt;p>But what if the average is lying? Imagine a city where half the neighborhoods warm up by 5 degrees and the other half cool down by 5 degrees. The citywide average temperature change is zero. A meteorologist reporting &amp;ldquo;no change&amp;rdquo; would be wrong &amp;mdash; there &lt;em>are&lt;/em> changes, just in opposite directions. This is exactly what we will discover with C-LASSO.&lt;/p>
&lt;hr>
&lt;h2 id="6-classifier-lasso-savings-static-model">6. Classifier-LASSO: Savings, Static Model&lt;/h2>
&lt;h3 id="61-estimation">6.1 Estimation&lt;/h3>
&lt;p>We start with the simplest C-LASSO specification: a static model without the lagged dependent variable. This lets us focus on the core mechanics before adding complexity.&lt;/p>
&lt;pre>&lt;code class="language-stata">classifylasso savings cpi interest gdp, grouplist(1/5) tolerance(1e-4)
&lt;/code>&lt;/pre>
&lt;p>The command searches over $K = 1$ to $K = 5$ groups and reports the information criterion (IC) for each:&lt;/p>
&lt;pre>&lt;code class="language-text">Estimation 1: Group Number = 1; IC = 0.054
Estimation 2: Group Number = 2; IC = -0.028 ← minimum
Estimation 3: Group Number = 3; IC = 0.059
Estimation 4: Group Number = 4; IC = 0.131
Estimation 5: Group Number = 5; IC = 0.213
* Selected Group Number: 2
&lt;/code>&lt;/pre>
&lt;p>The IC is minimized at $K = 2$, with values rising monotonically from $K = 3$ onward. This clear U-shape provides strong evidence for exactly two latent groups in the data.&lt;/p>
&lt;h3 id="62-group-specific-coefficients">6.2 Group-specific coefficients&lt;/h3>
&lt;pre>&lt;code class="language-stata">classoselect, postselection
predict gid_static, gid
tabulate gid_static
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Group 1 (34 countries, 510 obs): Within R-sq. = 0.2019
cpi | -0.1813 (z = -4.29, p &amp;lt; 0.001)
interest | -0.1966 (z = -4.64, p &amp;lt; 0.001)
gdp | 0.3346 (z = 7.98, p &amp;lt; 0.001)
Group 2 (22 countries, 330 obs): Within R-sq. = 0.2369
cpi | 0.4781 (z = 9.10, p &amp;lt; 0.001)
interest | 0.2631 (z = 5.01, p &amp;lt; 0.001)
gdp | 0.1117 (z = 2.23, p = 0.026)
&lt;/code>&lt;/pre>
&lt;p>The results are striking. Look at CPI.&lt;/p>
&lt;p>In &lt;strong>Group 1&lt;/strong> (34 countries), higher inflation &lt;em>reduces&lt;/em> savings: coefficient $-0.181$ ($p &amp;lt; 0.001$). In &lt;strong>Group 2&lt;/strong> (22 countries), higher inflation &lt;em>increases&lt;/em> savings: coefficient $+0.478$ ($p &amp;lt; 0.001$). The sign flips completely.&lt;/p>
&lt;p>The same reversal appears for the interest rate: $-0.197$ in Group 1 versus $+0.263$ in Group 2.&lt;/p>
&lt;p>Now the pooled CPI coefficient of $+0.030$ makes sense. It was averaging $-0.181$ and $+0.478$ &amp;mdash; a negative and a positive effect canceling each other out. The &amp;ldquo;insignificant&amp;rdquo; result was not evidence of no effect. It was evidence of &lt;strong>two opposing effects&lt;/strong> hidden inside the average.&lt;/p>
&lt;p>Why the reversal? In Group 1, higher inflation erodes the real value of savings, discouraging people from saving. In Group 2, higher inflation may trigger &lt;strong>precautionary savings&lt;/strong> &amp;mdash; households save &lt;em>more&lt;/em> precisely because the economic environment feels uncertain. Same macroeconomic shock, opposite behavioral response.&lt;/p>
&lt;h3 id="63-group-selection-plot">6.3 Group selection plot&lt;/h3>
&lt;pre>&lt;code class="language-stata">classogroup
graph export &amp;quot;stata_panel_lasso_cluster_fig2_group_selection_static.png&amp;quot;, replace width(2400)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_panel_lasso_cluster_fig2_group_selection_static.png" alt="Information criterion and iteration count by number of groups for the static savings model. IC is minimized at K=2.">
&lt;em>Figure 2: Group selection for the static savings model. The information criterion (left axis) is minimized at K=2, with a clear U-shape from K=3 onward.&lt;/em>&lt;/p>
&lt;p>The triangle marks the IC minimum at $K = 2$. The left axis shows IC values; the right axis shows iterations to convergence. Notice: $K = 2$ converged quickly (about 3 iterations). Models with $K \geq 3$ hit the maximum 20 iterations. When the algorithm struggles to converge, it is a sign of overparameterization &amp;mdash; too many groups for the data to support.&lt;/p>
&lt;p>So far, we have found two groups with a static model. But we omitted lagged savings. Let&amp;rsquo;s add it back.&lt;/p>
&lt;hr>
&lt;h2 id="7-classifier-lasso-savings-dynamic-model">7. Classifier-LASSO: Savings, Dynamic Model&lt;/h2>
&lt;h3 id="71-adding-the-lagged-dependent-variable">7.1 Adding the lagged dependent variable&lt;/h3>
&lt;p>Savings are highly persistent. The pooled coefficient on &lt;code>lagsavings&lt;/code> was 0.605 &amp;mdash; a country&amp;rsquo;s savings this year strongly predicts its savings next year. Omitting this variable may bias everything else. We now add it back and replicate Su, Shi, and Phillips (2016). The &lt;code>dynamic&lt;/code> option activates the half-panel jackknife to correct Nickell bias.&lt;/p>
&lt;pre>&lt;code class="language-stata">use &amp;quot;https://github.com/cmg777/starter-academic-v501/raw/master/content/post/stata_panel_lasso_cluster/refMaterials/saving.dta&amp;quot;, clear
xtset code year
classifylasso savings lagsavings cpi interest gdp, ///
grouplist(1/5) lambda(1.5485) tolerance(1e-4) dynamic
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">* Selected Group Number: 2
The algorithm takes 9min57s.
Group 1 (31 countries, 465 obs): Within R-sq. = 0.4988
lagsavings | 0.6952 (z = 18.15, p &amp;lt; 0.001)
cpi | -0.1602 (z = -4.09, p &amp;lt; 0.001)
interest | -0.1490 (z = -4.04, p &amp;lt; 0.001)
gdp | 0.2892 (z = 7.62, p &amp;lt; 0.001)
Group 2 (25 countries, 375 obs): Within R-sq. = 0.4372
lagsavings | 0.6939 (z = 19.45, p &amp;lt; 0.001)
cpi | 0.1967 (z = 4.93, p &amp;lt; 0.001)
interest | 0.1225 (z = 2.98, p = 0.003)
gdp | 0.1127 (z = 2.38, p = 0.018)
&lt;/code>&lt;/pre>
&lt;p>Again, C-LASSO selects $K = 2$ groups. The sign reversal on CPI survives: $-0.160$ in Group 1 versus $+0.197$ in Group 2. Same for the interest rate: $-0.149$ versus $+0.123$.&lt;/p>
&lt;p>Here is what is interesting about the &lt;code>lagsavings&lt;/code> coefficient. Both groups show nearly identical persistence: 0.695 in Group 1 and 0.694 in Group 2. Think of it like a speedometer. Both groups of countries cruise at the same speed (savings persistence). But they swerve in opposite directions when they hit a pothole (an inflation or interest rate shock). The heterogeneity is about &lt;em>reactions to shocks&lt;/em>, not about baseline behavior.&lt;/p>
&lt;p>Adding lagged savings also improved the fit. Within R-squared jumped from 0.20&amp;ndash;0.24 (static) to 0.44&amp;ndash;0.50 (dynamic). The lagged variable clearly matters.&lt;/p>
&lt;h3 id="72-coefficient-plots">7.2 Coefficient plots&lt;/h3>
&lt;p>The &lt;code>classocoef&lt;/code> postestimation command visualizes group-specific coefficients with 95% confidence bands:&lt;/p>
&lt;pre>&lt;code class="language-stata">classocoef cpi
graph export &amp;quot;stata_panel_lasso_cluster_fig3_coef_cpi.png&amp;quot;, replace width(2400)
classocoef interest
graph export &amp;quot;stata_panel_lasso_cluster_fig4_coef_interest.png&amp;quot;, replace width(2400)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_panel_lasso_cluster_fig3_coef_cpi.png" alt="CPI coefficient estimates and 95% confidence bands by group, showing a clear sign reversal with non-overlapping confidence intervals.">
&lt;em>Figure 3: Heterogeneous effects of CPI on savings. Group 1 (31 countries) shows a negative effect; Group 2 (25 countries) shows a positive effect. Confidence bands do not overlap.&lt;/em>&lt;/p>
&lt;p>This is the &amp;ldquo;smoking gun&amp;rdquo; figure. The two horizontal lines are the group-specific coefficients. The dashed lines show 95% confidence bands. The bands do not overlap. This is not a marginal difference. It is a robust sign reversal.&lt;/p>
&lt;p>For 31 countries (Group 1), higher inflation reduces savings ($-0.160$, $p &amp;lt; 0.001$). For 25 countries (Group 2), higher inflation increases savings ($+0.197$, $p &amp;lt; 0.001$). A pooled model averages these opposing forces and finds CPI &amp;ldquo;insignificant.&amp;rdquo; That is aggregation bias at work.&lt;/p>
&lt;p>&lt;img src="stata_panel_lasso_cluster_fig4_coef_interest.png" alt="Interest rate coefficient estimates and 95% confidence bands by group, showing the same sign reversal pattern as CPI.">
&lt;em>Figure 4: Heterogeneous effects of the interest rate on savings. The same sign reversal pattern as CPI: negative in Group 1, positive in Group 2.&lt;/em>&lt;/p>
&lt;p>The interest rate tells the same story. Group 1 countries save &lt;em>less&lt;/em> when rates rise ($-0.149$). Group 2 countries save &lt;em>more&lt;/em> ($+0.123$).&lt;/p>
&lt;p>Why? One interpretation: in Group 1 (more developed financial markets), higher returns make consumption more attractive &amp;mdash; the &lt;strong>substitution effect&lt;/strong> dominates. In Group 2 (limited financial access), higher returns make saving more rewarding &amp;mdash; the &lt;strong>income effect&lt;/strong> dominates.&lt;/p>
&lt;p>We have now established that latent groups exist in savings data. The next question: does the same pattern appear in a completely different economic context?&lt;/p>
&lt;hr>
&lt;h2 id="8-democracy-application-does-democracy-cause-growth">8. Democracy Application: Does Democracy Cause Growth?&lt;/h2>
&lt;h3 id="81-the-acemoglu-et-al-2019-question">8.1 The Acemoglu et al. (2019) question&lt;/h3>
&lt;p>&amp;ldquo;Democracy does cause growth.&amp;rdquo; That is the title of a famous 2019 paper by Acemoglu, Naidu, Restrepo, and Robinson in the &lt;em>Journal of Political Economy&lt;/em>. Their evidence: a pooled two-way fixed-effects model with lagged GDP finds a positive, significant effect.&lt;/p>
&lt;p>But we have learned to be skeptical of pooled estimates. Does this average apply to all 98 countries? Or does it mask the same kind of sign reversal we found in savings?&lt;/p>
&lt;h3 id="82-data-exploration">8.2 Data exploration&lt;/h3>
&lt;pre>&lt;code class="language-stata">use &amp;quot;https://github.com/cmg777/starter-academic-v501/raw/master/content/post/stata_panel_lasso_cluster/refMaterials/democracy.dta&amp;quot;, clear
xtset country year
summarize lnPGDP Democracy ly1
tabulate Democracy
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> Variable | Obs Mean Std. dev. Min Max
-------------+---------------------------------------------------------
lnPGDP | 4,018 758.5558 162.9137 405.6728 1094.003
Democracy | 4,018 .5450473 .4980286 0 1
ly1 | 3,920 757.7754 162.6702 405.6728 1094.003
Democracy | Freq. Percent
------------+-----------------------------------
0 | 1,828 45.50
1 | 2,190 54.50
&lt;/code>&lt;/pre>
&lt;p>The panel covers 98 countries from 1970 to 2010 &amp;mdash; 4,018 observations. The binary &lt;code>Democracy&lt;/code> indicator is 1 for democratic country-years and 0 otherwise. About 55% of observations are democratic, reflecting the global wave of democratization. The dependent variable &lt;code>lnPGDP&lt;/code> (log per-capita GDP, scaled) ranges from 406 to 1,094 &amp;mdash; the full spectrum from low-income to high-income countries.&lt;/p>
&lt;h3 id="83-pooled-fixed-effects-benchmark">8.3 Pooled fixed-effects benchmark&lt;/h3>
&lt;pre>&lt;code class="language-stata">reghdfe lnPGDP Democracy ly1, absorb(country year) cluster(country)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">HDFE Linear regression Number of obs = 3,920
R-squared = 0.9991
Within R-sq. = 0.9607
(Std. err. adjusted for 98 clusters in country)
lnPGDP | Coefficient Robust std. err. t P&amp;gt;|t|
Democracy | 1.054992 .369806 2.85 0.005
ly1 | .970495 .0059964 161.85 0.000
&lt;/code>&lt;/pre>
&lt;p>Democracy is associated with a 1.055-unit increase in log per-capita GDP ($p = 0.005$, clustered SE = 0.370). Lagged GDP has a coefficient of 0.970 &amp;mdash; strong persistence. This replicates Acemoglu et al. (2019): on average, democracy promotes growth.&lt;/p>
&lt;p>On average. But we already know what &amp;ldquo;on average&amp;rdquo; can hide. Let&amp;rsquo;s run C-LASSO.&lt;/p>
&lt;h3 id="84-c-lasso-revealing-the-heterogeneity">8.4 C-LASSO: revealing the heterogeneity&lt;/h3>
&lt;pre>&lt;code class="language-stata">classifylasso lnPGDP Democracy ly1, ///
grouplist(1/5) rho(0.2) absorb(country year) ///
cluster(country) dynamic optmaxiter(300)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">* Selected Group Number: 2
The algorithm takes 2h33min41s.
Group 1 (57 countries, 2,280 obs): Within R-sq. = 0.9609
Democracy | 2.151397 (z = 3.94, p &amp;lt; 0.001)
ly1 | 1.032752 (z = 149.97, p &amp;lt; 0.001)
Group 2 (41 countries, 1,640 obs): Within R-sq. = 0.9538
Democracy | -0.935589 (z = -2.69, p = 0.007)
ly1 | 0.979327 (z = 95.73, p &amp;lt; 0.001)
&lt;/code>&lt;/pre>
&lt;p>This is the tutorial&amp;rsquo;s most striking finding.&lt;/p>
&lt;p>The pooled coefficient of $+1.055$ is &lt;strong>not representative of any actual country group&lt;/strong>. It is a weighted average of two fundamentally different effects:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Group 1&lt;/strong> (57 countries): democracy effect = $+2.151$ ($p &amp;lt; 0.001$). More than twice the pooled estimate.&lt;/li>
&lt;li>&lt;strong>Group 2&lt;/strong> (41 countries): democracy effect = $-0.936$ ($p = 0.007$). Negative and significant.&lt;/li>
&lt;/ul>
&lt;p>The coefficient literally changes sign. For 58% of countries, democratic transitions are associated with GDP gains. For the remaining 42%, they are associated with GDP declines. The pooled model sees one number. C-LASSO sees two stories.&lt;/p>
&lt;p>Note: these are conditional associations within the panel model. A causal interpretation requires the same identifying assumptions as Acemoglu et al. (2019).&lt;/p>
&lt;h3 id="85-visualizing-the-democracy-growth-split">8.5 Visualizing the democracy-growth split&lt;/h3>
&lt;pre>&lt;code class="language-stata">classogroup
graph export &amp;quot;stata_panel_lasso_cluster_fig5_democracy_selection.png&amp;quot;, replace width(2400)
classocoef Democracy
graph export &amp;quot;stata_panel_lasso_cluster_fig6_democracy_coef.png&amp;quot;, replace width(2400)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_panel_lasso_cluster_fig5_democracy_selection.png" alt="Information criterion and iteration count for the democracy model. IC is minimized at K=2, though values are close across specifications.">
&lt;em>Figure 5: Group selection for the democracy-growth model. IC is minimized at K=2, though values are close across all K (range 3.267&amp;ndash;3.280).&lt;/em>&lt;/p>
&lt;p>The IC selects $K = 2$. But look closely: the IC values range from 3.267 to 3.280 &amp;mdash; a span of just 0.013. The 2-group structure is optimal but not overwhelmingly so. This is a useful reminder: always check sensitivity to the tuning parameter $\rho$.&lt;/p>
&lt;p>&lt;img src="stata_panel_lasso_cluster_fig6_democracy_coef.png" alt="Democracy coefficient polarization across two groups: Group 1 (57 countries) shows a positive effect around +2.2, Group 2 (41 countries) shows a negative effect around -1.0.">
&lt;em>Figure 6: Heterogeneous effects of democracy on economic growth. Group 1 (57 countries) shows a positive effect (+2.15); Group 2 (41 countries) shows a negative effect (-0.94). The pooled estimate of +1.05 describes neither group.&lt;/em>&lt;/p>
&lt;p>This is the key figure of the tutorial. Each dot is one country&amp;rsquo;s individual coefficient estimate. The horizontal lines show group-specific postlasso estimates with 95% confidence bands.&lt;/p>
&lt;p>The polarization is unmistakable. Group 1 (left cluster): strongly positive. Group 2 (right cluster): negative. Neither group&amp;rsquo;s confidence band crosses zero. Both effects are statistically significant.&lt;/p>
&lt;p>This is not &amp;ldquo;some countries benefit, others see no effect.&amp;rdquo; It is a genuine sign reversal. Democracy is associated with growth in one group and with decline in another.&lt;/p>
&lt;hr>
&lt;h2 id="9-comparison-what-the-pooled-model-misses">9. Comparison: What the Pooled Model Misses&lt;/h2>
&lt;h3 id="91-summary-table">9.1 Summary table&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>Pooled FE&lt;/th>
&lt;th>C-LASSO Group 1&lt;/th>
&lt;th>C-LASSO Group 2&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Democracy coefficient&lt;/strong>&lt;/td>
&lt;td>+1.055&lt;/td>
&lt;td>+2.151&lt;/td>
&lt;td>-0.936&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Standard error&lt;/strong>&lt;/td>
&lt;td>0.370&lt;/td>
&lt;td>0.546&lt;/td>
&lt;td>0.348&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>p-value&lt;/strong>&lt;/td>
&lt;td>0.005&lt;/td>
&lt;td>&amp;lt; 0.001&lt;/td>
&lt;td>0.007&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Lagged GDP&lt;/strong>&lt;/td>
&lt;td>0.970&lt;/td>
&lt;td>1.033&lt;/td>
&lt;td>0.979&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Countries&lt;/strong>&lt;/td>
&lt;td>98&lt;/td>
&lt;td>57&lt;/td>
&lt;td>41&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Observations&lt;/strong>&lt;/td>
&lt;td>3,920&lt;/td>
&lt;td>2,280&lt;/td>
&lt;td>1,640&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="92-simpsons-paradox-in-panel-data">9.2 Simpson&amp;rsquo;s paradox in panel data&lt;/h3>
&lt;p>This is &lt;strong>Simpson&amp;rsquo;s paradox&lt;/strong> &amp;mdash; the phenomenon where a trend that appears in aggregated data reverses when you look at subgroups.&lt;/p>
&lt;p>Here is a concrete analogy. A hospital treats two types of patients: mild cases and severe cases. For mild cases, Treatment A has a higher survival rate. For severe cases, Treatment A also has a higher survival rate. But when you pool all patients together, Treatment B appears better &amp;mdash; because it treats a disproportionate number of mild (easy) cases. The aggregate reverses the subgroup trend.&lt;/p>
&lt;p>The same thing happened here. The pooled democracy estimate of $+1.055$ sits between $+2.151$ and $-0.936$. It describes neither group accurately. A policymaker relying on the pooled result would conclude that democracy universally promotes growth. They would miss that for 41 countries (42% of the sample), the relationship runs in the opposite direction.&lt;/p>
&lt;p>The savings model showed the same pattern. The insignificant pooled CPI coefficient ($+0.030$) masked significant effects of $-0.160$ and $+0.197$. When effects have opposite signs, pooling does not just underestimate the magnitude. It produces a qualitatively wrong conclusion.&lt;/p>
&lt;h3 id="93-robustness-of-the-group-structure">9.3 Robustness of the group structure&lt;/h3>
&lt;p>Across all three C-LASSO specifications &amp;mdash; static savings, dynamic savings, and democracy &amp;mdash; the IC consistently selected $K = 2$ groups. The CPI sign reversal survived the switch from static to dynamic, despite a shift in group composition (34/22 to 31/25). This consistency suggests the latent groups are real structural features of the data, not artifacts of a particular specification.&lt;/p>
&lt;hr>
&lt;h2 id="10-summary-and-takeaways">10. Summary and Takeaways&lt;/h2>
&lt;h3 id="101-what-we-learned">10.1 What we learned&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Pooled estimates can be misleading.&lt;/strong> The insignificant pooled CPI coefficient ($+0.030$) in the savings model masked opposing effects of $-0.160$ and $+0.197$ in two latent groups. The pooled democracy coefficient ($+1.055$) masked a split of $+2.151$ versus $-0.936$.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>C-LASSO finds latent groups.&lt;/strong> In all three specifications, the information criterion selected $K = 2$ groups, revealing binary latent structures in both datasets. The &lt;code>classifylasso&lt;/code> command handles the full workflow: estimation, group selection, and postestimation.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>The &lt;code>dynamic&lt;/code> option corrects Nickell bias.&lt;/strong> When lagged dependent variables are included, the half-panel jackknife bias correction preserves the group structure while improving within-group R-squared (from 0.20&amp;ndash;0.24 in the static model to 0.44&amp;ndash;0.50 in the dynamic model).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Postestimation tools aid interpretation.&lt;/strong> The &lt;code>classogroup&lt;/code> command visualizes the information criterion, &lt;code>classocoef&lt;/code> plots group-specific coefficients with confidence bands, and &lt;code>predict gid&lt;/code> assigns countries to groups.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="102-limitations">10.2 Limitations&lt;/h3>
&lt;p>Three caveats. First, the IC values in the democracy model were very close across $K = 1$ through $K = 5$ (range 3.267&amp;ndash;3.280). The 2-group structure is optimal but not dominant. Second, the datasets use numeric country codes, not names. We cannot easily identify which countries are in which group. Third, C-LASSO is computationally intensive. The democracy model took over 2.5 hours. Plan accordingly.&lt;/p>
&lt;h3 id="103-exercises">10.3 Exercises&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Sensitivity analysis.&lt;/strong> Re-run the democracy model with &lt;code>rho(0.5)&lt;/code> and &lt;code>rho(1.0)&lt;/code> instead of &lt;code>rho(0.2)&lt;/code>. Does the selected number of groups change? How sensitive are the group assignments to this tuning parameter?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Extended lag structure.&lt;/strong> Following the reference &lt;code>empirical.do&lt;/code>, estimate the democracy model with 2, 3, and 4 lags of GDP (&lt;code>ly1-ly2&lt;/code>, &lt;code>ly1-ly3&lt;/code>, &lt;code>ly1-ly4&lt;/code>). Do the group-specific democracy coefficients remain stable?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Static vs dynamic comparison.&lt;/strong> Run &lt;code>classifylasso savings cpi interest gdp&lt;/code> (without &lt;code>dynamic&lt;/code>) on the savings data and compare group assignments with the dynamic model using &lt;code>tabulate gid_static gid_dynamic&lt;/code>. How many countries switch groups?&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="references">References&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>Su, L., Shi, Z., and Phillips, P. C. B. (2016). &lt;a href="https://doi.org/10.3982/ECTA12560" target="_blank" rel="noopener">Identifying latent structures in panel data&lt;/a>. &lt;em>Econometrica&lt;/em>, 84(6), 2215&amp;ndash;2264.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Huang, W., Wang, Y., and Zhou, L. (2024). &lt;a href="https://doi.org/10.1177/1536867X241233664" target="_blank" rel="noopener">Identify latent group structures in panel data: The classifylasso command&lt;/a>. &lt;em>Stata Journal&lt;/em>, 24(1), 173&amp;ndash;203.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Acemoglu, D., Naidu, S., Restrepo, P., and Robinson, J. A. (2019). &lt;a href="https://doi.org/10.1086/700936" target="_blank" rel="noopener">Democracy does cause growth&lt;/a>. &lt;em>Journal of Political Economy&lt;/em>, 127(1), 47&amp;ndash;100.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Dhaene, G. and Jochmans, K. (2015). &lt;a href="https://doi.org/10.1093/restud/rdv007" target="_blank" rel="noopener">Split-panel jackknife estimation of fixed-effect models&lt;/a>. &lt;em>Review of Economic Studies&lt;/em>, 82(3), 991&amp;ndash;1030.&lt;/p>
&lt;/li>
&lt;/ol></description></item><item><title>Dynamic Panel BMA: Which Factors Truly Drive Economic Growth?</title><link>https://carlos-mendez.org/post/r_dynamic_bma/</link><pubDate>Sun, 29 Mar 2026 00:00:00 +0000</pubDate><guid>https://carlos-mendez.org/post/r_dynamic_bma/</guid><description>&lt;h2 id="1-overview">1. Overview&lt;/h2>
&lt;p>Imagine you are advising a government on how to accelerate long-run economic growth. Your team has compiled a panel dataset covering 73 countries across four decades, with nine candidate drivers: investment, education, population growth, trade openness, government spending, life expectancy, democracy, investment prices, and population size. The natural question is: &lt;strong>which of these factors truly drive economic growth &amp;mdash; and can we trust our answers when today&amp;rsquo;s GDP might itself be shaped by those same factors?&lt;/strong>&lt;/p>
&lt;p>What is BMA? Imagine trying to predict salaries using education, experience, age, and industry. You could build one model with all four variables, or drop industry, or use only experience and education. With just 4 candidates, there are $2^4 = 16$ possible models. Which is correct? &lt;strong>Bayesian Model Averaging (BMA)&lt;/strong> does not pick one &amp;mdash; it averages predictions from all 16, giving more weight to models that fit the data well. This avoids betting everything on one specification that might be wrong.&lt;/p>
&lt;p>This last concern is &lt;em>reverse causality&lt;/em> &amp;mdash; the possibility that GDP growth causes higher investment rather than the other way around. Cross-sectional BMA handles model uncertainty this way, but it assumes regressors are strictly exogenous. When that assumption fails, BMA can confidently point to the wrong variables.&lt;/p>
&lt;p>This tutorial introduces the &lt;a href="https://cran.r-project.org/web/packages/bdsm/index.html" target="_blank" rel="noopener">Bayesian Dynamic Systems Modeling&lt;/a> R package &amp;mdash; which extends BMA to dynamic panel data with weakly exogenous regressors. Built on the methodology of Moral-Benito (2012, 2013, 2016), it simultaneously addresses model uncertainty and reverse causality by incorporating a lagged dependent variable, entity fixed effects, and time fixed effects into the BMA framework.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Companion tutorial.&lt;/strong> For a cross-sectional perspective using BMA, LASSO, and WALS on synthetic data, see the &lt;a href="https://carlos-mendez.org/post/r_bma_lasso_wals/">R tutorial on variable selection&lt;/a>. The current tutorial builds on those foundations by moving from cross-sectional to panel data and from strict to weak exogeneity.&lt;/p>
&lt;/blockquote>
&lt;p>&lt;strong>Learning objectives:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Understand why cross-sectional BMA can be misleading when regressors are endogenous, and how dynamic panel BMA addresses this&lt;/li>
&lt;li>Prepare panel data for the Bayesian DSM package using &lt;code>join_lagged_col()&lt;/code> and &lt;code>feature_standardization()&lt;/code>&lt;/li>
&lt;li>Run Bayesian Model Averaging with &lt;code>bma()&lt;/code> and interpret Posterior Inclusion Probabilities (PIPs &amp;mdash; how often a variable appears in the best-fitting models), posterior means, and model probabilities&lt;/li>
&lt;li>Assess the sensitivity of results to prior specification by varying the expected model size (how many variables the prior expects to matter) and applying dilution priors (which adjust for correlated variables)&lt;/li>
&lt;li>Analyze jointness (which variables tend to appear in models together) to discover which growth determinants are complements versus substitutes&lt;/li>
&lt;/ul>
&lt;p>The package also includes a smaller 3-regressor example (&lt;code>small_model_space&lt;/code>) for practice &amp;mdash; see the companion R script for details.&lt;/p>
&lt;p>&lt;strong>Data Prep&lt;/strong> (lag DV, demean, standardize) &lt;strong>→ Model Space&lt;/strong> (estimate all 2&lt;sup>K&lt;/sup> models)
&lt;strong>→ BMA&lt;/strong> (PIPs, posterior means) &lt;strong>→ Sensitivity&lt;/strong> (vary priors, EMS, dilution) &lt;strong>→ Jointness&lt;/strong> (complements vs. substitutes) &lt;strong>→ Findings&lt;/strong> (robust growth determinants)&lt;/p>
&lt;h2 id="2-setup">2. Setup&lt;/h2>
&lt;p>We need the Bayesian Dynamic Systems Modeling package for dynamic panel BMA and &lt;code>tidyverse&lt;/code> for data manipulation. The &lt;code>parallel&lt;/code> package (included with base R) enables parallel computing for the model space estimation step.&lt;/p>
&lt;pre>&lt;code class="language-r"># Install bdsm if needed
if (!requireNamespace(&amp;quot;bdsm&amp;quot;, quietly = TRUE)) {
install.packages(&amp;quot;bdsm&amp;quot;)
}
# Load packages
library(bdsm)
library(tidyverse)
library(parallel)
set.seed(42)
&lt;/code>&lt;/pre>
&lt;h2 id="3-why-dynamic-panel-bma">3. Why Dynamic Panel BMA?&lt;/h2>
&lt;h3 id="31-the-endogeneity-problem">3.1 The endogeneity problem&lt;/h3>
&lt;p>Standard BMA assumes that all regressors are &lt;em>strictly exogenous&lt;/em> &amp;mdash; meaning they are determined outside the model and are uncorrelated with the error term at any point in time. In growth economics, this assumption almost never holds.&lt;/p>
&lt;p>Think of it this way: imagine judging a runner&amp;rsquo;s training program by their final race time, but faster runners also &lt;em>chose&lt;/em> better programs. You cannot tell whether the program caused the speed or the speed attracted the program. This is &lt;strong>reverse causality&lt;/strong>, and it contaminates cross-sectional regressions. Countries that grow faster invest more, trade more, urbanize faster, and attract more education spending &amp;mdash; not just the other way around.&lt;/p>
&lt;p>When BMA is applied to cross-sectional data with endogenous regressors, it can confidently assign high inclusion probabilities to variables that appear important only because they are &lt;em>consequences&lt;/em> of growth rather than &lt;em>causes&lt;/em> of it. The model averaging machinery works perfectly &amp;mdash; but the individual models it averages over are biased.&lt;/p>
&lt;p>The solution is to include &lt;em>last period&amp;rsquo;s GDP&lt;/em> as a regressor. By controlling for where a country &lt;em>was&lt;/em>, we isolate which new factors push it forward &amp;mdash; breaking the feedback loop. The next section shows why this dynamic structure arises naturally from economic growth theory.&lt;/p>
&lt;h3 id="32-from-the-solow-model-to-a-dynamic-equation">3.2 From the Solow model to a dynamic equation&lt;/h3>
&lt;p>Why does a dynamic equation &amp;mdash; one with lagged GDP on the right-hand side &amp;mdash; arise naturally in growth economics? The answer comes from the &lt;strong>Solow growth model&lt;/strong> and its convergence prediction. The Solow model predicts that poorer countries should grow faster than richer ones, conditional on their structural characteristics (&lt;strong>beta convergence&lt;/strong>). Through a series of algebraic steps &amp;mdash; defining a persistence parameter, substituting observable country characteristics for the unobserved steady state, and adding fixed effects &amp;mdash; the convergence equation yields the following dynamic panel model:&lt;/p>
&lt;p>$$\ln y_{it} = \alpha \ln y_{i,t-1} + \beta' x_{it} + \eta_i + \zeta_t + v_{it}$$&lt;/p>
&lt;p>This is the &lt;strong>dynamic panel model&lt;/strong> that the Bayesian DSM package estimates. The coefficient $\alpha$ has a direct economic interpretation: it measures the &lt;strong>persistence of GDP&lt;/strong> across periods. A value of $\alpha$ close to 1 means slow convergence &amp;mdash; countries stay near their current income level for a long time. A value close to 0 means fast convergence &amp;mdash; countries quickly reach their steady state. Our BMA results will reveal $\alpha \approx 0.92$, indicating very slow convergence: after a decade, countries have closed only about 8% of the gap between their current GDP and their steady state.&lt;/p>
&lt;p>The key insight is that the lagged dependent variable is not an ad hoc addition &amp;mdash; it arises directly from the Solow model&amp;rsquo;s convergence prediction. Any study of growth determinants that omits lagged GDP is implicitly assuming $\alpha = 0$, which means assuming &lt;em>instantaneous convergence&lt;/em> &amp;mdash; a prediction strongly rejected by the data. For the full step-by-step derivation from the Solow convergence equation, see Appendix B.&lt;/p>
&lt;h3 id="33-weak-exogeneity-and-the-role-of-each-component">3.3 Weak exogeneity and the role of each component&lt;/h3>
&lt;p>Each component of the dynamic panel equation plays a distinct role:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Lagged dependent variable&lt;/strong> ($y_{it-1}$): Think of this as a student&amp;rsquo;s previous exam score &amp;mdash; it captures all the accumulated history that got a country to its current level. After controlling for where a country &lt;em>was&lt;/em>, we can ask: among countries at the same starting point, which factors predict who grows faster?&lt;/li>
&lt;li>&lt;strong>Entity fixed effects&lt;/strong> ($\eta_i$): Like grading on a curve within each classroom &amp;mdash; these absorb time-invariant country traits such as geography, colonial history, and institutional heritage. We compare each country to its own average, not to other countries.&lt;/li>
&lt;li>&lt;strong>Time fixed effects&lt;/strong> ($\zeta_t$): These remove global shocks that affect all countries simultaneously, such as oil crises or the Asian financial crisis.&lt;/li>
&lt;/ul>
&lt;p>To understand this assumption, consider a concrete example. Suppose an oil price shock in 1985 affects both GDP and trade openness simultaneously. Weak exogeneity allows this kind of contemporaneous correlation between regressors and the fixed effects. What it rules out is that the &lt;em>unexplained&lt;/em> part of today&amp;rsquo;s GDP shock &amp;mdash; the idiosyncratic error $v_{it}$ &amp;mdash; directly causes today&amp;rsquo;s investment to change within the same period.&lt;/p>
&lt;p>The key assumption is &lt;strong>weak exogeneity&lt;/strong>: current regressors can be correlated with &lt;em>past&lt;/em> shocks but not with the &lt;em>current&lt;/em> shock $v_{it}$. This is much weaker than strict exogeneity &amp;mdash; it allows past GDP growth to influence current investment (feedback effects) while requiring only that the current unexpected shock to GDP does not simultaneously cause changes in investment. In practical terms, weak exogeneity permits the realistic feedback loops that plague growth regressions while still allowing consistent estimation.&lt;/p>
&lt;h3 id="34-from-cross-sectional-to-dynamic-panel-bma">3.4 From cross-sectional to dynamic panel BMA&lt;/h3>
&lt;p>&lt;strong>Cross-sectional BMA&lt;/strong> uses a single time snapshot, assumes strict exogeneity, includes no lagged dependent variable, and has no fixed effects. &lt;strong>Dynamic panel BMA&lt;/strong> uses multiple time periods, requires only weak exogeneity, includes a lagged dependent variable, and controls for entity and time fixed effects. Both approaches address model uncertainty by averaging across all possible model specifications.&lt;/p>
&lt;p>In the &lt;a href="https://carlos-mendez.org/post/r_bma_lasso_wals/">companion cross-sectional tutorial&lt;/a>, we averaged across 4,096 models of CO&lt;sub>2&lt;/sub> emissions using synthetic data. Here we apply the same BMA principle &amp;mdash; weighting models by how well they fit the data &amp;mdash; but to a panel of 73 countries over four decades, using the methodology that handles the endogeneity that cross-sectional BMA cannot.&lt;/p>
&lt;h2 id="4-the-dataset">4. The Dataset&lt;/h2>
&lt;h3 id="41-loading-the-data">4.1 Loading the data&lt;/h3>
&lt;p>The package includes two versions of the Moral-Benito (2016) economic growth dataset. The &lt;code>economic_growth&lt;/code> version has the lagged dependent variable already merged into the panel structure (with NAs in the initial period), while &lt;code>original_economic_growth&lt;/code> keeps it as a separate column.&lt;/p>
&lt;pre>&lt;code class="language-r">data(&amp;quot;economic_growth&amp;quot;)
data(&amp;quot;original_economic_growth&amp;quot;)
cat(&amp;quot;economic_growth:&amp;quot;, dim(economic_growth), &amp;quot;\n&amp;quot;)
cat(&amp;quot;Countries:&amp;quot;, length(unique(economic_growth$country)), &amp;quot;\n&amp;quot;)
cat(&amp;quot;Years:&amp;quot;, sort(unique(economic_growth$year)), &amp;quot;\n&amp;quot;)
head(economic_growth, 5)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">economic_growth: 365 12
Countries: 73
Years: 1960 1970 1980 1990 2000
# A tibble: 5 x 12
year country gdp ish sed pgrw pop ipr opem gsh lnlex polity
&amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt;
1 1960 1 8.25 NA NA NA NA NA NA NA NA NA
2 1970 1 8.37 0.122 0.139 0.0235 10.9 61.1 1.08 0.191 3.88 0.15
3 1980 1 8.54 0.207 0.141 0.0300 13.9 92.3 1.06 0.203 4.00 0.15
4 1990 1 8.63 0.203 0.28 0.0303 18.9 100. 0.898 0.232 4.10 0.15
5 2000 1 8.66 0.115 0.774 0.0215 25.3 81.2 0.636 0.219 4.21 0.575
&lt;/code>&lt;/pre>
&lt;p>The panel covers 73 countries observed at 10-year intervals from 1960 to 2000, yielding 5 periods per country (365 total rows, including the initial 1960 observation). The 1960 row for each country contains only the initial GDP level &amp;mdash; all regressors are NA because there is no &amp;ldquo;previous decade&amp;rdquo; to compute changes from. The four subsequent decades (1970&amp;ndash;2000) contain the 292 usable observations.&lt;/p>
&lt;h3 id="42-variable-descriptions">4.2 Variable descriptions&lt;/h3>
&lt;p>The dataset contains the dependent variable (log GDP per capita) and 9 candidate growth determinants:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Variable&lt;/th>
&lt;th>Description&lt;/th>
&lt;th style="text-align:center">Expected sign&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>gdp&lt;/code>&lt;/td>
&lt;td>Log real GDP per capita (dependent variable)&lt;/td>
&lt;td style="text-align:center">&amp;mdash;&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ish&lt;/code>&lt;/td>
&lt;td>Investment share of GDP&lt;/td>
&lt;td style="text-align:center">+&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>sed&lt;/code>&lt;/td>
&lt;td>Secondary school enrollment rate&lt;/td>
&lt;td style="text-align:center">+&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>pgrw&lt;/code>&lt;/td>
&lt;td>Population growth rate&lt;/td>
&lt;td style="text-align:center">&amp;ndash;&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>pop&lt;/code>&lt;/td>
&lt;td>Population (millions)&lt;/td>
&lt;td style="text-align:center">?&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ipr&lt;/code>&lt;/td>
&lt;td>Investment price (relative to US)&lt;/td>
&lt;td style="text-align:center">&amp;ndash;&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>opem&lt;/code>&lt;/td>
&lt;td>Trade openness (imports + exports / GDP)&lt;/td>
&lt;td style="text-align:center">+&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>gsh&lt;/code>&lt;/td>
&lt;td>Government consumption share of GDP&lt;/td>
&lt;td style="text-align:center">&amp;ndash;&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>lnlex&lt;/code>&lt;/td>
&lt;td>Log life expectancy at birth&lt;/td>
&lt;td style="text-align:center">+&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>polity&lt;/code>&lt;/td>
&lt;td>Democracy index (0 = autocracy, 1 = democracy)&lt;/td>
&lt;td style="text-align:center">?&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>These variables are standard in the empirical growth literature, following Sala-i-Martin, Doppelhofer, and Miller (2004). Investment share and education are expected to have positive effects on growth, while population growth and government consumption are typically associated with slower growth. The signs for population and democracy are theoretically ambiguous.&lt;/p>
&lt;p>The 292 usable observations span 73 countries over four decades. Log GDP per capita ranges from 6.02 to 10.45, reflecting substantial income inequality &amp;mdash; the richest country is roughly 80 times wealthier than the poorest in per capita terms. Investment share averages 16.9% of GDP but ranges from 1.2% to 65.3%, indicating enormous variation in capital accumulation across countries and decades. Population growth averages 1.9% per decade, with one country experiencing slight population decline (&amp;ndash;0.6%).&lt;/p>
&lt;h2 id="5-data-preparation">5. Data Preparation&lt;/h2>
&lt;p>The Bayesian DSM package requires two data preprocessing steps before estimation: standardization (scaling) and demeaning (removing entity and time fixed effects). These steps ensure numerical stability and allow the model to focus on within-country, within-period variation.&lt;/p>
&lt;h3 id="51-understanding-the-data-structure">5.1 Understanding the data structure&lt;/h3>
&lt;p>If your data has the lagged dependent variable as a separate column (like &lt;code>original_economic_growth&lt;/code>), you first need to merge it into the panel structure using &lt;a href="https://cran.r-project.org/web/packages/bdsm/vignettes/bdsm_vignette.Rnw" target="_blank" rel="noopener">&lt;code>join_lagged_col()&lt;/code>&lt;/a>. This function creates the initial period row with NAs:&lt;/p>
&lt;pre>&lt;code class="language-r"># Demonstration: converting original format to package format
eg_joined &amp;lt;- join_lagged_col(
df = original_economic_growth,
col = gdp,
col_lagged = lag_gdp,
timestamp_col = year,
entity_col = country,
timestep = 10 # 10-year intervals
)
cat(&amp;quot;Result:&amp;quot;, dim(eg_joined), &amp;quot;\n&amp;quot;)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Result: 365 12
&lt;/code>&lt;/pre>
&lt;p>The &lt;code>economic_growth&lt;/code> dataset already has this structure, so we can use it directly.&lt;/p>
&lt;h3 id="52-standardization-and-demeaning">5.2 Standardization and demeaning&lt;/h3>
&lt;p>Data preparation involves two calls to &lt;a href="https://cran.r-project.org/web/packages/bdsm/vignettes/bdsm_vignette.Rnw" target="_blank" rel="noopener">&lt;code>feature_standardization()&lt;/code>&lt;/a>. The first call &lt;em>standardizes&lt;/em> all regressors to have mean zero and unit variance &amp;mdash; this puts all variables on the same scale so that the BMA coefficients are directly comparable. The second call &lt;em>demeans&lt;/em> by time period to remove time fixed effects.&lt;/p>
&lt;p>Think of demeaning by time as subtracting the global average for each decade. If every country&amp;rsquo;s GDP grew in the 1990s due to the tech boom, demeaning removes that common trend. What remains is each country&amp;rsquo;s deviation from the global pattern &amp;mdash; the variation that country-specific factors must explain.&lt;/p>
&lt;pre>&lt;code class="language-r"># Step 1: Standardize all regressors (mean=0, sd=1)
# Makes variables comparable: GDP and population are on vastly different scales
data_std &amp;lt;- feature_standardization(
df = economic_growth,
excluded_cols = c(country, year, gdp)
)
# Step 2: Demean by time period (remove time fixed effects)
# Subtracts each decade's global average, isolating country-specific variation
data_prepared &amp;lt;- feature_standardization(
df = data_std,
group_by_col = year,
excluded_cols = country,
scale = FALSE
)
head(data_prepared, 5)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"># A tibble: 5 x 12
year country gdp ish sed pgrw pop ipr opem gsh lnlex polity
&amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt; &amp;lt;dbl&amp;gt;
1 1960 1 0.292 NA NA NA NA NA NA NA NA NA
2 1970 1 0.121 -0.493 -0.534 0.163 -0.151 -0.271 1.13 0.0496 -0.549 -0.578
3 1980 1 0.0573 0.241 -0.697 0.942 -0.181 0.0635 1.07 -0.0226 0.0167 -0.578
4 1990 1 0.0724 0.456 -0.932 1.09 -0.203 0.208 0.724 0.101 -0.0655 -0.578
5 2000 1 -0.0823 -0.505 -0.778 0.465 -0.218 -0.0620 -0.120 0.120 -0.107 0.112
&lt;/code>&lt;/pre>
&lt;p>After preparation, all regressor values are centered around zero. Country 1&amp;rsquo;s investment share (&lt;code>ish&lt;/code>) was 0.49 standard deviations below the global average in 1970 but 0.46 standard deviations above average in 1990, showing meaningful within-country variation over time. The GDP column retains its original scale because it is the dependent variable.&lt;/p>
&lt;h2 id="6-estimating-the-full-model-space">6. Estimating the Full Model Space&lt;/h2>
&lt;p>With 9 candidate regressors, there are $2^9 = 512$ possible regression models. The package estimates every single one via numerical optimization of the &lt;em>marginal likelihood&lt;/em> &amp;mdash; the probability of observing the data given a particular model, after integrating out all parameter uncertainty. Think of this as a cooking competition with 512 recipes &amp;mdash; each uses a different combination of 9 ingredients, and the marginal likelihood scores each recipe by balancing flavor (fit) against unnecessary complexity (overfitting).&lt;/p>
&lt;p>To be concrete: model 1 might include only investment and education. Model 2 adds trade openness. Model 3 uses education and democracy but drops investment. Each of the 512 combinations gets its own likelihood estimated separately, and BMA weights them by how well they fit the data.&lt;/p>
&lt;p>The &lt;a href="https://cran.r-project.org/web/packages/bdsm/vignettes/bdsm_vignette.Rnw" target="_blank" rel="noopener">&lt;code>optim_model_space()&lt;/code>&lt;/a> function handles this computation. For the full 9-regressor case, this is the most computationally intensive step &amp;mdash; it can take several minutes depending on the machine. The package helpfully includes a precomputed &lt;code>full_model_space&lt;/code> object so we can skip the wait:&lt;/p>
&lt;pre>&lt;code class="language-r"># Load precomputed model space (or compute from scratch)
data(&amp;quot;full_model_space&amp;quot;)
# To compute from scratch (takes several minutes):
# full_model_space &amp;lt;- optim_model_space(
# df = data_prepared,
# dep_var_col = gdp,
# timestamp_col = year,
# entity_col = country,
# init_value = 0.5
# )
cat(&amp;quot;Parameters matrix:&amp;quot;, dim(full_model_space$params), &amp;quot;\n&amp;quot;)
cat(&amp;quot;Statistics matrix:&amp;quot;, dim(full_model_space$stats), &amp;quot;\n&amp;quot;)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Parameters matrix: 106 512
Statistics matrix: 22 512
&lt;/code>&lt;/pre>
&lt;p>The result is a list with two elements. The &lt;code>$params&lt;/code> matrix contains 106 estimated parameters for each of the 512 models &amp;mdash; these include the structural parameters ($\alpha$, $\beta$), reduced-form parameters, and variance components. The &lt;code>$stats&lt;/code> matrix stores 22 statistics per model, including the log-likelihood, BIC, regular standard errors, and robust (heteroskedasticity-consistent) standard errors.&lt;/p>
&lt;p>Why use marginal likelihood instead of R-squared? Unlike R-squared, which always improves when you add variables, the marginal likelihood penalizes complexity. It accounts for the fact that more parameters make it easier to fit noise. A model with 9 regressors that barely improves fit over a 5-regressor model will receive a &lt;em>lower&lt;/em> marginal likelihood score &amp;mdash; the extra parameters were not worth the complexity cost.&lt;/p>
&lt;p>Before jumping into BMA, let us first establish a benchmark using a standard regression approach &amp;mdash; this will help us appreciate what BMA adds.&lt;/p>
&lt;h2 id="7-benchmark-kitchen-sink-fixed-effects">7. Benchmark: Kitchen-Sink Fixed Effects&lt;/h2>
&lt;p>Before running BMA, it is useful to establish a benchmark. What happens if we simply throw all 9 regressors into a single fixed effects regression? This &amp;ldquo;kitchen-sink&amp;rdquo; approach is the default in applied work &amp;mdash; but it commits to one model specification and ignores the uncertainty about which variables belong.&lt;/p>
&lt;pre>&lt;code class="language-r"># Kitchen-sink FE regression with all 9 regressors
fe_full &amp;lt;- lm(gdp ~ lag_gdp + ish + sed + pgrw + pop + ipr +
opem + gsh + lnlex + polity +
factor(country) + factor(year),
data = original_economic_growth)
summary(fe_full)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">FE regression coefficients:
Estimate Std. Error t value Pr(&amp;gt;|t|)
lag_gdp 0.6188 0.0501 12.3521 0.0000
ish 0.4646 0.2331 1.9934 0.0475
sed 0.0162 0.0337 0.4798 0.6319
pgrw -2.3352 2.1409 -1.0907 0.2767
pop 0.0016 0.0004 4.5092 0.0000
ipr -0.0003 0.0003 -1.0817 0.2806
opem 0.1199 0.0379 3.1652 0.0018
gsh -0.7448 0.2700 -2.7585 0.0063
lnlex 0.1153 0.2440 0.4727 0.6369
polity -0.1656 0.0570 -2.9065 0.0041
Significant at 5%: lag_gdp, ish, pop, opem, gsh, polity
R-squared: 0.988
N observations: 292
&lt;/code>&lt;/pre>
&lt;p>The kitchen-sink model finds 6 of 10 variables significant at the 5% level: lagged GDP, investment share, population, trade openness, government share, and democracy. Education, population growth, investment price, and life expectancy are insignificant. But this result depends entirely on this particular specification &amp;mdash; drop one variable or add another, and the significance pattern may change. This is the model uncertainty problem that BMA is designed to solve.&lt;/p>
&lt;p>The lagged GDP coefficient of 0.619 is notably lower than the BMA posterior mean (0.919), suggesting that the kitchen-sink model&amp;rsquo;s coefficient estimates are pulled by multicollinearity among the 9 regressors. BMA handles this by averaging over specifications that include different subsets.&lt;/p>
&lt;p>Notice how the FE model forces a binary judgment: education is &amp;lsquo;insignificant&amp;rsquo; (p = 0.63) and trade is &amp;lsquo;significant&amp;rsquo; (p = 0.002). BMA replaces this all-or-nothing verdict with a nuanced probability scale: education has PIP = 0.72 (moderate evidence) and trade has PIP = 0.77 (positive evidence). The difference between &amp;lsquo;insignificant&amp;rsquo; and &amp;lsquo;moderate evidence&amp;rsquo; matters for policy &amp;mdash; a policymaker who ignores education entirely because of a p-value threshold may be discarding useful information.&lt;/p>
&lt;p>The kitchen-sink model commits to one specification and produces one set of p-values. But we saw that which variables look &amp;lsquo;significant&amp;rsquo; depends entirely on which others are in the model. Drop one variable, and the significance pattern reshuffles. BMA solves this by never committing to a single specification &amp;mdash; it averages over all 512, letting the data decide which matter most.&lt;/p>
&lt;h2 id="8-bayesian-model-averaging">8. Bayesian Model Averaging&lt;/h2>
&lt;h3 id="81-running-bma">8.1 Running BMA&lt;/h3>
&lt;p>Now we can perform Bayesian Model Averaging across all 512 models. The &lt;a href="https://cran.r-project.org/web/packages/bdsm/vignettes/bdsm_vignette.Rnw" target="_blank" rel="noopener">&lt;code>bma()&lt;/code>&lt;/a> function takes the precomputed model space and the prepared data, weights each model by its posterior probability, and computes weighted averages of the coefficients:&lt;/p>
&lt;p>&lt;em>Focus on two columns: &lt;strong>PIP&lt;/strong> (how important is this variable?) and &lt;strong>%(+)&lt;/strong> (is its effect consistently positive or negative?).&lt;/em>&lt;/p>
&lt;pre>&lt;code class="language-r">bma_results &amp;lt;- bma(full_model_space, df = data_prepared, round = 3)
# Binomial prior results
print(bma_results[[1]])
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> PIP PM PSD PSDR PMcon PSDcon PSDRcon %(+)
gdp_lag NA 0.919 0.077 0.109 0.919 0.077 0.109 100.000
ish 0.773 0.063 0.045 0.062 0.082 0.034 0.059 100.000
sed 0.717 0.030 0.057 0.074 0.042 0.064 0.084 69.922
pgrw 0.714 0.018 0.030 0.052 0.025 0.033 0.060 99.609
pop 0.990 0.119 0.065 0.082 0.121 0.064 0.081 100.000
ipr 0.656 -0.034 0.033 0.044 -0.051 0.027 0.046 0.000
opem 0.766 0.034 0.030 0.033 0.044 0.026 0.031 100.000
gsh 0.751 -0.015 0.041 0.091 -0.020 0.046 0.104 30.859
lnlex 0.864 0.088 0.075 0.098 0.102 0.071 0.099 100.000
polity 0.678 -0.057 0.046 0.053 -0.084 0.030 0.044 0.000
&lt;/code>&lt;/pre>
&lt;p>The binomial prior results reveal a clear hierarchy among the 9 candidate regressors. Population size (&lt;code>pop&lt;/code>) dominates with PIP = 0.990 &amp;mdash; appearing in virtually every high-quality model &amp;mdash; followed by life expectancy (&lt;code>lnlex&lt;/code>) at 0.864 and investment share (&lt;code>ish&lt;/code>) at 0.773. At the other end, investment price (&lt;code>ipr&lt;/code>) at 0.656 and democracy (&lt;code>polity&lt;/code>) at 0.678 show the weakest evidence, though even these exceed 0.5. The lagged GDP coefficient of 0.919 confirms strong persistence: a country&amp;rsquo;s current GDP is heavily determined by its past GDP.&lt;/p>
&lt;h3 id="82-understanding-the-bma-statistics">8.2 Understanding the BMA statistics&lt;/h3>
&lt;p>Each column in the BMA output captures a different aspect of the evidence:&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Beginner tip:&lt;/strong> For a first reading, focus on three columns: &lt;strong>PIP&lt;/strong> (does this variable matter?), &lt;strong>PM&lt;/strong> (what is its average effect?), and &lt;strong>%(+)&lt;/strong> (is the effect consistently positive or negative?). The remaining columns (PSDR, PMcon, PSDcon, PSDRcon) are useful for advanced robustness checks but can be skipped on a first pass.&lt;/p>
&lt;/blockquote>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Statistic&lt;/th>
&lt;th>Full name&lt;/th>
&lt;th>Interpretation&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>PIP&lt;/strong>&lt;/td>
&lt;td>Posterior Inclusion Probability&lt;/td>
&lt;td>Fraction of posterior probability mass in models that include this variable. Think of it as a &lt;strong>batting average&lt;/strong>: PIP = 0.99 means the variable appeared in 99% of high-scoring models&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>PM&lt;/strong>&lt;/td>
&lt;td>Posterior Mean&lt;/td>
&lt;td>Weighted average of the coefficient across all models (including zeros from models that exclude the variable)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>PSD&lt;/strong>&lt;/td>
&lt;td>Posterior Standard Deviation&lt;/td>
&lt;td>Uncertainty around PM, incorporating both within-model and across-model variation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>PSDR&lt;/strong>&lt;/td>
&lt;td>Posterior SD Ratio&lt;/td>
&lt;td>PSD divided by the conditional PM &amp;mdash; a robustness measure&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>PMcon&lt;/strong>&lt;/td>
&lt;td>Conditional Posterior Mean&lt;/td>
&lt;td>Average coefficient only across models that &lt;em>include&lt;/em> the variable&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>PSDcon&lt;/strong>&lt;/td>
&lt;td>Conditional PSD&lt;/td>
&lt;td>Uncertainty conditional on inclusion&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>%(+)&lt;/strong>&lt;/td>
&lt;td>Positive sign share&lt;/td>
&lt;td>Percentage of models where the coefficient is positive. Values near 0% or 100% indicate stable sign&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The central quantity driving all these statistics is the &lt;strong>posterior model probability&lt;/strong> (PMP). Each model $M_j$ receives a weight proportional to its marginal likelihood times its prior probability:&lt;/p>
&lt;p>$$\mathbb{P}(M_j | \text{data}) = \frac{\exp(-\frac{1}{2} BIC_j) \cdot \mathbb{P}(M_j)}{\sum_{i=1}^{2^K} \exp(-\frac{1}{2} BIC_i) \cdot \mathbb{P}(M_i)}$$&lt;/p>
&lt;p>In words, this equation says that each model&amp;rsquo;s posterior probability is its prior probability times a data-fit term (approximated by the BIC), divided by the sum across all $2^K$ models to ensure the probabilities add to 1. Models that fit the data well without too many parameters receive higher posterior probability. The PIP for a variable is then the sum of PMPs across all models that include it.&lt;/p>
&lt;p>To make this concrete: if model A has BIC = &amp;ndash;800 and model B has BIC = &amp;ndash;790, model A fits the data better. After exponentiating and normalizing, model A might receive 73% of the posterior probability while model B gets 27%. The PIP of a variable included only in model A would then be at least 0.73.&lt;/p>
&lt;p>The &lt;strong>PSDR&lt;/strong> (or equivalently |PM/PSD|) is a key robustness criterion. Raftery (1995) considers a variable &lt;em>robust&lt;/em> when |PM/PSD| &amp;gt; 1. More stringent thresholds include |PM/PSD| &amp;gt; 1.3 (Masanjala and Papageorgiou, 2008) and |PM/PSD| &amp;gt; 2 (Sala-i-Martin et al., 2004).&lt;/p>
&lt;h3 id="83-interpreting-pips-with-rafterys-classification">8.3 Interpreting PIPs with Raftery&amp;rsquo;s classification&lt;/h3>
&lt;p>Raftery (1995) provides a standard classification for the strength of evidence based on PIP values:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>PIP range&lt;/th>
&lt;th>Evidence&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&amp;gt; 0.99&lt;/td>
&lt;td>Very strong&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0.95 &amp;ndash; 0.99&lt;/td>
&lt;td>Strong&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0.75 &amp;ndash; 0.95&lt;/td>
&lt;td>Positive&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>0.50 &amp;ndash; 0.75&lt;/td>
&lt;td>Weak&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Under the binomial prior, &lt;code>pop&lt;/code> (PIP = 0.990) reaches &lt;em>strong&lt;/em> evidence &amp;mdash; just short of the &amp;ldquo;very strong&amp;rdquo; threshold at 0.99. Life expectancy (&lt;code>lnlex&lt;/code> at 0.864), investment share (&lt;code>ish&lt;/code> at 0.773), trade openness (&lt;code>opem&lt;/code> at 0.766), and government share (&lt;code>gsh&lt;/code> at 0.751) fall in the &lt;em>positive&lt;/em> evidence range. The remaining four variables &amp;mdash; education, population growth, investment price, and democracy &amp;mdash; show &lt;em>weak&lt;/em> evidence (0.65&amp;ndash;0.72). No variable has PIP below 0.5, suggesting the data supports relatively large models.&lt;/p>
&lt;p>The &lt;strong>sign stability&lt;/strong> column (%(+)) provides an additional robustness check. Seven of the nine regressors have perfectly stable signs: investment share, population growth, population, trade openness, and life expectancy are always positive (100%), while investment price and democracy are always negative (0%). Government share has %(+) = 30.9%, meaning its sign is negative in about 70% of models &amp;mdash; moderately unstable. Education has %(+) = 69.9%, with a positive coefficient in about 70% of models but negative in 30%.&lt;/p>
&lt;p>The following chart visualizes the PIPs with color-coded evidence tiers. We first define a dark-theme palette and extract the BMA statistics into a data frame, then build the plot:&lt;/p>
&lt;pre>&lt;code class="language-r"># Dark theme palette (matching site navbar/footer)
DARK_BG &amp;lt;- &amp;quot;#0f1729&amp;quot;
LIGHT_TEXT &amp;lt;- &amp;quot;#c8d0e0&amp;quot;
LIGHTER_TEXT &amp;lt;- &amp;quot;#e8ecf2&amp;quot;
# Extract BMA statistics into a data frame
bma_tab &amp;lt;- bma_results[[1]]
pip_df &amp;lt;- data.frame(
variable = rownames(bma_tab)[-1],
pip = bma_tab[-1, &amp;quot;PIP&amp;quot;],
pm = bma_tab[-1, &amp;quot;PM&amp;quot;],
psd = bma_tab[-1, &amp;quot;PSD&amp;quot;],
sign_pos = bma_tab[-1, &amp;quot;%(+)&amp;quot;]
)
# Readable labels and robustness classification
var_labels &amp;lt;- c(ish = &amp;quot;Investment share&amp;quot;, sed = &amp;quot;Education&amp;quot;,
pgrw = &amp;quot;Population growth&amp;quot;, pop = &amp;quot;Population&amp;quot;,
ipr = &amp;quot;Investment price&amp;quot;, opem = &amp;quot;Trade openness&amp;quot;,
gsh = &amp;quot;Government share&amp;quot;, lnlex = &amp;quot;Life expectancy&amp;quot;,
polity = &amp;quot;Democracy&amp;quot;)
pip_df$label &amp;lt;- var_labels[pip_df$variable]
pip_df$robustness &amp;lt;- cut(pip_df$pip,
breaks = c(0, 0.50, 0.75, 1),
labels = c(&amp;quot;Weak (PIP &amp;lt; 0.50)&amp;quot;, &amp;quot;Moderate (0.50-0.75)&amp;quot;,
&amp;quot;Positive (PIP &amp;gt;= 0.75)&amp;quot;),
include.lowest = TRUE)
# PIP bar chart
ggplot(pip_df, aes(x = reorder(label, pip), y = pip,
fill = robustness)) +
geom_col(width = 0.65) +
geom_hline(yintercept = 0.75, linetype = &amp;quot;dashed&amp;quot;,
color = LIGHT_TEXT) +
geom_hline(yintercept = 0.50, linetype = &amp;quot;dotted&amp;quot;,
color = LIGHT_TEXT, alpha = 0.6) +
coord_flip() +
scale_fill_manual(values = c(
&amp;quot;Positive (PIP &amp;gt;= 0.75)&amp;quot; = &amp;quot;#6a9bcc&amp;quot;,
&amp;quot;Moderate (0.50-0.75)&amp;quot; = &amp;quot;#00d4c8&amp;quot;,
&amp;quot;Weak (PIP &amp;lt; 0.50)&amp;quot; = &amp;quot;#d97757&amp;quot;)) +
labs(x = NULL, y = &amp;quot;Posterior Inclusion Probability (PIP)&amp;quot;,
fill = &amp;quot;Evidence strength&amp;quot;,
title = &amp;quot;BMA: Posterior Inclusion Probabilities&amp;quot;,
subtitle = &amp;quot;Binomial prior (EMS = 4.5), 512 models averaged&amp;quot;)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="r_dynamic_bma_pip.png" alt="Posterior Inclusion Probabilities for all 9 regressors, sorted by PIP with threshold lines.">&lt;/p>
&lt;p>Population dominates the chart at PIP = 0.990, followed by life expectancy at 0.864. Five variables clear the 0.75 &amp;ldquo;positive evidence&amp;rdquo; threshold, while the remaining four &amp;mdash; democracy, education, population growth, and investment price &amp;mdash; fall in the &amp;ldquo;moderate&amp;rdquo; zone between 0.50 and 0.75. Compared to the kitchen-sink benchmark where 6 of 10 variables were significant at 5%, BMA paints a more nuanced picture: it grades each variable on a continuous scale of importance rather than imposing a binary significant/insignificant cutoff.&lt;/p>
&lt;h2 id="9-visualizing-model-probabilities">9. Visualizing Model Probabilities&lt;/h2>
&lt;h3 id="91-prior-versus-posterior-model-probabilities">9.1 Prior versus posterior model probabilities&lt;/h3>
&lt;p>The &lt;a href="https://cran.r-project.org/web/packages/bdsm/vignettes/bdsm_vignette.Rnw" target="_blank" rel="noopener">&lt;code>model_pmp()&lt;/code>&lt;/a> function visualizes how the data transforms our prior beliefs about which models are best. The prior assigns probability to each of the 512 models, and the data concentrates posterior mass on the models that fit best:&lt;/p>
&lt;pre>&lt;code class="language-r">pmp_plots &amp;lt;- model_pmp(bma_results)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="r_bdsm_03_model_pmp_combined.png" alt="Prior and posterior model probabilities across all 512 models.">&lt;/p>
&lt;p>The prior (dashed line) is relatively flat, reflecting the uniform prior assumption. The posterior (solid line) concentrates dramatically: a handful of models capture the bulk of the posterior mass, while most models receive negligible probability. This concentration is the signature of informative data &amp;mdash; the 73-country, 4-decade panel provides enough information to strongly favor certain model specifications.&lt;/p>
&lt;h3 id="92-model-sizes">9.2 Model sizes&lt;/h3>
&lt;p>The &lt;a href="https://cran.r-project.org/web/packages/bdsm/vignettes/bdsm_vignette.Rnw" target="_blank" rel="noopener">&lt;code>model_sizes()&lt;/code>&lt;/a> function shows the distribution of prior and posterior probabilities across model sizes (number of included regressors, excluding the lagged dependent variable):&lt;/p>
&lt;pre>&lt;code class="language-r">size_plots &amp;lt;- model_sizes(bma_results)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="r_bdsm_05_model_sizes.png" alt="Prior and posterior distribution over model sizes.">&lt;/p>
&lt;p>The expected model sizes confirm this visually:&lt;/p>
&lt;pre>&lt;code class="language-r">print(bma_results[[16]])
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> Prior models size Posterior model size
Binomial 4.5 6.908
Binomial-beta 4.5 8.556
&lt;/code>&lt;/pre>
&lt;p>The posterior strongly favors larger models. While the binomial prior centers mass on models with 4&amp;ndash;5 regressors (EMS = 4.5), the posterior shifts toward 7 regressors (6.908). Under the binomial-beta prior, the shift is even more dramatic: the posterior expected model size reaches 8.556, meaning the data wants to include nearly all 9 candidate regressors. This is consistent with the finding that all variables have PIP above 0.65 &amp;mdash; the data sees signal in most candidates.&lt;/p>
&lt;h2 id="10-examining-top-models">10. Examining Top Models&lt;/h2>
&lt;p>The &lt;a href="https://cran.r-project.org/web/packages/bdsm/vignettes/bdsm_vignette.Rnw" target="_blank" rel="noopener">&lt;code>best_models()&lt;/code>&lt;/a> function lets us inspect the specific variable combinations and coefficient estimates in the top-ranked models:&lt;/p>
&lt;pre>&lt;code class="language-r">best8 &amp;lt;- best_models(bma_results, criterion = 1, best = 8)
print(best8[[1]]) # Inclusion matrix
&lt;/code>&lt;/pre>
&lt;p>&lt;em>Reading the inclusion matrix: each column is a model (ranked by fit), each row is a variable. A value of 1 means the variable is included in that model. Look for variables that appear in every top model &amp;mdash; those are the most robust.&lt;/em>&lt;/p>
&lt;pre>&lt;code class="language-text"> 'No. 1' 'No. 2' 'No. 3' 'No. 4' 'No. 5' 'No. 6' 'No. 7' 'No. 8'
gdp_lag 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
ish 1.000 1.000 1.000 1.000 1.000 1.000 1.000 0.000
sed 1.000 1.000 1.000 0.000 1.000 1.000 1.000 1.000
pgrw 1.000 1.000 1.000 1.000 0.000 1.000 1.000 1.000
pop 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
ipr 1.000 0.000 1.000 1.000 1.000 1.000 1.000 1.000
opem 1.000 1.000 1.000 1.000 1.000 1.000 0.000 1.000
gsh 1.000 1.000 1.000 1.000 1.000 0.000 1.000 1.000
lnlex 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
polity 1.000 1.000 0.000 1.000 1.000 1.000 1.000 1.000
PMP 0.089 0.044 0.042 0.036 0.035 0.029 0.026 0.025
&lt;/code>&lt;/pre>
&lt;p>A striking pattern emerges: the top model includes &lt;em>all 9 regressors&lt;/em> (PMP = 8.9%), and the next 7 best models are each formed by dropping exactly one variable from the full set. This &amp;ldquo;kitchen sink minus one&amp;rdquo; pattern confirms that the data supports large models.&lt;/p>
&lt;p>Two variables are never dropped across the top 8 models: &lt;code>pop&lt;/code> and &lt;code>lnlex&lt;/code> &amp;mdash; they appear in all 8, consistent with their high PIPs of 0.990 and 0.864. The variables dropped in models 2&amp;ndash;8 are &lt;code>ipr&lt;/code>, &lt;code>polity&lt;/code>, &lt;code>sed&lt;/code>, &lt;code>pgrw&lt;/code>, &lt;code>gsh&lt;/code>, &lt;code>opem&lt;/code>, and &lt;code>ish&lt;/code> &amp;mdash; precisely the variables with the lowest PIPs.&lt;/p>
&lt;p>We can also examine the coefficient estimates in the best model using the knitr-formatted output:&lt;/p>
&lt;pre>&lt;code class="language-r"># Estimation results for the best model (knitr format)
print(best8[[5]])
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Best model (No. 1) estimates:
gdp_lag 0.954 (0.076)*** pop 0.065 (0.056)
ish 0.079 (0.032)** ipr -0.056 (0.027)**
sed 0.034 (0.065) opem 0.043 (0.025)*
pgrw 0.025 (0.033) gsh -0.043 (0.050)
lnlex 0.151 (0.060)** polity -0.092 (0.032)***
&lt;/code>&lt;/pre>
&lt;p>In the best model (No. 1), the lagged GDP coefficient is 0.954 (SE = 0.076, significant at 1%), confirming the very slow convergence we derived from the Solow model. Investment share has a positive and significant coefficient of 0.079, while democracy has a negative and highly significant coefficient of &amp;ndash;0.092. Life expectancy is positive and significant at 0.151. Education, despite being included in 7 of the top 8 models, has a large standard error (0.034, SE = 0.065) &amp;mdash; explaining its moderate PIP despite frequent inclusion.&lt;/p>
&lt;p>This combination &amp;mdash; high inclusion rate but imprecise coefficient &amp;mdash; happens when most models agree that education &lt;em>belongs&lt;/em> in the model but disagree about its magnitude. Some estimate a positive effect of +0.08, others a negative &amp;ndash;0.02. The variable is probably relevant, but the data does not pin down its direction.&lt;/p>
&lt;p>Beyond these top models, how do the coefficients distribute across all 512 specifications? The next section examines the full posterior distributions.&lt;/p>
&lt;h2 id="11-coefficient-distributions">11. Coefficient Distributions&lt;/h2>
&lt;p>Before examining individual coefficient distributions, it is helpful to see all posterior means and their uncertainty at a glance. We compute approximate 95% credible intervals as the posterior mean plus or minus two posterior standard deviations:&lt;/p>
&lt;pre>&lt;code class="language-r"># Approximate 95% credible intervals
pip_df$ci_low &amp;lt;- pip_df$pm - 2 * pip_df$psd
pip_df$ci_high &amp;lt;- pip_df$pm + 2 * pip_df$psd
# Coefficient point-range plot
ggplot(pip_df, aes(x = reorder(label, pip), y = pm,
color = robustness)) +
geom_hline(yintercept = 0, linetype = &amp;quot;solid&amp;quot;,
color = LIGHT_TEXT, alpha = 0.4) +
geom_pointrange(aes(ymin = ci_low, ymax = ci_high),
size = 0.6, linewidth = 0.8) +
coord_flip() +
scale_color_manual(values = c(
&amp;quot;Positive (PIP &amp;gt;= 0.75)&amp;quot; = &amp;quot;#6a9bcc&amp;quot;,
&amp;quot;Moderate (0.50-0.75)&amp;quot; = &amp;quot;#00d4c8&amp;quot;,
&amp;quot;Weak (PIP &amp;lt; 0.50)&amp;quot; = &amp;quot;#d97757&amp;quot;)) +
labs(x = NULL, y = &amp;quot;Posterior Mean Coefficient&amp;quot;,
color = &amp;quot;Evidence strength&amp;quot;,
title = &amp;quot;BMA: Posterior Coefficient Estimates&amp;quot;,
subtitle = &amp;quot;Points = posterior mean, bars = PM +/- 2*PSD&amp;quot;)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="r_dynamic_bma_coef.png" alt="Posterior coefficient estimates with approximate 95% credible intervals for all 9 regressors.">&lt;/p>
&lt;p>Population and life expectancy have the largest positive posterior means, with credible intervals that do not cross zero &amp;mdash; consistent with their high PIPs. Democracy (polity) has a clearly negative effect, also with an interval that excludes zero. Investment price is negative but with a wider interval. Education and government share have credible intervals that straddle zero, reflecting sign instability. Compared to the kitchen-sink FE model, BMA produces posterior means that account for model uncertainty: the intervals are wider than standard confidence intervals because they incorporate variation &lt;em>across&lt;/em> model specifications, not just &lt;em>within&lt;/em> a single specification.&lt;/p>
&lt;p>The &lt;a href="https://cran.r-project.org/web/packages/bdsm/vignettes/bdsm_vignette.Rnw" target="_blank" rel="noopener">&lt;code>coef_hist()&lt;/code>&lt;/a> function provides more detailed views of the full posterior distribution of each coefficient across all 512 models, weighted by posterior model probability:&lt;/p>
&lt;pre>&lt;code class="language-r">coef_plots &amp;lt;- coef_hist(bma_results)
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>Population&lt;/strong> &amp;mdash; the most robust determinant:&lt;/p>
&lt;pre>&lt;code class="language-r">print(coef_plots[[5]])
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="r_bdsm_09_coef_hist_pop.png" alt="Posterior coefficient distribution for population.">&lt;/p>
&lt;p>Population has a tight, entirely positive distribution centered around 0.12, confirming strong and stable evidence for a positive effect on growth.&lt;/p>
&lt;p>These results hold under the default binomial prior. But how sensitive are they to our choice of prior? The next section stress-tests the findings.&lt;/p>
&lt;h2 id="12-sensitivity-to-prior-specification">12. Sensitivity to Prior Specification&lt;/h2>
&lt;p>A critical step in any BMA analysis is checking whether the results change when we alter our prior beliefs. If a variable&amp;rsquo;s PIP is high under one prior but low under another, we should be cautious about declaring it a robust determinant. The following chart compares PIPs across three prior specifications at a glance:&lt;/p>
&lt;pre>&lt;code class="language-r"># Extract PIPs from three prior specifications
bma_tab_bb &amp;lt;- bma_results[[2]] # Binomial-beta
bma_tab_ems2 &amp;lt;- bma_ems2[[1]] # Skeptical (EMS = 2)
sens_df &amp;lt;- data.frame(
label = pip_df$label,
Binomial = pip_df$pip,
BinBeta = bma_tab_bb[-1, &amp;quot;PIP&amp;quot;],
EMS2 = bma_tab_ems2[-1, &amp;quot;PIP&amp;quot;])
# Pivot to long format for ggplot
sens_long &amp;lt;- sens_df %&amp;gt;%
pivot_longer(cols = c(Binomial, BinBeta, EMS2),
names_to = &amp;quot;prior&amp;quot;, values_to = &amp;quot;pip&amp;quot;) %&amp;gt;%
mutate(prior = factor(prior,
levels = c(&amp;quot;EMS2&amp;quot;, &amp;quot;Binomial&amp;quot;, &amp;quot;BinBeta&amp;quot;),
labels = c(&amp;quot;Skeptical (EMS=2)&amp;quot;, &amp;quot;Binomial (EMS=4.5)&amp;quot;,
&amp;quot;Binomial-Beta&amp;quot;)))
# Connecting segments showing the range across priors
seg_df &amp;lt;- sens_df %&amp;gt;%
mutate(pip_min = pmin(Binomial, BinBeta, EMS2),
pip_max = pmax(Binomial, BinBeta, EMS2))
# Dumbbell chart
ggplot() +
geom_vline(xintercept = 0.75, linetype = &amp;quot;dashed&amp;quot;,
color = LIGHT_TEXT) +
geom_vline(xintercept = 0.50, linetype = &amp;quot;dotted&amp;quot;,
color = LIGHT_TEXT, alpha = 0.6) +
geom_segment(data = seg_df,
aes(x = pip_min, xend = pip_max,
y = reorder(label, Binomial),
yend = reorder(label, Binomial)),
color = LIGHT_TEXT, alpha = 0.3, linewidth = 1.5) +
geom_point(data = sens_long,
aes(x = pip, y = reorder(label, pip), color = prior),
size = 3.5) +
scale_color_manual(values = c(
&amp;quot;Skeptical (EMS=2)&amp;quot; = &amp;quot;#d97757&amp;quot;,
&amp;quot;Binomial (EMS=4.5)&amp;quot; = &amp;quot;#6a9bcc&amp;quot;,
&amp;quot;Binomial-Beta&amp;quot; = &amp;quot;#00d4c8&amp;quot;)) +
labs(x = &amp;quot;Posterior Inclusion Probability (PIP)&amp;quot;, y = NULL,
color = &amp;quot;Model prior&amp;quot;,
title = &amp;quot;Prior Sensitivity: How Robust Are the PIPs?&amp;quot;,
subtitle = &amp;quot;Same data, three different prior specifications&amp;quot;)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="r_dynamic_bma_sensitivity.png" alt="Prior sensitivity: PIPs under three different prior specifications.">&lt;/p>
&lt;p>The width of each horizontal segment shows how much a variable&amp;rsquo;s PIP changes across priors. Population is rock-solid: its PIP barely moves (0.964&amp;ndash;0.998) regardless of the prior. Life expectancy shows moderate sensitivity (0.637&amp;ndash;0.974). The bottom four variables &amp;mdash; democracy, education, population growth, and investment price &amp;mdash; are the most sensitive, with PIPs ranging from 0.34&amp;ndash;0.94 depending on the prior. This visual makes the key message immediately clear: &lt;strong>only population and life expectancy are robust across all prior specifications&lt;/strong>.&lt;/p>
&lt;h3 id="121-binomial-versus-binomial-beta-prior">12.1 Binomial versus binomial-beta prior&lt;/h3>
&lt;p>The default analysis already computes both priors. The &lt;strong>binomial prior&lt;/strong> assigns each variable an independent probability of inclusion equal to EMS/K (where EMS is the expected model size and K is the number of regressors). The &lt;strong>binomial-beta prior&lt;/strong> is more flexible &amp;mdash; it places a prior on the inclusion probability itself, allowing the data to determine how many variables should be included.&lt;/p>
&lt;p>Under the binomial-beta prior, all PIPs increase substantially. Population reaches 0.998, life expectancy reaches 0.974, and even the lowest-ranked variable (investment price) reaches 0.924. The posterior expected model size jumps to 8.556 &amp;mdash; the binomial-beta prior allows the data to express its preference for large models even more strongly than the binomial prior.&lt;/p>
&lt;p>Comparing PIPs across the two priors:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Variable&lt;/th>
&lt;th style="text-align:center">PIP (Binomial)&lt;/th>
&lt;th style="text-align:center">PIP (Binomial-Beta)&lt;/th>
&lt;th style="text-align:center">Sign&lt;/th>
&lt;th style="text-align:center">Evidence strength&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>pop&lt;/td>
&lt;td style="text-align:center">0.990&lt;/td>
&lt;td style="text-align:center">0.998&lt;/td>
&lt;td style="text-align:center">+&lt;/td>
&lt;td style="text-align:center">Very strong&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>lnlex&lt;/td>
&lt;td style="text-align:center">0.864&lt;/td>
&lt;td style="text-align:center">0.974&lt;/td>
&lt;td style="text-align:center">+&lt;/td>
&lt;td style="text-align:center">Strong&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ish&lt;/td>
&lt;td style="text-align:center">0.773&lt;/td>
&lt;td style="text-align:center">0.954&lt;/td>
&lt;td style="text-align:center">+&lt;/td>
&lt;td style="text-align:center">Positive → Strong&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>opem&lt;/td>
&lt;td style="text-align:center">0.766&lt;/td>
&lt;td style="text-align:center">0.952&lt;/td>
&lt;td style="text-align:center">+&lt;/td>
&lt;td style="text-align:center">Positive → Strong&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>gsh&lt;/td>
&lt;td style="text-align:center">0.751&lt;/td>
&lt;td style="text-align:center">0.948&lt;/td>
&lt;td style="text-align:center">&amp;ndash;/+&lt;/td>
&lt;td style="text-align:center">Positive → Strong&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>sed&lt;/td>
&lt;td style="text-align:center">0.717&lt;/td>
&lt;td style="text-align:center">0.938&lt;/td>
&lt;td style="text-align:center">+/&amp;ndash;&lt;/td>
&lt;td style="text-align:center">Weak → Strong&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>pgrw&lt;/td>
&lt;td style="text-align:center">0.714&lt;/td>
&lt;td style="text-align:center">0.938&lt;/td>
&lt;td style="text-align:center">+&lt;/td>
&lt;td style="text-align:center">Weak → Strong&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>polity&lt;/td>
&lt;td style="text-align:center">0.678&lt;/td>
&lt;td style="text-align:center">0.929&lt;/td>
&lt;td style="text-align:center">&amp;ndash;&lt;/td>
&lt;td style="text-align:center">Weak → Strong&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ipr&lt;/td>
&lt;td style="text-align:center">0.656&lt;/td>
&lt;td style="text-align:center">0.924&lt;/td>
&lt;td style="text-align:center">&amp;ndash;&lt;/td>
&lt;td style="text-align:center">Weak → Strong&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The ranking is stable across priors &amp;mdash; &lt;code>pop&lt;/code> and &lt;code>lnlex&lt;/code> remain the top two, and &lt;code>ipr&lt;/code> and &lt;code>polity&lt;/code> remain the bottom two. However, the absolute PIP values depend heavily on the prior, with the binomial-beta prior being far more inclusive. This is expected: the binomial-beta prior concentrates mass on larger models when the data supports them.&lt;/p>
&lt;h3 id="122-varying-expected-model-size">12.2 Varying expected model size&lt;/h3>
&lt;p>The expected model size (EMS) controls how many regressors the prior expects to be relevant. The default EMS = K/2 = 4.5. Let us see what happens with a skeptical prior (EMS = 2, expecting only 2 of 9 regressors to matter) and a generous prior (EMS = 8):&lt;/p>
&lt;p>With the skeptical EMS = 2 prior, only &lt;code>pop&lt;/code> (PIP = 0.964) and &lt;code>lnlex&lt;/code> (PIP = 0.637) remain above 0.5 under the binomial prior. Investment share drops to 0.483 and democracy falls to 0.372. This tells us that population and life expectancy are the most robust determinants &amp;mdash; they survive even when the prior is heavily biased toward sparse models.&lt;/p>
&lt;p>With EMS = 8, all PIPs exceed 0.94 &amp;mdash; nearly identical to the binomial-beta results, confirming that the data&amp;rsquo;s preference for large models is consistent across prior specifications.&lt;/p>
&lt;p>Full output tables for each prior specification are in Appendix C.&lt;/p>
&lt;h3 id="123-dilution-prior">12.3 Dilution prior&lt;/h3>
&lt;p>Imagine two variables that measure almost the same thing &amp;mdash; say, &amp;lsquo;years of schooling&amp;rsquo; and &amp;lsquo;literacy rate.&amp;rsquo; Including both in a model is redundant, and any model that includes both gets an inflated likelihood simply because it has two ways to capture the same variation.&lt;/p>
&lt;p>When regressors are correlated with each other, standard priors can overcount evidence by giving high probability to models that include near-duplicate variables. The &lt;strong>dilution prior&lt;/strong> (George, 2010) penalizes models whose regressors are highly correlated, adjusting the model prior by the determinant of the correlation matrix:&lt;/p>
&lt;p>$$\mathbb{P}_D(M_j) \propto \mathbb{P}(M_j) \cdot |COR_j|^{\omega}$$&lt;/p>
&lt;p>In words, this formula says that the diluted prior for model $j$ equals the standard prior multiplied by a penalty term. The penalty is the determinant of the correlation matrix among model $j$&amp;rsquo;s regressors, raised to the power $\omega$. When regressors are highly correlated, this determinant is close to zero, pushing the diluted prior toward zero. The parameter $\omega$ controls the strength of the penalty (default = 0.5).&lt;/p>
&lt;pre>&lt;code class="language-r"># Dilution prior with default omega = 0.5
bma_dil &amp;lt;- bma(full_model_space, df = data_prepared,
round = 3, dilution = 1)
print(bma_dil[[1]])
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> PIP PM PSD PSDR PMcon PSDcon PSDRcon %(+)
gdp_lag NA 0.919 0.077 0.107 0.919 0.077 0.107 100.000
ish 0.718 0.058 0.046 0.062 0.081 0.034 0.059 100.000
sed 0.640 0.026 0.055 0.070 0.041 0.064 0.084 69.922
pgrw 0.653 0.017 0.030 0.050 0.026 0.034 0.060 99.609
pop 0.989 0.125 0.065 0.082 0.126 0.064 0.081 100.000
ipr 0.638 -0.033 0.033 0.044 -0.052 0.027 0.045 0.000
opem 0.743 0.034 0.030 0.033 0.046 0.026 0.031 100.000
gsh 0.740 -0.013 0.040 0.090 -0.017 0.046 0.104 30.859
lnlex 0.808 0.081 0.075 0.098 0.100 0.071 0.099 100.000
polity 0.598 -0.049 0.047 0.053 -0.083 0.030 0.044 0.000
&lt;/code>&lt;/pre>
&lt;p>The dilution prior modestly reduces PIPs compared to the standard binomial prior &amp;mdash; for example, &lt;code>ish&lt;/code> drops from 0.773 to 0.718, and &lt;code>polity&lt;/code> drops from 0.678 to 0.598. The posterior expected model size decreases from 6.91 to 6.53. Importantly, the ranking remains unchanged: &lt;code>pop&lt;/code> and &lt;code>lnlex&lt;/code> stay at the top, and the sign stability is unaffected. The dilution prior provides a useful robustness check against multicollinearity inflation.&lt;/p>
&lt;pre>&lt;code class="language-r">sizes_dil &amp;lt;- model_sizes(bma_dil)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="r_bdsm_16_sizes_dilution.png" alt="Model sizes under the dilution prior.">&lt;/p>
&lt;p>Having examined the evidence from every angle &amp;mdash; PIPs, coefficients, and sensitivity &amp;mdash; let us now synthesize the findings.&lt;/p>
&lt;h2 id="13-summary-of-findings">13. Summary of Findings&lt;/h2>
&lt;h3 id="131-the-robust-determinants">13.1 The robust determinants&lt;/h3>
&lt;p>Combining evidence across all prior specifications, we can classify each regressor by its robustness:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Variable&lt;/th>
&lt;th style="text-align:center">PIP (Bin.)&lt;/th>
&lt;th style="text-align:center">PIP (Bin-Beta)&lt;/th>
&lt;th style="text-align:center">PIP (EMS=2)&lt;/th>
&lt;th style="text-align:center">Sign&lt;/th>
&lt;th style="text-align:center">Verdict&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>pop&lt;/td>
&lt;td style="text-align:center">0.990&lt;/td>
&lt;td style="text-align:center">0.998&lt;/td>
&lt;td style="text-align:center">0.964&lt;/td>
&lt;td style="text-align:center">+&lt;/td>
&lt;td style="text-align:center">&lt;strong>Robust&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>lnlex&lt;/td>
&lt;td style="text-align:center">0.864&lt;/td>
&lt;td style="text-align:center">0.974&lt;/td>
&lt;td style="text-align:center">0.637&lt;/td>
&lt;td style="text-align:center">+&lt;/td>
&lt;td style="text-align:center">&lt;strong>Robust&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ish&lt;/td>
&lt;td style="text-align:center">0.773&lt;/td>
&lt;td style="text-align:center">0.954&lt;/td>
&lt;td style="text-align:center">0.483&lt;/td>
&lt;td style="text-align:center">+&lt;/td>
&lt;td style="text-align:center">Positive&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>opem&lt;/td>
&lt;td style="text-align:center">0.766&lt;/td>
&lt;td style="text-align:center">0.952&lt;/td>
&lt;td style="text-align:center">0.468&lt;/td>
&lt;td style="text-align:center">+&lt;/td>
&lt;td style="text-align:center">Positive&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>gsh&lt;/td>
&lt;td style="text-align:center">0.751&lt;/td>
&lt;td style="text-align:center">0.948&lt;/td>
&lt;td style="text-align:center">0.459&lt;/td>
&lt;td style="text-align:center">&amp;ndash;&lt;/td>
&lt;td style="text-align:center">Positive&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>sed&lt;/td>
&lt;td style="text-align:center">0.717&lt;/td>
&lt;td style="text-align:center">0.938&lt;/td>
&lt;td style="text-align:center">0.420&lt;/td>
&lt;td style="text-align:center">+/&amp;ndash;&lt;/td>
&lt;td style="text-align:center">Sensitive&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>pgrw&lt;/td>
&lt;td style="text-align:center">0.714&lt;/td>
&lt;td style="text-align:center">0.938&lt;/td>
&lt;td style="text-align:center">0.414&lt;/td>
&lt;td style="text-align:center">+&lt;/td>
&lt;td style="text-align:center">Sensitive&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>polity&lt;/td>
&lt;td style="text-align:center">0.678&lt;/td>
&lt;td style="text-align:center">0.929&lt;/td>
&lt;td style="text-align:center">0.372&lt;/td>
&lt;td style="text-align:center">&amp;ndash;&lt;/td>
&lt;td style="text-align:center">Sensitive&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ipr&lt;/td>
&lt;td style="text-align:center">0.656&lt;/td>
&lt;td style="text-align:center">0.924&lt;/td>
&lt;td style="text-align:center">0.344&lt;/td>
&lt;td style="text-align:center">&amp;ndash;&lt;/td>
&lt;td style="text-align:center">Sensitive&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;blockquote>
&lt;p>&lt;strong>Bottom line:&lt;/strong> If you are advising a government on growth policy, population dynamics and public health (life expectancy) are the two levers with the strongest evidence across all modeling assumptions. Investment and trade openness show promise under the default prior but become ambiguous under skeptical specifications. Education and democracy &amp;mdash; despite their intuitive appeal &amp;mdash; are fragile in this framework.&lt;/p>
&lt;/blockquote>
&lt;p>Only two variables &amp;mdash; &lt;strong>population&lt;/strong> and &lt;strong>life expectancy&lt;/strong> &amp;mdash; survive as robust determinants across all prior specifications, maintaining PIP above 0.5 even under the most skeptical prior (EMS = 2). Both have stable positive signs and their coefficients are precisely estimated. Investment share and trade openness show positive evidence under the default prior but become ambiguous under the skeptical prior.&lt;/p>
&lt;h3 id="132-connecting-to-cross-sectional-results">13.2 Connecting to cross-sectional results&lt;/h3>
&lt;p>In the &lt;a href="https://carlos-mendez.org/post/r_bma_lasso_wals/">companion cross-sectional tutorial&lt;/a>, we found that BMA, LASSO, and WALS converged on the same set of robust variables for CO&lt;sub>2&lt;/sub> emissions in synthetic data. The dynamic panel BMA analysis here reveals an important nuance: &lt;strong>controlling for reverse causality through the lagged dependent variable and fixed effects changes the landscape of robust determinants&lt;/strong>. The strong persistence of GDP (lagged coefficient = 0.92) absorbs much of the cross-sectional variation, leaving fewer variables with strong independent explanatory power. This is exactly the kind of insight that cross-sectional BMA misses.&lt;/p>
&lt;h2 id="14-conclusion">14. Conclusion&lt;/h2>
&lt;h3 id="141-key-takeaways">14.1 Key takeaways&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Method insight:&lt;/strong> Dynamic panel BMA handles endogeneity that cross-sectional BMA cannot. By including a lagged dependent variable ($\alpha$ = 0.92) and entity/time fixed effects, the Bayesian DSM package allows BMA to work with weakly exogenous regressors, avoiding the bias that plagues standard growth regressions.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Data insight:&lt;/strong> Of 9 candidate growth determinants, only population size (PIP = 0.990) and life expectancy (PIP = 0.864) are robust across all prior specifications. This confirms the &amp;ldquo;fragility&amp;rdquo; of growth determinants documented by Sala-i-Martin et al. (2004) &amp;mdash; most variables that appear important in one specification become ambiguous under different priors.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Sensitivity insight:&lt;/strong> Results are moderately sensitive to prior choice. Under the skeptical EMS = 2 prior, only &lt;code>pop&lt;/code> (PIP = 0.964) remains very strong, while even &lt;code>lnlex&lt;/code> drops to 0.637. The binomial-beta prior pushes all variables above PIP = 0.92, reflecting the data&amp;rsquo;s preference for large models (posterior EMS = 8.6).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Jointness insight:&lt;/strong> All regressor pairs are complements (HCGHM &amp;gt; 0), with the strongest complementarity between population and life expectancy (0.71). No substitution effects were detected, suggesting these growth determinants capture distinct dimensions of the development process. See Appendix A for the full jointness analysis.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h3 id="142-limitations-and-next-steps">14.2 Limitations and next steps&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Computation cost:&lt;/strong> The &lt;code>optim_model_space()&lt;/code> step estimates all $2^K$ models via numerical optimization. With 9 regressors (512 models), this is feasible. With 15+ regressors ($2^{15}$ = 32,768 models), computation time grows exponentially. For larger variable sets, Markov Chain Monte Carlo (MCMC) sampling over the model space may be necessary.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Weak exogeneity assumption:&lt;/strong> While weaker than strict exogeneity, the weak exogeneity assumption still requires that current regressors are uncorrelated with current shocks. If contemporaneous feedback is strong (e.g., a GDP shock immediately changes investment in the same period), the estimates may still be biased.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Extensions:&lt;/strong> The package offers additional features not covered here, including parallel computing for faster model space estimation (&lt;code>cl&lt;/code> parameter in &lt;code>optim_model_space()&lt;/code>), robust standard errors for heteroskedasticity, and the full suite of reduced-form parameters for understanding the dynamic feedback structure.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="143-exercises">14.3 Exercises&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Vary the dilution parameter.&lt;/strong> Run &lt;code>bma()&lt;/code> with &lt;code>dilution = 1&lt;/code> and &lt;code>dil.Par = 2&lt;/code> (stronger dilution). How do the PIPs change compared to &lt;code>dil.Par = 0.5&lt;/code>? Which variables are most affected by multicollinearity adjustment?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Examine the small model space.&lt;/strong> Use &lt;code>small_model_space&lt;/code> with only &lt;code>ish&lt;/code>, &lt;code>sed&lt;/code>, and &lt;code>pgrw&lt;/code>. Run the full BMA workflow (including &lt;code>model_pmp()&lt;/code>, &lt;code>model_sizes()&lt;/code>, &lt;code>best_models()&lt;/code>, and &lt;code>jointness()&lt;/code>). Do the PIP rankings change when the competition among regressors is limited to 3?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Compare standard and robust standard errors.&lt;/strong> Run &lt;code>best_models()&lt;/code> with &lt;code>robust = TRUE&lt;/code> and compare the coefficient significance to the default (regular SE). Are there variables that lose or gain significance under robust inference?&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="appendix-a-jointness-analysis">Appendix A: Jointness Analysis&lt;/h2>
&lt;h3 id="what-is-jointness">What is jointness?&lt;/h3>
&lt;p>So far we have examined each regressor individually. But growth determinants do not work in isolation &amp;mdash; they interact. &lt;strong>Jointness&lt;/strong> measures whether two regressors tend to appear in models &lt;em>together&lt;/em> (complements) or &lt;em>separately&lt;/em> (substitutes).&lt;/p>
&lt;p>Think of peanut butter and jelly: each is fine alone, but they show up together so often that their inclusion is correlated. In growth regressions, investment and trade openness might be complements &amp;mdash; countries that invest heavily also trade more, and models that capture one effect benefit from including the other. Conversely, two measures of education (enrollment and literacy) might be substitutes &amp;mdash; including one makes the other redundant.&lt;/p>
&lt;h3 id="three-jointness-measures">Three jointness measures&lt;/h3>
&lt;p>The package implements three jointness measures. The &lt;a href="https://cran.r-project.org/web/packages/bdsm/vignettes/bdsm_vignette.Rnw" target="_blank" rel="noopener">&lt;code>jointness()&lt;/code>&lt;/a> function computes pairwise relationships between all regressors:&lt;/p>
&lt;p>&lt;strong>Hofmarcher et al. (HCGHM)&lt;/strong> ranges from &amp;ndash;1 (perfect substitutes) to +1 (perfect complements), with 0 indicating independence. This is the recommended default measure.&lt;/p>
&lt;p>&lt;strong>Ley-Strazicich (LS)&lt;/strong> ranges from 0 to infinity, where higher values indicate stronger complementarity.&lt;/p>
&lt;p>&lt;strong>Doppelhofer-Weeks (DW)&lt;/strong> classifies relationships as: below &amp;ndash;2 (strong substitutes), &amp;ndash;2 to &amp;ndash;1 (significant substitutes), &amp;ndash;1 to 1 (unrelated), 1 to 2 (significant complements), above 2 (strong complements).&lt;/p>
&lt;h3 id="jointness-matrices">Jointness matrices&lt;/h3>
&lt;p>The HCGHM jointness matrix (above diagonal = binomial prior, below diagonal = binomial-beta prior):&lt;/p>
&lt;pre>&lt;code class="language-r">jointness(bma_results, measure = &amp;quot;HCGHM&amp;quot;)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> ish sed pgrw pop ipr opem gsh lnlex polity
ish NA 0.216 0.207 0.530 0.150 0.262 0.243 0.366 0.181
sed 0.805 NA 0.154 0.421 0.115 0.199 0.189 0.288 0.125
pgrw 0.805 0.778 NA 0.416 0.124 0.198 0.186 0.283 0.131
pop 0.905 0.874 0.874 NA 0.304 0.517 0.489 0.711 0.346
ipr 0.781 0.756 0.758 0.845 NA 0.153 0.138 0.209 0.102
opem 0.829 0.801 0.802 0.902 0.780 NA 0.241 0.372 0.169
gsh 0.821 0.794 0.794 0.893 0.772 0.819 NA 0.340 0.154
lnlex 0.864 0.835 0.835 0.944 0.810 0.863 0.853 NA 0.227
polity 0.790 0.763 0.764 0.855 0.744 0.787 0.779 0.817 NA
&lt;/code>&lt;/pre>
&lt;p>All HCGHM values are positive, meaning every pair of regressors acts as complements rather than substitutes. The strongest complementarity under the binomial prior (above diagonal) is between &lt;code>pop&lt;/code> and &lt;code>lnlex&lt;/code> at 0.711 &amp;mdash; population size and life expectancy tend to appear in the best models together. The &lt;code>pop&lt;/code>-&lt;code>ish&lt;/code> pair (0.530) and &lt;code>pop&lt;/code>-&lt;code>opem&lt;/code> pair (0.517) are also moderately complementary. Investment price (&lt;code>ipr&lt;/code>) shows the weakest complementarity with other variables, consistent with its lowest PIP.&lt;/p>
&lt;p>Under the binomial-beta prior (below diagonal), all jointness values increase substantially &amp;mdash; reaching 0.944 for the &lt;code>pop&lt;/code>-&lt;code>lnlex&lt;/code> pair. This is because the binomial-beta prior favors larger models, making it more likely that any two variables appear together.&lt;/p>
&lt;p>The Doppelhofer-Weeks measure confirms these patterns: all pairwise DW values fall between &amp;ndash;1 and +1, with the strongest relationship again between population and life expectancy (DW = 0.153).&lt;/p>
&lt;h2 id="appendix-b-solow-convergence-derivation">Appendix B: Solow Convergence Derivation&lt;/h2>
&lt;p>The Solow model predicts that poorer countries should grow faster than richer ones, conditional on their structural characteristics. This is called &lt;strong>beta convergence&lt;/strong>. Mathematically, the model implies that around the steady state, log GDP per capita evolves according to (Barro and Sala-i-Martin, 2004):&lt;/p>
&lt;p>$$\ln y_{it} = (1 - e^{-\lambda \tau}) \ln y^*_i + e^{-\lambda \tau} \ln y_{i,t-1}$$&lt;/p>
&lt;p>In words, a country&amp;rsquo;s current GDP ($\ln y_{it}$) is a weighted average of two forces: its long-run steady-state level ($\ln y^*_i$), determined by fundamentals like savings and technology, and its GDP in the previous period ($\ln y_{i,t-1}$), which captures where the country currently stands. The parameter $\lambda$ is the &lt;strong>speed of convergence&lt;/strong> &amp;mdash; how fast countries close the gap to their steady state &amp;mdash; and $\tau$ is the time between observations (10 years in our data).&lt;/p>
&lt;p>Now define $\alpha = e^{-\lambda \tau}$. The convergence equation becomes:&lt;/p>
&lt;p>$$\ln y_{it} = \alpha \ln y_{i,t-1} + (1 - \alpha) \ln y^*_i$$&lt;/p>
&lt;p>This is already a dynamic equation &amp;mdash; current GDP depends on lagged GDP. The next step is to recognize that the steady state $\ln y^*_i$ is not observed directly. Instead, it depends on country characteristics such as investment rates, education, trade openness, and institutional quality. Writing these as $\beta' x_{it}$, and adding country fixed effects ($\eta_i$) for unobserved fundamentals, time effects ($\zeta_t$) for global shocks, and an error term ($v_{it}$), we arrive at the dynamic panel equation presented in Section 3.2.&lt;/p>
&lt;h2 id="appendix-c-full-sensitivity-output">Appendix C: Full Sensitivity Output&lt;/h2>
&lt;h3 id="binomial-beta-prior">Binomial-beta prior&lt;/h3>
&lt;pre>&lt;code class="language-r"># Binomial-beta results (already computed)
print(bma_results[[2]])
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> PIP PM PSD PSDR PMcon PSDcon PSDRcon %(+)
gdp_lag NA 0.943 0.078 0.130 0.943 0.078 0.130 100.000
ish 0.954 0.076 0.036 0.066 0.079 0.032 0.065 100.000
sed 0.938 0.035 0.063 0.094 0.037 0.064 0.097 69.922
pgrw 0.938 0.024 0.033 0.059 0.026 0.033 0.061 99.609
pop 0.998 0.080 0.062 0.083 0.080 0.062 0.083 100.000
ipr 0.924 -0.050 0.030 0.052 -0.054 0.027 0.052 0.000
opem 0.952 0.041 0.026 0.034 0.043 0.025 0.034 100.000
gsh 0.948 -0.034 0.049 0.120 -0.036 0.049 0.123 30.859
lnlex 0.974 0.134 0.069 0.105 0.138 0.066 0.104 100.000
polity 0.929 -0.084 0.038 0.053 -0.090 0.031 0.049 0.000
&lt;/code>&lt;/pre>
&lt;h3 id="skeptical-prior-ems--2">Skeptical prior (EMS = 2)&lt;/h3>
&lt;pre>&lt;code class="language-r"># Skeptical prior: EMS = 2
bma_ems2 &amp;lt;- bma(full_model_space, df = data_prepared, round = 3, EMS = 2)
print(bma_ems2[[1]])
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> PIP PM PSD PSDR PMcon PSDcon PSDRcon %(+)
gdp_lag NA 0.922 0.081 0.102 0.922 0.081 0.102 100.000
ish 0.483 0.042 0.050 0.059 0.088 0.034 0.057 100.000
sed 0.420 0.015 0.046 0.057 0.036 0.065 0.084 69.922
pgrw 0.414 0.009 0.025 0.040 0.023 0.034 0.061 99.609
pop 0.964 0.144 0.066 0.082 0.149 0.061 0.079 100.000
ipr 0.344 -0.019 0.031 0.037 -0.055 0.028 0.045 0.000
opem 0.468 0.024 0.032 0.033 0.052 0.026 0.030 100.000
gsh 0.459 -0.003 0.032 0.071 -0.007 0.047 0.105 30.859
lnlex 0.637 0.051 0.068 0.087 0.081 0.069 0.097 100.000
polity 0.372 -0.029 0.042 0.046 -0.079 0.031 0.043 0.000
&lt;/code>&lt;/pre>
&lt;hr>
&lt;h2 id="references">References&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://doi.org/10.1162/REST_a_00154" target="_blank" rel="noopener">Moral-Benito, E. (2012). Determinants of Economic Growth: A Bayesian Panel Data Approach. &lt;em>Review of Economics and Statistics&lt;/em>, 94(2), 566&amp;ndash;579.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1080/07350015.2013.818003" target="_blank" rel="noopener">Moral-Benito, E. (2013). Likelihood-Based Estimation of Dynamic Panels with Predetermined Regressors. &lt;em>Journal of Business and Economic Statistics&lt;/em>, 31(4), 451&amp;ndash;472.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1002/jae.2429" target="_blank" rel="noopener">Moral-Benito, E. (2016). Growth Empirics in Panel Data Under Model Uncertainty and Weak Exogeneity. &lt;em>Journal of Applied Econometrics&lt;/em>, 31(3), 582&amp;ndash;602.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://cran.r-project.org/web/packages/bdsm/index.html" target="_blank" rel="noopener">Wyszynski, M., Beck, K., and Dubel, M. (2025). Bayesian Dynamic Systems Modeling. R package version 0.3.0. CRAN.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1257/0002828042002570" target="_blank" rel="noopener">Sala-i-Martin, X., Doppelhofer, G., and Miller, R.I. (2004). Determinants of Long-Term Growth: A Bayesian Averaging of Classical Estimates (BACE) Approach. &lt;em>American Economic Review&lt;/em>, 94(4), 813&amp;ndash;835.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1002/jae.623" target="_blank" rel="noopener">Fernandez, C., Ley, E., and Steel, M.F.J. (2001). Model Uncertainty in Cross-Country Growth Regressions. &lt;em>Journal of Applied Econometrics&lt;/em>, 16(5), 563&amp;ndash;576.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1002/jae.1046" target="_blank" rel="noopener">Doppelhofer, G. and Weeks, M. (2009). Jointness of Growth Determinants. &lt;em>Journal of Applied Econometrics&lt;/em>, 24(2), 209&amp;ndash;244.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1002/jae.1057" target="_blank" rel="noopener">Ley, E. and Steel, M.F.J. (2009). On the Effect of Prior Assumptions in Bayesian Model Averaging with Applications to Growth Regression. &lt;em>Journal of Applied Econometrics&lt;/em>, 24(4), 651&amp;ndash;674.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.2307/271063" target="_blank" rel="noopener">Raftery, A.E. (1995). Bayesian Model Selection in Social Research. &lt;em>Sociological Methodology&lt;/em>, 25, 111&amp;ndash;163.&lt;/a>&lt;/li>
&lt;/ol></description></item><item><title>Taming Model Uncertainty in the Environmental Kuznets Curve: BMA and Double-Selection LASSO with Panel Data</title><link>https://carlos-mendez.org/post/stata_bma_dsl/</link><pubDate>Sun, 29 Mar 2026 00:00:00 +0000</pubDate><guid>https://carlos-mendez.org/post/stata_bma_dsl/</guid><description>&lt;h2 id="1-overview">1. Overview&lt;/h2>
&lt;p>Can countries grow their way out of pollution? The &lt;strong>Environmental Kuznets Curve (EKC)&lt;/strong> hypothesis says yes &amp;mdash; up to a point. As economies develop, pollution first rises with industrialization and then falls as countries grow wealthy enough to afford cleaner technology. But recent research suggests a more complex &lt;strong>inverted-N&lt;/strong> shape: pollution falls at very low incomes, rises through industrialization, and then falls again at high incomes.&lt;/p>
&lt;p>Testing for this shape requires a cubic polynomial in GDP per capita &amp;mdash; and beyond GDP, many other factors might affect CO&lt;sub>2&lt;/sub> emissions. With 12 candidate control variables, there are $2^{12} = 4{,}096$ possible regression models. &lt;strong>Which model should we estimate?&lt;/strong> This is the &lt;strong>model uncertainty problem&lt;/strong>.&lt;/p>
&lt;p>This tutorial introduces two principled solutions:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Bayesian Model Averaging (BMA)&lt;/strong> estimates thousands of models and averages the results, weighting each by how well it fits the data. Each variable gets a &lt;strong>Posterior Inclusion Probability (PIP)&lt;/strong> &amp;mdash; the fraction of high-quality models that include it.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Post-Double-Selection LASSO (DSL)&lt;/strong> uses LASSO to automatically select which controls matter &amp;mdash; once for the outcome, once for each variable of interest &amp;mdash; then runs OLS with the union of all selected controls. This &amp;ldquo;select, then regress&amp;rdquo; approach protects against omitted variable bias.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>We use &lt;strong>synthetic panel data&lt;/strong> with a known &amp;ldquo;answer key&amp;rdquo; &amp;mdash; we designed the data so that 5 controls truly affect CO&lt;sub>2&lt;/sub> and 7 are pure noise. This lets us grade each method: does it correctly identify the true predictors? The data is inspired by the panel dataset of Gravina and Lanzafame (2025) but is fully synthetic and not identical to the original.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Companion tutorial.&lt;/strong> For a cross-sectional perspective using R with BMA, LASSO, and WALS, see the &lt;a href="https://carlos-mendez.org/post/r_bma_lasso_wals/">R tutorial on variable selection&lt;/a>.&lt;/p>
&lt;/blockquote>
&lt;p>&lt;strong>Learning objectives:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Understand the EKC hypothesis and why a cubic polynomial tests for an inverted-N shape&lt;/li>
&lt;li>Recognize model uncertainty as a practical challenge when many controls are available&lt;/li>
&lt;li>Implement BMA with &lt;code>bmaregress&lt;/code> and interpret PIPs and coefficient densities&lt;/li>
&lt;li>Implement post-double-selection LASSO with &lt;code>dsregress&lt;/code> and understand its four-step algorithm: LASSO on outcome, LASSO on each variable of interest, union, then OLS&lt;/li>
&lt;li>Evaluate both methods against a known ground truth to assess their accuracy&lt;/li>
&lt;/ul>
&lt;p>The following diagram summarizes the methodological sequence of this tutorial. We begin with exploratory data analysis to visualize the raw income&amp;ndash;pollution relationship, then estimate baseline fixed effects regressions to expose the model uncertainty problem. Next, we apply BMA and DSL as two alternative solutions, and finally compare both methods against the known answer key.&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph LR
A[&amp;quot;&amp;lt;b&amp;gt;EDA&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Scatter plot&amp;quot;] --&amp;gt; B[&amp;quot;&amp;lt;b&amp;gt;Baseline FE&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Standard panel&amp;lt;br/&amp;gt;regressions&amp;quot;]
B --&amp;gt; C[&amp;quot;&amp;lt;b&amp;gt;BMA&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Bayesian Model&amp;lt;br/&amp;gt;Averaging&amp;quot;]
C --&amp;gt; D[&amp;quot;&amp;lt;b&amp;gt;DSL&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Double-Selection&amp;lt;br/&amp;gt;LASSO&amp;quot;]
D --&amp;gt; E[&amp;quot;&amp;lt;b&amp;gt;Comparison&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Check against&amp;lt;br/&amp;gt;answer key&amp;quot;]
style A fill:#141413,stroke:#141413,color:#fff
style B fill:#6a9bcc,stroke:#141413,color:#fff
style C fill:#d97757,stroke:#141413,color:#fff
style D fill:#00d4c8,stroke:#141413,color:#141413
style E fill:#1a3a8a,stroke:#141413,color:#fff
&lt;/code>&lt;/pre>
&lt;h2 id="2-setup-and-synthetic-data">2. Setup and Synthetic Data&lt;/h2>
&lt;h3 id="21-why-synthetic-data">2.1 Why synthetic data?&lt;/h3>
&lt;p>Real-world datasets rarely come with an answer key. We never know which control variables &lt;em>truly&lt;/em> belong in the model. By generating synthetic data with a known data-generating process (DGP), we can verify whether BMA and DSL correctly recover the truth. This is the same &amp;ldquo;answer key&amp;rdquo; approach used in the &lt;a href="https://carlos-mendez.org/post/r_bma_lasso_wals/">companion R tutorial&lt;/a>, applied here to panel data.&lt;/p>
&lt;h3 id="22-the-data-generating-process">2.2 The data-generating process&lt;/h3>
&lt;p>The outcome &amp;mdash; log CO&lt;sub>2&lt;/sub> per capita &amp;mdash; follows a cubic EKC with country and year fixed effects:&lt;/p>
&lt;p>$$\ln(\text{CO2})_{it} = \beta_1 \ln(\text{GDP})_{it} + \beta_2 [\ln(\text{GDP})_{it}]^2 + \beta_3 [\ln(\text{GDP})_{it}]^3 + \mathbf{X}_{it}^{\text{true}} \boldsymbol{\gamma} + \alpha_i + \delta_t + \varepsilon_{it}$$&lt;/p>
&lt;p>In words, log CO&lt;sub>2&lt;/sub> depends on a cubic function of log GDP (producing the inverted-N shape), five true control variables $\mathbf{X}^{\text{true}}$, country fixed effects $\alpha_i$, year fixed effects $\delta_t$, and random noise $\varepsilon_{it}$.&lt;/p>
&lt;p>The &lt;strong>answer key&lt;/strong> &amp;mdash; which variables are true predictors and which are noise:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Variable&lt;/th>
&lt;th>Group&lt;/th>
&lt;th>In DGP?&lt;/th>
&lt;th>True coef.&lt;/th>
&lt;th>GDP corr.&lt;/th>
&lt;th>Role&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>fossil_fuel&lt;/code>&lt;/td>
&lt;td>Energy&lt;/td>
&lt;td>&lt;strong>Yes&lt;/strong>&lt;/td>
&lt;td>+0.015&lt;/td>
&lt;td>moderate&lt;/td>
&lt;td>More fossil fuels → more CO&lt;sub>2&lt;/sub>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>renewable&lt;/code>&lt;/td>
&lt;td>Energy&lt;/td>
&lt;td>&lt;strong>Yes&lt;/strong>&lt;/td>
&lt;td>&amp;ndash;0.010&lt;/td>
&lt;td>moderate&lt;/td>
&lt;td>More renewables → less CO&lt;sub>2&lt;/sub>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>urban&lt;/code>&lt;/td>
&lt;td>Socio&lt;/td>
&lt;td>&lt;strong>Yes&lt;/strong>&lt;/td>
&lt;td>+0.007&lt;/td>
&lt;td>moderate&lt;/td>
&lt;td>More urbanization → more CO&lt;sub>2&lt;/sub>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>democracy&lt;/code>&lt;/td>
&lt;td>Institutional&lt;/td>
&lt;td>&lt;strong>Yes&lt;/strong>&lt;/td>
&lt;td>&amp;ndash;0.005&lt;/td>
&lt;td>low&lt;/td>
&lt;td>More democracy → less CO&lt;sub>2&lt;/sub>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>industry&lt;/code>&lt;/td>
&lt;td>Economic&lt;/td>
&lt;td>&lt;strong>Yes&lt;/strong>&lt;/td>
&lt;td>+0.010&lt;/td>
&lt;td>moderate&lt;/td>
&lt;td>More industry → more CO&lt;sub>2&lt;/sub>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>globalization&lt;/code>&lt;/td>
&lt;td>Socio&lt;/td>
&lt;td>No&lt;/td>
&lt;td>0&lt;/td>
&lt;td>&lt;strong>high&lt;/strong>&lt;/td>
&lt;td>Noise &amp;mdash; tricky (correlated with GDP)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>pop_density&lt;/code>&lt;/td>
&lt;td>Socio&lt;/td>
&lt;td>No&lt;/td>
&lt;td>0&lt;/td>
&lt;td>low&lt;/td>
&lt;td>Noise&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>corruption&lt;/code>&lt;/td>
&lt;td>Institutional&lt;/td>
&lt;td>No&lt;/td>
&lt;td>0&lt;/td>
&lt;td>low&lt;/td>
&lt;td>Noise&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>services&lt;/code>&lt;/td>
&lt;td>Economic&lt;/td>
&lt;td>No&lt;/td>
&lt;td>0&lt;/td>
&lt;td>&lt;strong>high&lt;/strong>&lt;/td>
&lt;td>Noise &amp;mdash; tricky (correlated with GDP)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>trade&lt;/code>&lt;/td>
&lt;td>Economic&lt;/td>
&lt;td>No&lt;/td>
&lt;td>0&lt;/td>
&lt;td>moderate&lt;/td>
&lt;td>Noise &amp;mdash; tricky (correlated with GDP)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>fdi&lt;/code>&lt;/td>
&lt;td>Economic&lt;/td>
&lt;td>No&lt;/td>
&lt;td>0&lt;/td>
&lt;td>low&lt;/td>
&lt;td>Noise&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>credit&lt;/code>&lt;/td>
&lt;td>Economic&lt;/td>
&lt;td>No&lt;/td>
&lt;td>0&lt;/td>
&lt;td>moderate&lt;/td>
&lt;td>Noise &amp;mdash; tricky (correlated with GDP)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The &amp;ldquo;GDP corr.&amp;rdquo; column is key to understanding why this problem is non-trivial. Four noise variables (&lt;code>globalization&lt;/code>, &lt;code>services&lt;/code>, &lt;code>trade&lt;/code>, &lt;code>credit&lt;/code>) are deliberately correlated with GDP. A naive regression would find them &amp;ldquo;significant&amp;rdquo; because they piggyback on GDP&amp;rsquo;s true effect. The challenge for BMA and DSL is to see through this correlation and correctly identify that only the 5 true controls belong in the model.&lt;/p>
&lt;p>With the DGP and answer key defined, we now load the synthetic data and set up the Stata environment.&lt;/p>
&lt;h3 id="23-load-the-data">2.3 Load the data&lt;/h3>
&lt;p>The synthetic data is hosted on GitHub for reproducibility. It was generated by &lt;code>generate_data.do&lt;/code> (see the link above).&lt;/p>
&lt;pre>&lt;code class="language-stata">* Load synthetic data from GitHub
import delimited &amp;quot;https://github.com/cmg777/starter-academic-v501/raw/master/content/post/stata_bma_dsl/synthetic_ekc_panel.csv&amp;quot;, clear
xtset country_id year, yearly
&lt;/code>&lt;/pre>
&lt;h3 id="24-define-macros">2.4 Define macros&lt;/h3>
&lt;p>We define all variable groups as global macros &amp;mdash; used in every command throughout the tutorial:&lt;/p>
&lt;pre>&lt;code class="language-stata">global outcome &amp;quot;ln_co2&amp;quot;
global gdp_vars &amp;quot;ln_gdp ln_gdp_sq ln_gdp_cb&amp;quot;
global energy &amp;quot;fossil_fuel renewable&amp;quot;
global socio &amp;quot;urban globalization pop_density&amp;quot;
global inst &amp;quot;democracy corruption&amp;quot;
global econ &amp;quot;industry services trade fdi credit&amp;quot;
global controls &amp;quot;$energy $socio $inst $econ&amp;quot;
global fe &amp;quot;i.country_id i.year&amp;quot;
* Ground truth (for evaluation)
global true_vars &amp;quot;fossil_fuel renewable urban democracy industry&amp;quot;
global noise_vars &amp;quot;globalization pop_density corruption services trade fdi credit&amp;quot;
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-stata">summarize $outcome $gdp_vars $controls
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> Variable | Obs Mean Std. dev. Min Max
-------------+---------------------------------------------------------
ln_co2 | 1,600 -19.0385 .7863276 -21.03685 -16.8315
ln_gdp | 1,600 9.58387 1.329675 6.974263 11.9704
ln_gdp_sq | 1,600 93.6174 25.55106 48.64035 143.2904
ln_gdp_cb | 1,600 931.105 373.829 339.2306 1715.243
fossil_fuel | 1,600 54.7724 19.14168 6.36807 95
renewable | 1,600 29.5413 11.96568 1 64.2207
urban | 1,600 53.6742 14.778 15.95174 91.63234
globalizat~n | 1,600 57.6498 12.71537 26.75758 95
pop_density | 1,600 121.344 210.2646 1 1571.771
democracy | 1,600 2.33346 4.179503 -6.12244 10
corruption | 1,600 52.3523 28.52792 0 100
industry | 1,600 24.6433 6.180478 5.843938 45.32926
services | 1,600 43.5598 9.366089 17.82623 64.07455
trade | 1,600 67.4355 19.36148 10.04306 128.0595
fdi | 1,600 2.98237 4.373857 -11.50437 16.19903
credit | 1,600 53.4402 18.20204 11.32991 123.2399
&lt;/code>&lt;/pre>
&lt;p>The dataset contains 1,600 observations from 80 countries over 20 years (1995&amp;ndash;2014). Log GDP per capita ranges from 6.97 to 11.97, spanning the full income spectrum from about \$1,065 to \$158,000 in synthetic international dollars. Log CO&lt;sub>2&lt;/sub> has a mean of &amp;ndash;19.04 with substantial variation (standard deviation 0.79), reflecting the wide range of development levels in our synthetic panel. With the data loaded, we next visualize the raw income&amp;ndash;pollution relationship.&lt;/p>
&lt;h2 id="3-exploratory-data-analysis">3. Exploratory Data Analysis&lt;/h2>
&lt;p>Before modeling, let us look at the raw relationship between income and emissions.&lt;/p>
&lt;pre>&lt;code class="language-stata">twoway (scatter $outcome ln_gdp, ///
msize(vsmall) mcolor(&amp;quot;106 155 204&amp;quot;%40) msymbol(circle)), ///
ytitle(&amp;quot;Log CO2 per capita&amp;quot;) ///
xtitle(&amp;quot;Log GDP per capita&amp;quot;) ///
title(&amp;quot;Synthetic Data: CO2 vs. Income&amp;quot;, size(medium)) ///
subtitle(&amp;quot;80 countries, 1995-2014 (N = 1,600)&amp;quot;, size(small)) ///
scheme(s2color)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_bma_dsl_fig1_scatter.png" alt="Scatter plot of log CO2 per capita versus log GDP per capita for 80 synthetic countries. The cloud of points shows a clear nonlinear pattern consistent with the inverted-N EKC shape.">&lt;/p>
&lt;p>The scatter reveals a distinctly nonlinear pattern. At low income levels, CO&lt;sub>2&lt;/sub> emissions increase steeply with GDP. At higher income levels, the relationship flattens and bends. This curvature motivates the cubic EKC specification. The diagram below shows the two competing EKC shapes &amp;mdash; the classic inverted-U (quadratic) and the more complex inverted-N (cubic) with its three distinct phases:&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph TD
EKC[&amp;quot;&amp;lt;b&amp;gt;Environmental Kuznets Curve&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;How does pollution change&amp;lt;br/&amp;gt;as income grows?&amp;quot;]
EKC --&amp;gt; IU[&amp;quot;&amp;lt;b&amp;gt;Inverted-U&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Quadratic: β₁ &amp;gt; 0, β₂ &amp;lt; 0&amp;lt;br/&amp;gt;One turning point&amp;quot;]
EKC --&amp;gt; IN[&amp;quot;&amp;lt;b&amp;gt;Inverted-N&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Cubic: β₁ &amp;lt; 0, β₂ &amp;gt; 0, β₃ &amp;lt; 0&amp;lt;br/&amp;gt;Two turning points&amp;quot;]
IN --&amp;gt; P1[&amp;quot;&amp;lt;b&amp;gt;Phase 1: Declining&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Very poor countries&amp;quot;]
IN --&amp;gt; P2[&amp;quot;&amp;lt;b&amp;gt;Phase 2: Rising&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Industrializing countries&amp;quot;]
IN --&amp;gt; P3[&amp;quot;&amp;lt;b&amp;gt;Phase 3: Declining&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Wealthy countries&amp;quot;]
style EKC fill:#141413,stroke:#141413,color:#fff
style IU fill:#6a9bcc,stroke:#141413,color:#fff
style IN fill:#d97757,stroke:#141413,color:#fff
style P1 fill:#00d4c8,stroke:#141413,color:#141413
style P2 fill:#d97757,stroke:#141413,color:#fff
style P3 fill:#00d4c8,stroke:#141413,color:#141413
&lt;/code>&lt;/pre>
&lt;p>For an inverted-N, we need $\beta_1 &amp;lt; 0$, $\beta_2 &amp;gt; 0$, $\beta_3 &amp;lt; 0$. Our synthetic DGP was designed with exactly this sign pattern ($\beta_1 = -7.1$, $\beta_2 = 0.81$, $\beta_3 = -0.03$), so BMA and DSL should recover it &amp;mdash; but can they also correctly identify which of the 12 controls truly matter? Let us start with standard panel regressions to see how sensitive the GDP coefficients are to the choice of controls.&lt;/p>
&lt;h2 id="4-baseline-----standard-fixed-effects">4. Baseline &amp;mdash; Standard Fixed Effects&lt;/h2>
&lt;p>Before reaching for sophisticated methods, let us see what standard panel regressions say. We run two specifications using macros:&lt;/p>
&lt;h3 id="41-sparse-specification">4.1 Sparse specification&lt;/h3>
&lt;pre>&lt;code class="language-stata">reghdfe $outcome $gdp_vars, absorb(country_id year) vce(cluster country_id)
estimates store fe_sparse
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">HDFE Linear regression Number of obs = 1,600
R-squared = 0.9620
Within R-sq. = 0.0354
Number of clusters (country_id) = 80
(Std. err. adjusted for 80 clusters in country_id)
------------------------------------------------------------------------------
| Robust
ln_co2 | Coefficient std. err. t P&amp;gt;|t| [95% conf. interval]
-------------+----------------------------------------------------------------
ln_gdp | -7.498046 1.623988 -4.62 0.000 -10.73051 -4.26558
ln_gdp_sq | .848967 .1704533 4.98 0.000 .5096881 1.188246
ln_gdp_cb | -.0314993 .005931 -5.31 0.000 -.0433047 -.019694
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>The sparse model finds the inverted-N sign pattern ($\beta_1 &amp;lt; 0$, $\beta_2 &amp;gt; 0$, $\beta_3 &amp;lt; 0$), all significant at the 0.1% level with cluster-robust standard errors (clustered at the country level). The within R² is just 0.035 &amp;mdash; the GDP polynomial alone explains only about 3.5% of within-country CO&lt;sub>2&lt;/sub> variation after absorbing country and year fixed effects. The overall R² of 0.96 is high because the country fixed effects capture most of the variation.&lt;/p>
&lt;h3 id="42-kitchen-sink-specification">4.2 Kitchen-sink specification&lt;/h3>
&lt;pre>&lt;code class="language-stata">reghdfe $outcome $gdp_vars $controls, absorb(country_id year) vce(cluster country_id)
estimates store fe_kitchen
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">HDFE Linear regression Number of obs = 1,600
R-squared = 0.9655
Within R-sq. = 0.1249
Number of clusters (country_id) = 80
(Std. err. adjusted for 80 clusters in country_id)
------------------------------------------------------------------------------
| Robust
ln_co2 | Coefficient std. err. t P&amp;gt;|t| [95% conf. interval]
-------------+----------------------------------------------------------------
ln_gdp | -7.130693 1.562581 -4.56 0.000 -10.24093 -4.020453
ln_gdp_sq | .8059928 .1647973 4.89 0.000 .477972 1.134014
ln_gdp_cb | -.0298133 .0057365 -5.20 0.000 -.0412314 -.0183951
fossil_fuel | .0138444 .0014853 9.32 0.000 .010888 .0168008
renewable | -.006795 .0019322 -3.52 0.001 -.0106409 -.0029491
urban | .0057534 .0021432 2.68 0.009 .0014875 .0100192
globalizat~n | .0015186 .0012832 1.18 0.240 -.0010357 .0040728
pop_density | .0000794 .0002303 0.34 0.731 -.000379 .0005378
democracy | -.0002971 .007735 -0.04 0.969 -.0156933 .0150991
corruption | .0009812 .0008415 1.17 0.247 -.0006936 .0026561
industry | .0086336 .0017848 4.84 0.000 .0050811 .0121861
services | -.0005642 .0017205 -0.33 0.744 -.0039889 .0028604
trade | -.0002458 .0007695 -0.32 0.750 -.0017774 .0012858
fdi | -.0017599 .0019509 -0.90 0.370 -.005643 .0021232
credit | -.00139 .0007516 -1.85 0.068 -.002886 .0001061
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>Adding all 12 controls raises the within R² from 0.035 to 0.125 &amp;mdash; a meaningful improvement, though the country and year FE still dominate the overall explanatory power (R² = 0.966). The three strongest true predictors (fossil fuel, industry, urban) are clearly significant, while most noise variables are statistically insignificant. Democracy&amp;rsquo;s estimate (&amp;ndash;0.0003, p = 0.97) is far from its true value (&amp;ndash;0.005) and indistinguishable from zero &amp;mdash; illustrating why weak signals are hard to detect even with the correct model.&lt;/p>
&lt;p>The critical question is: which specification should we trust? The next subsection shows that the GDP coefficients &amp;mdash; and hence the EKC shape &amp;mdash; shift depending on which controls we include.&lt;/p>
&lt;h3 id="43-the-model-uncertainty-problem">4.3 The model uncertainty problem&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Coefficient&lt;/th>
&lt;th>Sparse FE&lt;/th>
&lt;th>Kitchen-Sink FE&lt;/th>
&lt;th>True DGP&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>$\beta_1$ (GDP)&lt;/td>
&lt;td>&amp;ndash;7.498&lt;/td>
&lt;td>&amp;ndash;7.131&lt;/td>
&lt;td>&amp;ndash;7.100&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>$\beta_2$ (GDP²)&lt;/td>
&lt;td>0.849&lt;/td>
&lt;td>0.806&lt;/td>
&lt;td>0.810&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>$\beta_3$ (GDP³)&lt;/td>
&lt;td>&amp;ndash;0.031&lt;/td>
&lt;td>&amp;ndash;0.030&lt;/td>
&lt;td>&amp;ndash;0.030&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Both specifications recover the correct sign pattern, but the magnitudes shift. The kitchen-sink FE estimates (&amp;ndash;7.131, 0.806, &amp;ndash;0.030) are closer to the true DGP values (&amp;ndash;7.100, 0.810, &amp;ndash;0.030) than the sparse FE (&amp;ndash;7.498, 0.849, &amp;ndash;0.031), because the omitted true controls create bias in the sparse model. But which of the 12 controls actually belongs?&lt;/p>
&lt;pre>&lt;code class="language-stata">* Compare coefficients side by side (simplified from analysis.do)
graph twoway ///
(bar value order if spec == &amp;quot;Sparse FE&amp;quot;, ///
barwidth(0.35) color(&amp;quot;106 155 204&amp;quot;)) ///
(bar value order if spec == &amp;quot;Kitchen-Sink FE&amp;quot;, ///
barwidth(0.35) color(&amp;quot;217 119 87&amp;quot;)), ///
xlabel(1 `&amp;quot;&amp;quot;b1&amp;quot; &amp;quot;(GDP)&amp;quot;&amp;quot;' 2 `&amp;quot;&amp;quot;b2&amp;quot; &amp;quot;(GDP sq)&amp;quot;&amp;quot;' 3 `&amp;quot;&amp;quot;b3&amp;quot; &amp;quot;(GDP cb)&amp;quot;&amp;quot;' ///
4 `&amp;quot;&amp;quot;b1&amp;quot; &amp;quot;(GDP)&amp;quot;&amp;quot;' 5 `&amp;quot;&amp;quot;b2&amp;quot; &amp;quot;(GDP sq)&amp;quot;&amp;quot;' 6 `&amp;quot;&amp;quot;b3&amp;quot; &amp;quot;(GDP cb)&amp;quot;&amp;quot;') ///
xline(3.5, lcolor(gs10) lpattern(dash)) ///
ytitle(&amp;quot;Coefficient value&amp;quot;) ///
title(&amp;quot;Coefficient Instability Across Specifications&amp;quot;) ///
legend(order(1 &amp;quot;Sparse FE (no controls)&amp;quot; 2 &amp;quot;Kitchen-Sink FE (all 12 controls)&amp;quot;) ///
rows(1) position(6)) ///
scheme(s2color)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_bma_dsl_fig2_instability.png" alt="Bar chart comparing GDP polynomial coefficients between sparse and kitchen-sink fixed effects specifications. The coefficients shift between the two models, demonstrating model uncertainty.">&lt;/p>
&lt;p>To understand the practical implications of these coefficient shifts, we compute the income thresholds where emissions change direction. The &lt;strong>turning points&lt;/strong> are found by setting the first derivative of the cubic to zero:&lt;/p>
&lt;p>$$x^* = \frac{-\hat{\beta}_2 \pm \sqrt{\hat{\beta}_2^2 - 3\hat{\beta}_1\hat{\beta}_3}}{3\hat{\beta}_3}, \quad \text{GDP}^* = \exp(x^*)$$&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Turning point&lt;/th>
&lt;th>Sparse FE&lt;/th>
&lt;th>Kitchen-Sink FE&lt;/th>
&lt;th>True DGP&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Minimum (CO&lt;sub>2&lt;/sub> starts rising)&lt;/td>
&lt;td>\$2,478&lt;/td>
&lt;td>\$2,426&lt;/td>
&lt;td>\$1,895&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Maximum (CO&lt;sub>2&lt;/sub> starts falling)&lt;/td>
&lt;td>\$25,656&lt;/td>
&lt;td>\$27,694&lt;/td>
&lt;td>\$34,647&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The turning points shift modestly between specifications &amp;mdash; the minimum stays near \$2,400&amp;ndash;\$2,500 while the maximum moves from \$25,656 to \$27,694 depending on controls. Neither matches the true DGP values perfectly, motivating BMA and DSL as principled alternatives to ad hoc control selection.&lt;/p>
&lt;h2 id="5-bayesian-model-averaging">5. Bayesian Model Averaging&lt;/h2>
&lt;h3 id="51-the-idea">5.1 The idea&lt;/h3>
&lt;p>Think of BMA as betting on a horse race. Instead of putting all your money on one model, BMA spreads bets across the field, wagering more on models with better track records.&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph TD
Start[&amp;quot;&amp;lt;b&amp;gt;12 Candidate Controls&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;2¹² = 4,096&amp;lt;br/&amp;gt;possible models&amp;quot;] --&amp;gt; MCMC[&amp;quot;&amp;lt;b&amp;gt;MCMC Sampling&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Draw 50,000 models&amp;quot;]
MCMC --&amp;gt; Post[&amp;quot;&amp;lt;b&amp;gt;Posterior Probability&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Weight by fit × parsimony&amp;quot;]
Post --&amp;gt; Avg[&amp;quot;&amp;lt;b&amp;gt;Weighted Average&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Coefficients averaged&amp;lt;br/&amp;gt;across models&amp;quot;]
Post --&amp;gt; PIP[&amp;quot;&amp;lt;b&amp;gt;PIPs&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Inclusion probability&amp;lt;br/&amp;gt;for each variable&amp;quot;]
style Start fill:#141413,stroke:#141413,color:#fff
style MCMC fill:#6a9bcc,stroke:#141413,color:#fff
style Post fill:#d97757,stroke:#141413,color:#fff
style Avg fill:#00d4c8,stroke:#141413,color:#141413
style PIP fill:#00d4c8,stroke:#141413,color:#141413
&lt;/code>&lt;/pre>
&lt;p>Formally, this betting process follows Bayes' rule, which tells us how to weight models by their fit and complexity.&lt;/p>
&lt;p>&lt;strong>Step 1: Model posterior probabilities.&lt;/strong> The posterior probability of model $M_k$ is:&lt;/p>
&lt;p>$$P(M_k | \text{data}) = \frac{P(\text{data} | M_k) \cdot P(M_k)}{\sum_{l=1}^{K} P(\text{data} | M_l) \cdot P(M_l)}$$&lt;/p>
&lt;p>In words, the probability of model $k$ being correct equals how well it fits the data (the &lt;em>marginal likelihood&lt;/em> $P(\text{data} | M_k)$) times our prior belief ($P(M_k)$), divided by the total across all models. Models that fit the data well &lt;em>and&lt;/em> are parsimonious receive higher posterior weight &amp;mdash; this is BMA&amp;rsquo;s built-in Occam&amp;rsquo;s razor.&lt;/p>
&lt;p>The marginal likelihood $P(\text{data} | M_k)$ is not the same as the ordinary likelihood. It integrates over all possible coefficient values, penalizing models with many parameters that &amp;ldquo;waste&amp;rdquo; probability mass on parameter regions the data does not support:&lt;/p>
&lt;p>$$P(\text{data} | M_k) = \int P(\text{data} | \boldsymbol{\beta}_k, M_k) \, P(\boldsymbol{\beta}_k | M_k) \, d\boldsymbol{\beta}_k$$&lt;/p>
&lt;p>In words, the marginal likelihood asks: &amp;ldquo;If we averaged this model&amp;rsquo;s fit across all plausible coefficient values (weighted by the prior $P(\boldsymbol{\beta}_k | M_k)$), how well does it explain the data?&amp;rdquo; This integral is what makes BMA automatically penalize overly complex models &amp;mdash; a model with many parameters spreads its prior probability thinly across a high-dimensional space, and only recovers that probability if the data strongly supports those extra dimensions.&lt;/p>
&lt;p>&lt;strong>Step 2: Posterior Inclusion Probabilities.&lt;/strong> The &lt;strong>PIP&lt;/strong> for variable $j$ sums the posterior probabilities across all models that include it:&lt;/p>
&lt;p>$$\text{PIP}_j = \sum_{k:\, x_j \in M_k} P(M_k | \text{data})$$&lt;/p>
&lt;p>In words, PIP answers: &amp;ldquo;Across all the models BMA considered, what fraction of the total posterior weight belongs to models that include variable $j$?&amp;rdquo; If fossil fuel appears in every high-probability model, its PIP approaches 1.0. If democracy only appears in low-probability models, its PIP stays near 0.&lt;/p>
&lt;p>&lt;strong>Step 3: BMA posterior mean.&lt;/strong> BMA does not just select variables &amp;mdash; it also produces model-averaged coefficient estimates. The posterior mean of coefficient $\beta_j$ averages across all models, weighted by their posterior probabilities:&lt;/p>
&lt;p>$$\hat{\beta}_j^{\text{BMA}} = \sum_{k=1}^{K} P(M_k | \text{data}) \cdot \hat{\beta}_{j,k}$$&lt;/p>
&lt;p>where $\hat{\beta}_{j,k}$ is the coefficient estimate of variable $j$ in model $M_k$ (set to zero if $j$ is not in $M_k$). In words, the BMA estimate is a weighted average of the coefficient across all models, including models where the variable is absent (contributing zero). This shrinks the coefficient toward zero in proportion to the evidence against inclusion &amp;mdash; a variable with PIP = 0.5 has its BMA coefficient shrunk by roughly half compared to its conditional estimate.&lt;/p>
&lt;p>Think of PIP as a &lt;strong>democratic vote&lt;/strong> across all candidate models. Each model casts a weighted vote for which variables matter, with better-fitting models getting louder voices. &lt;a href="https://doi.org/10.2307/271063" target="_blank" rel="noopener">Raftery (1995)&lt;/a> proposed standard interpretation thresholds based on the strength of evidence:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>PIP range&lt;/th>
&lt;th>Evidence&lt;/th>
&lt;th>Analogy&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>$\geq 0.99$&lt;/td>
&lt;td>Decisive&lt;/td>
&lt;td>Beyond reasonable doubt&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>$0.95 - 0.99$&lt;/td>
&lt;td>Very strong&lt;/td>
&lt;td>Strong consensus&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>$0.80 - 0.95$&lt;/td>
&lt;td>Strong (robust)&lt;/td>
&lt;td>Clear majority&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>$0.50 - 0.80$&lt;/td>
&lt;td>Borderline&lt;/td>
&lt;td>Split vote&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>$&amp;lt; 0.50$&lt;/td>
&lt;td>Weak/none (fragile)&lt;/td>
&lt;td>Minority opinion&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>We use &lt;strong>PIP $\geq$ 0.80&lt;/strong> as our robustness threshold throughout this tutorial &amp;mdash; a variable with PIP above 0.80 appears in the vast majority of the probability-weighted model space, providing &amp;ldquo;strong evidence&amp;rdquo; by Raftery&amp;rsquo;s classification. This is the most widely used cutoff in applied BMA studies.&lt;/p>
&lt;p>A key assumption underlying BMA is that the true data-generating process is well-approximated by a weighted combination of the candidate models (the &amp;ldquo;M-closed&amp;rdquo; assumption). When the candidate set omits important functional forms or interactions, BMA&amp;rsquo;s posterior probabilities may be unreliable.&lt;/p>
&lt;h3 id="52-key-options">5.2 Key options&lt;/h3>
&lt;p>With the conceptual framework in place, we now turn to implementation. Stata 18&amp;rsquo;s &lt;a href="https://www.stata.com/manuals/bmabmaregress.pdf" target="_blank" rel="noopener">&lt;code>bmaregress&lt;/code>&lt;/a> command has three families of options: &lt;strong>priors&lt;/strong> (what you believe before seeing the data), &lt;strong>MCMC controls&lt;/strong> (how the algorithm explores the model space), and &lt;strong>output formatting&lt;/strong> (what gets displayed). The full option list is in the &lt;a href="https://www.stata.com/manuals/bmabmaregress.pdf" target="_blank" rel="noopener">Stata manual&lt;/a>; here we explain the ones used in this tutorial:&lt;/p>
&lt;p>&lt;strong>Prior specifications&lt;/strong> (see &lt;a href="https://www.stata.com/manuals/bmabmaregresspostestimation.pdf" target="_blank" rel="noopener">&lt;code>bmaregress&lt;/code> priors&lt;/a> for alternatives):&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;a href="https://www.stata.com/manuals/bmabmaregress.pdf" target="_blank" rel="noopener">&lt;code>gprior(uip)&lt;/code>&lt;/a>&lt;/strong> &amp;mdash; Unit Information Prior: sets the prior precision on coefficients equal to the information in one observation ($g = N$). This is a standard, relatively uninformative choice that lets the data dominate. Alternatives include &lt;code>gprior(bric)&lt;/code> (benchmark risk inflation criterion, $g = \max(N, p^2)$), &lt;code>gprior(zs)&lt;/code> (Zellner-Siow), and &lt;code>gprior(hyper)&lt;/code> (hyper-g prior with data-driven $g$)&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://www.stata.com/manuals/bmabmaregress.pdf" target="_blank" rel="noopener">&lt;code>mprior(uniform)&lt;/code>&lt;/a>&lt;/strong> &amp;mdash; all $2^{12} = 4{,}096$ models are equally likely a priori; no model is privileged before seeing the data. The alternative &lt;code>mprior(binomial)&lt;/code> applies a beta-binomial prior that penalizes very large or very small models, often producing more conservative PIPs&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>MCMC controls:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>mcmcsize(50000)&lt;/code>&lt;/strong> &amp;mdash; draws 50,000 models from the model space using MC$^3$ (Markov chain Monte Carlo model composition) sampling. Larger values improve posterior estimates but increase computation time&lt;/li>
&lt;li>&lt;strong>&lt;code>burnin(5000)&lt;/code>&lt;/strong> &amp;mdash; discards the first 5,000 draws to allow the chain to reach its stationary distribution before collecting samples&lt;/li>
&lt;li>&lt;strong>&lt;code>rseed(9988)&lt;/code>&lt;/strong> &amp;mdash; fixes the random number seed for exact reproducibility. Students running the same command will get identical results&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://www.stata.com/manuals/bmabmaregress.pdf" target="_blank" rel="noopener">&lt;code>groupfv&lt;/code>&lt;/a>&lt;/strong> &amp;mdash; treats all dummies from a single factor variable as one group that enters or exits models together. Without &lt;code>groupfv&lt;/code>, writing &lt;code>i.country_id&lt;/code> would create 80 individual dummy variables, and BMA would consider including or excluding each one independently &amp;mdash; producing an astronomical model space ($2^{80}$ combinations of country dummies alone) that is both computationally infeasible and conceptually meaningless. With &lt;code>groupfv&lt;/code>, the 80 country dummies move as a &lt;em>package&lt;/em>: either all 80 are in the model or none are. Think of it like hiring a sports team &amp;mdash; you recruit the whole roster, not individual players one by one. In the output, this is why you see &amp;ldquo;Groups = 15&amp;rdquo; instead of 113: BMA treats the 80 country dummies as 1 group, the 19 year dummies as 1 group, and each of the 12 candidate controls + 3 GDP terms as their own groups ($1 + 1 + 15 = 17$, minus 2 that are &amp;ldquo;always&amp;rdquo; included = 15 groups subject to selection)&lt;/li>
&lt;li>&lt;strong>&lt;code>($fe, always)&lt;/code>&lt;/strong> &amp;mdash; country and year fixed effects are always included in every model; they are not subject to model selection. This is standard practice in panel data BMA: we want to control for unobserved country and time heterogeneity in &lt;em>every&lt;/em> model, and only let BMA decide about the candidate controls&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Output formatting:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>pipcutoff(0.8)&lt;/code>&lt;/strong> &amp;mdash; display only variables with PIP above 0.80 in the output table. This is a &lt;em>display&lt;/em> threshold only &amp;mdash; it does not affect the underlying estimation&lt;/li>
&lt;li>&lt;strong>&lt;code>inputorder&lt;/code>&lt;/strong> &amp;mdash; display variables in the order they were specified in the command, rather than sorted by PIP&lt;/li>
&lt;/ul>
&lt;h3 id="53-estimation">5.3 Estimation&lt;/h3>
&lt;pre>&lt;code class="language-stata">bmaregress $outcome $gdp_vars $controls ///
($fe, always), ///
mprior(uniform) groupfv gprior(uip) ///
mcmcsize(50000) rseed(9988) inputorder pipcutoff(0.8)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Bayesian model averaging No. of obs = 1,600
Linear regression No. of predictors = 113
MC3 sampling Groups = 15
Always = 98
No. of models = 163
Priors: Mean model size = 104.578
Models: Uniform MCMC sample size = 50,000
Coef.: Zellner's g Acceptance rate = 0.0904
g: Unit-information, g = 1,600 Shrinkage, g/(1+g) = 0.9994
Sampling correlation = 0.9997
------------------------------------------------------------------------------
ln_co2 | Mean Std. dev. Group PIP
-------------+----------------------------------------------------------------
ln_gdp | -7.13901 1.811093 1 .99401
ln_gdp_sq | .8078437 .1892418 2 .99991
ln_gdp_cb | -.0299182 .0065105 3 .99976
fossil_fuel | .0138139 .001283 4 1
renewable | -.0068332 .0023506 5 .95945
industry | .0085503 .0019766 11 .99867
------------------------------------------------------------------------------
Note: 9 predictors with PIP less than .8 not shown.
&lt;/code>&lt;/pre>
&lt;blockquote>
&lt;p>The Stata output says &amp;ldquo;PIP less than .8&amp;rdquo; because we set &lt;code>pipcutoff(0.8)&lt;/code> as the display threshold &amp;mdash; only variables exceeding this stricter robustness criterion appear in the table. The 9 hidden variables are the two weak true controls (urban, democracy) and all 7 noise variables (services, trade, FDI, credit, population density, corruption, globalization). Figure 3 below shows PIP values for all 15 variables.&lt;/p>
&lt;/blockquote>
&lt;p>The output shows 113 predictors in 15 groups: the 80 country dummies (grouped as 1 by &lt;code>groupfv&lt;/code>) + 19 year dummies (grouped as 1) + 12 candidate controls (each its own group) + the 3 GDP terms (each its own group) = 15 selection groups total, with 98 variables &amp;ldquo;always&amp;rdquo; included (the country and year FE). BMA sampled 163 distinct models out of 4,096 possible. This might seem low, but the MC$^3$ algorithm does not need to visit every model &amp;mdash; it concentrates on the high-posterior-probability region. The sampling correlation of 0.9997 (very close to 1.0) confirms that the MC$^3$ chain adequately explored the model space &amp;mdash; the posterior probability is concentrated on a relatively small number of high-quality models. The acceptance rate of 0.09 is below the typical 20&amp;ndash;40% range, but the high sampling correlation provides reassurance that the results are reliable. Six variables have PIP above the 0.80 robustness threshold: the three GDP terms (PIP = 0.994&amp;ndash;1.000) and three of the five true controls &amp;mdash; fossil fuel (PIP = 1.000), industry (PIP = 0.999), and renewable energy (PIP = 0.959). The BMA posterior means (&amp;ndash;7.139, 0.808, &amp;ndash;0.030) are remarkably close to the true DGP values (&amp;ndash;7.100, 0.810, &amp;ndash;0.030), substantially closer than the sparse FE estimates.&lt;/p>
&lt;p>Two true controls &amp;mdash; urban (coefficient 0.007) and democracy (coefficient &amp;ndash;0.005) &amp;mdash; have PIPs well below 0.80. Their true effects are small, making them hard to distinguish from noise. This is a realistic limitation: even a powerful method like BMA struggles with weak signals.&lt;/p>
&lt;h3 id="54-turning-points">5.4 Turning points&lt;/h3>
&lt;p>Using the BMA posterior means, the turning points are:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Minimum:&lt;/strong> \$2,411 GDP per capita (true: \$1,895)&lt;/li>
&lt;li>&lt;strong>Maximum:&lt;/strong> \$27,269 GDP per capita (true: \$34,647)&lt;/li>
&lt;/ul>
&lt;p>Both turning points are in the right ballpark but not exact. The turning point formula amplifies small differences across all three coefficients &amp;mdash; even though each BMA posterior mean is within 1% of the true DGP value, the compound effect shifts the maximum turning point from \$34,647 (true) to \$27,269 (BMA). The inverted-N shape is clearly recovered.&lt;/p>
&lt;h3 id="55-posterior-inclusion-probabilities">5.5 Posterior Inclusion Probabilities&lt;/h3>
&lt;p>The PIP chart is BMA&amp;rsquo;s signature output. We extract PIPs from the estimation results, label each variable, and color-code bars by ground truth: steel blue for true predictors, gray for noise.&lt;/p>
&lt;pre>&lt;code class="language-stata">* Extract PIPs and create a horizontal bar chart
matrix pip_mat = e(pip)
* ... (create dataset of variable names and PIPs, add readable labels) ...
* Mark true vs noise predictors
gen is_true = inlist(varname, &amp;quot;fossil_fuel&amp;quot;, &amp;quot;renewable&amp;quot;, &amp;quot;urban&amp;quot;, ///
&amp;quot;democracy&amp;quot;, &amp;quot;industry&amp;quot;, &amp;quot;ln_gdp&amp;quot;, &amp;quot;ln_gdp_sq&amp;quot;, &amp;quot;ln_gdp_cb&amp;quot;)
gsort -pip
graph twoway ///
(bar pip order if is_true == 1, horizontal barwidth(0.6) ///
color(&amp;quot;106 155 204&amp;quot;)) ///
(bar pip order if is_true == 0, horizontal barwidth(0.6) ///
color(gs11)), ///
xline(0.8, lcolor(&amp;quot;217 119 87&amp;quot;) lpattern(dash) lwidth(medium)) ///
ylabel(1(1)15, valuelabel angle(0) labsize(small)) ///
xlabel(0(0.2)1, format(%3.1f)) ///
xtitle(&amp;quot;Posterior Inclusion Probability (PIP)&amp;quot;) ///
title(&amp;quot;BMA: Which Variables Matter?&amp;quot;) ///
legend(order(1 &amp;quot;True predictor (in DGP)&amp;quot; 2 &amp;quot;Noise variable (not in DGP)&amp;quot;) ///
rows(1) position(6)) ///
scheme(s2color)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_bma_dsl_fig3_pip.png" alt="Horizontal bar chart showing Posterior Inclusion Probabilities for all 15 variables. True predictors are colored in steel blue, noise variables in gray. A dashed orange line marks the 0.80 robustness threshold.">&lt;/p>
&lt;p>The PIP chart cleanly separates the variables into two groups. At the top (PIP near 1.0): fossil fuel share, GDP terms, industry, and renewable energy &amp;mdash; all true predictors correctly identified. At the bottom (PIP near 0.0): the seven noise variables (globalization, corruption, services, trade, FDI, credit, population density) plus urban population and democracy. BMA correctly assigns zero-like PIPs to all noise variables, and correctly flags 3 of 5 true predictors as robust. The two misses (urban, democracy) have small true coefficients (0.007 and &amp;ndash;0.005), making them genuinely hard to detect.&lt;/p>
&lt;h3 id="56-coefficient-density-plots">5.6 Coefficient density plots&lt;/h3>
&lt;p>The &lt;a href="https://www.stata.com/manuals/bmabmagraphcoefdensity.pdf" target="_blank" rel="noopener">&lt;code>bmagraph coefdensity&lt;/code>&lt;/a> command shows the posterior distribution of each coefficient across all sampled models. We plot all six variables with PIP above 0.80 in a 3x2 grid &amp;mdash; the three GDP polynomial terms (top row) and the three robust controls (bottom row). In each panel, the blue curve shows the density conditional on the variable being included in the model, and the red horizontal line shows the probability of noninclusion (1 &amp;ndash; PIP). When the red line is flat near zero and the blue curve is far from zero, the variable is strongly supported.&lt;/p>
&lt;pre>&lt;code class="language-stata">* Consistent formatting for all panels
local panel_opts `&amp;quot; xtitle(&amp;quot;Coefficient value&amp;quot;, size(vsmall)) &amp;quot;'
local panel_opts `&amp;quot; `panel_opts' ytitle(&amp;quot;Density&amp;quot;, size(vsmall)) &amp;quot;'
local panel_opts `&amp;quot; `panel_opts' ylabel(, labsize(vsmall) angle(0)) &amp;quot;'
local panel_opts `&amp;quot; `panel_opts' xlabel(, labsize(vsmall)) &amp;quot;'
local panel_opts `&amp;quot; `panel_opts' legend(off) scheme(s2color) &amp;quot;'
* Generate density for all 6 robust variables (PIP &amp;gt; 0.80)
bmagraph coefdensity ln_gdp, title(&amp;quot;GDP per capita (log)&amp;quot;, size(small)) `panel_opts' name(dens_gdp, replace)
bmagraph coefdensity ln_gdp_sq, title(&amp;quot;GDP squared (log)&amp;quot;, size(small)) `panel_opts' name(dens_gdp_sq, replace)
bmagraph coefdensity ln_gdp_cb, title(&amp;quot;GDP cubed (log)&amp;quot;, size(small)) `panel_opts' name(dens_gdp_cb, replace)
bmagraph coefdensity fossil_fuel, title(&amp;quot;Fossil fuel share (%)&amp;quot;, size(small)) `panel_opts' name(dens_fossil, replace)
bmagraph coefdensity renewable, title(&amp;quot;Renewable energy (%)&amp;quot;, size(small)) `panel_opts' name(dens_renewable, replace)
bmagraph coefdensity industry, title(&amp;quot;Industry VA (% GDP)&amp;quot;, size(small)) `panel_opts' name(dens_industry, replace)
graph combine dens_gdp dens_gdp_sq dens_gdp_cb ///
dens_fossil dens_renewable dens_industry, ///
cols(3) rows(2) imargin(small) ///
title(&amp;quot;BMA: Posterior Coefficient Densities&amp;quot;, size(medsmall)) ///
subtitle(&amp;quot;All 6 robust variables (PIP &amp;gt; 0.80)&amp;quot;, size(small)) ///
note(&amp;quot;Blue curve = posterior density conditional on inclusion.&amp;quot; ///
&amp;quot;Red line = probability of noninclusion (1 - PIP).&amp;quot; ///
&amp;quot;Near-zero red line + blue curve far from zero = strong evidence.&amp;quot;, size(vsmall)) ///
scheme(s2color) xsize(12) ysize(7)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_bma_dsl_fig4_coefdensity.png" alt="Posterior coefficient density plots for all six robust variables in a 3x2 grid. Top row: GDP linear, squared, and cubic terms. Bottom row: fossil fuel, renewable energy, and industry. All densities are concentrated well away from zero.">&lt;/p>
&lt;p>All six densities are concentrated well away from zero, confirming that every variable with PIP above 0.80 has a genuinely non-zero effect. The three GDP terms (top row) form the inverted-N polynomial: the linear term is centered near &amp;ndash;7.1 (true: &amp;ndash;7.1), the squared term near +0.81 (true: +0.81), and the cubic term near &amp;ndash;0.030 (true: &amp;ndash;0.030). The three controls (bottom row) show tight, unimodal densities: fossil fuel near +0.014 (true: +0.015), renewable energy near &amp;ndash;0.007 (true: &amp;ndash;0.010), and industry near +0.009 (true: +0.010). Renewable energy&amp;rsquo;s posterior mean (&amp;ndash;0.007) is slightly attenuated compared to the true value (&amp;ndash;0.010), reflecting the BMA shrinkage that occurs when a variable&amp;rsquo;s PIP is below 1.0 &amp;mdash; models that exclude it pull the average toward zero.&lt;/p>
&lt;h3 id="57-pooled-bma-without-fixed-effects">5.7 Pooled BMA (without fixed effects)&lt;/h3>
&lt;p>To parallel the pooled DSL comparison in Section 6.6, we also run BMA without country or year fixed effects &amp;mdash; treating the panel as a pooled cross-section. This removes the &lt;code>($fe, always)&lt;/code> and &lt;code>groupfv&lt;/code> options, leaving only the 12 candidate controls and 3 GDP terms as predictors (15 total, vs 113 with FE).&lt;/p>
&lt;pre>&lt;code class="language-stata">* BMA without FE -- pooled cross-section
bmaregress ln_co2 ln_gdp ln_gdp_sq ln_gdp_cb ///
fossil_fuel renewable urban industry democracy ///
services trade fdi credit pop_density ///
corruption globalization, ///
mprior(uniform) gprior(uip) ///
mcmcsize(50000) rseed(9988) pipcutoff(0.5) burnin(5000)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Bayesian model averaging No. of obs = 1,600
Linear regression No. of predictors = 15
MC3 sampling Groups = 15
Always = 0
No. of models = 34
Priors: Mean model size = 11.978
Models: Uniform MCMC sample size = 50,000
Coef.: Zellner's g Acceptance rate = 0.0733
g: Unit-information, g = 1,600 Shrinkage, g/(1+g) = 0.9994
Sampling correlation = 0.9996
------------------------------------------------------------------------------
ln_co2 | Mean Std. dev. Group PIP
-------------+----------------------------------------------------------------
ln_gdp | -21.25807 1.641676 1 1
ln_gdp_sq | 2.284729 .1748838 2 1
ln_gdp_cb | -.0813937 .0061308 3 1
fossil_fuel | .0188853 .0010554 4 1
renewable | -.0192089 .0013911 5 1
urban | .0103139 .0012072 6 1
industry | .0138361 .0023478 7 1
services | .0164633 .0016573 9 1
pop_density | -.0004314 .0000567 13 1
credit | .0041017 .0008414 12 .99984
trade | -.0020939 .001084 10 .86009
democracy | .007879 .0042984 8 .84142
------------------------------------------------------------------------------
Note: 3 predictors with PIP less than .5 not shown.
&lt;/code>&lt;/pre>
&lt;p>The pooled BMA results are striking in two ways. First, the GDP coefficients are severely biased &amp;mdash; the same pattern as pooled DSL: $\beta_1 = -21.26$ (true: &amp;ndash;7.10), $\beta_2 = 2.28$ (true: 0.81), $\beta_3 = -0.081$ (true: &amp;ndash;0.03). Without country fixed effects, the GDP terms absorb persistent cross-country differences in emissions levels, inflating the coefficients by a factor of 2&amp;ndash;3x.&lt;/p>
&lt;p>Second, the PIPs tell a completely different story than with FE. Without fixed effects, &lt;strong>12 of 15 variables have PIP above 0.80&lt;/strong> &amp;mdash; including noise variables like services (PIP = 1.000), population density (PIP = 1.000), credit (PIP = 1.000), and trade (PIP = 0.860). With FE, only 6 variables cleared the 0.80 threshold and all 7 noise variables had PIPs near zero. The pooled BMA commits &lt;strong>5 false positives&lt;/strong> (services, pop_density, credit, trade, and democracy incorrectly flagged as robust noise variables or given inflated PIPs) compared to &lt;strong>zero&lt;/strong> false positives with FE. This happens because the noise variables are correlated with omitted country effects &amp;mdash; without FE to absorb those effects, the correlations create spurious associations that BMA interprets as genuine predictive power.&lt;/p>
&lt;p>The turning points (\$5,752 minimum, \$23,298 maximum) are far from the truth, and the 95% credible intervals fail to cover the true values for all three GDP terms &amp;mdash; the same coverage failure seen in pooled DSL. The lesson is clear: &lt;strong>fixed effects are not optional in panel BMA&lt;/strong>. They are essential for correct variable selection, not just coefficient estimation.&lt;/p>
&lt;h2 id="6-post-double-selection-lasso">6. Post-Double-Selection LASSO&lt;/h2>
&lt;h3 id="61-the-idea">6.1 The idea&lt;/h3>
&lt;p>Stata&amp;rsquo;s &lt;a href="https://www.stata.com/manuals/lassodsregress.pdf" target="_blank" rel="noopener">&lt;code>dsregress&lt;/code>&lt;/a> implements the &lt;strong>post-double-selection&lt;/strong> method of Belloni, Chernozhukov, and Hansen (2014). Think of it as a smart research assistant who reads the data twice &amp;mdash; once to find controls that predict the outcome (CO&lt;sub>2&lt;/sub>), and again to find controls that predict the variables of interest (GDP terms) &amp;mdash; then runs a clean OLS regression using only the controls that survived at least one selection.&lt;/p>
&lt;p>The &amp;ldquo;double&amp;rdquo; in double-selection refers to the &lt;strong>union&lt;/strong> of two separate LASSO selections. Why is this union necessary? If a control variable predicts both CO&lt;sub>2&lt;/sub> &lt;em>and&lt;/em> GDP but a single LASSO run on CO&lt;sub>2&lt;/sub> happens to miss it, omitting it from the final regression would bias the GDP coefficient. The second LASSO step (on GDP) catches variables that the first step might miss, and vice versa.&lt;/p>
&lt;p>The algorithm has four steps:&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph TD
Controls[&amp;quot;&amp;lt;b&amp;gt;12 Candidate Controls&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;+ country &amp;amp; year FE&amp;quot;]
Controls --&amp;gt; Step1[&amp;quot;&amp;lt;b&amp;gt;Step 1: LASSO on Outcome&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;CO2 ~ all controls&amp;lt;br/&amp;gt;→ Selected set X̃y&amp;quot;]
Controls --&amp;gt; Step2[&amp;quot;&amp;lt;b&amp;gt;Step 2: LASSO on Each Variable of Interest&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;GDP ~ all controls → X̃₁&amp;lt;br/&amp;gt;GDP² ~ all controls → X̃₂&amp;lt;br/&amp;gt;GDP³ ~ all controls → X̃₃&amp;quot;]
Step1 --&amp;gt; Union[&amp;quot;&amp;lt;b&amp;gt;Step 3: Take the Union&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;X̂ = X̃y ∪ X̃₁ ∪ X̃₂ ∪ X̃₃&amp;lt;br/&amp;gt;Only controls surviving&amp;lt;br/&amp;gt;at least one selection&amp;quot;]
Step2 --&amp;gt; Union
Union --&amp;gt; OLS[&amp;quot;&amp;lt;b&amp;gt;Step 4: Final OLS&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;CO2 ~ GDP + GDP² + GDP³ + X̂&amp;lt;br/&amp;gt;Standard OLS with valid&amp;lt;br/&amp;gt;inference on GDP terms&amp;quot;]
style Controls fill:#141413,stroke:#141413,color:#fff
style Step1 fill:#6a9bcc,stroke:#141413,color:#fff
style Step2 fill:#d97757,stroke:#141413,color:#fff
style Union fill:#1a3a8a,stroke:#141413,color:#fff
style OLS fill:#00d4c8,stroke:#141413,color:#141413
&lt;/code>&lt;/pre>
&lt;p>At the heart of each LASSO step is a penalized regression that shrinks irrelevant coefficients to exactly zero:&lt;/p>
&lt;p>$$\hat{\boldsymbol{\beta}}^{\text{LASSO}} = \arg\min_{\boldsymbol{\beta}} \left\{ \frac{1}{2N} \sum_{i=1}^{N}(y_i - \mathbf{x}_i'\boldsymbol{\beta})^2 + \lambda \sum_{j=1}^{p} |\beta_j| \right\}$$&lt;/p>
&lt;p>In words, LASSO minimizes the sum of squared residuals (the usual OLS objective) plus a penalty term $\lambda \sum |\beta_j|$ that charges a cost proportional to the &lt;em>absolute value&lt;/em> of each coefficient. The tuning parameter $\lambda$ controls how harsh this penalty is &amp;mdash; think of it as a &amp;ldquo;strictness dial.&amp;rdquo; When $\lambda = 0$, LASSO is just OLS. As $\lambda$ increases, more coefficients are forced to exactly zero. The L1 (absolute value) penalty is what makes LASSO a variable selector: unlike the L2 (squared) penalty used in Ridge regression, the L1 penalty has sharp corners at zero that drive weak coefficients to exactly zero rather than merely shrinking them.&lt;/p>
&lt;p>&lt;strong>Why &amp;ldquo;double&amp;rdquo; selection?&lt;/strong> The key insight of Belloni, Chernozhukov, and Hansen (2014) is that a single LASSO selection can miss important confounders. Consider our panel setting. We want to estimate the effect of GDP terms ($\mathbf{D}$) on CO&lt;sub>2&lt;/sub> ($Y$), controlling for other variables ($\mathbf{W}$). The model is:&lt;/p>
&lt;p>$$Y_i = \mathbf{D}_i' \boldsymbol{\alpha} + \mathbf{W}_i' \boldsymbol{\beta} + \varepsilon_i$$&lt;/p>
&lt;p>A confounder $W_j$ that affects both $Y$ and $\mathbf{D}$ must be included to avoid omitted variable bias. But if $W_j$ has a weak effect on $Y$, the LASSO on $Y$ might miss it. The double-selection strategy solves this by running LASSO twice:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Step 1&lt;/strong> selects controls that predict $Y$: $\quad \hat{S}_Y = \{j : \hat{\beta}_j^{\text{LASSO}(Y)} \neq 0\}$&lt;/li>
&lt;li>&lt;strong>Step 2&lt;/strong> selects controls that predict each $D_k$: $\quad \hat{S}_{D_k} = \{j : \hat{\gamma}_{j,k}^{\text{LASSO}(D_k)} \neq 0\}$&lt;/li>
&lt;li>&lt;strong>Step 3&lt;/strong> takes the union: $\quad \hat{S} = \hat{S}_Y \cup \hat{S}_{D_1} \cup \hat{S}_{D_2} \cup \hat{S}_{D_3}$&lt;/li>
&lt;li>&lt;strong>Step 4&lt;/strong> runs OLS of $Y$ on $\mathbf{D}$ and $\mathbf{W}_{\hat{S}}$ with standard inference&lt;/li>
&lt;/ul>
&lt;p>The union in Step 3 ensures that a confounder missed by the $Y$-LASSO but caught by the $D$-LASSO is still included. This &amp;ldquo;safety net&amp;rdquo; property is what gives post-double-selection its valid inference guarantees &amp;mdash; the final OLS produces consistent estimates of $\boldsymbol{\alpha}$ even if each individual LASSO makes some selection mistakes.&lt;/p>
&lt;p>The &lt;code>dsregress&lt;/code> command uses a &amp;ldquo;plugin&amp;rdquo; method to choose $\lambda$ &amp;mdash; an analytical formula that sets the penalty based on the sample size and noise level, without requiring cross-validation. A key assumption underlying DSL is &lt;em>approximate sparsity&lt;/em>: only a small number of controls truly matter, so LASSO can safely set the rest to zero. When the true model is dense (many small effects rather than a few large ones), LASSO may struggle to select the right variables.&lt;/p>
&lt;p>Before implementing DSL, it helps to see the two methods side by side:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Feature&lt;/th>
&lt;th>BMA&lt;/th>
&lt;th>Post-Double-Selection&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Philosophy&lt;/td>
&lt;td>Bayesian (posteriors)&lt;/td>
&lt;td>Frequentist (p-values)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Strategy&lt;/td>
&lt;td>Average across models&lt;/td>
&lt;td>Select controls, then OLS&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Output&lt;/td>
&lt;td>PIPs for every variable&lt;/td>
&lt;td>Set of selected controls&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Speed&lt;/td>
&lt;td>Minutes (MCMC)&lt;/td>
&lt;td>Seconds (optimization)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Reference&lt;/td>
&lt;td>Raftery et al. (1997)&lt;/td>
&lt;td>Belloni, Chernozhukov, Hansen (2014)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="62-key-options">6.2 Key options&lt;/h3>
&lt;p>With the algorithm clear, let us examine the Stata implementation. The &lt;a href="https://www.stata.com/manuals/lassodsregress.pdf" target="_blank" rel="noopener">&lt;code>dsregress&lt;/code>&lt;/a> command has a concise syntax, but each element plays a specific role. The full option list is in the &lt;a href="https://www.stata.com/manuals/lasso.pdf" target="_blank" rel="noopener">Stata LASSO manual&lt;/a>; here we explain the ones used in this tutorial:&lt;/p>
&lt;p>&lt;strong>Syntax structure:&lt;/strong> &lt;code>dsregress depvar varsofinterest, controls(controlvars) [options]&lt;/code>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>$outcome&lt;/code>&lt;/strong> (&lt;code>ln_co2&lt;/code>) &amp;mdash; the dependent variable. DSL will run LASSO on this variable against all controls (Step 1)&lt;/li>
&lt;li>&lt;strong>&lt;code>$gdp_vars&lt;/code>&lt;/strong> (&lt;code>ln_gdp ln_gdp_sq ln_gdp_cb&lt;/code>) &amp;mdash; the &lt;em>variables of interest&lt;/em>. These are never penalized by LASSO; they always appear in the final OLS. DSL runs a separate LASSO for each one against all controls (Steps 2a&amp;ndash;2c)&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://www.stata.com/manuals/lassodsregress.pdf" target="_blank" rel="noopener">&lt;code>controls(($fe) $controls)&lt;/code>&lt;/a>&lt;/strong> &amp;mdash; the candidate controls subject to LASSO selection. Parentheses around &lt;code>$fe&lt;/code> tell Stata to treat factor variables (country and year dummies) as always-included in the LASSO penalty but available for selection. The 12 candidate controls are subject to the standard LASSO penalty&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://www.stata.com/manuals/lassodsregress.pdf" target="_blank" rel="noopener">&lt;code>vce(cluster country_id)&lt;/code>&lt;/a>&lt;/strong> &amp;mdash; compute cluster-robust standard errors at the country level in the final OLS (Step 4). This also affects the LASSO penalty through the &lt;a href="https://www.stata.com/manuals/lassolasso.pdf" target="_blank" rel="noopener">&lt;code>selection(plugin)&lt;/code>&lt;/a> method, which adjusts $\lambda$ for cluster dependence&lt;/li>
&lt;li>&lt;strong>&lt;code>selection(plugin)&lt;/code>&lt;/strong> (default) &amp;mdash; choose $\lambda$ using a data-driven analytical formula rather than cross-validation. The alternative &lt;a href="https://www.stata.com/manuals/lassolasso.pdf" target="_blank" rel="noopener">&lt;code>selection(cv)&lt;/code>&lt;/a> uses cross-validation but is slower&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://www.stata.com/manuals/lassolassoinfo.pdf" target="_blank" rel="noopener">&lt;code>lassoinfo&lt;/code>&lt;/a>&lt;/strong> (post-estimation) &amp;mdash; reports the number of selected controls and the $\lambda$ value for each LASSO step&lt;/li>
&lt;li>&lt;strong>&lt;a href="https://www.stata.com/manuals/lassolassocoef.pdf" target="_blank" rel="noopener">&lt;code>lassocoef&lt;/code>&lt;/a>&lt;/strong> (post-estimation) &amp;mdash; displays which specific variables were selected or dropped by LASSO&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>Related commands.&lt;/strong> Stata also offers &lt;a href="https://www.stata.com/manuals/lassoporegress.pdf" target="_blank" rel="noopener">&lt;code>poregress&lt;/code>&lt;/a> (partialing-out regression), which &lt;em>residualizes&lt;/em> both the outcome and the treatment against all controls instead of selecting then regressing. Both methods provide valid inference. &lt;a href="https://www.stata.com/manuals/lassoxporegress.pdf" target="_blank" rel="noopener">&lt;code>xporegress&lt;/code>&lt;/a> extends this to cross-fit partialing-out for even more robust inference. This tutorial uses &lt;code>dsregress&lt;/code> because its select-then-regress logic is more intuitive for beginners.&lt;/p>
&lt;/blockquote>
&lt;h3 id="63-estimation">6.3 Estimation&lt;/h3>
&lt;pre>&lt;code class="language-stata">dsregress $outcome $gdp_vars, ///
controls(($fe) $controls) ///
vce(cluster country_id)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Double-selection linear model Number of obs = 1,600
Number of controls = 112
Number of selected controls = 102
Wald chi2(3) = 53.15
Prob &amp;gt; chi2 = 0.0000
(Std. err. adjusted for 80 clusters in country_id)
------------------------------------------------------------------------------
| Robust
ln_co2 | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
ln_gdp | -7.433319 1.628321 -4.57 0.000 -10.62477 -4.241868
ln_gdp_sq | .8401567 .1713522 4.90 0.000 .5043126 1.176001
ln_gdp_cb | -.0310764 .005952 -5.22 0.000 -.0427421 -.0194107
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>Post-double-selection completed in seconds with cluster-robust standard errors at the country level. Internally, &lt;code>dsregress&lt;/code> ran four separate LASSO regressions (Step 1 on CO&lt;sub>2&lt;/sub>, Steps 2a&amp;ndash;2c on each GDP term), took the union of all selected controls, and then ran a final OLS of CO&lt;sub>2&lt;/sub> on the GDP terms plus that union. All three GDP terms are significant at the 0.1% level. The Wald test strongly rejects the null that GDP terms are jointly zero ($\chi^2 = 53.15$, p &amp;lt; 0.001).&lt;/p>
&lt;h3 id="64-turning-points">6.4 Turning points&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Minimum:&lt;/strong> \$2,429 GDP per capita (true: \$1,895)&lt;/li>
&lt;li>&lt;strong>Maximum:&lt;/strong> \$27,672 GDP per capita (true: \$34,647)&lt;/li>
&lt;/ul>
&lt;p>The post-double-selection turning points (\$2,429 and \$27,672) fall between the sparse FE and kitchen-sink estimates, closer to the BMA values. With cluster-robust standard errors, the LASSO selection retained 102 of 112 controls for the outcome equation and 100 for each GDP term. The union of selected controls in Step 3 includes a few more candidate variables than without clustering, producing coefficients (&amp;ndash;7.433, 0.840, &amp;ndash;0.031) that lie between the sparse and kitchen-sink specifications.&lt;/p>
&lt;h3 id="65-lasso-selection">6.5 LASSO selection&lt;/h3>
&lt;p>To understand which controls LASSO kept and which it dropped, we inspect the selection details:&lt;/p>
&lt;pre>&lt;code class="language-stata">lassoinfo
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> Estimate: active
Command: dsregress
------------------------------------------------------
| No. of
| Selection selected
Variable | Model method lambda variables
------------+-----------------------------------------
ln_co2 | linear plugin .3818852 102
ln_gdp | linear plugin .3818852 100
ln_gdp_sq | linear plugin .3818852 100
ln_gdp_cb | linear plugin .3818852 100
------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>The &lt;code>lassoinfo&lt;/code> output shows each of the four LASSO steps. The outcome equation selected 102 of 112 controls, while each GDP equation selected 100. The 112 candidates include 80 country dummies + 19 year dummies = 99 FE dummies, plus the 12 candidate variables and the constant. LASSO retains nearly all informative FE dummies and drops about 10&amp;ndash;12 of the weakest candidates at each step. The union across all four steps (Step 3) yields the final control set for Step 4&amp;rsquo;s OLS. With cluster-robust standard errors, the lambda is larger (0.382 vs 0.090 without clustering), leading to slightly different selection and producing DSL coefficients (&amp;ndash;7.433, 0.840, &amp;ndash;0.031) that fall between the sparse and kitchen-sink FE.&lt;/p>
&lt;p>Why does DSL not match BMA&amp;rsquo;s accuracy here? In panel data settings where FE dummies dominate the control set (99 of 112 variables), LASSO retains nearly all FE dummies and has limited room to discriminate among the 12 candidate controls of interest &amp;mdash; it dropped only 10&amp;ndash;12 variables at each step, most of them weak FE dummies rather than noise controls. This &amp;ldquo;almost everything selected&amp;rdquo; outcome means DSL&amp;rsquo;s final OLS is close to the kitchen-sink specification, which explains why its coefficients (&amp;ndash;7.433, 0.840, &amp;ndash;0.031) fall between sparse and kitchen-sink FE rather than converging to the true DGP. To see LASSO&amp;rsquo;s selection power unleashed, we next run DSL &lt;em>without&lt;/em> fixed effects.&lt;/p>
&lt;h3 id="66-pooled-dsl-without-fixed-effects">6.6 Pooled DSL (without fixed effects)&lt;/h3>
&lt;p>What happens when LASSO has only 12 candidate controls instead of 112? To answer this, we run DSL on the pooled data &amp;mdash; treating the panel as a cross-sectional dataset without country or year fixed effects. This gives LASSO full room to discriminate among the candidate controls, but at the cost of omitting the unobserved country heterogeneity that fixed effects would absorb.&lt;/p>
&lt;pre>&lt;code class="language-stata">* DSL without FE -- pooled cross-section with cluster-robust SEs
dsregress $outcome $gdp_vars, ///
controls($controls) ///
vce(cluster country_id)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Double-selection linear model Number of obs = 1,600
Number of controls = 12
Number of selected controls = 7
Wald chi2(3) = 25.05
Prob &amp;gt; chi2 = 0.0000
(Std. err. adjusted for 80 clusters in country_id)
------------------------------------------------------------------------------
| Robust
ln_co2 | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
ln_gdp | -22.03297 5.277295 -4.18 0.000 -32.37628 -11.68966
ln_gdp_sq | 2.366878 .5652276 4.19 0.000 1.259052 3.474703
ln_gdp_cb | -.084224 .0199055 -4.23 0.000 -.1232381 -.04521
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>The pooled DSL still finds the correct inverted-N sign pattern ($\beta_1 &amp;lt; 0$, $\beta_2 &amp;gt; 0$, $\beta_3 &amp;lt; 0$), but the magnitudes are dramatically different from the true DGP. The linear coefficient (&amp;ndash;22.03) is more than &lt;em>three times&lt;/em> the true value (&amp;ndash;7.10), and the other terms are similarly inflated. This is &lt;strong>omitted variable bias&lt;/strong>: without country fixed effects, the GDP terms absorb not only their own effect on CO&lt;sub>2&lt;/sub> but also the persistent cross-country differences in emissions levels that fixed effects would have captured.&lt;/p>
&lt;pre>&lt;code class="language-stata">lassoinfo
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> Estimate: active
Command: dsregress
------------------------------------------------------
| No. of
| Selection selected
Variable | Model method lambda variables
------------+-----------------------------------------
ln_co2 | linear plugin .3818852 5
ln_gdp | linear plugin .3818852 7
ln_gdp_sq | linear plugin .3818852 7
ln_gdp_cb | linear plugin .3818852 7
------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>Now the contrast with the FE-based DSL is stark. The outcome LASSO selected only &lt;strong>5 of 12&lt;/strong> controls (vs 102 of 112 with FE), and the GDP LASSOes selected &lt;strong>7 of 12&lt;/strong> (vs 100 of 112). Without FE dummies flooding the candidate set, LASSO can genuinely discriminate &amp;mdash; it zeroed out 5&amp;ndash;7 controls as irrelevant. The turning points are \$5,581 (minimum) and \$24,532 (maximum), far from the true values.&lt;/p>
&lt;p>This comparison illustrates a fundamental tradeoff in panel data econometrics: &lt;strong>fixed effects remove bias but limit LASSO&amp;rsquo;s selection power&lt;/strong>. With FE, the estimates are unbiased but LASSO selects almost everything. Without FE, LASSO selects sharply but the estimates are biased by unobserved heterogeneity. The FE-based DSL from Section 6.3 is the correct specification for this data, even though LASSO&amp;rsquo;s selection looks less impressive.&lt;/p>
&lt;h2 id="7-head-to-head-comparison">7. Head-to-Head Comparison&lt;/h2>
&lt;h3 id="71-coefficient-comparison">7.1 Coefficient comparison&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>Sparse FE&lt;/th>
&lt;th>Kitchen-Sink FE&lt;/th>
&lt;th>BMA (FE)&lt;/th>
&lt;th>DSL (FE)&lt;/th>
&lt;th>BMA (pooled)&lt;/th>
&lt;th>DSL (pooled)&lt;/th>
&lt;th>True DGP&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>$\beta_1$ (GDP)&lt;/td>
&lt;td>&amp;ndash;7.498&lt;/td>
&lt;td>&amp;ndash;7.131&lt;/td>
&lt;td>&amp;ndash;7.139&lt;/td>
&lt;td>&amp;ndash;7.433&lt;/td>
&lt;td>&amp;ndash;21.258&lt;/td>
&lt;td>&amp;ndash;22.033&lt;/td>
&lt;td>&amp;ndash;7.100&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>$\beta_2$ (GDP²)&lt;/td>
&lt;td>0.849&lt;/td>
&lt;td>0.806&lt;/td>
&lt;td>0.808&lt;/td>
&lt;td>0.840&lt;/td>
&lt;td>2.285&lt;/td>
&lt;td>2.367&lt;/td>
&lt;td>0.810&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>$\beta_3$ (GDP³)&lt;/td>
&lt;td>&amp;ndash;0.031&lt;/td>
&lt;td>&amp;ndash;0.030&lt;/td>
&lt;td>&amp;ndash;0.030&lt;/td>
&lt;td>&amp;ndash;0.031&lt;/td>
&lt;td>&amp;ndash;0.081&lt;/td>
&lt;td>&amp;ndash;0.084&lt;/td>
&lt;td>&amp;ndash;0.030&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Min TP&lt;/strong>&lt;/td>
&lt;td>\$2,478&lt;/td>
&lt;td>\$2,426&lt;/td>
&lt;td>\$2,411&lt;/td>
&lt;td>\$2,429&lt;/td>
&lt;td>\$5,752&lt;/td>
&lt;td>\$5,581&lt;/td>
&lt;td>\$1,895&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Max TP&lt;/strong>&lt;/td>
&lt;td>\$25,656&lt;/td>
&lt;td>\$27,694&lt;/td>
&lt;td>\$27,269&lt;/td>
&lt;td>\$27,672&lt;/td>
&lt;td>\$23,298&lt;/td>
&lt;td>\$24,532&lt;/td>
&lt;td>\$34,647&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The table reveals a sharp divide between FE-based and pooled specifications. The four FE-based methods (columns 2&amp;ndash;5) all produce GDP coefficients within a narrow range of the true values &amp;mdash; BMA (FE) and Kitchen-Sink FE are closest, with estimates within 1% of the truth. The two pooled methods (columns 6&amp;ndash;7) are dramatically biased, with coefficients inflated 2&amp;ndash;3x. Strikingly, BMA (pooled) and DSL (pooled) agree closely with &lt;em>each other&lt;/em> (&amp;ndash;21.26 vs &amp;ndash;22.03 for $\beta_1$), confirming that the bias comes from omitting fixed effects, not from the choice of variable selection method. Both pooled methods produce turning points displaced from the truth (\$5,600&amp;ndash;5,800 vs true \$1,895 for the minimum).&lt;/p>
&lt;h3 id="72-uncertainty-confidence-and-credible-intervals">7.2 Uncertainty: confidence and credible intervals&lt;/h3>
&lt;p>Point estimates tell only half the story. How &lt;em>uncertain&lt;/em> is each method, and does the interval actually contain the truth? The table below shows 95% confidence intervals (for the frequentist methods) and approximate 95% credible intervals (for BMA, computed as posterior mean $\pm$ 2 posterior SD). The last column checks whether the true DGP value falls inside the interval.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>$\beta_1$ (GDP) interval&lt;/th>
&lt;th>Covers true?&lt;/th>
&lt;th>$\beta_2$ (GDP²) interval&lt;/th>
&lt;th>Covers true?&lt;/th>
&lt;th>$\beta_3$ (GDP³) interval&lt;/th>
&lt;th>Covers true?&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Sparse FE&lt;/strong>&lt;/td>
&lt;td>[&amp;ndash;10.731, &amp;ndash;4.266]&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>[0.510, 1.188]&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>[&amp;ndash;0.043, &amp;ndash;0.020]&lt;/td>
&lt;td>Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Kitchen-Sink FE&lt;/strong>&lt;/td>
&lt;td>[&amp;ndash;10.241, &amp;ndash;4.021]&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>[0.478, 1.134]&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>[&amp;ndash;0.041, &amp;ndash;0.018]&lt;/td>
&lt;td>Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>BMA (FE)&lt;/strong> (credible)&lt;/td>
&lt;td>[&amp;ndash;10.761, &amp;ndash;3.517]&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>[0.429, 1.186]&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>[&amp;ndash;0.043, &amp;ndash;0.017]&lt;/td>
&lt;td>Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>DSL (FE)&lt;/strong>&lt;/td>
&lt;td>[&amp;ndash;10.625, &amp;ndash;4.242]&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>[0.504, 1.176]&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>[&amp;ndash;0.043, &amp;ndash;0.019]&lt;/td>
&lt;td>Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>BMA (pooled)&lt;/strong> (credible)&lt;/td>
&lt;td>[&amp;ndash;24.541, &amp;ndash;17.975]&lt;/td>
&lt;td>&lt;strong>No&lt;/strong>&lt;/td>
&lt;td>[1.935, 2.635]&lt;/td>
&lt;td>&lt;strong>No&lt;/strong>&lt;/td>
&lt;td>[&amp;ndash;0.094, &amp;ndash;0.069]&lt;/td>
&lt;td>&lt;strong>No&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>DSL (pooled)&lt;/strong>&lt;/td>
&lt;td>[&amp;ndash;32.376, &amp;ndash;11.690]&lt;/td>
&lt;td>&lt;strong>No&lt;/strong>&lt;/td>
&lt;td>[1.259, 3.475]&lt;/td>
&lt;td>&lt;strong>No&lt;/strong>&lt;/td>
&lt;td>[&amp;ndash;0.123, &amp;ndash;0.045]&lt;/td>
&lt;td>&lt;strong>No&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>True DGP&lt;/strong>&lt;/td>
&lt;td>&amp;ndash;7.100&lt;/td>
&lt;td>&lt;/td>
&lt;td>0.810&lt;/td>
&lt;td>&lt;/td>
&lt;td>&amp;ndash;0.030&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The four FE-based methods all produce intervals that contain the true parameter values &amp;mdash; a reassuring result. Both pooled methods, however, &lt;strong>fail to cover the truth for any of the three coefficients&lt;/strong>. The pooled DSL intervals are wide (the $\beta_1$ interval spans 20.7 units) but centered so far from the truth that even this width cannot compensate. The pooled BMA credible intervals are actually &lt;em>narrower&lt;/em> (spanning 6.6 units for $\beta_1$) but even more precisely wrong &amp;mdash; they are tightly concentrated around the biased estimate. This is the worst-case scenario: &lt;strong>false precision from a misspecified model&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>Width reflects uncertainty.&lt;/strong> Among the FE-based methods, BMA produces the widest interval for $\beta_1$ (width = 7.24), followed by Sparse FE (6.47), DSL with FE (6.38), and Kitchen-Sink FE (6.22). BMA&amp;rsquo;s wider intervals reflect its honest accounting of model uncertainty &amp;mdash; it averages across thousands of models, each contributing slightly different coefficient estimates, which inflates the posterior standard deviation. The frequentist methods condition on a single model and therefore understate the total uncertainty.&lt;/p>
&lt;p>&lt;strong>Centering reflects bias.&lt;/strong> Kitchen-Sink FE and BMA center their intervals closest to the true value (&amp;ndash;7.131 and &amp;ndash;7.139 vs. true &amp;ndash;7.100), while Sparse FE (&amp;ndash;7.498) and DSL with FE (&amp;ndash;7.433) are slightly further away. The pooled DSL (&amp;ndash;22.033) is dramatically off-center, illustrating that omitted variable bias overwhelms any precision gained from better variable selection.&lt;/p>
&lt;p>&lt;strong>Coverage requires correct specification.&lt;/strong> The pooled DSL result drives home a critical lesson: a confidence interval is only as good as the model behind it. The 95% label promises that, in repeated sampling, 95% of intervals would contain the truth &amp;mdash; but this guarantee holds only if the model is correctly specified. When country fixed effects are omitted, the model is misspecified, and the intervals fail despite being statistically &amp;ldquo;valid&amp;rdquo; within the pooled framework.&lt;/p>
&lt;p>&lt;strong>Bayesian vs frequentist interpretation.&lt;/strong> BMA&amp;rsquo;s credible intervals have a different interpretation: a 95% BMA credible interval says &amp;ldquo;given the data and priors, there is a 95% posterior probability the true coefficient lies in this range,&amp;rdquo; while a 95% confidence interval says &amp;ldquo;if we repeated this procedure many times, 95% of the intervals would contain the truth.&amp;rdquo; In practice, both require correct model specification to be reliable.&lt;/p>
&lt;h3 id="73-predicted-ekc-curves">7.3 Predicted EKC curves&lt;/h3>
&lt;p>The curves are normalized to zero at the sample-mean GDP so both methods are directly comparable:&lt;/p>
&lt;pre>&lt;code class="language-stata">* Generate predicted EKC curves for BMA and DSL, normalized at mean GDP
summarize ln_gdp
local xmin = r(min)
local xmax = r(max)
local xmean = r(mean)
clear
set obs 500
gen lngdp = `xmin' + (_n - 1) * (`xmax' - `xmin') / 499
* Cubic component for each method (using stored coefficients)
gen fit_bma = `b1_bma' * lngdp + `b2_bma' * lngdp^2 + `b3_bma' * lngdp^3
gen fit_dsl = `b1_dsl' * lngdp + `b2_dsl' * lngdp^2 + `b3_dsl' * lngdp^3
* Normalize: subtract value at sample-mean GDP
local norm_bma = `b1_bma' * `xmean' + `b2_bma' * `xmean'^2 + `b3_bma' * `xmean'^3
local norm_dsl = `b1_dsl' * `xmean' + `b2_dsl' * `xmean'^2 + `b3_dsl' * `xmean'^3
replace fit_bma = fit_bma - `norm_bma'
replace fit_dsl = fit_dsl - `norm_dsl'
twoway ///
(line fit_bma lngdp, lcolor(&amp;quot;106 155 204&amp;quot;) lwidth(medthick)) ///
(line fit_dsl lngdp, lcolor(&amp;quot;217 119 87&amp;quot;) lwidth(medthick) lpattern(dash)), ///
xline(`lnmin_bma', lcolor(&amp;quot;106 155 204&amp;quot;%50) lpattern(shortdash)) ///
xline(`lnmax_bma', lcolor(&amp;quot;106 155 204&amp;quot;%50) lpattern(shortdash)) ///
ytitle(&amp;quot;Predicted log CO2 (normalized at mean GDP)&amp;quot;) ///
xtitle(&amp;quot;Log GDP per capita&amp;quot;) ///
title(&amp;quot;Predicted EKC Shape: BMA vs. DSL&amp;quot;) ///
legend(order(1 &amp;quot;BMA&amp;quot; 2 &amp;quot;DSL&amp;quot;) rows(1) position(6)) ///
scheme(s2color)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_bma_dsl_fig5_ekc_curves.png" alt="Predicted EKC curves from BMA and DSL, normalized at the sample mean. Both methods trace a clear inverted-N shape with closely aligned turning points.">&lt;/p>
&lt;p>Both curves trace a clear inverted-N: CO&lt;sub>2&lt;/sub> falls at low incomes, rises through industrialization, and falls again at high incomes. The BMA curve (solid blue) and DSL curve (dashed orange) are nearly indistinguishable, with turning points closely aligned. The normalization at mean GDP makes the shape immediately visible &amp;mdash; a major improvement over plotting raw cubic components that would sit at different y-levels.&lt;/p>
&lt;h3 id="74-answer-key-grading-the-methods">7.4 Answer key: grading the methods&lt;/h3>
&lt;p>The ultimate test: do BMA and DSL correctly identify the 5 true predictors and reject the 7 noise variables?&lt;/p>
&lt;pre>&lt;code class="language-stata">* Dot plot: BMA PIPs color-coded by ground truth
* (extract PIPs, label variables, mark true vs noise --- see analysis.do)
graph twoway ///
(scatter order pip if is_true == 1, ///
mcolor(&amp;quot;106 155 204&amp;quot;) msymbol(circle) msize(large)) ///
(scatter order pip if is_true == 0, ///
mcolor(gs9) msymbol(diamond) msize(large)), ///
xline(0.8, lcolor(&amp;quot;217 119 87&amp;quot;) lpattern(dash) lwidth(medium)) ///
ylabel(1(1)15, valuelabel angle(0) labsize(small)) ///
xlabel(0(0.2)1, format(%3.1f)) ///
xtitle(&amp;quot;BMA Posterior Inclusion Probability&amp;quot;) ///
title(&amp;quot;Answer Key: Do BMA and DSL Recover the Truth?&amp;quot;) ///
legend(order(1 &amp;quot;True predictor&amp;quot; 2 &amp;quot;Noise variable&amp;quot;) ///
rows(1) position(6)) ///
scheme(s2color)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_bma_dsl_fig6_answer_key.png" alt="Dot plot showing BMA Posterior Inclusion Probabilities for each variable, color-coded by ground truth. True predictors (circles, blue) cluster above the 0.80 threshold; noise variables (diamonds, gray) cluster below it.">&lt;/p>
&lt;p>&lt;strong>BMA&amp;rsquo;s report card:&lt;/strong> Of the 8 true predictors (3 GDP terms + 5 controls), BMA correctly assigns PIP &amp;gt; 0.80 to 6 &amp;mdash; the three GDP terms, fossil fuel, industry, and renewable energy. It misses urban (PIP ~ 0.27) and democracy (PIP ~ 0.02), whose true coefficients are small (0.007 and &amp;ndash;0.005). All 7 noise variables receive PIPs well below 0.80. BMA makes &lt;strong>zero false positives&lt;/strong> (no noise variable incorrectly flagged as robust) and &lt;strong>two false negatives&lt;/strong> (two weak true predictors missed).&lt;/p>
&lt;p>&lt;strong>Post-double-selection&amp;rsquo;s report card:&lt;/strong> With cluster-robust SEs, the union of all four LASSO steps selected 102 of 112 total controls (including FE dummies). The resulting DSL coefficients (&amp;ndash;7.433, 0.840, &amp;ndash;0.031) fall between the sparse and kitchen-sink FE, closer to the true DGP than the sparse specification. The entire procedure runs in seconds rather than minutes.&lt;/p>
&lt;p>&lt;strong>Bottom line:&lt;/strong> Both methods recover the inverted-N EKC shape. BMA provides more granular variable-level inference (PIPs), while DSL provides fast, valid coefficient estimates. The synthetic data &amp;ldquo;answer key&amp;rdquo; confirms that both are doing their job &amp;mdash; with the expected limitation that weak signals are hard to detect.&lt;/p>
&lt;h2 id="8-discussion">8. Discussion&lt;/h2>
&lt;h3 id="81-what-the-results-mean-for-the-ekc">8.1 What the results mean for the EKC&lt;/h3>
&lt;p>Both BMA and DSL identify the &lt;strong>inverted-N&lt;/strong> EKC shape with turning points close to the true DGP values. BMA correctly identifies 6 of 8 true predictors (3 GDP terms + fossil fuel, industry, renewable) with zero false positives among noise variables. The inverted-N shape implies three phases of the income&amp;ndash;pollution relationship:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Declining phase&lt;/strong> (below ~\$2,400): Very poor countries where CO&lt;sub>2&lt;/sub> may fall as subsistence agriculture shifts toward slightly cleaner energy.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Rising phase&lt;/strong> (~\$2,400 to ~\$27,000): Industrializing countries where emissions rise sharply. Most of the world&amp;rsquo;s population lives here.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Declining phase&lt;/strong> (above ~\$27,000): Wealthy countries where clean technology and regulation reduce emissions.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>The policy implication is important: the inverted-N suggests that the &amp;ldquo;environmental improvement&amp;rdquo; phase is not automatic. Unlike the simpler inverted-U hypothesis, which predicts a single turning point after which pollution monotonically declines, the inverted-N warns that countries at very low income levels may &lt;em>already&lt;/em> be on a declining emissions path that reverses once industrialization begins. This makes the middle-income range &amp;mdash; where emissions rise steeply &amp;mdash; the critical window for environmental policy intervention.&lt;/p>
&lt;p>The three robust control variables identified by BMA reinforce this narrative:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Fossil fuel dependence&lt;/strong> (PIP = 1.000) is the single strongest predictor of CO&lt;sub>2&lt;/sub> emissions, with a coefficient close to the true DGP value.&lt;/li>
&lt;li>&lt;strong>Renewable energy share&lt;/strong> (PIP = 0.959) enters with a negative sign, confirming that energy mix transitions reduce emissions.&lt;/li>
&lt;li>&lt;strong>Industry value-added&lt;/strong> (PIP = 0.999) captures the composition effect &amp;mdash; economies dominated by manufacturing produce more CO&lt;sub>2&lt;/sub> per unit of GDP than service-based economies.&lt;/li>
&lt;/ul>
&lt;h3 id="82-when-to-use-bma-vs-post-double-selection">8.2 When to use BMA vs post-double-selection&lt;/h3>
&lt;p>The two methods answer fundamentally different research questions:&lt;/p>
&lt;p>&lt;strong>Use BMA&lt;/strong> when the question is &lt;em>&amp;ldquo;which variables robustly predict the outcome?&amp;quot;&lt;/em> BMA provides PIPs, coefficient densities, and a complete picture of the model space. It excels in exploratory settings where variable importance is the goal. In our simulation, BMA produced the most accurate coefficient estimates (&amp;ndash;7.139 vs true &amp;ndash;7.100) and provided rich diagnostics (PIP chart, density plots) that make the evidence for each variable transparent. The cost is computational: BMA requires MCMC sampling (minutes to hours depending on the model space).&lt;/p>
&lt;p>&lt;strong>Use post-double-selection&lt;/strong> when the question is &lt;em>&amp;ldquo;what is the causal effect of a specific variable of interest, controlling for high-dimensional confounders?&amp;quot;&lt;/em> DSL provides fast, valid inference on the coefficients of interest with standard errors and confidence intervals. It is designed for settings where you have a clear treatment variable and many potential controls. In our simulation, DSL completed in seconds and produced valid standard errors, but its coefficient estimates (&amp;ndash;7.433) were less accurate than BMA&amp;rsquo;s because LASSO had limited room to discriminate among controls in the FE-heavy panel setting.&lt;/p>
&lt;p>&lt;strong>Use both together&lt;/strong> (as in this tutorial) when you want the strongest possible evidence. If a Bayesian and a frequentist method agree on the sign, magnitude, and significance of an effect, the finding is unlikely to be an artifact of any single modeling choice. Disagreements between the methods are also informative &amp;mdash; they signal areas where the evidence is sensitive to assumptions.&lt;/p>
&lt;h3 id="83-pooled-vs-fixed-effects-a-cautionary-comparison">8.3 Pooled vs fixed effects: a cautionary comparison&lt;/h3>
&lt;p>The pooled specifications (Sections 5.7 and 6.6) provide a powerful pedagogical contrast. When we strip away fixed effects and run both BMA and DSL on pooled data, three things happen simultaneously:&lt;/p>
&lt;p>&lt;strong>LASSO selection improves but estimates worsen.&lt;/strong> Without 99 FE dummies diluting the candidate set, LASSO in pooled DSL selected only 5&amp;ndash;7 of 12 controls (vs 102 of 112 with FE). This is closer to the &amp;ldquo;textbook&amp;rdquo; LASSO scenario where the method has genuine discriminating power. Yet the resulting coefficient estimates are 2&amp;ndash;3x the true values because omitted country heterogeneity biases everything.&lt;/p>
&lt;p>&lt;strong>BMA PIPs become unreliable.&lt;/strong> With fixed effects, BMA assigned PIP near zero to all 7 noise variables &amp;mdash; zero false positives. Without FE, 5 noise variables (services, pop_density, credit, trade, and inflated democracy) received PIPs above 0.80. The noise variables are correlated with omitted country effects, and BMA interprets these spurious correlations as genuine predictive power. This demonstrates that &lt;strong>PIP thresholds are only meaningful when the model set is correctly specified&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>Both methods agree on the bias.&lt;/strong> Pooled BMA and pooled DSL produce remarkably similar biased coefficients ($\beta_1 = -21.26$ vs $-22.03$), confirming that the problem is not the variable selection method but the omitted fixed effects. The agreement between a Bayesian and a frequentist method on the &lt;em>wrong&lt;/em> answer reinforces the lesson: &lt;strong>method agreement is not a substitute for correct model specification&lt;/strong>.&lt;/p>
&lt;p>The practical takeaway for applied researchers: in panel data settings, always include entity fixed effects (or equivalent controls for unobserved heterogeneity) before applying BMA or DSL. Running these methods on pooled data without FE will produce misleading results &amp;mdash; not because the methods fail, but because the models they average over or select from are all misspecified.&lt;/p>
&lt;h3 id="84-limitations-and-caveats">8.4 Limitations and caveats&lt;/h3>
&lt;p>&lt;strong>Synthetic vs real data.&lt;/strong> This is synthetic data &amp;mdash; the patterns are sharper than real-world data, and we can verify ground truth only because we designed the DGP. With real data, model uncertainty is genuinely unresolvable, and there is no answer key to check against. The separation between true predictors and noise variables is cleaner here than in most applications.&lt;/p>
&lt;p>&lt;strong>Weak signals are hard to detect.&lt;/strong> Both methods missed urban population (PIP = 0.27) and democracy (PIP = 0.02), whose true coefficients are small (0.007 and &amp;ndash;0.005). This is not a failure of the methods &amp;mdash; it is a fundamental statistical limitation. Detecting a coefficient of 0.005 in the presence of panel-level noise requires either a much larger sample or a stronger signal.&lt;/p>
&lt;p>&lt;strong>Panel FE and LASSO.&lt;/strong> In our panel setting, 99 of 112 candidate controls are FE dummies that LASSO retains almost entirely. This limits DSL&amp;rsquo;s ability to discriminate among the 12 candidate controls. In cross-sectional settings or settings with many genuinely irrelevant variables, DSL would have more room to operate and potentially match BMA&amp;rsquo;s accuracy.&lt;/p>
&lt;p>&lt;strong>Extensions.&lt;/strong> Researchers working with real EKC data should also consider endogeneity (via 2SLS-BMA, as in Gravina and Lanzafame, 2025), alternative pollutants (SO&lt;sub>2&lt;/sub>, PM2.5), spatial dependence across countries, and structural breaks in the income&amp;ndash;pollution relationship.&lt;/p>
&lt;h2 id="9-summary-and-next-steps">9. Summary and Next Steps&lt;/h2>
&lt;h3 id="takeaways">Takeaways&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Both methods confirm the inverted-N shape.&lt;/strong> BMA (Bayesian, averaging across models) and post-double-selection (frequentist, LASSO-based) both recover the inverted-N EKC. BMA produces coefficients closest to the true DGP (&amp;ndash;7.139 vs &amp;ndash;7.100 for $\beta_1$). DSL with cluster-robust SEs gives &amp;ndash;7.433, falling between the sparse and kitchen-sink FE. Both methods outperform the naive sparse specification.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Both methods recover the ground truth.&lt;/strong> BMA correctly identifies 6 of 8 true predictors with zero false positives. The three strongest true controls (fossil fuel, industry, renewable energy) all receive PIPs above 0.95. The two misses (urban, democracy) have small true coefficients, illustrating that even good methods have limits with weak signals.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Model uncertainty is real.&lt;/strong> The GDP linear coefficient shifts from &amp;ndash;7.498 (sparse) to &amp;ndash;7.131 (kitchen-sink) depending on which controls are included. The maximum turning point moves by \$2,000. BMA and DSL provide principled solutions.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>BMA and post-double-selection serve different purposes.&lt;/strong> BMA excels at variable selection (PIPs, coefficient densities) and produced the most accurate coefficient estimates in this setting. Post-double-selection is fastest and provides standard frequentist inference with cluster-robust SEs. In panel settings dominated by FE dummies, LASSO has limited room to discriminate among candidate controls; DSL would be more powerful in cross-sectional settings with many irrelevant variables.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Fixed effects are essential, not optional.&lt;/strong> Running either method on pooled data without FE produces coefficients inflated 2&amp;ndash;3x (BMA pooled: &amp;ndash;21.26, DSL pooled: &amp;ndash;22.03, vs true &amp;ndash;7.10 for $\beta_1$). Worse, pooled BMA assigns high PIPs to 5 noise variables that the FE-based BMA correctly rejects. Confidence and credible intervals from pooled models fail to cover the true values for all three coefficients. The lesson: always include fixed effects in panel data before applying variable selection methods.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="exercises">Exercises&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Sensitivity to the g-prior.&lt;/strong> Re-run &lt;code>bmaregress&lt;/code> with &lt;code>gprior(bric)&lt;/code> instead of &lt;code>gprior(uip)&lt;/code>. The BIC prior penalizes model complexity more heavily. Do the PIPs change? Does it still identify fossil fuel, industry, and renewable as robust? (&lt;em>Hint:&lt;/em> BIC priors tend to be more conservative, so borderline variables may drop below the threshold.)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Test for inverted-U.&lt;/strong> Drop &lt;code>ln_gdp_cb&lt;/code> and re-run with only linear and squared GDP terms. What do BMA and DSL say about the simpler quadratic specification? (&lt;em>Hint:&lt;/em> since the DGP includes a cubic term, the quadratic model is misspecified &amp;mdash; check whether the coefficients absorb the cubic effect or produce a visibly different EKC shape.)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Increase noise.&lt;/strong> Re-generate the synthetic data with &lt;code>sigma_eps = 0.30&lt;/code> (double the noise) in &lt;code>generate_data.do&lt;/code> and re-run the full analysis. How does this affect BMA&amp;rsquo;s ability to distinguish true predictors from noise? (&lt;em>Hint:&lt;/em> expect more variables with PIPs in the ambiguous 0.3&amp;ndash;0.7 range, and possibly some noise variables crossing the 0.80 threshold &amp;mdash; false positives become more likely with noisier data.)&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="appendix-a-first-differences-analysis">Appendix A: First-Differences Analysis&lt;/h2>
&lt;h3 id="a1-motivation">A.1 Motivation&lt;/h3>
&lt;p>The fixed effects estimator removes time-invariant country heterogeneity by demeaning each variable within country. An alternative approach is &lt;strong>first differencing&lt;/strong>: computing the change between the last and first year for each country ($\Delta x_i = x_{i,2014} - x_{i,1995}$). This also removes time-invariant effects and produces a pure &lt;strong>cross-sectional&lt;/strong> dataset of 80 observations &amp;mdash; one per country. The cross-sectional setting is where LASSO-based methods are most powerful, because there are no FE dummies diluting the candidate set.&lt;/p>
&lt;p>The tradeoff is statistical power: first differencing uses only two data points per country (discarding 18 intermediate years), while the within-estimator uses all 20. We expect noisier estimates but cleaner variable selection.&lt;/p>
&lt;h3 id="a2-constructing-the-first-difference-dataset">A.2 Constructing the first-difference dataset&lt;/h3>
&lt;pre>&lt;code class="language-stata">* Keep only first (1995) and last (2014) years, reshape, compute differences
keep if year == 1995 | year == 2014
reshape wide $outcome $gdp_vars $controls, i(country_id) j(year)
foreach v in $outcome $gdp_vars $controls {
gen d_`v' = `v'2014 - `v'1995
}
&lt;/code>&lt;/pre>
&lt;p>This produces 80 observations, each representing how much a country&amp;rsquo;s variables changed over the 20-year period. For example, &lt;code>d_ln_gdp&lt;/code> measures the log growth in GDP per capita from 1995 to 2014.&lt;/p>
&lt;h3 id="a3-baseline-ols-on-first-differences">A.3 Baseline OLS on first differences&lt;/h3>
&lt;pre>&lt;code class="language-stata">* Sparse: GDP terms only
regress d_ln_co2 d_ln_gdp d_ln_gdp_sq d_ln_gdp_cb, robust
* Kitchen-sink: all 12 controls
regress d_ln_co2 d_ln_gdp d_ln_gdp_sq d_ln_gdp_cb ///
d_fossil_fuel d_renewable d_urban d_industry d_democracy ///
d_services d_trade d_fdi d_credit d_pop_density ///
d_corruption d_globalization, robust
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>FD Sparse OLS:&lt;/strong>&lt;/p>
&lt;pre>&lt;code class="language-text">Linear regression Number of obs = 80
Prob &amp;gt; F = 0.0009
R-squared = 0.1433
------------------------------------------------------------------------------
| Robust
d_ln_co2 | Coefficient std. err. t P&amp;gt;|t| [95% conf. interval]
-------------+----------------------------------------------------------------
d_ln_gdp | -10.36189 4.092422 -2.53 0.013 -18.51265 -2.211121
d_ln_gdp_sq | 1.155962 .4223643 2.74 0.008 .3147506 1.997173
d_ln_gdp_cb | -.0414947 .0143721 -2.89 0.005 -.0701192 -.0128702
_cons | -.3036562 .0724366 -4.19 0.000 -.4479262 -.1593861
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>FD Kitchen-sink OLS:&lt;/strong>&lt;/p>
&lt;pre>&lt;code class="language-text">Linear regression Number of obs = 80
Prob &amp;gt; F = 0.0029
R-squared = 0.3707
------------------------------------------------------------------------------
| Robust
d_ln_co2 | Coefficient std. err. t P&amp;gt;|t| [95% conf. interval]
-------------+----------------------------------------------------------------
d_ln_gdp | -8.109709 5.031758 -1.61 0.112 -18.1618 1.942382
d_ln_gdp_sq | .9238864 .5213262 1.77 0.081 -.1175823 1.965355
d_ln_gdp_cb | -.0336221 .0179583 -1.87 0.066 -.0694979 .0022536
d_fossil_f~l | .0147108 .0067313 2.19 0.033 .0012635 .0281582
d_renewable | -.0237808 .0110384 -2.15 0.035 -.0458327 -.001729
d_urban | .0002501 .014913 0.02 0.987 -.0295421 .0300424
d_industry | .0309085 .0105974 2.92 0.005 .0097377 .0520793
d_democracy | .019337 .0290345 0.67 0.508 -.038666 .07734
d_services | -.0047239 .0098816 -0.48 0.634 -.0244647 .0150169
d_trade | .006726 .0044062 1.53 0.132 -.0020764 .0155284
d_fdi | .0000124 .0091898 0.00 0.999 -.0183463 .0183712
d_credit | .0028644 .0043456 0.66 0.512 -.0058169 .0115457
d_pop_dens~y | .0006396 .0004991 1.28 0.205 -.0003575 .0016366
d_corruption | -.0036115 .0033497 -1.08 0.285 -.0103033 .0030803
d_globaliz~n | -.0004567 .0082494 -0.06 0.956 -.0169368 .0160235
_cons | -.0085823 .1746184 -0.05 0.961 -.3574226 .340258
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>The FD sparse OLS finds the inverted-N sign pattern with all three terms significant at the 5% level &amp;mdash; but the coefficients are noisier than the FE estimates (e.g., $\beta_1 = -10.36$ vs &amp;ndash;7.50 for sparse FE). The R² of 0.14 is low, reflecting the loss of within-country time-series variation when collapsing 20 years into a single difference.&lt;/p>
&lt;p>Adding controls in the kitchen-sink raises R² to 0.37 but makes the GDP terms individually insignificant (p = 0.07&amp;ndash;0.11) &amp;mdash; a consequence of having only 80 observations and 15 regressors. Among the controls, fossil fuel (p = 0.033), renewable energy (p = 0.035), and industry (p = 0.005) are significant &amp;mdash; the same three strong predictors identified by BMA with fixed effects.&lt;/p>
&lt;h3 id="a4-bma-on-first-differences">A.4 BMA on first differences&lt;/h3>
&lt;pre>&lt;code class="language-stata">bmaregress d_ln_co2 d_ln_gdp d_ln_gdp_sq d_ln_gdp_cb ///
d_fossil_fuel d_renewable d_urban d_industry d_democracy ///
d_services d_trade d_fdi d_credit d_pop_density ///
d_corruption d_globalization, ///
mprior(uniform) gprior(uip) ///
mcmcsize(50000) rseed(9988) pipcutoff(0.5) burnin(5000)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Bayesian model averaging No. of obs = 80
Linear regression No. of predictors = 15
MC3 sampling Groups = 15
Always = 0
No. of models = 2,317
For CPMP &amp;gt;= .9 = 581
Priors: Mean model size = 3.304
Models: Uniform Burn-in = 5,000
Cons.: Noninformative MCMC sample size = 50,000
Coef.: Zellner's g Acceptance rate = 0.3080
g: Unit-information, g = 80 Shrinkage, g/(1+g) = 0.9877
sigma2: Noninformative Mean sigma2 = 0.051
Sampling correlation = 0.9958
------------------------------------------------------------------------------
d_ln_co2 | Mean Std. dev. Group PIP
-------------+----------------------------------------------------------------
d_industry | .0364834 .0090778 7 .99823
------------------------------------------------------------------------------
Note: 14 predictors with PIP less than .5 not shown.
&lt;/code>&lt;/pre>
&lt;p>The FD-BMA result is dramatically different from the FE-based BMA. Only &lt;strong>one variable&lt;/strong> passes the 0.50 PIP display threshold: the change in industry share (PIP = 0.998). The three GDP polynomial terms all have PIPs below 0.30:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Variable&lt;/th>
&lt;th>PIP (FD-BMA)&lt;/th>
&lt;th>PIP (FE-BMA)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>d_ln_gdp&lt;/td>
&lt;td>0.298&lt;/td>
&lt;td>0.994&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>d_ln_gdp_sq&lt;/td>
&lt;td>0.267&lt;/td>
&lt;td>1.000&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>d_ln_gdp_cb&lt;/td>
&lt;td>0.271&lt;/td>
&lt;td>1.000&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>d_fossil_fuel&lt;/td>
&lt;td>0.183&lt;/td>
&lt;td>1.000&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>d_renewable&lt;/td>
&lt;td>0.350&lt;/td>
&lt;td>0.959&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>d_urban&lt;/td>
&lt;td>0.096&lt;/td>
&lt;td>0.268&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>d_industry&lt;/td>
&lt;td>&lt;strong>0.998&lt;/strong>&lt;/td>
&lt;td>0.999&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>d_democracy&lt;/td>
&lt;td>0.094&lt;/td>
&lt;td>0.023&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>With only 80 cross-sectional observations, BMA&amp;rsquo;s evidence threshold is much harder to clear. The GDP terms &amp;mdash; which are &lt;em>the core of the EKC&lt;/em> &amp;mdash; do not survive because the 20-year differences are noisy and the cubic polynomial requires precise estimation of three correlated terms simultaneously.&lt;/p>
&lt;p>The change in industry share is the only variable with a strong enough signal-to-noise ratio to clear BMA&amp;rsquo;s bar. The FE-based BMA (N = 1,600) has 20x more observations to work with, which is why it identifies 6 robust variables.&lt;/p>
&lt;h3 id="a5-dsl-on-first-differences">A.5 DSL on first differences&lt;/h3>
&lt;pre>&lt;code class="language-stata">dsregress d_ln_co2 d_ln_gdp d_ln_gdp_sq d_ln_gdp_cb, ///
controls(d_fossil_fuel d_renewable d_urban d_industry d_democracy ///
d_services d_trade d_fdi d_credit d_pop_density ///
d_corruption d_globalization) ///
rseed(9988)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Double-selection linear model Number of obs = 80
Number of controls = 12
Number of selected controls = 1
Wald chi2(3) = 10.65
Prob &amp;gt; chi2 = 0.0138
------------------------------------------------------------------------------
| Robust
d_ln_co2 | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
d_ln_gdp | -5.047196 4.558593 -1.11 0.268 -13.98187 3.887483
d_ln_gdp_sq | .5943786 .4700569 1.26 0.206 -.326916 1.515673
d_ln_gdp_cb | -.0220809 .0160386 -1.38 0.169 -.0535159 .0093541
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-stata">lassoinfo
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> Estimate: active
Command: dsregress
------------------------------------------------------
| No. of
| Selection selected
Variable | Model method lambda variables
------------+-----------------------------------------
d_ln_co2 | linear plugin .3818852 1
d_ln_gdp | linear plugin .3818852 0
d_ln_gdp_sq | linear plugin .3818852 0
d_ln_gdp_cb | linear plugin .3818852 0
------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>FD-DSL selected only &lt;strong>1 control&lt;/strong> for the outcome equation (likely d_industry, consistent with BMA) and &lt;strong>zero controls&lt;/strong> for each of the three GDP equations. With such sparse selection, the final OLS is essentially a regression of d_ln_co2 on the three GDP terms plus one control &amp;mdash; and none of the three GDP terms are individually significant (p = 0.17&amp;ndash;0.27). The Wald test for joint significance is borderline (p = 0.014), suggesting the GDP terms collectively have some explanatory power, but the individual estimates are too noisy for inference.&lt;/p>
&lt;h3 id="a6-comparison-first-differences-vs-fixed-effects">A.6 Comparison: first differences vs fixed effects&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>FD Sparse&lt;/th>
&lt;th>FD Kitchen&lt;/th>
&lt;th>FD BMA&lt;/th>
&lt;th>FD DSL&lt;/th>
&lt;th>FE BMA&lt;/th>
&lt;th>FE DSL&lt;/th>
&lt;th>True DGP&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>$\beta_1$ (GDP)&lt;/td>
&lt;td>&amp;ndash;10.362&lt;/td>
&lt;td>&amp;ndash;8.110&lt;/td>
&lt;td>n/a&lt;/td>
&lt;td>&amp;ndash;5.047&lt;/td>
&lt;td>&amp;ndash;7.139&lt;/td>
&lt;td>&amp;ndash;7.433&lt;/td>
&lt;td>&amp;ndash;7.100&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>$\beta_2$ (GDP²)&lt;/td>
&lt;td>1.156&lt;/td>
&lt;td>0.924&lt;/td>
&lt;td>n/a&lt;/td>
&lt;td>0.594&lt;/td>
&lt;td>0.808&lt;/td>
&lt;td>0.840&lt;/td>
&lt;td>0.810&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>$\beta_3$ (GDP³)&lt;/td>
&lt;td>&amp;ndash;0.041&lt;/td>
&lt;td>&amp;ndash;0.034&lt;/td>
&lt;td>n/a&lt;/td>
&lt;td>&amp;ndash;0.022&lt;/td>
&lt;td>&amp;ndash;0.030&lt;/td>
&lt;td>&amp;ndash;0.031&lt;/td>
&lt;td>&amp;ndash;0.030&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>GDP terms robust?&lt;/strong>&lt;/td>
&lt;td>Yes (p &amp;lt; 0.05)&lt;/td>
&lt;td>No (p &amp;gt; 0.05)&lt;/td>
&lt;td>&lt;strong>No&lt;/strong> (PIP &amp;lt; 0.30)&lt;/td>
&lt;td>No (p &amp;gt; 0.05)&lt;/td>
&lt;td>&lt;strong>Yes&lt;/strong> (PIP &amp;gt; 0.99)&lt;/td>
&lt;td>Yes (p &amp;lt; 0.001)&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Controls selected&lt;/strong>&lt;/td>
&lt;td>n/a&lt;/td>
&lt;td>n/a&lt;/td>
&lt;td>1 of 12&lt;/td>
&lt;td>1 of 12&lt;/td>
&lt;td>6 of 12&lt;/td>
&lt;td>102 of 112&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Min TP&lt;/strong>&lt;/td>
&lt;td>\$1,913&lt;/td>
&lt;td>\$1,465&lt;/td>
&lt;td>n/a&lt;/td>
&lt;td>\$987&lt;/td>
&lt;td>\$2,411&lt;/td>
&lt;td>\$2,429&lt;/td>
&lt;td>\$1,895&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Max TP&lt;/strong>&lt;/td>
&lt;td>\$60,817&lt;/td>
&lt;td>\$61,655&lt;/td>
&lt;td>n/a&lt;/td>
&lt;td>\$62,983&lt;/td>
&lt;td>\$27,269&lt;/td>
&lt;td>\$27,672&lt;/td>
&lt;td>\$34,647&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;blockquote>
&lt;p>&lt;strong>Note.&lt;/strong> FD-BMA posterior means for the GDP terms are heavily shrunk toward zero (because their PIPs are ~0.27&amp;ndash;0.30), so we report &amp;ldquo;n/a&amp;rdquo; rather than misleading point estimates.&lt;/p>
&lt;/blockquote>
&lt;p>The comparison reveals a stark trade-off between the two identification strategies:&lt;/p>
&lt;p>&lt;strong>Fixed effects win on accuracy.&lt;/strong> The FE-based estimates are close to the true DGP values, with BMA (FE) achieving the best accuracy ($\beta_1 = -7.139$ vs true &amp;ndash;7.100). The FD estimates are noisier: FD-sparse overshoots ($\beta_1 = -10.36$), while FD-DSL undershoots (&amp;ndash;5.05). The FD turning points are wildly inaccurate &amp;mdash; the maximum turning point is \$61,000&amp;ndash;63,000 in first differences vs \$27,000 with FE (true: \$34,647).&lt;/p>
&lt;p>&lt;strong>First differences struggle with the cubic polynomial.&lt;/strong> Estimating a cubic EKC requires precise measurement of three highly correlated terms ($\ln GDP$, $(\ln GDP)^2$, $(\ln GDP)^3$). With only 80 observations (one 20-year change per country), the multicollinearity among differenced GDP terms is severe. Both BMA and DSL respond rationally: BMA gives all three terms PIPs below 0.30, and DSL selects zero controls for the GDP equations. Neither method &amp;ldquo;trusts&amp;rdquo; the cubic specification in this small sample.&lt;/p>
&lt;p>&lt;strong>Industry is the strongest cross-sectional signal.&lt;/strong> Both FD-BMA (PIP = 0.998) and FD-DSL (selected as the sole control) identify the change in industry share as the most important cross-sectional predictor of CO&lt;sub>2&lt;/sub> change. This makes economic sense: countries that industrialized the most over 1995&amp;ndash;2014 also increased their emissions the most, regardless of their income trajectory.&lt;/p>
&lt;p>&lt;strong>Practical implication.&lt;/strong> First differences are appropriate when the research question is about &lt;em>long-run changes&lt;/em> rather than &lt;em>levels&lt;/em>. But for testing the EKC cubic shape, the panel FE approach is far more powerful because it uses all 1,600 observations rather than collapsing to 80. The FD analysis confirms that the inverted-N result in the main body is robust to the identification strategy in spirit (the signs are correct in FD-sparse OLS), but the magnitudes and statistical power are substantially weaker.&lt;/p>
&lt;h2 id="references">References&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://doi.org/10.1016/j.eneco.2025.108649" target="_blank" rel="noopener">Gravina, A. F. &amp;amp; Lanzafame, M. (2025). What&amp;rsquo;s your shape? Bayesian model averaging and double machine learning for the Environmental Kuznets Curve. &lt;em>Energy Economics&lt;/em>, 108649.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1002/jae.623" target="_blank" rel="noopener">Fernandez, C., Ley, E., &amp;amp; Steel, M. F. J. (2001). Model uncertainty in cross-country growth regressions. &lt;em>Journal of Applied Econometrics&lt;/em>, 16(5), 563&amp;ndash;576.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1093/restud/rdt044" target="_blank" rel="noopener">Belloni, A., Chernozhukov, V., &amp;amp; Hansen, C. (2014). Inference on treatment effects after selection among high-dimensional controls. &lt;em>Review of Economic Studies&lt;/em>, 81(2), 608&amp;ndash;650.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1080/01621459.1997.10473615" target="_blank" rel="noopener">Raftery, A. E., Madigan, D., &amp;amp; Hoeting, J. A. (1997). Bayesian model averaging for linear regression models. &lt;em>Journal of the American Statistical Association&lt;/em>, 92(437), 179&amp;ndash;191.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.2307/271063" target="_blank" rel="noopener">Raftery, A. E. (1995). Bayesian model selection in social research. &lt;em>Sociological Methodology&lt;/em>, 25, 111&amp;ndash;163.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.stata.com/manuals/bmabmaregress.pdf" target="_blank" rel="noopener">Stata 18 Manual: &lt;code>bmaregress&lt;/code> &amp;mdash; Bayesian Model Averaging regression&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.stata.com/manuals/lassodsregress.pdf" target="_blank" rel="noopener">Stata 18 Manual: &lt;code>dsregress&lt;/code> &amp;mdash; Double-Selection LASSO linear regression&lt;/a>&lt;/li>
&lt;/ol></description></item><item><title>Three Methods for Robust Variable Selection: BMA, LASSO, and WALS</title><link>https://carlos-mendez.org/post/r_bma_lasso_wals/</link><pubDate>Mon, 23 Mar 2026 00:00:00 +0000</pubDate><guid>https://carlos-mendez.org/post/r_bma_lasso_wals/</guid><description>&lt;h2 id="1-overview">1. Overview&lt;/h2>
&lt;p>Imagine you are an economist advising a government on climate policy. Your team has collected cross-country data on a dozen potential drivers of CO&lt;sub>2&lt;/sub> emissions: GDP per capita, fossil fuel dependence, urbanization, industrial output, democratic governance, trade networks, agricultural activity, trade openness, foreign direct investment, corruption, tourism, and domestic credit. The government has a limited budget and wants to know: &lt;strong>which of these factors truly drive CO&lt;sub>2&lt;/sub> emissions, and which are red herrings?&lt;/strong>&lt;/p>
&lt;p>This is the &lt;strong>variable selection&lt;/strong> problem, and it is harder than it sounds. With 12 candidate variables, each either included or excluded from a regression, there are $2^{12} = 4,096$ possible models you could estimate. Run one model and report it as &amp;ldquo;the answer,&amp;rdquo; and you have implicitly assumed the other 4,095 models are wrong. That is a very strong assumption &amp;mdash; and almost certainly unjustified.&lt;/p>
&lt;p>In practice, researchers handle this by &lt;em>specification searching&lt;/em>: they try many models, drop insignificant variables, and report whichever specification &amp;ldquo;works best.&amp;rdquo; This process inflates false discoveries. A noise variable that happens to look significant in one specification gets reported, while the many failed specifications are hidden in the researcher&amp;rsquo;s desk drawer. This is sometimes called the &lt;strong>file drawer problem&lt;/strong> or &lt;strong>pretesting bias&lt;/strong>.&lt;/p>
&lt;p>This tutorial introduces three principled approaches to the variable selection problem:&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph TD
Q[&amp;quot;&amp;lt;b&amp;gt;Variable Selection&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Which of 12 variables&amp;lt;br/&amp;gt;truly matter?&amp;quot;] --&amp;gt; BMA
Q --&amp;gt; LASSO
Q --&amp;gt; WALS
BMA[&amp;quot;&amp;lt;b&amp;gt;BMA&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Bayesian Model Averaging&amp;lt;br/&amp;gt;PIPs from 4,096 models&amp;quot;] --&amp;gt; R[&amp;quot;&amp;lt;b&amp;gt;Convergence&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Variables identified&amp;lt;br/&amp;gt;by all 3 methods&amp;quot;]
LASSO[&amp;quot;&amp;lt;b&amp;gt;LASSO&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;L1 penalized regression&amp;lt;br/&amp;gt;Automatic selection&amp;quot;] --&amp;gt; R
WALS[&amp;quot;&amp;lt;b&amp;gt;WALS&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Frequentist averaging&amp;lt;br/&amp;gt;t-statistics&amp;quot;] --&amp;gt; R
style Q fill:#141413,stroke:#141413,color:#fff
style BMA fill:#6a9bcc,stroke:#141413,color:#fff
style LASSO fill:#d97757,stroke:#141413,color:#fff
style WALS fill:#00d4c8,stroke:#141413,color:#fff
style R fill:#1a3a8a,stroke:#141413,color:#fff
&lt;/code>&lt;/pre>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Bayesian Model Averaging (BMA)&lt;/strong>: Average across all 4,096 models, weighting each by how well it fits the data. Variables that appear important across many models earn a high &amp;ldquo;inclusion probability.&amp;rdquo;&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>LASSO (Least Absolute Shrinkage and Selection Operator)&lt;/strong>: Add a penalty to the regression that forces the coefficients of irrelevant variables to be &lt;em>exactly zero&lt;/em>, performing automatic selection.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Weighted Average Least Squares (WALS)&lt;/strong>: A fast frequentist model-averaging method that transforms the problem so each variable can be evaluated independently.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>We use &lt;strong>synthetic data&lt;/strong> throughout this tutorial. This means we &lt;em>know the true data-generating process&lt;/em> &amp;mdash; which variables truly matter and which do not. This &amp;ldquo;answer key&amp;rdquo; lets us verify whether each method correctly recovers the truth. By the end, you will understand not just &lt;em>how&lt;/em> to run each method, but &lt;em>why&lt;/em> it works and &lt;em>when&lt;/em> to prefer one over the others.&lt;/p>
&lt;p>&lt;strong>Learning objectives:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Understand the variable selection problem and why running a single model is insufficient when model uncertainty is large&lt;/li>
&lt;li>Implement Bayesian Model Averaging in R and interpret Posterior Inclusion Probabilities (PIPs)&lt;/li>
&lt;li>Apply LASSO with cross-validation to perform automatic variable selection and use Post-LASSO for unbiased estimation&lt;/li>
&lt;li>Run WALS as a fast frequentist model-averaging alternative and interpret its t-statistics&lt;/li>
&lt;li>Compare results across all three methods to identify truly robust determinants via methodological triangulation&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Content outline.&lt;/strong> Section 2 sets up the R environment. Section 3 introduces the synthetic dataset and its built-in &amp;ldquo;answer key&amp;rdquo; &amp;mdash; 7 true predictors and 5 noise variables with realistic multicollinearity. Section 4 runs naive OLS to illustrate the spurious significance problem. Sections 5&amp;ndash;8 cover BMA: Bayes' rule foundations, the PIP framework, a toy example, and full implementation. Sections 9&amp;ndash;12 cover LASSO: the bias-variance tradeoff, L1/L2 geometry, cross-validated implementation, and Post-LASSO. Sections 13&amp;ndash;16 cover WALS: frequentist model averaging, the semi-orthogonal transformation, the Laplace prior, and implementation. Section 17 brings all three methods together for a grand comparison. Section 18 summarizes key takeaways and provides further reading.&lt;/p>
&lt;h2 id="2-setup">2. Setup&lt;/h2>
&lt;p>Before running the analysis, install the required packages if needed. The following code checks for missing packages and installs them automatically.&lt;/p>
&lt;pre>&lt;code class="language-r"># List all packages needed for this tutorial
required_packages &amp;lt;- c(
&amp;quot;tidyverse&amp;quot;, # data manipulation and ggplot2 visualization
&amp;quot;BMS&amp;quot;, # Bayesian Model Averaging via the bms() function
&amp;quot;glmnet&amp;quot;, # LASSO and Ridge regression via coordinate descent
&amp;quot;WALS&amp;quot;, # Weighted Average Least Squares estimation
&amp;quot;scales&amp;quot;, # nice axis formatting in plots
&amp;quot;patchwork&amp;quot;, # combine multiple ggplot panels
&amp;quot;ggrepel&amp;quot;, # non-overlapping text labels on plots
&amp;quot;corrplot&amp;quot;, # correlation matrix heatmaps
&amp;quot;broom&amp;quot; # tidy model summaries
)
# Install any packages not yet available
missing &amp;lt;- required_packages[!sapply(required_packages, requireNamespace, quietly = TRUE)]
if (length(missing) &amp;gt; 0) {
install.packages(missing, repos = &amp;quot;https://cloud.r-project.org&amp;quot;)
}
# Load libraries
library(tidyverse)
library(BMS)
library(glmnet)
library(WALS)
library(scales)
library(patchwork)
library(ggrepel)
library(corrplot)
library(broom)
&lt;/code>&lt;/pre>
&lt;h2 id="3-the-synthetic-dataset">3. The Synthetic Dataset&lt;/h2>
&lt;h3 id="31-the-data-generating-process-our-answer-key">3.1 The data-generating process (our &amp;ldquo;answer key&amp;rdquo;)&lt;/h3>
&lt;p>We use a cross-sectional dataset of 120 fictional countries. The key design choices:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>7 variables have true nonzero effects&lt;/strong> on CO&lt;sub>2&lt;/sub> emissions&lt;/li>
&lt;li>&lt;strong>5 variables are pure noise&lt;/strong> (their true coefficients are exactly zero)&lt;/li>
&lt;li>The noise variables are &lt;strong>correlated with GDP and other true predictors&lt;/strong>, creating realistic multicollinearity. This makes variable selection genuinely challenging &amp;mdash; naive OLS will find spurious &amp;ldquo;significant&amp;rdquo; results for noise variables.&lt;/li>
&lt;/ul>
&lt;p>Think of this as setting up a controlled experiment. We know the answer before we begin, so we can grade each method&amp;rsquo;s performance.&lt;/p>
&lt;p>The data-generating process below shows exactly how the synthetic dataset was built. The CSV file &lt;code>synthetic-co2-cross-section.csv&lt;/code> was generated with &lt;code>set.seed(2017)&lt;/code> and can be loaded directly from GitHub for full reproducibility.&lt;/p>
&lt;pre>&lt;code class="language-r"># --- DATA-GENERATING PROCESS (reference) ---
set.seed(2017)
n &amp;lt;- 120 # number of &amp;quot;countries&amp;quot;
# GDP drives many other variables (realistic: richer countries
# have higher urbanization, more industry, etc.)
log_gdp &amp;lt;- rnorm(n, mean = 8.5, sd = 1.5)
# --- TRUE PREDICTORS (correlated with GDP) ---
fossil_fuel &amp;lt;- 30 + 3 * log_gdp + rnorm(n, 0, 10) # higher in richer countries
urban_pop &amp;lt;- 20 + 5 * log_gdp + rnorm(n, 0, 12) # increases with income
industry &amp;lt;- 15 + 1.5 * log_gdp + rnorm(n, 0, 6) # industry share
democracy &amp;lt;- 5 + 2 * log_gdp + rnorm(n, 0, 8) # democracy index
trade_network &amp;lt;- 0.2 + 0.05 * log_gdp + rnorm(n, 0, 0.15) # trade centrality
agriculture &amp;lt;- 40 - 3 * log_gdp + rnorm(n, 0, 8) # negatively correlated with GDP
# --- NOISE VARIABLES (correlated with GDP but NO true effect) ---
log_trade &amp;lt;- 3.5 + 0.1 * log_gdp + rnorm(n, 0, 0.5)
fdi &amp;lt;- 2 + rnorm(n, 0, 4)
corruption &amp;lt;- 0.8 - 0.05 * log_gdp + rnorm(n, 0, 0.15)
log_tourism &amp;lt;- 12 + 0.3 * log_gdp + rnorm(n, 0, 1.2)
log_credit &amp;lt;- 2.5 + 0.15 * log_gdp + rnorm(n, 0, 0.6)
# --- TRUE DATA-GENERATING PROCESS ---
log_co2 &amp;lt;- 2.0 + # intercept
1.200 * log_gdp + # GDP: strong positive (elasticity)
0.008 * industry + # industry: positive
0.012 * fossil_fuel + # fossil fuel: positive
0.010 * urban_pop + # urbanization: positive
0.004 * democracy + # democracy: small positive
0.500 * trade_network + # trade network: moderate positive
0.005 * agriculture + # agriculture: weak positive
# NOISE VARIABLES HAVE ZERO TRUE EFFECT
rnorm(n, 0, 0.3) # random noise (sigma = 0.3)
&lt;/code>&lt;/pre>
&lt;p>The true coefficients serve as our &amp;ldquo;answer key&amp;rdquo;:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">Variable&lt;/th>
&lt;th style="text-align:left">True $\beta$&lt;/th>
&lt;th style="text-align:left">Role&lt;/th>
&lt;th style="text-align:left">Interpretation&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">log_gdp&lt;/td>
&lt;td style="text-align:left">1.200&lt;/td>
&lt;td style="text-align:left">True predictor&lt;/td>
&lt;td style="text-align:left">1% more GDP $\to$ 1.2% more CO&lt;sub>2&lt;/sub>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">trade_network&lt;/td>
&lt;td style="text-align:left">0.500&lt;/td>
&lt;td style="text-align:left">True predictor&lt;/td>
&lt;td style="text-align:left">Moderate positive effect&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">fossil_fuel&lt;/td>
&lt;td style="text-align:left">0.012&lt;/td>
&lt;td style="text-align:left">True predictor&lt;/td>
&lt;td style="text-align:left">1 pp more fossil fuel $\to$ 1.2% more CO&lt;sub>2&lt;/sub>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">urban_pop&lt;/td>
&lt;td style="text-align:left">0.010&lt;/td>
&lt;td style="text-align:left">True predictor&lt;/td>
&lt;td style="text-align:left">1 pp more urbanization $\to$ 1.0% more CO&lt;sub>2&lt;/sub>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">industry&lt;/td>
&lt;td style="text-align:left">0.008&lt;/td>
&lt;td style="text-align:left">True predictor&lt;/td>
&lt;td style="text-align:left">Positive composition effect&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">agriculture&lt;/td>
&lt;td style="text-align:left">0.005&lt;/td>
&lt;td style="text-align:left">True predictor&lt;/td>
&lt;td style="text-align:left">Weak positive effect&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">democracy&lt;/td>
&lt;td style="text-align:left">0.004&lt;/td>
&lt;td style="text-align:left">True predictor&lt;/td>
&lt;td style="text-align:left">Small positive effect&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">log_trade&lt;/td>
&lt;td style="text-align:left">0&lt;/td>
&lt;td style="text-align:left">Noise&lt;/td>
&lt;td style="text-align:left">No true effect&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">fdi&lt;/td>
&lt;td style="text-align:left">0&lt;/td>
&lt;td style="text-align:left">Noise&lt;/td>
&lt;td style="text-align:left">No true effect&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">corruption&lt;/td>
&lt;td style="text-align:left">0&lt;/td>
&lt;td style="text-align:left">Noise&lt;/td>
&lt;td style="text-align:left">No true effect&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">log_tourism&lt;/td>
&lt;td style="text-align:left">0&lt;/td>
&lt;td style="text-align:left">Noise&lt;/td>
&lt;td style="text-align:left">No true effect&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">log_credit&lt;/td>
&lt;td style="text-align:left">0&lt;/td>
&lt;td style="text-align:left">Noise&lt;/td>
&lt;td style="text-align:left">No true effect&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Now let us load the pre-generated dataset:&lt;/p>
&lt;pre>&lt;code class="language-r"># Load the synthetic dataset directly from GitHub
DATA_URL &amp;lt;- &amp;quot;https://raw.githubusercontent.com/cmg777/starter-academic-v501/master/content/post/r_bma_lasso_wals/synthetic-co2-cross-section.csv&amp;quot;
synth_data &amp;lt;- read.csv(DATA_URL)
cat(&amp;quot;Dataset:&amp;quot;, nrow(synth_data), &amp;quot;countries,&amp;quot;, ncol(synth_data), &amp;quot;variables\n&amp;quot;)
head(synth_data)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Dataset: 120 countries, 14 variables
country log_co2 log_gdp industry fossil_fuel urban_pop democracy trade_network
1 Country_001 13.27 9.47 29.25 66.94 67.97 25.67 0.77
2 Country_002 12.18 8.44 24.97 51.43 66.14 20.51 0.85
3 Country_003 13.50 10.16 28.19 50.62 73.91 29.08 0.73
...
&lt;/code>&lt;/pre>
&lt;h3 id="32-descriptive-statistics">3.2 Descriptive statistics&lt;/h3>
&lt;p>The following summary statistics give us a first look at the data structure. Note the wide range of scales: GDP is in log units (mean around 8.5), while percentage variables like fossil fuel share and urbanization range from single digits to near 100.&lt;/p>
&lt;pre>&lt;code class="language-r"># Descriptive statistics for all 13 numeric variables
synth_data |&amp;gt;
select(-country) |&amp;gt;
pivot_longer(everything(), names_to = &amp;quot;variable&amp;quot;, values_to = &amp;quot;value&amp;quot;) |&amp;gt;
summarise(
n = n(),
mean = round(mean(value), 2),
sd = round(sd(value), 2),
min = round(min(value), 2),
max = round(max(value), 2),
.by = variable
)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> variable n mean sd min max
log_co2 120 14.22 2.11 8.76 20.36
log_gdp 120 8.53 1.57 4.61 13.21
industry 120 27.87 6.21 8.32 44.98
fossil_fuel 120 55.49 9.62 24.72 81.22
urban_pop 120 62.52 13.25 29.81 97.62
democracy 120 22.94 8.32 3.10 45.00
trade_network 120 0.64 0.17 0.18 1.04
agriculture 120 13.87 8.11 1.00 37.11
log_trade 120 4.43 0.46 3.45 5.84
fdi 120 2.23 4.19 -5.00 13.62
corruption 120 0.37 0.16 0.05 0.71
log_tourism 120 14.61 1.32 11.54 19.63
log_credit 120 3.83 0.65 2.30 5.50
&lt;/code>&lt;/pre>
&lt;p>The dataset has 120 observations and 14 variables (1 dependent, 12 candidate regressors, 1 country identifier). The dependent variable &lt;code>log_co2&lt;/code> has a mean of 14.22 with a standard deviation of 2.11 log points, reflecting substantial cross-country variation in emissions. The candidate regressors span very different scales &amp;mdash; trade_network ranges from 0.18 to 1.04, while urban_pop ranges from 29.8 to 97.6 &amp;mdash; which is why BMA, LASSO, and WALS each handle scaling internally.&lt;/p>
&lt;h3 id="33-correlation-structure">3.3 Correlation structure&lt;/h3>
&lt;p>A key feature of our synthetic data is that the noise variables are correlated with the true predictors &amp;mdash; especially with GDP. This correlation is what makes variable selection difficult: in a standard OLS regression, the noise variables will &amp;ldquo;borrow&amp;rdquo; explanatory power from the true predictors.&lt;/p>
&lt;pre>&lt;code class="language-r"># Compute correlation matrix for all 12 candidate regressors
cor_matrix &amp;lt;- synth_data |&amp;gt;
select(-country, -log_co2) |&amp;gt;
cor()
# Draw the heatmap
corrplot(cor_matrix, method = &amp;quot;color&amp;quot;, type = &amp;quot;lower&amp;quot;,
addCoef.col = &amp;quot;black&amp;quot;, number.cex = 0.7,
col = colorRampPalette(c(&amp;quot;#d97757&amp;quot;, &amp;quot;white&amp;quot;, &amp;quot;#6a9bcc&amp;quot;))(200),
diag = FALSE)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="bma_lasso_wals_01_correlation.png" alt="Correlation matrix heatmap showing that noise variables like trade openness, tourism, and credit are correlated with GDP and other true predictors, creating the multicollinearity that makes variable selection challenging.">&lt;/p>
&lt;p>The correlation heatmap reveals the realistic structure we built into the data. GDP is positively correlated with fossil fuel use, urbanization, industry, and the trade network &amp;mdash; but also with the noise variables like trade openness, tourism, and credit. This multicollinearity is precisely what makes a naive &amp;ldquo;throw everything into OLS&amp;rdquo; approach unreliable. For example, log_tourism has a correlation of approximately 0.3 with log_gdp, which means it can pick up GDP&amp;rsquo;s signal even though its true effect is zero.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Note.&lt;/strong> We created a synthetic dataset where we &lt;em>know&lt;/em> which 7 variables truly affect CO&lt;sub>2&lt;/sub> emissions and which 5 are noise. The noise variables are deliberately correlated with the true predictors, mimicking the multicollinearity found in real cross-country data.&lt;/p>
&lt;/blockquote>
&lt;h2 id="4-the-general-model">4. The General Model&lt;/h2>
&lt;p>Our goal is to estimate the following linear model:&lt;/p>
&lt;p>$$
\log(\text{CO}_{2,i}) = \beta_0 + \sum_{j=1}^{12} \beta_j x_{j,i} + \varepsilon_i
$$&lt;/p>
&lt;p>where:&lt;/p>
&lt;ul>
&lt;li>$\log(\text{CO}_{2,i})$ is the log of CO&lt;sub>2&lt;/sub> emissions for country $i$&lt;/li>
&lt;li>$\beta_0$ is the &lt;strong>intercept&lt;/strong> (the predicted log CO&lt;sub>2&lt;/sub> when all regressors are zero)&lt;/li>
&lt;li>$\beta_j$ is the &lt;strong>coefficient&lt;/strong> on the $j$-th regressor: the change in log CO&lt;sub>2&lt;/sub> associated with a one-unit increase in $x_j$, holding all other variables constant&lt;/li>
&lt;li>$\varepsilon_i$ is the &lt;strong>error term&lt;/strong>: everything that affects CO&lt;sub>2&lt;/sub> emissions but is not captured by the 12 regressors&lt;/li>
&lt;/ul>
&lt;p>Because the dependent variable is in logs, the interpretation of each coefficient depends on whether the regressor is also in logs:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">Regressor type&lt;/th>
&lt;th style="text-align:left">Interpretation of $\beta_j$&lt;/th>
&lt;th style="text-align:left">Example&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">Log-log (e.g., log GDP)&lt;/td>
&lt;td style="text-align:left">&lt;strong>Elasticity&lt;/strong>: a 1% increase in GDP is associated with a $\beta_j$% change in CO&lt;sub>2&lt;/sub>&lt;/td>
&lt;td style="text-align:left">$\beta = 1.2$ means 1% more GDP $\to$ 1.2% more CO&lt;sub>2&lt;/sub>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">Level-log (e.g., fossil fuel %)&lt;/td>
&lt;td style="text-align:left">&lt;strong>Semi-elasticity&lt;/strong>: a 1-unit increase in the regressor is associated with a $100 \times \beta_j$% change in CO&lt;sub>2&lt;/sub>&lt;/td>
&lt;td style="text-align:left">$\beta = 0.012$ means 1 pp more fossil fuel $\to$ 1.2% more CO&lt;sub>2&lt;/sub>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>We want to determine &lt;strong>which $\beta_j$ are truly nonzero&lt;/strong>. We know the answer (we designed the data), but let us first see what happens if we just run OLS with all 12 variables.&lt;/p>
&lt;pre>&lt;code class="language-r"># Run OLS with all 12 candidate regressors
ols_full &amp;lt;- lm(log_co2 ~ log_gdp + industry + fossil_fuel + urban_pop +
democracy + trade_network + agriculture +
log_trade + fdi + corruption + log_tourism + log_credit,
data = synth_data)
# Display summary
summary(ols_full)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Coefficients:
Estimate Std. Error t value Pr(&amp;gt;|t|)
(Intercept) 2.283773 0.494736 4.616 1.06e-05 ***
log_gdp 1.163669 0.032747 35.537 &amp;lt; 2e-16 ***
industry 0.017577 0.005004 3.513 0.000661 ***
fossil_fuel 0.011988 0.003240 3.698 0.000349 ***
urban_pop 0.008221 0.002689 3.057 0.002794 **
democracy 0.010497 0.003975 2.640 0.009549 **
trade_network 0.912828 0.203681 4.482 1.94e-05 ***
agriculture -0.000629 0.004242 -0.148 0.882568
log_trade -0.055738 0.064829 -0.860 0.391509
fdi 0.000789 0.007045 0.112 0.910964
corruption 0.010767 0.201954 0.053 0.957573
log_tourism -0.028025 0.024415 -1.148 0.253610
log_credit 0.045689 0.049690 0.919 0.360252
---
Multiple R-squared: 0.9801, Adjusted R-squared: 0.9779
&lt;/code>&lt;/pre>
&lt;p>Look carefully at the noise variables. For example, log_trade has a t-statistic of $-0.86$ (p = 0.392) and corruption has a t-statistic of $0.05$ (p = 0.958). None reach conventional significance in this sample. However, their estimated coefficients can be non-negligible in magnitude &amp;mdash; and in a different random sample, some noise variables could easily cross the 5% threshold. This is the risk of &lt;strong>spurious significance&lt;/strong>, caused by the correlation between noise variables and the true predictors. It is precisely this problem that motivates the three methods we study next.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Warning.&lt;/strong> With 12 correlated regressors and only 120 observations, OLS can produce misleading significance levels. A variable with a true coefficient of zero may appear significant simply because it is correlated with a genuinely important predictor. This is why we need principled variable selection methods.&lt;/p>
&lt;/blockquote>
&lt;div style="background: linear-gradient(135deg, #6a9bcc 0%, #00d4c8 100%); padding: 1.5em 2em; border-radius: 8px; margin: 2em 0; color: #fff; font-size: 1.3em; font-weight: 600;">
PART 1: Bayesian Model Averaging
&lt;/div>
&lt;h2 id="5-bayes-rule-----the-foundation">5. Bayes' Rule &amp;mdash; The Foundation&lt;/h2>
&lt;p>Before we can understand Bayesian Model Averaging, we need to understand &lt;strong>Bayes' rule&lt;/strong> &amp;mdash; the mathematical machinery that powers the entire framework.&lt;/p>
&lt;h3 id="51-a-coin-flip-example">5.1 A coin-flip example&lt;/h3>
&lt;p>Suppose a friend gives you a coin. You want to know: &lt;strong>is this coin fair&lt;/strong> (probability of heads = 0.5), or is it &lt;strong>biased&lt;/strong> (probability of heads = 0.7)?&lt;/p>
&lt;p>Before flipping, you have no strong opinion. You assign equal &lt;strong>prior probabilities&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>$P(\text{fair}) = 0.5$ (50% chance the coin is fair)&lt;/li>
&lt;li>$P(\text{biased}) = 0.5$ (50% chance the coin is biased)&lt;/li>
&lt;/ul>
&lt;p>Now you flip the coin 10 times and observe &lt;strong>7 heads&lt;/strong>. How should you update your beliefs?&lt;/p>
&lt;p>The &lt;strong>likelihood&lt;/strong> of seeing 7 heads in 10 flips is:&lt;/p>
&lt;ul>
&lt;li>If the coin is fair ($p = 0.5$): $P(\text{7 heads} | \text{fair}) = \binom{10}{7} (0.5)^{10} = 0.1172$&lt;/li>
&lt;li>If the coin is biased ($p = 0.7$): $P(\text{7 heads} | \text{biased}) = \binom{10}{7} (0.7)^7 (0.3)^3 = 0.2668$&lt;/li>
&lt;/ul>
&lt;p>The biased coin makes the data more likely. Bayes' rule combines the prior and the likelihood:&lt;/p>
&lt;p>$$
P(H|D) = \frac{P(D|H) \cdot P(H)}{P(D)}
$$&lt;/p>
&lt;p>where:&lt;/p>
&lt;ul>
&lt;li>$P(H|D)$ = &lt;strong>posterior probability&lt;/strong> (what we believe &lt;em>after&lt;/em> seeing the data)&lt;/li>
&lt;li>$P(D|H)$ = &lt;strong>likelihood&lt;/strong> (how probable the data is under hypothesis $H$)&lt;/li>
&lt;li>$P(H)$ = &lt;strong>prior probability&lt;/strong> (what we believed &lt;em>before&lt;/em> seeing the data)&lt;/li>
&lt;li>$P(D)$ = &lt;strong>marginal likelihood&lt;/strong> (a normalizing constant that ensures probabilities sum to 1)&lt;/li>
&lt;/ul>
&lt;p>For our coin:&lt;/p>
&lt;p>$$
P(\text{fair}|\text{7H}) = \frac{0.1172 \times 0.5}{0.1172 \times 0.5 + 0.2668 \times 0.5} = \frac{0.0586}{0.1920} = 0.305
$$&lt;/p>
&lt;p>$$
P(\text{biased}|\text{7H}) = \frac{0.2668 \times 0.5}{0.1920} = 0.695
$$&lt;/p>
&lt;p>After seeing 7 heads, we update from 50&amp;ndash;50 to roughly 30&amp;ndash;70 in favor of the biased coin. &lt;strong>The data shifted our beliefs, but did not erase the prior entirely.&lt;/strong>&lt;/p>
&lt;h3 id="52-the-bridge-to-model-averaging">5.2 The bridge to model averaging&lt;/h3>
&lt;p>Now replace &amp;ldquo;fair coin&amp;rdquo; and &amp;ldquo;biased coin&amp;rdquo; with &lt;em>regression models&lt;/em>:&lt;/p>
&lt;ul>
&lt;li>Hypothesis = &amp;ldquo;Which variables belong in the model?&amp;rdquo;&lt;/li>
&lt;li>Prior = &amp;ldquo;Before seeing data, any combination of variables is equally plausible&amp;rdquo;&lt;/li>
&lt;li>Likelihood = &amp;ldquo;How well does each model fit the data?&amp;rdquo;&lt;/li>
&lt;li>Posterior = &amp;ldquo;After seeing data, which models are most credible?&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>This is exactly what BMA does. Instead of two coin hypotheses, we have 4,096 model hypotheses &amp;mdash; but the logic of Bayes' rule is identical.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Note.&lt;/strong> Bayes' rule updates prior beliefs using data. The posterior probability of any hypothesis is proportional to its prior probability times its likelihood. BMA applies this same logic to regression models instead of coin flips.&lt;/p>
&lt;/blockquote>
&lt;h2 id="6-the-bma-framework">6. The BMA Framework&lt;/h2>
&lt;h3 id="61-posterior-model-probability">6.1 Posterior model probability&lt;/h3>
&lt;p>With 12 candidate variables, there are $K = 12$ regressors and $2^K = 4,096$ possible models. Denote the $k$-th model as $M_k$. BMA assigns each model a &lt;strong>posterior probability&lt;/strong>:&lt;/p>
&lt;p>$$
P(M_k | y) = \frac{P(y | M_k) \cdot P(M_k)}{\sum_{l=1}^{2^K} P(y | M_l) \cdot P(M_l)}
$$&lt;/p>
&lt;p>This is just Bayes' rule applied to models. Let us unpack each piece:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>$P(y | M_k)$&lt;/strong> is the &lt;strong>marginal likelihood&lt;/strong> of model $M_k$. It measures how well the model fits the data, &lt;em>automatically penalizing complexity&lt;/em>. A model with many parameters can fit the data closely, but the marginal likelihood integrates over all possible parameter values, spreading the probability thin. This acts as a built-in &lt;strong>Occam&amp;rsquo;s razor&lt;/strong>: simpler models that fit the data well receive higher marginal likelihoods than complex models that fit only slightly better.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>$P(M_k)$&lt;/strong> is the &lt;strong>prior model probability&lt;/strong>. With no prior information, we use a &lt;strong>uniform prior&lt;/strong>: every model is equally likely, so $P(M_k) = 1/4,096$ for all $k$. This means the posterior is driven entirely by the data.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The &lt;strong>denominator&lt;/strong> is a normalizing constant that ensures all posterior model probabilities sum to 1.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="62-posterior-inclusion-probability-pip">6.2 Posterior Inclusion Probability (PIP)&lt;/h3>
&lt;p>We do not really care about individual models &amp;mdash; we care about individual &lt;em>variables&lt;/em>. The &lt;strong>Posterior Inclusion Probability&lt;/strong> of variable $j$ is the sum of the posterior probabilities of all models that include variable $j$:&lt;/p>
&lt;p>$$
\text{PIP}_j = \sum_{k:\, j \in M_k} P(M_k | y)
$$&lt;/p>
&lt;p>Think of it as a &lt;strong>democratic vote&lt;/strong>. Each of the 4,096 models casts a vote for which variables matter. But the votes are &lt;em>weighted&lt;/em>: models that fit the data well get louder voices. If variable $j$ appears in most of the high-probability models, it earns a high PIP.&lt;/p>
&lt;p>The standard interpretation thresholds (Raftery, 1995):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">PIP range&lt;/th>
&lt;th style="text-align:left">Interpretation&lt;/th>
&lt;th style="text-align:left">Analogy&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">$\geq 0.99$&lt;/td>
&lt;td style="text-align:left">Decisive evidence&lt;/td>
&lt;td style="text-align:left">Beyond reasonable doubt&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">$0.95 - 0.99$&lt;/td>
&lt;td style="text-align:left">Very strong evidence&lt;/td>
&lt;td style="text-align:left">Strong consensus&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">$0.80 - 0.95$&lt;/td>
&lt;td style="text-align:left">Strong evidence (robust)&lt;/td>
&lt;td style="text-align:left">Clear majority&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">$0.50 - 0.80$&lt;/td>
&lt;td style="text-align:left">Borderline evidence&lt;/td>
&lt;td style="text-align:left">Split vote&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">$&amp;lt; 0.50$&lt;/td>
&lt;td style="text-align:left">Weak/no evidence (fragile)&lt;/td>
&lt;td style="text-align:left">Minority opinion&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>We will use &lt;strong>PIP $\geq$ 0.80&lt;/strong> as our threshold for &amp;ldquo;robust&amp;rdquo; throughout this tutorial.&lt;/p>
&lt;h3 id="63-posterior-mean">6.3 Posterior mean&lt;/h3>
&lt;p>Once we know which variables matter, we want to know &lt;em>how much&lt;/em> they matter. The &lt;strong>posterior mean&lt;/strong> of coefficient $j$ is:&lt;/p>
&lt;p>$$
E[\beta_j | y] = \sum_{k=1}^{2^K} \hat{\beta}_{j,k} \cdot P(M_k | y)
$$&lt;/p>
&lt;p>where $\hat{\beta}_{j,k}$ is the estimated coefficient of variable $j$ in model $k$ (and zero if $j$ is not in model $k$). This is a weighted average of the coefficient across all models. Variables with high PIPs get posterior means close to their &amp;ldquo;full model&amp;rdquo; estimates; variables with low PIPs get posterior means shrunk toward zero.&lt;/p>
&lt;h2 id="7-toy-example-----bma-on-3-variables">7. Toy Example &amp;mdash; BMA on 3 Variables&lt;/h2>
&lt;p>Before running BMA on all 12 variables, let us work through a small example by hand. We pick just 3 variables: &lt;strong>log_gdp&lt;/strong> and &lt;strong>fossil_fuel&lt;/strong> (true predictors) and &lt;strong>log_trade&lt;/strong> (noise). With 3 variables, each can be either IN or OUT of the model, giving us $2^3 = 8$ possible models &amp;mdash; small enough to examine every single one.&lt;/p>
&lt;p>Here are all 8 models written out explicitly:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">Model&lt;/th>
&lt;th style="text-align:left">Formula&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">$M_1$&lt;/td>
&lt;td style="text-align:left">log_co2 $\sim$ 1 (intercept only)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">$M_2$&lt;/td>
&lt;td style="text-align:left">log_co2 $\sim$ log_gdp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">$M_3$&lt;/td>
&lt;td style="text-align:left">log_co2 $\sim$ fossil_fuel&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">$M_4$&lt;/td>
&lt;td style="text-align:left">log_co2 $\sim$ log_trade&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">$M_5$&lt;/td>
&lt;td style="text-align:left">log_co2 $\sim$ log_gdp + fossil_fuel&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">$M_6$&lt;/td>
&lt;td style="text-align:left">log_co2 $\sim$ log_gdp + log_trade&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">$M_7$&lt;/td>
&lt;td style="text-align:left">log_co2 $\sim$ fossil_fuel + log_trade&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">$M_8$&lt;/td>
&lt;td style="text-align:left">log_co2 $\sim$ log_gdp + fossil_fuel + log_trade&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="71-step-1-----fit-every-model-and-compute-bic">7.1 Step 1 &amp;mdash; Fit every model and compute BIC&lt;/h3>
&lt;p>We fit each of the 8 models using OLS and compute its BIC score. Remember: &lt;strong>lower BIC = better&lt;/strong> (the model explains the data well without unnecessary complexity).&lt;/p>
&lt;pre>&lt;code class="language-r"># Select our 3 variables
toy_data &amp;lt;- synth_data |&amp;gt;
select(log_co2, log_gdp, fossil_fuel, log_trade)
# Write out all 8 model formulas explicitly
model_formulas &amp;lt;- c(
&amp;quot;log_co2 ~ 1&amp;quot;, # M1: intercept only
&amp;quot;log_co2 ~ log_gdp&amp;quot;, # M2
&amp;quot;log_co2 ~ fossil_fuel&amp;quot;, # M3
&amp;quot;log_co2 ~ log_trade&amp;quot;, # M4
&amp;quot;log_co2 ~ log_gdp + fossil_fuel&amp;quot;, # M5
&amp;quot;log_co2 ~ log_gdp + log_trade&amp;quot;, # M6
&amp;quot;log_co2 ~ fossil_fuel + log_trade&amp;quot;, # M7
&amp;quot;log_co2 ~ log_gdp + fossil_fuel + log_trade&amp;quot; # M8
)
# Fit each model and extract its BIC
bic_values &amp;lt;- sapply(model_formulas, function(f) {
BIC(lm(as.formula(f), data = toy_data))
})
# Organize results in a table
toy_results &amp;lt;- tibble(
model = paste0(&amp;quot;M&amp;quot;, 1:8),
formula = model_formulas,
bic = round(bic_values, 1)
) |&amp;gt;
arrange(bic)
print(toy_results)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> model formula bic
M5 log_co2 ~ log_gdp + fossil_fuel 114.1
M8 log_co2 ~ log_gdp + fossil_fuel + log_trade 118.5
M2 log_co2 ~ log_gdp 120.7
M6 log_co2 ~ log_gdp + log_trade 125.4
M3 log_co2 ~ fossil_fuel 514.4
M7 log_co2 ~ fossil_fuel + log_trade 519.0
M1 log_co2 ~ 1 528.3
M4 log_co2 ~ log_trade 533.0
&lt;/code>&lt;/pre>
&lt;p>The winner is $M_5$ (log_gdp + fossil_fuel) with BIC = 114.1 &amp;mdash; exactly the two true predictors, no noise. The runner-up $M_8$ adds log_trade but its BIC is worse (118.5), meaning the extra variable does not improve the fit enough to justify the added complexity. Models without GDP ($M_1$, $M_3$, $M_4$, $M_7$) have dramatically worse BIC scores, confirming GDP&amp;rsquo;s dominant role.&lt;/p>
&lt;h3 id="72-step-2-----convert-bic-to-posterior-probabilities">7.2 Step 2 &amp;mdash; Convert BIC to posterior probabilities&lt;/h3>
&lt;p>Now we turn each BIC into a posterior model probability. The formula is:&lt;/p>
&lt;p>$$
P(M_k | y) = \frac{\exp(-0.5 \cdot \text{BIC}_k)}{\sum_{l=1}^{8} \exp(-0.5 \cdot \text{BIC}_l)}
$$&lt;/p>
&lt;p>Because the BIC values can be very large, we work with &lt;strong>differences from the best model&lt;/strong> to avoid numerical overflow. Subtracting the minimum BIC from all values does not change the probabilities:&lt;/p>
&lt;p>$$
P(M_k | y) = \frac{\exp\bigl(-0.5 \cdot (\text{BIC}_k - \text{BIC}_{\min})\bigr)}{\sum_{l=1}^{8} \exp\bigl(-0.5 \cdot (\text{BIC}_l - \text{BIC}_{\min})\bigr)}
$$&lt;/p>
&lt;p>Let us plug in the numbers. The best model ($M_5$) has BIC = 114.1, so $\Delta_5 = 0$. The runner-up ($M_8$) has $\Delta_8 = 118.5 - 114.1 = 4.4$:&lt;/p>
&lt;p>$$
w_5 = \exp(-0.5 \times 0) = 1.000, \quad w_8 = \exp(-0.5 \times 4.4) = 0.111
$$&lt;/p>
&lt;p>The remaining models have much larger $\Delta$ values, so their weights are essentially zero. After normalizing by the sum of all weights ($1.000 + 0.111 + 0.037 + \ldots \approx 1.151$):&lt;/p>
&lt;p>$$
P(M_5 | y) = \frac{1.000}{1.151} = 0.869, \quad P(M_8 | y) = \frac{0.111}{1.151} = 0.096
$$&lt;/p>
&lt;pre>&lt;code class="language-r"># Convert BIC to posterior probabilities using the delta-BIC trick
toy_results &amp;lt;- toy_results |&amp;gt;
mutate(
delta_bic = bic - min(bic), # difference from best
weight = exp(-0.5 * delta_bic), # unnormalized weight
post_prob = round(weight / sum(weight), 4) # normalize to sum to 1
)
toy_results |&amp;gt; select(model, bic, delta_bic, weight, post_prob)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> model bic delta_bic weight post_prob
M5 114.1 0.0 1.0000 0.8687
M8 118.5 4.4 0.1108 0.0962
M2 120.7 6.6 0.0369 0.0320
M6 125.4 11.3 0.0035 0.0031
M3 514.4 400.3 0.0000 0.0000
M7 519.0 404.9 0.0000 0.0000
M1 528.3 414.2 0.0000 0.0000
M4 533.0 418.9 0.0000 0.0000
&lt;/code>&lt;/pre>
&lt;p>One model dominates: $M_5$ captures 86.9% of the posterior probability &amp;mdash; exactly the two true predictors. The runner-up $M_8$ (adding log_trade) gets only 9.6%, and $M_2$ (GDP alone) gets 3.2%. The remaining 5 models share less than 0.4% of the total weight. BMA&amp;rsquo;s Occam&amp;rsquo;s razor is at work: adding log_trade to the model ($M_8$) does not improve the fit enough to overcome the complexity penalty, so the simpler model ($M_5$) wins decisively.&lt;/p>
&lt;h3 id="73-step-3-----compute-posterior-inclusion-probabilities">7.3 Step 3 &amp;mdash; Compute Posterior Inclusion Probabilities&lt;/h3>
&lt;p>Finally, we compute the PIP of each variable by summing the posterior probabilities of all models that include it. For example, log_trade appears in models $M_4$, $M_6$, $M_7$, and $M_8$, so:&lt;/p>
&lt;p>$$
\text{PIP}_{\text{log_trade}} = P(M_4 | y) + P(M_6 | y) + P(M_7 | y) + P(M_8 | y) = 0.000 + 0.003 + 0.000 + 0.096 = 0.099
$$&lt;/p>
&lt;p>That is well below the 0.50 threshold &amp;mdash; fragile evidence, exactly what we expect for a noise variable.&lt;/p>
&lt;pre>&lt;code class="language-r"># Compute PIPs: for each variable, sum P(M|y) across models that include it
pip_toy &amp;lt;- tibble(
variable = c(&amp;quot;log_gdp&amp;quot;, &amp;quot;fossil_fuel&amp;quot;, &amp;quot;log_trade&amp;quot;),
true_effect = c(&amp;quot;True&amp;quot;, &amp;quot;True&amp;quot;, &amp;quot;Noise&amp;quot;),
pip = c(
# log_gdp appears in M2, M5, M6, M8
sum(toy_results$post_prob[toy_results$model %in% c(&amp;quot;M2&amp;quot;,&amp;quot;M5&amp;quot;,&amp;quot;M6&amp;quot;,&amp;quot;M8&amp;quot;)]),
# fossil_fuel appears in M3, M5, M7, M8
sum(toy_results$post_prob[toy_results$model %in% c(&amp;quot;M3&amp;quot;,&amp;quot;M5&amp;quot;,&amp;quot;M7&amp;quot;,&amp;quot;M8&amp;quot;)]),
# log_trade appears in M4, M6, M7, M8
sum(toy_results$post_prob[toy_results$model %in% c(&amp;quot;M4&amp;quot;,&amp;quot;M6&amp;quot;,&amp;quot;M7&amp;quot;,&amp;quot;M8&amp;quot;)])
)
)
print(pip_toy)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> variable true_effect pip
log_gdp True 1.000
fossil_fuel True 0.965
log_trade Noise 0.099
&lt;/code>&lt;/pre>
&lt;p>Even with this simple 3-variable example, BMA correctly identifies the two true predictors. GDP has a PIP of 1.000 (decisive evidence) and fossil_fuel has a PIP of 0.965 (robust) &amp;mdash; they appear in every high-probability model. Log_trade has a PIP of only 0.099 (fragile) &amp;mdash; well below the 0.50 threshold. BMA&amp;rsquo;s built-in Occam&amp;rsquo;s razor penalizes models that include noise variables without substantially improving the fit.&lt;/p>
&lt;h2 id="8-bma-on-all-12-variables">8. BMA on All 12 Variables&lt;/h2>
&lt;h3 id="81-running-bma">8.1 Running BMA&lt;/h3>
&lt;p>Now we apply BMA to the full dataset with all 12 candidate regressors using the &lt;code>BMS&lt;/code> package. Because 4,096 models is computationally manageable, the MCMC sampler explores the full model space efficiently.&lt;/p>
&lt;pre>&lt;code class="language-r">set.seed(2021) # reproducibility for MCMC sampling
# Prepare the data matrix: DV in first column, regressors follow
bma_data &amp;lt;- synth_data |&amp;gt;
select(log_co2, log_gdp, industry, fossil_fuel, urban_pop,
democracy, trade_network, agriculture,
log_trade, fdi, corruption, log_tourism, log_credit) |&amp;gt;
as.data.frame()
# Run BMA
bma_fit &amp;lt;- bms(
X.data = bma_data, # data with DV in column 1
burn = 50000, # burn-in iterations
iter = 200000, # post-burn-in iterations
g = &amp;quot;BRIC&amp;quot;, # BRIC g-prior (robust default)
mprior = &amp;quot;uniform&amp;quot;, # uniform model prior
nmodel = 2000, # store top 2000 models
mcmc = &amp;quot;bd&amp;quot;, # birth-death MCMC sampler
user.int = FALSE # suppress interactive output
)
&lt;/code>&lt;/pre>
&lt;p>The key parameters deserve explanation:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>burn = 50,000&lt;/strong>: the first 50,000 MCMC draws are discarded as &amp;ldquo;burn-in&amp;rdquo; to ensure the sampler has converged to the posterior distribution&lt;/li>
&lt;li>&lt;strong>iter = 200,000&lt;/strong>: the next 200,000 draws are used for inference&lt;/li>
&lt;li>&lt;strong>g = &amp;ldquo;BRIC&amp;rdquo;&lt;/strong>: the Benchmark Risk Inflation Criterion prior on the regression coefficients, a robust default choice&lt;/li>
&lt;li>&lt;strong>mprior = &amp;ldquo;uniform&amp;rdquo;&lt;/strong>: every model is equally likely a priori, so the posterior is driven entirely by the data&lt;/li>
&lt;/ul>
&lt;h3 id="82-pip-bar-chart">8.2 PIP bar chart&lt;/h3>
&lt;p>The PIP bar chart classifies each variable as robust (PIP $\geq$ 0.80), borderline (0.50&amp;ndash;0.80), or fragile (PIP $&amp;lt;$ 0.50). This visualization makes it easy to see which variables earn strong support across the model space and which are effectively irrelevant.&lt;/p>
&lt;pre>&lt;code class="language-r"># Extract PIPs and posterior means
bma_coefs &amp;lt;- coef(bma_fit)
bma_df &amp;lt;- as.data.frame(bma_coefs) |&amp;gt;
rownames_to_column(&amp;quot;variable&amp;quot;) |&amp;gt;
as_tibble() |&amp;gt;
rename(pip = PIP, post_mean = `Post Mean`, post_sd = `Post SD`) |&amp;gt;
select(variable, pip, post_mean, post_sd) |&amp;gt;
mutate(
true_beta = true_beta_lookup[variable],
robustness = case_when(
pip &amp;gt;= 0.80 ~ &amp;quot;Robust (PIP &amp;gt;= 0.80)&amp;quot;,
pip &amp;gt;= 0.50 ~ &amp;quot;Borderline&amp;quot;,
TRUE ~ &amp;quot;Fragile (PIP &amp;lt; 0.50)&amp;quot;
),
ci_low = post_mean - 2 * post_sd,
ci_high = post_mean + 2 * post_sd
)
# Plot PIPs
ggplot(bma_df, aes(x = reorder(variable, pip), y = pip, fill = robustness)) +
geom_col(width = 0.65) +
geom_hline(yintercept = 0.80, linetype = &amp;quot;dashed&amp;quot;) +
coord_flip() +
labs(x = NULL, y = &amp;quot;Posterior Inclusion Probability (PIP)&amp;quot;)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="bma_lasso_wals_04_bma_pip.png" alt="BMA Posterior Inclusion Probabilities. Green bars indicate robust variables with PIP greater than or equal to 0.80; teal bars indicate borderline variables; orange bars indicate fragile variables with PIP less than 0.50.">&lt;/p>
&lt;p>The PIP bar chart reveals a clear separation between signal and noise. GDP dominates with a PIP of 1.00, followed by trade_network (0.986), fossil_fuel (0.948), and industry (0.841) &amp;mdash; all with PIPs above the 0.80 robustness threshold. The noise variables (log_trade, fdi, corruption, log_tourism, log_credit) all have PIPs well below 0.15, confirming that BMA correctly classifies them as fragile. Urban_pop ($\beta = 0.010$, PIP = 0.648) and democracy ($\beta = 0.004$, PIP = 0.607) land in the borderline range &amp;mdash; true predictors whose effects are moderate enough that BMA hedges between including and excluding them. Agriculture ($\beta = 0.005$, PIP = 0.087) is classified as fragile, an honest reflection of the sample&amp;rsquo;s limited power to detect its very small effect.&lt;/p>
&lt;h3 id="83-posterior-coefficient-plot">8.3 Posterior coefficient plot&lt;/h3>
&lt;p>Beyond knowing &lt;em>which&lt;/em> variables matter, we want to know &lt;em>how much&lt;/em> they matter and how precisely they are estimated. The posterior coefficient plot displays the BMA-estimated effect size for each variable along with approximate 95% credible intervals (posterior mean $\pm$ 2 posterior standard deviations).&lt;/p>
&lt;pre>&lt;code class="language-r"># Coefficient plot with 95% credible intervals
ggplot(bma_df, aes(x = reorder(variable, pip), y = post_mean, color = robustness)) +
geom_pointrange(aes(ymin = ci_low, ymax = ci_high)) +
geom_hline(yintercept = 0, linetype = &amp;quot;solid&amp;quot;, color = &amp;quot;gray50&amp;quot;) +
coord_flip()
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="bma_lasso_wals_05_bma_coefs.png" alt="BMA posterior mean coefficients with approximate 95 percent credible intervals. Variables ordered by PIP. Robust variables have intervals that do not cross zero.">&lt;/p>
&lt;p>The posterior coefficient plot shows the BMA-estimated effect sizes with uncertainty bands. GDP&amp;rsquo;s posterior mean of approximately 1.19 closely recovers the true value of 1.200, and its 95% credible interval is narrow, reflecting high precision. Trade_network has a posterior mean of 0.87, overshooting its true value of 0.500 &amp;mdash; but its wide credible interval honestly reflects substantial estimation uncertainty. The noise variables and low-PIP variables like agriculture have posterior means shrunk very close to zero &amp;mdash; this is BMA&amp;rsquo;s shrinkage at work. Variables with low PIPs appear in few high-probability models, so their posterior means are averaged with many models where the coefficient is zero, pulling the estimate toward zero.&lt;/p>
&lt;h3 id="84-variable-inclusion-map">8.4 Variable-inclusion map&lt;/h3>
&lt;p>The variable-inclusion map shows &lt;em>which&lt;/em> variables appear in the highest-probability models and whether their coefficients are positive or negative. Unlike a simple heatmap, the &lt;strong>width of each column is proportional to the model&amp;rsquo;s posterior probability&lt;/strong> &amp;mdash; so wide columns represent models that the data strongly supports. The x-axis shows cumulative posterior model probability: if the first model has PMP = 0.15, it occupies the region from 0 to 0.15; the second model fills from 0.15 to 0.15 + its PMP, and so on. A solid band of color stretching across most of the x-axis means the variable appears in virtually every high-probability model.&lt;/p>
&lt;pre>&lt;code class="language-r"># Extract top 100 models and their coefficient estimates
top_coefs &amp;lt;- topmodels.bma(bma_fit)
n_top &amp;lt;- min(100, ncol(top_coefs))
top_coefs &amp;lt;- top_coefs[, 1:n_top]
# Extract posterior model probabilities (MCMC-based)
model_pmps &amp;lt;- pmp.bma(bma_fit)[1:n_top, 1]
# Cumulative x positions: each model's width = its PMP
cum_pmp &amp;lt;- c(0, cumsum(model_pmps))
# Order variables by PIP (highest at top)
var_order &amp;lt;- bma_df |&amp;gt; arrange(desc(pip)) |&amp;gt; pull(variable)
# Build rectangle data for every variable × model combination
rect_data &amp;lt;- expand.grid(
var_idx = seq_len(nrow(top_coefs)),
model_idx = seq_len(n_top)
) |&amp;gt;
mutate(
variable = rownames(top_coefs)[var_idx],
coef_value = mapply(function(v, m) top_coefs[v, m], var_idx, model_idx),
sign = case_when(
coef_value &amp;gt; 0 ~ &amp;quot;Positive&amp;quot;,
coef_value &amp;lt; 0 ~ &amp;quot;Negative&amp;quot;,
TRUE ~ &amp;quot;Not included&amp;quot;
),
xmin = cum_pmp[model_idx],
xmax = cum_pmp[model_idx + 1],
variable = factor(variable, levels = rev(var_order))
)
# Plot the variable-inclusion map
ggplot(rect_data, aes(xmin = xmin, xmax = xmax,
ymin = as.numeric(variable) - 0.45,
ymax = as.numeric(variable) + 0.45,
fill = sign)) +
geom_rect() +
scale_fill_manual(
name = &amp;quot;Coefficient&amp;quot;,
values = c(&amp;quot;Positive&amp;quot; = &amp;quot;#6a9bcc&amp;quot;,
&amp;quot;Negative&amp;quot; = &amp;quot;#d97757&amp;quot;,
&amp;quot;Not included&amp;quot; = &amp;quot;#d0cdc8&amp;quot;)
) +
scale_x_continuous(expand = c(0, 0),
labels = scales::label_number(accuracy = 0.1)) +
scale_y_continuous(breaks = seq_along(var_order),
labels = rev(var_order),
expand = c(0, 0)) +
labs(title = &amp;quot;Variable-Inclusion Map&amp;quot;,
subtitle = paste0(&amp;quot;Top &amp;quot;, n_top, &amp;quot; models shown out of &amp;quot;,
nrow(pmp.bma(bma_fit)), &amp;quot; visited&amp;quot;),
x = &amp;quot;Cumulative posterior model probability&amp;quot;,
y = NULL)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="bma_lasso_wals_06_bma_inclusion.png" alt="Variable-inclusion map showing the top 100 BMA models. The x-axis is cumulative posterior model probability, so wider columns represent more probable models. Blue indicates a positive coefficient, orange indicates a negative coefficient, and gray indicates the variable is not included. Variables are ordered by PIP from top to bottom.">&lt;/p>
&lt;p>The variable-inclusion map reveals clear structure. The top variables &amp;mdash; log_gdp, trade_network, fossil_fuel, and industry &amp;mdash; form solid blue bands stretching across nearly the entire x-axis, meaning they appear with positive coefficients in virtually every high-probability model. Urban_pop and democracy also show substantial inclusion, consistent with their borderline PIPs. In contrast, the noise variables (log_trade, fdi, corruption, log_tourism, log_credit) appear as mostly gray with occasional patches of blue or orange, indicating they enter and exit models sporadically and sometimes with the wrong sign. The fact that noise variables occasionally appear with negative coefficients (orange patches) is another sign of fragility &amp;mdash; their coefficient estimates are unstable because they have no true effect.&lt;/p>
&lt;h3 id="85-bma-results-vs-known-truth">8.5 BMA results vs. known truth&lt;/h3>
&lt;pre>&lt;code class="language-r"># Compare BMA results with the true DGP
bma_summary &amp;lt;- bma_df |&amp;gt;
mutate(
bma_robust = pip &amp;gt;= 0.80,
true_nonzero = true_beta != 0,
correct = bma_robust == true_nonzero
) |&amp;gt;
select(variable, true_beta, pip, post_mean, bma_robust, true_nonzero, correct)
print(bma_summary)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> variable true_beta pip post_mean bma_robust true_nonzero correct
log_gdp 1.200 1.000 1.1854 TRUE TRUE TRUE
trade_network 0.500 0.986 0.8727 TRUE TRUE TRUE
fossil_fuel 0.012 0.948 0.0117 TRUE TRUE TRUE
industry 0.008 0.841 0.0142 TRUE TRUE TRUE
urban_pop 0.010 0.648 0.0049 FALSE TRUE FALSE
democracy 0.004 0.607 0.0066 FALSE TRUE FALSE
log_tourism 0.000 0.130 -0.0039 FALSE FALSE TRUE
log_credit 0.000 0.104 0.0051 FALSE FALSE TRUE
agriculture 0.005 0.087 -0.0002 FALSE TRUE FALSE
log_trade 0.000 0.084 -0.0037 FALSE FALSE TRUE
corruption 0.000 0.078 0.0026 FALSE FALSE TRUE
fdi 0.000 0.077 -0.0000 FALSE FALSE TRUE
&lt;/code>&lt;/pre>
&lt;p>BMA correctly classifies 9 of 12 variables. The four strongest true predictors (GDP, trade_network, fossil_fuel, industry) all receive PIPs above 0.80 &amp;mdash; these are the &amp;ldquo;robust&amp;rdquo; determinants. All five noise variables receive PIPs below 0.15 &amp;mdash; correctly identified as fragile. Urban_pop (PIP = 0.648) and democracy (PIP = 0.607) fall in the borderline range &amp;mdash; they are true predictors, but BMA&amp;rsquo;s conservative Occam&amp;rsquo;s razor hedges because their effects are moderate. Agriculture ($\beta = 0.005$, PIP = 0.087) is missed entirely. This reveals an important nuance: BMA prioritizes precision over sensitivity. It would rather miss a small true effect than falsely include a noise variable.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Note.&lt;/strong> BMA on all 12 variables correctly gives high PIPs to the strong true predictors (GDP, trade network, fossil fuel, industry) and low PIPs to the noise variables. Variables with moderate or small true effects may land in the borderline zone. The variable-inclusion map shows that the top models consistently include the core predictors.&lt;/p>
&lt;/blockquote>
&lt;div style="background: linear-gradient(135deg, #d97757 0%, #d97757 100%); padding: 1.5em 2em; border-radius: 8px; margin: 2em 0; color: #fff; font-size: 1.3em; font-weight: 600;">
PART 2: LASSO
&lt;/div>
&lt;h2 id="9-regularization-----adding-a-penalty">9. Regularization &amp;mdash; Adding a Penalty&lt;/h2>
&lt;h3 id="91-the-bias-variance-tradeoff">9.1 The bias-variance tradeoff&lt;/h3>
&lt;p>OLS is an &lt;strong>unbiased&lt;/strong> estimator &amp;mdash; on average, it gets the coefficients right. But with many correlated regressors, OLS coefficients have &lt;strong>high variance&lt;/strong>: they bounce around from sample to sample. Adding or removing a single variable can drastically change the estimates.&lt;/p>
&lt;p>The key insight of regularization is that a &lt;strong>little bias can buy a lot of variance reduction&lt;/strong>, lowering the overall prediction error. The &lt;strong>total error&lt;/strong> of a prediction decomposes as:&lt;/p>
&lt;p>$$
\text{MSE} = \text{Bias}^2 + \text{Variance} + \text{Irreducible noise}
$$&lt;/p>
&lt;p>&lt;img src="bma_lasso_wals_02_bias_variance.png" alt="The bias-variance tradeoff. As model complexity increases (more variables, less regularization), bias decreases but variance increases. The optimal point is a compromise between the two, minimizing total MSE.">&lt;/p>
&lt;p>The figure illustrates the fundamental tradeoff. At low complexity (strong regularization), bias is high but variance is low. At high complexity (weak or no regularization, like OLS), bias is near zero but variance explodes. The optimal point lies in between &amp;mdash; this is exactly where regularized methods like LASSO operate. Think of the penalty as a &amp;ldquo;budget constraint&amp;rdquo; on coefficient sizes: variables that do not contribute enough to prediction are not worth the cost, so their coefficients are set to zero.&lt;/p>
&lt;h2 id="10-l1-vs-l2-geometry">10. L1 vs. L2 Geometry&lt;/h2>
&lt;h3 id="101-the-lasso-l1-penalty">10.1 The LASSO (L1) penalty&lt;/h3>
&lt;p>The LASSO solves the following optimization problem:&lt;/p>
&lt;p>$$
\hat{\beta}_{\text{LASSO}} = \arg\min_\beta \; \frac{1}{2n}\|y - X\beta\|^2 + \lambda \|\beta\|_1
$$&lt;/p>
&lt;p>where:&lt;/p>
&lt;ul>
&lt;li>$\frac{1}{2n}\|y - X\beta\|^2$ is the &lt;strong>sum of squared residuals&lt;/strong> (the usual OLS loss, scaled)&lt;/li>
&lt;li>$\|\beta\|_1 = \sum_{j=1}^{p} |\beta_j|$ is the &lt;strong>L1 norm&lt;/strong> (sum of absolute values)&lt;/li>
&lt;li>$\lambda \geq 0$ is the &lt;strong>regularization parameter&lt;/strong>: it controls how much we penalize large coefficients. When $\lambda = 0$, LASSO reduces to OLS. As $\lambda \to \infty$, all coefficients are shrunk to zero.&lt;/li>
&lt;/ul>
&lt;h3 id="102-the-ridge-l2-penalty">10.2 The Ridge (L2) penalty&lt;/h3>
&lt;p>For comparison, &lt;strong>Ridge regression&lt;/strong> uses the L2 norm instead:&lt;/p>
&lt;p>$$
\hat{\beta}_{\text{Ridge}} = \arg\min_\beta \; \frac{1}{2n}\|y - X\beta\|^2 + \lambda \|\beta\|_2^2
$$&lt;/p>
&lt;p>where $\|\beta\|_2^2 = \sum_{j=1}^{p} \beta_j^2$ is the sum of squared coefficients.&lt;/p>
&lt;h3 id="103-why-lasso-selects-variables-and-ridge-does-not">10.3 Why LASSO selects variables and Ridge does not&lt;/h3>
&lt;p>The geometric explanation is one of the most elegant ideas in modern statistics. The constraint region for LASSO (L1) is a &lt;strong>diamond&lt;/strong>, while the constraint region for Ridge (L2) is a &lt;strong>circle&lt;/strong>. When the elliptical OLS contours meet the diamond, they typically hit a &lt;strong>corner&lt;/strong>, where one or more coefficients are exactly zero. When they meet the circle, they hit a smooth curve &amp;mdash; coefficients are shrunk but never exactly zero.&lt;/p>
&lt;p>&lt;img src="bma_lasso_wals_03_l1_l2_geometry.png" alt="Side-by-side comparison of L1 and L2 constraint geometry. Left panel shows the LASSO diamond where OLS contours hit a corner, setting beta-1 to exactly zero. Right panel shows the Ridge circle where contours hit a smooth boundary, producing no exact zeros.">&lt;/p>
&lt;p>The key insight: &lt;strong>the L1 diamond has corners where coefficients are exactly zero &amp;mdash; this is why LASSO selects variables.&lt;/strong> The L2 circle has no corners, so Ridge shrinks coefficients toward zero but never reaches it. LASSO performs &lt;em>simultaneous estimation and variable selection&lt;/em>; Ridge only estimates.&lt;/p>
&lt;h2 id="11-lasso-on-all-12-variables">11. LASSO on All 12 Variables&lt;/h2>
&lt;h3 id="111-running-lasso-with-cross-validation">11.1 Running LASSO with cross-validation&lt;/h3>
&lt;p>The LASSO has one tuning parameter: $\lambda$, which controls the strength of the penalty. Too small and we include noise; too large and we exclude true predictors. We choose $\lambda$ using &lt;strong>10-fold cross-validation&lt;/strong>: split the data into 10 folds, train on 9, predict the 10th, and repeat. The $\lambda$ that minimizes the average prediction error across folds is called &lt;strong>lambda.min&lt;/strong>.&lt;/p>
&lt;pre>&lt;code class="language-r">set.seed(2021) # reproducibility for cross-validation folds
# Prepare the design matrix X and response vector y
X &amp;lt;- synth_data |&amp;gt;
select(log_gdp, industry, fossil_fuel, urban_pop, democracy,
trade_network, agriculture, log_trade, fdi, corruption,
log_tourism, log_credit) |&amp;gt;
as.matrix()
y &amp;lt;- synth_data$log_co2
# Run LASSO (alpha = 1) with 10-fold cross-validation
lasso_cv &amp;lt;- cv.glmnet(
x = X,
y = y,
alpha = 1, # alpha=1 is LASSO (alpha=0 is Ridge)
nfolds = 10,
standardize = TRUE # standardize predictors internally
)
&lt;/code>&lt;/pre>
&lt;h3 id="112-regularization-path">11.2 Regularization path&lt;/h3>
&lt;pre>&lt;code class="language-r"># Fit the full LASSO path
lasso_full &amp;lt;- glmnet(X, y, alpha = 1, standardize = TRUE)
# Plot coefficient paths
ggplot(path_df, aes(x = log_lambda, y = coefficient, color = variable)) +
geom_line() +
geom_vline(xintercept = log(lasso_cv$lambda.min), linetype = &amp;quot;dashed&amp;quot;) +
geom_vline(xintercept = log(lasso_cv$lambda.1se), linetype = &amp;quot;dotted&amp;quot;)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="bma_lasso_wals_07_lasso_path.png" alt="LASSO regularization path showing how each variable&amp;rsquo;s coefficient changes as the penalty lambda increases from left to right. Steel blue lines represent true predictors, orange lines represent noise variables. GDP (the strongest predictor) is the last to be shrunk to zero.">&lt;/p>
&lt;p>The regularization path reveals the story of LASSO variable selection. Reading from left to right (increasing penalty), the noise variables (orange lines) are the first to be driven to zero &amp;mdash; they provide too little predictive value to justify their &amp;ldquo;cost&amp;rdquo; under the penalty. GDP (the strongest predictor with $\beta = 1.200$) persists the longest, requiring the largest penalty to be eliminated. The vertical lines mark lambda.min (minimum CV error) and lambda.1se (most parsimonious model within 1 SE of the minimum). The gap between them represents the tension between fitting the data well and keeping the model simple.&lt;/p>
&lt;h3 id="113-cross-validation-curve">11.3 Cross-validation curve&lt;/h3>
&lt;pre>&lt;code class="language-r"># Plot the CV curve
ggplot(cv_df, aes(x = log_lambda, y = mse)) +
geom_ribbon(aes(ymin = mse_lo, ymax = mse_hi), fill = &amp;quot;gray85&amp;quot;, alpha = 0.5) +
geom_line(color = &amp;quot;#6a9bcc&amp;quot;) +
geom_vline(xintercept = log(lasso_cv$lambda.min), linetype = &amp;quot;dashed&amp;quot;)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="bma_lasso_wals_08_lasso_cv.png" alt="Ten-fold cross-validation curve for LASSO. The left dashed line marks lambda.min (minimum CV error); the right dotted line marks lambda.1se (most parsimonious model within 1 standard error of the minimum). The shaded band shows plus or minus 1 standard error.">&lt;/p>
&lt;p>The cross-validation curve shows how prediction error varies with the penalty strength. The curve has a characteristic U-shape: too little penalty (left) allows overfitting (high error from variance), while too much penalty (right) underfits (high error from bias). The &amp;ldquo;1 standard error rule&amp;rdquo; is a common default: since CV error estimates are noisy, any model within 1 SE of the best is statistically indistinguishable from the best. We prefer the simpler one (lambda.1se).&lt;/p>
&lt;h3 id="114-selected-variables">11.4 Selected variables&lt;/h3>
&lt;pre>&lt;code class="language-r"># Extract LASSO coefficients at lambda.1se
lasso_coefs_1se &amp;lt;- coef(lasso_cv, s = &amp;quot;lambda.1se&amp;quot;)
lasso_df &amp;lt;- tibble(
variable = rownames(lasso_coefs_1se)[-1],
lasso_coef = as.numeric(lasso_coefs_1se)[-1]
) |&amp;gt;
mutate(
selected = lasso_coef != 0,
true_beta = true_beta_lookup[variable],
is_noise = true_beta == 0,
bar_color = case_when(
!selected ~ &amp;quot;Not selected&amp;quot;,
is_noise ~ &amp;quot;Noise (false positive)&amp;quot;,
TRUE ~ &amp;quot;True predictor (correct)&amp;quot;
)
)
# Plot selected variables
ggplot(lasso_df, aes(x = reorder(variable, abs(lasso_coef)), y = lasso_coef, fill = bar_color)) +
geom_col(width = 0.6) + coord_flip()
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="bma_lasso_wals_09_lasso_selected.png" alt="LASSO-selected variables at lambda.1se. Steel blue bars indicate true predictors correctly retained; orange bars indicate noise variables falsely included (if any). Gray bars show variables not selected.">&lt;/p>
&lt;p>At lambda.1se, LASSO selects a sparse subset of the 12 candidate variables. The selected variables are shown with colored bars: steel blue for true predictors correctly retained, orange for any noise variables falsely included. Variables with zero coefficients (gray) have been excluded by the LASSO penalty. The key question is: did LASSO keep the right variables and drop the right ones?&lt;/p>
&lt;h2 id="12-post-lasso">12. Post-LASSO&lt;/h2>
&lt;p>LASSO coefficients are &lt;strong>biased&lt;/strong> because the L1 penalty shrinks them toward zero. The selected variables are correct (we hope), but the coefficient values are too small. This is by design &amp;mdash; the penalty trades bias for variance reduction &amp;mdash; but for &lt;em>interpretation&lt;/em> we want unbiased estimates.&lt;/p>
&lt;p>The fix is simple: &lt;strong>Post-LASSO&lt;/strong> (Belloni and Chernozhukov, 2013). Run OLS using only the variables that LASSO selected. The LASSO does the selection; OLS does the estimation.&lt;/p>
&lt;pre>&lt;code class="language-r"># Identify which variables LASSO selected at lambda.1se
selected_vars &amp;lt;- lasso_df |&amp;gt; filter(selected) |&amp;gt; pull(variable)
# Build the Post-LASSO formula
post_lasso_formula &amp;lt;- as.formula(
paste(&amp;quot;log_co2 ~&amp;quot;, paste(selected_vars, collapse = &amp;quot; + &amp;quot;))
)
# Run OLS on the selected variables only
post_lasso_fit &amp;lt;- lm(post_lasso_formula, data = synth_data)
# Compare: LASSO vs Post-LASSO vs True coefficients
post_lasso_summary &amp;lt;- broom::tidy(post_lasso_fit) |&amp;gt;
filter(term != &amp;quot;(Intercept)&amp;quot;) |&amp;gt;
rename(variable = term, post_lasso_coef = estimate) |&amp;gt;
select(variable, post_lasso_coef) |&amp;gt;
left_join(lasso_df |&amp;gt; select(variable, lasso_coef, true_beta), by = &amp;quot;variable&amp;quot;)
print(post_lasso_summary)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> variable lasso_coef post_lasso_coef true_beta
log_gdp 1.1899 1.1646 1.200
industry 0.0090 0.0176 0.008
fossil_fuel 0.0072 0.0118 0.012
urban_pop 0.0041 0.0078 0.010
democracy 0.0046 0.0113 0.004
trade_network 0.6309 0.8978 0.500
&lt;/code>&lt;/pre>
&lt;p>Notice how the Post-LASSO coefficients are closer to the true values than the raw LASSO coefficients. For example, fossil_fuel&amp;rsquo;s LASSO coefficient is 0.007 (shrunk from the true 0.012), but the Post-LASSO estimate is 0.012 &amp;mdash; recovering the truth almost exactly. Similarly, urban_pop recovers from 0.004 (LASSO) to 0.008 (Post-LASSO), closer to the true value of 0.010. Trade_network&amp;rsquo;s Post-LASSO estimate (0.898) overshoots the true value (0.500), reflecting the difficulty of precisely estimating a coefficient on a low-variance variable. The LASSO selected the right variables; Post-LASSO recovered unbiased magnitudes.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Note.&lt;/strong> LASSO coefficients are shrunk toward zero by design. Post-LASSO runs OLS on only the LASSO-selected variables, producing unbiased coefficient estimates while retaining the variable selection from LASSO.&lt;/p>
&lt;/blockquote>
&lt;div style="background: linear-gradient(135deg, #00d4c8 0%, #00d4c8 100%); padding: 1.5em 2em; border-radius: 8px; margin: 2em 0; color: #141413; font-size: 1.3em; font-weight: 600;">
PART 3: Weighted Average Least Squares (WALS)
&lt;/div>
&lt;h2 id="13-frequentist-model-averaging">13. Frequentist Model Averaging&lt;/h2>
&lt;p>WALS (Weighted Average Least Squares) is a &lt;strong>frequentist&lt;/strong> approach to model averaging. Like BMA, it averages over models instead of selecting just one. But unlike BMA, it does not require MCMC sampling or the specification of a full Bayesian prior.&lt;/p>
&lt;p>The key structural assumption is that regressors are split into two groups:&lt;/p>
&lt;p>$$
y = X_1 \beta_1 + X_2 \beta_2 + \varepsilon
$$&lt;/p>
&lt;p>where:&lt;/p>
&lt;ul>
&lt;li>$X_1$ are &lt;strong>focus regressors&lt;/strong>: variables you are certain belong in the model. In a cross-sectional setting, this is typically just the &lt;strong>intercept&lt;/strong>.&lt;/li>
&lt;li>$X_2$ are &lt;strong>auxiliary regressors&lt;/strong>: the 12 candidate variables whose inclusion is uncertain.&lt;/li>
&lt;li>$\beta_1$ are always estimated; $\beta_2$ are the coefficients we are uncertain about.&lt;/li>
&lt;/ul>
&lt;p>WALS was introduced by Magnus, Powell, and Prufer (2010) and offers a compelling advantage over BMA: &lt;strong>it is extremely fast&lt;/strong>. While BMA explores thousands or millions of models via MCMC, WALS uses a mathematical trick to reduce the problem to $K$ independent averaging problems &amp;mdash; one per auxiliary variable.&lt;/p>
&lt;h2 id="14-the-semi-orthogonal-transformation">14. The Semi-Orthogonal Transformation&lt;/h2>
&lt;h3 id="why-correlated-variables-make-averaging-hard">Why correlated variables make averaging hard&lt;/h3>
&lt;p>In our synthetic data, GDP is correlated with fossil fuel use, urbanization, and even with the noise variables. This means that the decision to include one variable affects the importance of another. If GDP is in the model, fossil fuel&amp;rsquo;s coefficient is partially &amp;ldquo;absorbed&amp;rdquo; by GDP.&lt;/p>
&lt;p>In BMA, this problem is handled by averaging over all model combinations &amp;mdash; but at a high computational cost ($2^{12} = 4,096$ models). WALS uses a different strategy: &lt;strong>transform the auxiliary variables so they become orthogonal&lt;/strong> (uncorrelated with each other). Once orthogonal, each variable can be averaged independently.&lt;/p>
&lt;h3 id="the-mathematical-trick">The mathematical trick&lt;/h3>
&lt;p>The semi-orthogonal transformation works as follows:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Remove the influence of focus regressors&lt;/strong>: project out $X_1$ from both $y$ and $X_2$, obtaining residuals $\tilde{y}$ and $\tilde{X}_2$.&lt;/li>
&lt;li>&lt;strong>Orthogonalize the auxiliaries&lt;/strong>: apply a rotation matrix $P$ (from the eigendecomposition of $\tilde{X}_2'\tilde{X}_2$) to create $Z = \tilde{X}_2 P$, where $Z&amp;rsquo;Z$ is diagonal.&lt;/li>
&lt;li>&lt;strong>Average independently&lt;/strong>: because the columns of $Z$ are orthogonal, the model-averaging problem decomposes into $K$ independent problems. Each transformed variable is averaged separately.&lt;/li>
&lt;/ol>
&lt;p>The computational savings grow dramatically: with 12 variables, we solve &lt;strong>12 independent problems&lt;/strong> instead of enumerating 4,096 models. Think of it as untangling a web of correlated strings until each hangs independently &amp;mdash; once separated, you can measure each string&amp;rsquo;s pull without interference from the others.&lt;/p>
&lt;h2 id="15-the-laplace-prior">15. The Laplace Prior&lt;/h2>
&lt;p>WALS requires a prior distribution for the transformed coefficients. The default and recommended choice is the &lt;strong>Laplace (double-exponential) prior&lt;/strong>:&lt;/p>
&lt;p>$$
p(\gamma_j) \propto \exp(-|\gamma_j| / \tau)
$$&lt;/p>
&lt;p>where $\gamma_j$ is the transformed coefficient and $\tau$ controls the spread. The Laplace prior has two key features:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Peaked at zero&lt;/strong>: it encodes &lt;em>skepticism&lt;/em> &amp;mdash; the prior believes most variables probably have small effects&lt;/li>
&lt;li>&lt;strong>Heavy tails&lt;/strong>: it allows large effects if the data strongly supports them &amp;mdash; variables with strong signal can &amp;ldquo;break through&amp;rdquo; the prior&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="bma_lasso_wals_11_priors.png" alt="Three prior distributions used in model averaging. The Laplace prior (used by WALS) is peaked at zero with heavy tails. The Normal prior (used by BMA g-prior) is also centered at zero but has thinner tails. The Uniform prior assigns equal weight everywhere.">&lt;/p>
&lt;h3 id="the-deep-connection-to-lasso">The deep connection to LASSO&lt;/h3>
&lt;p>Here is a remarkable fact: &lt;strong>the LASSO&amp;rsquo;s L1 penalty is the negative log of a Laplace prior&lt;/strong>. The MAP (maximum a posteriori) estimate under a Laplace prior is:&lt;/p>
&lt;p>$$
\hat{\beta}_{\text{MAP}} = \arg\min_\beta \; \frac{1}{2n}\|y - X\beta\|^2 + \frac{\sigma^2}{\tau} \sum_{j=1}^{p}|\beta_j|
$$&lt;/p>
&lt;p>This is identical to the LASSO objective with $\lambda = \sigma^2 / \tau$. The LASSO penalty and the Laplace prior are two sides of the same coin.&lt;/p>
&lt;p>This means &lt;strong>LASSO and WALS encode the same prior belief&lt;/strong> &amp;mdash; that most coefficients are probably zero or small &amp;mdash; but they use it differently:&lt;/p>
&lt;ul>
&lt;li>LASSO uses the Laplace prior for &lt;strong>selection&lt;/strong>: it finds the single most probable model (the MAP estimate), which sets some coefficients to exactly zero&lt;/li>
&lt;li>WALS uses the Laplace prior for &lt;strong>averaging&lt;/strong>: it averages over all models, weighted by the Laplace prior, producing continuous (nonzero) coefficient estimates with uncertainty measures&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>Note.&lt;/strong> The Laplace prior is peaked at zero (skeptical) with heavy tails (open-minded). It is the same prior that underlies LASSO&amp;rsquo;s L1 penalty. LASSO uses it for hard selection (zeros vs. nonzeros); WALS uses it for soft averaging (continuous weights).&lt;/p>
&lt;/blockquote>
&lt;h2 id="16-wals-on-all-12-variables">16. WALS on All 12 Variables&lt;/h2>
&lt;h3 id="161-running-wals">16.1 Running WALS&lt;/h3>
&lt;pre>&lt;code class="language-r"># WALS splits regressors into two groups:
# X1 = focus regressors (always included): just the intercept
# X2 = auxiliary regressors (uncertain): our 12 candidate variables
# Prepare the focus regressor matrix (intercept only)
X1_wals &amp;lt;- matrix(1, nrow = nrow(synth_data), ncol = 1)
colnames(X1_wals) &amp;lt;- &amp;quot;(Intercept)&amp;quot;
# Prepare the auxiliary regressor matrix (all 12 candidates)
X2_wals &amp;lt;- synth_data |&amp;gt;
select(log_gdp, industry, fossil_fuel, urban_pop, democracy,
trade_network, agriculture, log_trade, fdi, corruption,
log_tourism, log_credit) |&amp;gt;
as.matrix()
y_wals &amp;lt;- synth_data$log_co2
# Fit WALS with the Laplace prior (the recommended default)
wals_fit &amp;lt;- wals(
x = X1_wals, # focus regressors (intercept)
x2 = X2_wals, # auxiliary regressors (12 candidates)
y = y_wals, # response variable
prior = laplace() # Laplace prior for auxiliaries
)
wals_summary &amp;lt;- summary(wals_fit)
&lt;/code>&lt;/pre>
&lt;p>The WALS function call is remarkably concise. Unlike BMA, there is no MCMC sampling, no burn-in period, and no convergence diagnostics to worry about. The computation is essentially instantaneous.&lt;/p>
&lt;pre>&lt;code class="language-r"># Extract results
aux_coefs &amp;lt;- wals_summary$auxCoefs
wals_df &amp;lt;- tibble(
variable = rownames(aux_coefs),
estimate = aux_coefs[, &amp;quot;Estimate&amp;quot;],
se = aux_coefs[, &amp;quot;Std. Error&amp;quot;],
t_stat = estimate / se
) |&amp;gt;
mutate(
true_beta = true_beta_lookup[variable],
abs_t = abs(t_stat),
wals_robust = abs_t &amp;gt;= 2
)
print(wals_df |&amp;gt; arrange(desc(abs_t)) |&amp;gt; select(variable, estimate, t_stat, true_beta))
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> variable estimate t_stat true_beta
log_gdp 1.1333 34.62 1.200
trade_network 0.8458 4.39 0.500
industry 0.0187 4.01 0.008
fossil_fuel 0.0099 3.26 0.012
urban_pop 0.0082 3.11 0.010
democracy 0.0097 2.58 0.004
log_credit 0.0659 1.43 0.000
agriculture -0.0046 -1.13 0.005
log_tourism -0.0148 -0.64 0.000
log_trade 0.0196 0.31 0.000
fdi -0.0011 -0.17 0.000
corruption -0.0165 -0.09 0.000
&lt;/code>&lt;/pre>
&lt;p>WALS produces familiar t-statistics for each auxiliary variable. Using the $|t| \geq 2$ threshold as our robustness criterion (analogous to BMA&amp;rsquo;s PIP $\geq$ 0.80), we can classify each variable as robust or fragile.&lt;/p>
&lt;h3 id="162-t-statistic-bar-chart">16.2 t-statistic bar chart&lt;/h3>
&lt;p>The t-statistic bar chart provides a visual summary of WALS robustness classification. Variables with $|t| \geq 2$ pass the robustness threshold (analogous to BMA&amp;rsquo;s PIP $\geq$ 0.80), while those below the threshold are considered fragile.&lt;/p>
&lt;pre>&lt;code class="language-r"># Classify each variable for the bar chart
wals_df &amp;lt;- wals_df |&amp;gt;
mutate(
bar_color = case_when(
wals_robust &amp;amp; true_nonzero ~ &amp;quot;True positive&amp;quot;,
wals_robust &amp;amp; !true_nonzero ~ &amp;quot;False positive&amp;quot;,
!wals_robust &amp;amp; true_nonzero ~ &amp;quot;False negative&amp;quot;,
TRUE ~ &amp;quot;True negative&amp;quot;
)
)
ggplot(wals_df, aes(x = reorder(variable, abs_t), y = t_stat, fill = bar_color)) +
geom_col(width = 0.6) +
geom_hline(yintercept = c(-2, 2), linetype = &amp;quot;dashed&amp;quot;) +
coord_flip()
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="bma_lasso_wals_10_wals_tstat.png" alt="WALS t-statistics for all 12 variables. The dashed lines mark the t equals 2 robustness threshold. Variables with absolute t-statistic greater than or equal to 2 are considered robust.">&lt;/p>
&lt;p>The t-statistic bar chart shows a clear separation. GDP towers above all others with $|t| = 34.62$, followed by trade_network ($|t| = 4.39$), industry ($|t| = 4.01$), fossil_fuel ($|t| = 3.26$), urban_pop ($|t| = 3.11$), and democracy ($|t| = 2.58$). These six variables pass the $|t| \geq 2$ threshold. The noise variables all have $|t| &amp;lt; 1.5$, confirming they are not robust determinants. Agriculture ($|t| = 1.13$) falls just below the robustness threshold &amp;mdash; its true effect ($\beta = 0.005$) is simply too small to detect reliably with this sample size.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Note.&lt;/strong> WALS produces t-statistics for each auxiliary variable. Using the $|t| \geq 2$ threshold, we can classify variables as robust or fragile. WALS is extremely fast (no MCMC) and provides a frequentist complement to BMA&amp;rsquo;s Bayesian PIPs.&lt;/p>
&lt;/blockquote>
&lt;div style="background: linear-gradient(135deg, #1a3a8a 0%, #141413 100%); padding: 1.5em 2em; border-radius: 8px; margin: 2em 0; color: #fff; font-size: 1.3em; font-weight: 600;">
PART 4: Grand Comparison
&lt;/div>
&lt;h2 id="17-three-methods-same-question-same-data">17. Three Methods, Same Question, Same Data&lt;/h2>
&lt;p>We have now applied all three methods to the same synthetic dataset. Time for the moment of truth: &lt;strong>which variables do all three methods agree on?&lt;/strong>&lt;/p>
&lt;h3 id="171-comprehensive-comparison-table">17.1 Comprehensive comparison table&lt;/h3>
&lt;pre>&lt;code class="language-r"># Merge all results
grand_table &amp;lt;- bma_compare |&amp;gt;
left_join(lasso_compare, by = &amp;quot;variable&amp;quot;) |&amp;gt;
left_join(wals_compare, by = &amp;quot;variable&amp;quot;) |&amp;gt;
mutate(
true_beta = true_beta_lookup[variable],
bma_robust = bma_pip &amp;gt;= 0.80,
n_methods = bma_robust + lasso_selected + wals_robust,
triple_robust = n_methods == 3,
true_nonzero = true_beta != 0
)
print(grand_table |&amp;gt;
select(variable, true_beta, bma_pip, bma_robust, lasso_selected, wals_t, wals_robust, n_methods) |&amp;gt;
arrange(desc(n_methods)))
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> variable true_beta bma_pip bma_robust lasso_selected wals_t wals_robust n_methods
log_gdp 1.200 1.000 TRUE TRUE 34.62 TRUE 3
trade_network 0.500 0.986 TRUE TRUE 4.39 TRUE 3
fossil_fuel 0.012 0.948 TRUE TRUE 3.26 TRUE 3
industry 0.008 0.841 TRUE TRUE 4.01 TRUE 3
urban_pop 0.010 0.648 FALSE TRUE 3.11 TRUE 2
democracy 0.004 0.607 FALSE TRUE 2.58 TRUE 2
log_tourism 0.000 0.130 FALSE FALSE -0.64 FALSE 0
log_credit 0.000 0.104 FALSE FALSE 1.43 FALSE 0
agriculture 0.005 0.087 FALSE FALSE -1.13 FALSE 0
log_trade 0.000 0.084 FALSE FALSE 0.31 FALSE 0
corruption 0.000 0.078 FALSE FALSE -0.09 FALSE 0
fdi 0.000 0.077 FALSE FALSE -0.17 FALSE 0
&lt;/code>&lt;/pre>
&lt;p>The results are striking. Four variables are &lt;strong>triple-robust&lt;/strong> &amp;mdash; identified by all three methods: log_gdp, trade_network, fossil_fuel, and industry. Two more variables &amp;mdash; urban_pop and democracy &amp;mdash; are &lt;strong>double-robust&lt;/strong>, selected by LASSO and WALS but landing in BMA&amp;rsquo;s borderline zone (PIPs of 0.648 and 0.607). All five noise variables are correctly excluded by all three methods. Agriculture ($\beta = 0.005$) is the only true predictor missed by all methods &amp;mdash; its effect is simply too small to detect.&lt;/p>
&lt;h3 id="172-method-agreement-heatmap">17.2 Method agreement heatmap&lt;/h3>
&lt;p>&lt;img src="bma_lasso_wals_12_heatmap.png" alt="Method agreement heatmap showing 12 variables by 3 methods. Steel blue indicates the variable was identified as robust; orange indicates it was not. True predictors are in the top rows, noise variables in the bottom rows.">&lt;/p>
&lt;p>The heatmap provides a visual summary of agreement. The top four rows (GDP, trade_network, fossil_fuel, industry) are solid steel blue across all three columns &amp;mdash; unanimous agreement that these variables matter. Urban_pop and democracy show steel blue for LASSO and WALS but orange for BMA, visualizing BMA&amp;rsquo;s greater conservatism. The bottom five rows (noise) are solid orange &amp;mdash; unanimous agreement that they do not matter. Agriculture is also orange throughout, reflecting all methods' consensus that its tiny effect ($\beta = 0.005$) cannot be reliably distinguished from zero.&lt;/p>
&lt;h3 id="173-bma-pip-vs-wals-t-statistic">17.3 BMA PIP vs. WALS |t-statistic|&lt;/h3>
&lt;p>&lt;img src="bma_lasso_wals_13_pip_vs_t.png" alt="BMA PIP plotted against WALS absolute t-statistic. Point color indicates true status (steel blue for true predictors, orange for noise). Point shape indicates LASSO selection (triangle for selected, cross for not selected). The upper-right quadrant contains variables robust by both BMA and WALS.">&lt;/p>
&lt;p>The scatter plot reveals a strong positive relationship between BMA PIP and WALS $|t|$. Variables in the upper-right quadrant are robust by both methods &amp;mdash; GDP, trade_network, fossil_fuel, and industry. Urban_pop and democracy sit in an interesting middle zone: high WALS $|t|$ (above 2) but moderate BMA PIP (below 0.80), illustrating BMA&amp;rsquo;s more conservative threshold. The noise variables cluster in the lower-left corner (low PIP, low $|t|$). LASSO selection (triangle markers) aligns with the WALS threshold, selecting the same six variables that pass $|t| \geq 2$.&lt;/p>
&lt;h3 id="174-coefficient-comparison">17.4 Coefficient comparison&lt;/h3>
&lt;p>&lt;img src="bma_lasso_wals_14_coef_comparison.png" alt="Coefficient estimates from the three methods compared to the true values in a three-panel faceted scatter plot. Points close to the dashed 45-degree line indicate accurate coefficient recovery.">&lt;/p>
&lt;p>The coefficient comparison plot shows how well each method recovers the true effect sizes. Points on the dashed 45-degree line represent perfect recovery. GDP ($\beta = 1.200$) is recovered almost exactly by all three methods. The smaller coefficients (fossil_fuel at 0.012, urban_pop at 0.010) are also well-estimated. Trade_network&amp;rsquo;s coefficient is overestimated by all methods (true 0.500, estimates around 0.85&amp;ndash;0.90), reflecting the difficulty of precisely estimating an effect on a low-variance variable. BMA&amp;rsquo;s posterior means are slightly attenuated for variables with PIPs below 1.0 (the averaging shrinks them toward zero).&lt;/p>
&lt;h3 id="175-agreement-summary">17.5 Agreement summary&lt;/h3>
&lt;p>&lt;img src="bma_lasso_wals_15_agreement.png" alt="Bar chart showing how many methods (out of 3) identified each variable as robust. Steel blue bars are true predictors, orange bars are noise variables. Four variables achieve triple-robust status and two achieve double-robust status.">&lt;/p>
&lt;p>The agreement bar chart tells a nuanced story: four variables are triple-robust (identified by all three methods), two are double-robust (identified by LASSO and WALS but not BMA), and six are identified by none. The &amp;ldquo;split votes&amp;rdquo; on urban_pop and democracy reveal a genuine methodological difference: LASSO and WALS are more liberal in including moderate-effect variables, while BMA&amp;rsquo;s Bayesian Occam&amp;rsquo;s razor demands stronger evidence. This pattern &amp;mdash; where methods &lt;em>mostly&lt;/em> agree but diverge on borderline cases &amp;mdash; is what makes methodological triangulation valuable.&lt;/p>
&lt;h3 id="176-method-performance">17.6 Method performance&lt;/h3>
&lt;pre>&lt;code class="language-r"># Sensitivity, specificity, and accuracy for each method
results_by_method &amp;lt;- tibble(
method = c(&amp;quot;BMA&amp;quot;, &amp;quot;LASSO&amp;quot;, &amp;quot;WALS&amp;quot;),
true_pos = c(4, 6, 6), # true predictors correctly identified
false_pos = c(0, 0, 0), # noise variables falsely identified
false_neg = c(3, 1, 1), # true predictors missed
true_neg = c(5, 5, 5), # noise variables correctly excluded
sensitivity = true_pos / 7,
specificity = true_neg / 5,
accuracy = (true_pos + true_neg) / 12
)
print(results_by_method)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> method true_pos false_pos false_neg true_neg sensitivity specificity accuracy
BMA 4 0 3 5 0.571 1.000 0.750
LASSO 6 0 1 5 0.857 1.000 0.917
WALS 6 0 1 5 0.857 1.000 0.917
&lt;/code>&lt;/pre>
&lt;p>All three methods achieve &lt;strong>perfect specificity&lt;/strong> (zero false positives) &amp;mdash; none mistakenly identifies a noise variable as robust. The key difference is in &lt;strong>sensitivity&lt;/strong>: LASSO and WALS each detect 6 of 7 true predictors (85.7%), while BMA detects only 4 (57.1%). BMA&amp;rsquo;s lower sensitivity reflects its conservative Bayesian Occam&amp;rsquo;s razor: it places urban_pop and democracy in the &amp;ldquo;borderline&amp;rdquo; zone rather than committing to their inclusion. The one variable missed by all methods &amp;mdash; agriculture ($\beta = 0.005$) &amp;mdash; has an effect so small that it is indistinguishable from noise given our sample size.&lt;/p>
&lt;h3 id="177-when-to-use-which-method">17.7 When to use which method&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">Method&lt;/th>
&lt;th style="text-align:left">Best for&lt;/th>
&lt;th style="text-align:left">Strengths&lt;/th>
&lt;th style="text-align:left">Limitations&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">BMA&lt;/td>
&lt;td style="text-align:left">Full uncertainty quantification&lt;/td>
&lt;td style="text-align:left">Probabilistic (PIPs), handles model uncertainty formally, coefficient intervals&lt;/td>
&lt;td style="text-align:left">Slower (MCMC), requires prior specification&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">LASSO&lt;/td>
&lt;td style="text-align:left">Prediction, sparse models&lt;/td>
&lt;td style="text-align:left">Fast, automatic selection, works with many variables&lt;/td>
&lt;td style="text-align:left">Binary (in/out), biased coefficients (use Post-LASSO)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">WALS&lt;/td>
&lt;td style="text-align:left">Speed, frequentist inference&lt;/td>
&lt;td style="text-align:left">Very fast, produces t-statistics, no MCMC&lt;/td>
&lt;td style="text-align:left">Less common, limited software support&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The strongest recommendation: &lt;strong>use all three&lt;/strong>. When they converge on the same variables (as with our four triple-robust predictors), you have the strongest possible evidence. When they disagree (as with urban_pop and democracy, where LASSO and WALS say &amp;ldquo;yes&amp;rdquo; but BMA hedges), the disagreement itself is informative &amp;mdash; it tells you the evidence is real but not overwhelming. In real-world data, complications such as nonlinearity, heteroskedasticity, and endogeneity may affect method performance and should be addressed before applying these techniques.&lt;/p>
&lt;h2 id="18-conclusion">18. Conclusion&lt;/h2>
&lt;h3 id="181-summary">18.1 Summary&lt;/h3>
&lt;p>This tutorial introduced three principled approaches to the variable selection problem:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Bayesian Model Averaging (BMA)&lt;/strong> averages over all possible models, weighting each by its posterior probability. It produces Posterior Inclusion Probabilities (PIPs) that quantify how robust each variable is across the entire model space. Variables with PIP $\geq$ 0.80 are considered robust.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>LASSO&lt;/strong> adds an L1 penalty to the OLS objective, forcing irrelevant coefficients to exactly zero. Cross-validation selects the penalty strength. Post-LASSO recovers unbiased coefficient estimates for the selected variables.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>WALS&lt;/strong> uses a semi-orthogonal transformation to decompose the model-averaging problem into independent subproblems &amp;mdash; one per variable. It is extremely fast and produces familiar t-statistics for robustness assessment.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h3 id="182-key-takeaways">18.2 Key takeaways&lt;/h3>
&lt;p>&lt;strong>The methods mostly converge &amp;mdash; and their disagreements are informative.&lt;/strong> Four variables are identified by all three methods (triple-robust), and all methods achieve perfect specificity (zero false positives). LASSO and WALS are more sensitive (detecting 6 of 7 true predictors), while BMA is more conservative (detecting 4). The two variables where they disagree &amp;mdash; urban_pop and democracy &amp;mdash; have moderate effects that BMA&amp;rsquo;s Bayesian Occam&amp;rsquo;s razor treats as borderline. This pattern illustrates the value of methodological triangulation across fundamentally different statistical paradigms.&lt;/p>
&lt;p>&lt;strong>Model uncertainty is real but addressable.&lt;/strong> With 12 candidate variables, there are 4,096 possible models. Rather than pretending one of them is &amp;ldquo;the&amp;rdquo; model, these methods account for the uncertainty explicitly. The result is more honest inference.&lt;/p>
&lt;p>&lt;strong>Synthetic data lets us verify.&lt;/strong> Because we designed the data-generating process, we could check each method&amp;rsquo;s performance against the known truth. In practice, the truth is unknown &amp;mdash; which is precisely why using multiple methods is so valuable.&lt;/p>
&lt;h3 id="183-applying-this-to-your-own-research">18.3 Applying this to your own research&lt;/h3>
&lt;p>The code in this tutorial is designed to be modular. To apply these methods to your own data:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Replace the CSV&lt;/strong>: load your own cross-sectional dataset instead of the synthetic one&lt;/li>
&lt;li>&lt;strong>Define the variable list&lt;/strong>: specify which variables are candidates for selection&lt;/li>
&lt;li>&lt;strong>Run the three methods&lt;/strong>: use the same &lt;code>bms()&lt;/code>, &lt;code>cv.glmnet()&lt;/code>, and &lt;code>wals()&lt;/code> function calls&lt;/li>
&lt;li>&lt;strong>Compare results&lt;/strong>: build the same comparison table and heatmap&lt;/li>
&lt;/ol>
&lt;p>The interpretation framework &amp;mdash; PIPs for BMA, selection for LASSO, t-statistics for WALS &amp;mdash; applies regardless of the specific dataset.&lt;/p>
&lt;h3 id="184-further-reading">18.4 Further reading&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>BMA&lt;/strong>: Hoeting, J.A., Madigan, D., Raftery, A.E., and Volinsky, C.T. (1999). &amp;ldquo;Bayesian Model Averaging: A Tutorial.&amp;rdquo; &lt;em>Statistical Science&lt;/em>, 14(4), 382&amp;ndash;417.&lt;/li>
&lt;li>&lt;strong>LASSO&lt;/strong>: Tibshirani, R. (1996). &amp;ldquo;Regression Shrinkage and Selection via the Lasso.&amp;rdquo; &lt;em>Journal of the Royal Statistical Society, Series B&lt;/em>, 58(1), 267&amp;ndash;288.&lt;/li>
&lt;li>&lt;strong>WALS&lt;/strong>: Magnus, J.R., Powell, O., and Prufer, P. (2010). &amp;ldquo;A Comparison of Two Model Averaging Techniques with an Application to Growth Empirics.&amp;rdquo; &lt;em>Journal of Econometrics&lt;/em>, 154(2), 139&amp;ndash;153.&lt;/li>
&lt;li>&lt;strong>Application&lt;/strong>: Aller, C., Ductor, L., and Grechyna, D. (2021). &amp;ldquo;Robust Determinants of CO&lt;sub>2&lt;/sub> Emissions.&amp;rdquo; &lt;em>Energy Economics&lt;/em>, 96, 105154.&lt;/li>
&lt;li>&lt;strong>Post-LASSO&lt;/strong>: Belloni, A. and Chernozhukov, V. (2013). &amp;ldquo;Least Squares After Model Selection in High-Dimensional Sparse Models.&amp;rdquo; &lt;em>Bernoulli&lt;/em>, 19(2), 521&amp;ndash;547.&lt;/li>
&lt;li>&lt;strong>R Packages&lt;/strong>: &lt;a href="https://cran.r-project.org/web/packages/BMS/vignettes/bms.pdf" target="_blank" rel="noopener">BMS vignette&lt;/a>, &lt;a href="https://glmnet.stanford.edu/articles/glmnet.html" target="_blank" rel="noopener">glmnet vignette&lt;/a>, &lt;a href="https://cran.r-project.org/package=WALS" target="_blank" rel="noopener">WALS package&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="references">References&lt;/h2>
&lt;ol>
&lt;li>Hoeting, J.A., Madigan, D., Raftery, A.E., and Volinsky, C.T. (1999). Bayesian Model Averaging: A Tutorial. &lt;em>Statistical Science&lt;/em>, 14(4), 382&amp;ndash;417.&lt;/li>
&lt;li>Tibshirani, R. (1996). Regression Shrinkage and Selection via the Lasso. &lt;em>Journal of the Royal Statistical Society, Series B&lt;/em>, 58(1), 267&amp;ndash;288.&lt;/li>
&lt;li>Magnus, J.R., Powell, O., and Prufer, P. (2010). A Comparison of Two Model Averaging Techniques with an Application to Growth Empirics. &lt;em>Journal of Econometrics&lt;/em>, 154(2), 139&amp;ndash;153.&lt;/li>
&lt;li>Raftery, A.E. (1995). Bayesian Model Selection in Social Research. &lt;em>Sociological Methodology&lt;/em>, 25, 111&amp;ndash;163.&lt;/li>
&lt;li>Aller, C., Ductor, L., and Grechyna, D. (2021). Robust Determinants of CO&lt;sub>2&lt;/sub> Emissions. &lt;em>Energy Economics&lt;/em>, 96, 105154.&lt;/li>
&lt;li>Belloni, A. and Chernozhukov, V. (2013). Least Squares After Model Selection in High-Dimensional Sparse Models. &lt;em>Bernoulli&lt;/em>, 19(2), 521&amp;ndash;547.&lt;/li>
&lt;/ol></description></item></channel></rss>