<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Resource Curse | Carlos Mendez</title><link>https://carlos-mendez.org/category/resource-curse/</link><atom:link href="https://carlos-mendez.org/category/resource-curse/index.xml" rel="self" type="application/rss+xml"/><description>Resource Curse</description><generator>Wowchemy (https://wowchemy.com)</generator><language>en-us</language><copyright>Carlos Mendez</copyright><lastBuildDate>Thu, 07 May 2026 00:00:00 +0000</lastBuildDate><image><url>https://carlos-mendez.org/media/icon_huedfae549300b4ca5d201a9bd09a3ecd5_79625_512x512_fill_lanczos_center_3.png</url><title>Resource Curse</title><link>https://carlos-mendez.org/category/resource-curse/</link></image><item><title>Causal Machine Learning and the Resource Curse with Python EconML</title><link>https://carlos-mendez.org/post/python_econml/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://carlos-mendez.org/post/python_econml/</guid><description>&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>Can natural resource wealth be both a blessing and a curse? And can local institutions determine which way it goes? In this tutorial, we use &lt;strong>EconML&amp;rsquo;s &lt;code>CausalForestDML&lt;/code>&lt;/strong> to estimate &lt;strong>heterogeneous causal effects&lt;/strong> of mining and mineral prices on economic development &amp;mdash; and test whether institutional quality moderates those effects differently for mining versus price shocks.&lt;/p>
&lt;p>We use &lt;strong>simulated data with known ground-truth parameters&lt;/strong> so we can verify that the method recovers the correct answers. The simulated dataset mirrors the structure of Hodler, Lechner &amp;amp; Raschky (2023), who studied 3,800 Sub-Saharan African districts using a Modified Causal Forest. This tutorial focuses on the &lt;strong>DML methodology&lt;/strong>: how the Double Machine Learning framework separates nuisance estimation from causal effect estimation to produce valid, efficient heterogeneous treatment effect estimates.&lt;/p>
&lt;p>For the &lt;strong>economic narrative&lt;/strong> and a companion implementation in Stata 19, see &lt;a href="https://carlos-mendez.org/post/stata_cate2/">Causal Machine Learning and the Resource Curse with Stata 19&lt;/a>.&lt;/p>
&lt;h3 id="learning-objectives">Learning objectives&lt;/h3>
&lt;p>By the end of this tutorial, you will be able to:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Understand&lt;/strong> the Double Machine Learning (DML) framework and why residualization enables valid causal inference&lt;/li>
&lt;li>&lt;strong>Distinguish&lt;/strong> heterogeneity features (X) from nuisance controls (W) in &lt;code>CausalForestDML&lt;/code>&lt;/li>
&lt;li>&lt;strong>Configure&lt;/strong> &lt;code>CausalForestDML&lt;/code> for discrete multi-valued treatments with panel data&lt;/li>
&lt;li>&lt;strong>Estimate&lt;/strong> Average Treatment Effects (ATEs) and Group Average Treatment Effects (GATEs) with proper BLB inference&lt;/li>
&lt;li>&lt;strong>Interpret&lt;/strong> GATE patterns to identify which variables moderate treatment effects&lt;/li>
&lt;li>&lt;strong>Use&lt;/strong> EconML-specific tools like &lt;code>SingleTreeCateInterpreter&lt;/code> for data-driven subgroup discovery&lt;/li>
&lt;li>&lt;strong>Evaluate&lt;/strong> results against known ground-truth parameters&lt;/li>
&lt;/ol>
&lt;h2 id="the-dml-causal-forest">The DML Causal Forest&lt;/h2>
&lt;h3 id="what-is-a-conditional-average-treatment-effect">What is a Conditional Average Treatment Effect?&lt;/h3>
&lt;p>The &lt;strong>Conditional Average Treatment Effect&lt;/strong> (CATE) measures how a treatment effect varies across individuals with different characteristics:&lt;/p>
&lt;p>$$\tau(\mathbf{x}) = E\{Y_i(1) - Y_i(0) \mid \mathbf{X}_i = \mathbf{x}\}$$&lt;/p>
&lt;p>In words: $\tau(\mathbf{x})$ is the expected difference in potential outcomes for a unit with covariates $\mathbf{x}$. When $\tau(\mathbf{x})$ varies across $\mathbf{x}$, we have &lt;strong>treatment effect heterogeneity&lt;/strong> &amp;mdash; the treatment helps some units more than others.&lt;/p>
&lt;h3 id="the-partial-linear-model">The Partial Linear Model&lt;/h3>
&lt;p>EconML&amp;rsquo;s &lt;code>CausalForestDML&lt;/code> estimates CATEs within the &lt;strong>partially linear model&lt;/strong>:&lt;/p>
&lt;p>$$Y = T \cdot \tau(\mathbf{x}) + g_0(\mathbf{x}, \mathbf{w}) + \varepsilon, \qquad T = m_0(\mathbf{x}, \mathbf{w}) + v$$&lt;/p>
&lt;p>where $g_0(\cdot)$ and $m_0(\cdot)$ are flexible &lt;strong>nuisance functions&lt;/strong> estimated by machine learning (Gradient Boosting in our case), $\tau(\mathbf{x})$ is the heterogeneous treatment effect function we want to learn, $\mathbf{x}$ are the covariates that may moderate the treatment effect, and $\mathbf{w}$ are additional controls used only in the first-stage nuisance models.&lt;/p>
&lt;p>The key insight of &lt;strong>Double Machine Learning&lt;/strong> (Chernozhukov et al., 2018) is to &lt;strong>residualize&lt;/strong> both the outcome and the treatment before fitting the causal forest. Think of residualization like noise-canceling headphones: the first stage removes the &amp;ldquo;background noise&amp;rdquo; of confounders from both the outcome and treatment, so the causal forest only hears the &amp;ldquo;signal&amp;rdquo; of the treatment effect. This two-step approach has a property called &lt;strong>Neyman orthogonality&lt;/strong>: first-stage estimation errors have only a second-order impact on the causal estimates. This means the causal forest remains valid even when the nuisance models converge at slower-than-parametric rates.&lt;/p>
&lt;h3 id="three-levels-of-effects">Three levels of effects&lt;/h3>
&lt;p>The causal forest produces per-observation CATE estimates, which aggregate to three levels:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Level&lt;/th>
&lt;th>Name&lt;/th>
&lt;th>What it measures&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>CATE&lt;/strong>&lt;/td>
&lt;td>Conditional ATE&lt;/td>
&lt;td>Effect for an individual with covariates $\mathbf{x}$&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>GATE&lt;/strong>&lt;/td>
&lt;td>Group ATE&lt;/td>
&lt;td>Average effect for a subgroup defined by a variable $Z$&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>ATE&lt;/strong>&lt;/td>
&lt;td>Average TE&lt;/td>
&lt;td>Overall average across all units&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="dml-pipeline">DML pipeline&lt;/h3>
&lt;pre>&lt;code class="language-mermaid">flowchart LR
A[&amp;quot;&amp;lt;b&amp;gt;Panel Data&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;3,000 obs&amp;quot;]:::data
B[&amp;quot;&amp;lt;b&amp;gt;First Stage&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;GBM nuisance&amp;lt;br/&amp;gt;models&amp;quot;]:::first
C[&amp;quot;&amp;lt;b&amp;gt;Residualize&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Y&amp;amp;#771; = Y - g&amp;amp;#770;(X,W)&amp;lt;br/&amp;gt;T&amp;amp;#771; = T - m&amp;amp;#770;(X,W)&amp;quot;]:::resid
D[&amp;quot;&amp;lt;b&amp;gt;Causal Forest&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;500 honest trees&amp;quot;]:::forest
E[&amp;quot;&amp;lt;b&amp;gt;CATEs&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Per-observation&amp;lt;br/&amp;gt;effects&amp;quot;]:::cate
A --&amp;gt; B --&amp;gt; C --&amp;gt; D --&amp;gt; E
classDef data fill:#6a9bcc,stroke:#141413,color:#fff
classDef first fill:#d97757,stroke:#141413,color:#fff
classDef resid fill:#00d4c8,stroke:#141413,color:#141413
classDef forest fill:#141413,stroke:#d97757,color:#fff
classDef cate fill:#6a9bcc,stroke:#141413,color:#fff
&lt;/code>&lt;/pre>
&lt;h2 id="setup-and-configuration">Setup and configuration&lt;/h2>
&lt;p>We use &lt;code>CausalForestDML&lt;/code> from EconML with Gradient Boosting nuisance models. The ground-truth parameters are defined inline so the tutorial is fully self-contained.&lt;/p>
&lt;pre>&lt;code class="language-python">import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from econml.dml import CausalForestDML
from sklearn.ensemble import (GradientBoostingRegressor,
GradientBoostingClassifier)
# Ground-truth ATEs from the data-generating process
TRUE_ATES = {
'1-0': 0.250, # Mining effect
'2-0': 0.300, # Mining + medium price
'3-0': 0.550, # Mining + high price
'2-1': 0.050, # Medium price premium (small)
'3-1': 0.300, # High price premium (large)
'3-2': 0.250, # High vs medium step
}
&lt;/code>&lt;/pre>
&lt;h2 id="load-the-simulated-data">Load the simulated data&lt;/h2>
&lt;p>The dataset simulates 300 districts across 8 countries observed over 10 years (2003&amp;ndash;2012), following the structure of Hodler, Lechner &amp;amp; Raschky (2023). Treatment has four levels: no mining (0), mining at low prices (1), medium prices (2), and high prices (3).&lt;/p>
&lt;pre>&lt;code class="language-python">DATA_URL = (&amp;quot;https://github.com/cmg777/starter-academic-v501&amp;quot;
&amp;quot;/raw/master/content/post/python_EconML/sim_resource_curse.csv&amp;quot;)
df = pd.read_csv(DATA_URL)
print(f&amp;quot;Dataset: {len(df):,} observations&amp;quot;)
print(f&amp;quot;Districts: {df['district_id'].nunique()}, &amp;quot;
f&amp;quot;Countries: {df['country_id'].nunique()}&amp;quot;)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Dataset: 3,000 observations
Districts: 300, Countries: 8
&lt;/code>&lt;/pre>
&lt;p>The dataset contains 3,000 district-year observations with a &lt;strong>heavily imbalanced&lt;/strong> treatment: 85% of observations are untreated (no mining), while each of the three mining groups comprises only 5% of the data. This imbalance makes causal inference challenging &amp;mdash; the causal forest must learn from relatively few treated observations.&lt;/p>
&lt;h2 id="descriptive-statistics">Descriptive statistics&lt;/h2>
&lt;h3 id="treatment-distribution">Treatment distribution&lt;/h3>
&lt;pre>&lt;code class="language-python">labels = {0: 'No mining', 1: 'Low prices',
2: 'Med prices', 3: 'High prices'}
for t, n in df['treatment'].value_counts().sort_index().items():
print(f&amp;quot; {t} ({labels[t]}): {n:,} ({n/len(df):.1%})&amp;quot;)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> 0 (No mining): 2,550 (85.0%)
1 (Low prices): 150 (5.0%)
2 (Med prices): 150 (5.0%)
3 (High prices): 150 (5.0%)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="python_econml_treatment_dist.png" alt="Treatment distribution across the four groups">
&lt;em>Treatment distribution across the four groups. The 85/5/5/5 imbalance makes causal inference challenging.&lt;/em>&lt;/p>
&lt;p>The 85/5/5/5 split means the causal forest has 2,550 control observations but only 150 per treatment level. For within-mining comparisons (e.g., 3-1), only 300 observations contribute, making standard errors larger for price-effect estimates.&lt;/p>
&lt;h3 id="outcomes-by-treatment-group">Outcomes by treatment group&lt;/h3>
&lt;pre>&lt;code class="language-python">for t in sorted(df['treatment'].unique()):
mask = df['treatment'] == t
m_ntl = df.loc[mask, 'ntl_log'].mean()
m_conf = df.loc[mask, 'conflict'].mean()
print(f&amp;quot; {t} ({labels[t]}): NTL={m_ntl:.3f} Conflict={m_conf:.1%}&amp;quot;)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> 0 (No mining): NTL=-1.137 Conflict=10.7%
1 (Low prices): NTL=-1.028 Conflict=18.0%
2 (Med prices): NTL=-0.930 Conflict=18.0%
3 (High prices): NTL=-0.615 Conflict=28.0%
&lt;/code>&lt;/pre>
&lt;p>The raw means show a clear gradient: higher treatment levels are associated with higher NTL and higher conflict rates. But these raw comparisons are &lt;strong>biased&lt;/strong> because mining districts differ systematically from non-mining districts in geography, institutions, and economic development.&lt;/p>
&lt;h2 id="naive-comparison-why-we-need-causal-ml">Naive comparison: why we need causal ML&lt;/h2>
&lt;pre>&lt;code class="language-python">for comp in ['1-0', '2-1', '3-1']:
a, b = int(comp[0]), int(comp[2])
naive = df.loc[df['treatment']==a, 'ntl_log'].mean() - \
df.loc[df['treatment']==b, 'ntl_log'].mean()
truth = TRUE_ATES[comp]
print(f&amp;quot; {comp}: Naive={naive:.3f} Truth={truth:.3f} Bias={naive-truth:+.3f}&amp;quot;)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> 1-0: Naive=0.109 Truth=0.250 Bias=-0.141
2-1: Naive=0.098 Truth=0.050 Bias=+0.048
3-1: Naive=0.413 Truth=0.300 Bias=+0.113
&lt;/code>&lt;/pre>
&lt;p>The naive 1-0 estimate of &lt;strong>0.109&lt;/strong> is severely biased downward from the true effect of &lt;strong>0.250&lt;/strong> &amp;mdash; a 56% underestimate. This happens because mining districts tend to have worse geographic and institutional characteristics that independently reduce development. The DML Causal Forest removes this &lt;strong>selection bias&lt;/strong> by residualizing both the outcome and the treatment against observed confounders before estimating the causal effect.&lt;/p>
&lt;h2 id="econml-estimation">EconML estimation&lt;/h2>
&lt;h3 id="configuration">Configuration&lt;/h3>
&lt;p>We separate covariates into two groups with distinct roles in the DML framework:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>X features&lt;/strong> (10 variables): Enter the causal forest and can drive treatment effect heterogeneity. These include &lt;code>exec_constraints&lt;/code>, &lt;code>quality_of_govt&lt;/code>, &lt;code>gdp_pc&lt;/code>, &lt;code>elevation&lt;/code>, &lt;code>temperature&lt;/code>, &lt;code>ruggedness&lt;/code>, &lt;code>distance_capital&lt;/code>, &lt;code>agri_suitability&lt;/code>, &lt;code>population&lt;/code>, and &lt;code>ethnic_frac&lt;/code>.&lt;/li>
&lt;li>&lt;strong>W controls&lt;/strong> (2 variables): Used only in the first-stage nuisance models (&lt;code>country_id&lt;/code>, &lt;code>year&lt;/code>). These absorb country and time fixed effects but do not enter the causal forest.&lt;/li>
&lt;/ul>
&lt;pre>&lt;code class="language-python">X_COLS = ['exec_constraints', 'quality_of_govt', 'gdp_pc',
'elevation', 'temperature', 'ruggedness',
'distance_capital', 'agri_suitability', 'population',
'ethnic_frac']
W_COLS = ['country_id', 'year']
&lt;/code>&lt;/pre>
&lt;h3 id="fitting-the-model">Fitting the model&lt;/h3>
&lt;pre>&lt;code class="language-python">Y = df['ntl_log'].values
T = df['treatment'].values
X = df[X_COLS].values
W = df[W_COLS].values
est_ntl = CausalForestDML(
model_y=GradientBoostingRegressor(n_estimators=200, max_depth=4,
random_state=42),
model_t=GradientBoostingClassifier(n_estimators=200, max_depth=4,
random_state=42),
discrete_treatment=True,
categories=[0, 1, 2, 3],
n_estimators=500,
min_samples_leaf=10,
honest=True, # Separate split/estimation samples
inference=True, # BLB confidence intervals
cv=5, # 5-fold cross-fitting
n_jobs=1,
random_state=42,
)
est_ntl.fit(Y, T, X=X, W=W, groups=df['district_id'].values)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> NTL: fitted in 25s
&lt;/code>&lt;/pre>
&lt;p>Several configuration choices deserve explanation. &lt;strong>Honest trees&lt;/strong> use separate subsamples for choosing splits versus estimating leaf values &amp;mdash; like having one team write the exam questions and a different team take the exam, this prevents the tree from &amp;ldquo;memorizing&amp;rdquo; the training data and enables valid confidence intervals. &lt;strong>GroupKFold&lt;/strong> via &lt;code>groups=district_id&lt;/code> ensures that cross-fitting (splitting data into K folds, training nuisance models on K-1 folds, and predicting on the held-out fold) does not split observations from the same district across folds, preventing data leakage in panel data. Note that this does &lt;strong>not&lt;/strong> provide clustered standard errors &amp;mdash; it only prevents within-district information from leaking across folds.&lt;/p>
&lt;h3 id="causal-identification">Causal identification&lt;/h3>
&lt;p>The Causal Forest requires the &lt;strong>Conditional Independence Assumption&lt;/strong> (CIA): treatment assignment is independent of potential outcomes conditional on observed covariates $(X, W)$. In our simulated data, the CIA holds by construction because all confounders are observed. In real data, unobserved confounders (geological surveys, political connections) could bias the estimates.&lt;/p>
&lt;h2 id="average-treatment-effects">Average Treatment Effects&lt;/h2>
&lt;p>EconML&amp;rsquo;s &lt;code>ate_inference()&lt;/code> provides ATEs with proper confidence intervals via the &lt;strong>Bootstrap of Little Bags&lt;/strong> (BLB) method &amp;mdash; a computationally efficient bootstrap that resamples within subsets (&amp;ldquo;bags&amp;rdquo;) of the data to estimate uncertainty without refitting the entire forest. We compute all six pairwise treatment contrasts:&lt;/p>
&lt;pre>&lt;code class="language-python">comparisons = [
('1-0', 0, 1), ('2-0', 0, 2), ('3-0', 0, 3),
('2-1', 1, 2), ('3-1', 1, 3), ('3-2', 2, 3),
]
for comp_label, t0, t1 in comparisons:
res = est_ntl.ate_inference(X, T0=t0, T1=t1)
lo, hi = res.conf_int_mean(alpha=0.1)
print(f&amp;quot; {comp_label}: ATE={res.mean_point:.4f} &amp;quot;
f&amp;quot;SE={res.stderr_mean:.4f} 90%CI=[{lo:.3f}, {hi:.3f}]&amp;quot;)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> 1-0: ATE=0.2398 SE=0.0702 90%CI=[0.124, 0.355]
2-0: ATE=0.2684 SE=0.0791 90%CI=[0.138, 0.399]
3-0: ATE=0.4598 SE=0.0811 90%CI=[0.327, 0.593]
2-1: ATE=0.0286 SE=0.1008 90%CI=[-0.137, 0.194]
3-1: ATE=0.2200 SE=0.1014 90%CI=[0.053, 0.387]
3-2: ATE=0.1914 SE=0.1092 90%CI=[0.012, 0.371]
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>Finding 1: Mining increases economic development and conflict.&lt;/strong> All three mining-vs-no-mining contrasts (1-0, 2-0, 3-0) are positive and highly significant. The ATE for the basic mining effect (1-0) is &lt;strong>0.240&lt;/strong>, close to the ground truth of 0.250. This represents a 24% increase in nighttime lights from mining activity, after controlling for geographic and institutional confounders.&lt;/p>
&lt;p>&lt;strong>Finding 2: Price effects are non-linear.&lt;/strong> The contrast 2-1 (medium vs low prices) is &lt;strong>0.029&lt;/strong> and statistically insignificant ($p &amp;gt; 0.10$) &amp;mdash; medium prices add essentially nothing beyond the basic mining effect. But the contrast 3-1 (high vs low prices) is &lt;strong>0.220&lt;/strong> and significant at the 5% level. This asymmetry confirms that price effects &amp;ldquo;jump&amp;rdquo; only at high commodity prices. The DML Causal Forest correctly recovers this non-linearity from the data.&lt;/p>
&lt;h2 id="treatment-effect-heterogeneity-gates">Treatment effect heterogeneity (GATEs)&lt;/h2>
&lt;h3 id="computing-gates-manually">Computing GATEs manually&lt;/h3>
&lt;p>Unlike Stata 19&amp;rsquo;s &lt;code>cate&lt;/code> command which computes GATEs automatically, EconML requires manual computation. We estimate individual-level CATEs via &lt;code>effect_inference()&lt;/code>, group observations by an institutional variable, and compute the mean CATE within each group:&lt;/p>
&lt;pre>&lt;code class="language-python">def compute_gate(est, df, z_var, t0, t1):
inf = est.effect_inference(X, T0=t0, T1=t1)
ite, ite_se = inf.point_estimate, inf.stderr
for z in sorted(df[z_var].unique()):
mask = df[z_var].values == z
gate = ite[mask].mean()
# Propagate BLB standard errors
gate_se = np.sqrt(np.mean(ite_se[mask]**2) / mask.sum())
&lt;/code>&lt;/pre>
&lt;p>The standard error formula &lt;code>sqrt(mean(se_i^2) / n)&lt;/code> propagates the per-observation BLB standard errors to the group level, capturing estimation uncertainty rather than just within-group heterogeneity.&lt;/p>
&lt;h3 id="gates-by-executive-constraints">GATEs by Executive Constraints&lt;/h3>
&lt;p>The mining effect (1-0) should vary with institutional quality, while the price effect (3-1) should be flat:&lt;/p>
&lt;p>&lt;img src="python_econml_gate_ntl_1v0_exec.png" alt="GATEs for NTL mining effect (1-0) by Executive Constraints">
&lt;em>GATEs for the mining effect (1-0) by executive constraints. The upward slope shows that stronger institutions amplify the economic benefits of mining.&lt;/em>&lt;/p>
&lt;p>&lt;img src="python_econml_gate_ntl_3v1_exec.png" alt="GATEs for NTL price effect (3-1) by Executive Constraints">
&lt;em>GATEs for the price effect (3-1) by executive constraints. The flat pattern confirms that institutions do not moderate price effects.&lt;/em>&lt;/p>
&lt;pre>&lt;code class="language-text"> 1-0 (Mining vs No Mining):
Exec. Constr. GATE 90% CI N
----------------------------------------------------
1 0.175 [0.168, 0.182] 300
2 0.255 [0.249, 0.262] 330
3 0.240 [0.236, 0.244] 720
4 0.242 [0.238, 0.246] 780
5 0.243 [0.237, 0.250] 420
6 0.264 [0.259, 0.269] 450
Range: 0.089
3-1 (High vs Low Prices):
Exec. Constr. GATE 90% CI N
----------------------------------------------------
1 0.242 [0.232, 0.252] 300
2 0.197 [0.187, 0.206] 330
3 0.217 [0.211, 0.224] 720
4 0.227 [0.221, 0.233] 780
5 0.224 [0.216, 0.231] 420
6 0.211 [0.204, 0.219] 450
Range: 0.045
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>Finding 3: Institutions moderate mining effects but NOT price effects.&lt;/strong> The mining effect GATEs (1-0) show a range of &lt;strong>0.089&lt;/strong> across executive constraint levels, with the lowest effect (0.175) at the weakest institutions (exec_constraints=1) rising to &lt;strong>0.264&lt;/strong> at the strongest (exec_constraints=6). The price effect GATEs (3-1) show a much narrower range of only &lt;strong>0.045&lt;/strong>, with no clear monotone pattern. This asymmetric pattern &amp;mdash; institutions shaping the mining-vs-no-mining margin but not the price margin &amp;mdash; is exactly the structural finding embedded in the DGP and reported in Hodler, Lechner &amp;amp; Raschky (2023).&lt;/p>
&lt;h3 id="gates-by-quality-of-government">GATEs by Quality of Government&lt;/h3>
&lt;p>The same pattern appears when we use a continuous institutional measure:&lt;/p>
&lt;p>&lt;img src="python_econml_gate_ntl_1v0_qog.png" alt="GATEs for NTL mining effect (1-0) by Quality of Government">
&lt;em>GATEs for the mining effect (1-0) by quality of government. The positive relationship cross-validates the executive constraints finding.&lt;/em>&lt;/p>
&lt;p>&lt;img src="python_econml_gate_ntl_3v1_qog.png" alt="GATEs for NTL price effect (3-1) by Quality of Government">
&lt;em>GATEs for the price effect (3-1) by quality of government. The flat pattern is consistent across institutional measures.&lt;/em>&lt;/p>
&lt;p>The mining effect (1-0) shows a positive relationship with quality of government, while the price effect (3-1) remains approximately flat across the institutional quality distribution. This cross-validates Finding 3 using a different institutional measure.&lt;/p>
&lt;h2 id="variable-importance">Variable importance&lt;/h2>
&lt;p>EconML computes feature importances as the normalized contribution of each variable to treatment effect heterogeneity across all forest splits:&lt;/p>
&lt;pre>&lt;code class="language-python">importances = est_ntl.feature_importances_
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> distance_capital 0.172
ethnic_frac 0.141
ruggedness 0.135
population 0.126
agri_suitability 0.120
temperature 0.120
elevation 0.120
gdp_pc 0.036
quality_of_govt 0.019
exec_constraints 0.010
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="python_econml_var_importance.png" alt="Feature importance for treatment effect heterogeneity">
&lt;em>Feature importance for treatment effect heterogeneity. Geographic variables dominate splitting frequency, but institutional variables are the true moderators.&lt;/em>&lt;/p>
&lt;p>Geographic variables dominate the importances because they have &lt;strong>continuous variation&lt;/strong> that the forest can split on finely. Institutional variables (&lt;code>exec_constraints&lt;/code>, &lt;code>quality_of_govt&lt;/code>) rank lower despite being the true moderators in the DGP &amp;mdash; they have limited discrete values (6 levels for executive constraints), so the forest cannot split on them as frequently. This illustrates an important caveat: feature importance measures &lt;strong>splitting frequency&lt;/strong>, not causal importance for moderation. The GATE analysis (which directly tests moderation) is more informative than feature importance for answering the question &amp;ldquo;which variables moderate treatment effects?&amp;rdquo;&lt;/p>
&lt;h2 id="cate-interpreter">CATE Interpreter&lt;/h2>
&lt;p>EconML provides a &lt;code>SingleTreeCateInterpreter&lt;/code> that fits a &lt;strong>shallow decision tree&lt;/strong> to the estimated CATEs, creating interpretable subgroups. This is an EconML-specific feature not available in Stata&amp;rsquo;s &lt;code>cate&lt;/code> command.&lt;/p>
&lt;pre>&lt;code class="language-python">from econml.cate_interpreter import SingleTreeCateInterpreter
intrp = SingleTreeCateInterpreter(max_depth=2, min_samples_leaf=100)
intrp.interpret(est_ntl, X)
intrp.plot(feature_names=X_COLS)
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="python_econml_cate_tree.png" alt="Decision tree summarizing CATE heterogeneity for the mining effect">
&lt;em>Decision tree summarizing CATE heterogeneity for the mining effect (1-0). The shallow tree identifies data-driven subgroups with different treatment effects.&lt;/em>&lt;/p>
&lt;p>The interpreter tree identifies data-driven subgroups with different treatment effects using a depth-2 tree. This provides a complementary view to the GATE analysis: while GATEs test &lt;strong>pre-specified hypotheses&lt;/strong> about institutional moderation, the CATE interpreter discovers subgroups that the analyst may not have considered.&lt;/p>
&lt;h2 id="discussion">Discussion&lt;/h2>
&lt;h3 id="limitations">Limitations&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>No clustered standard errors&lt;/strong>: EconML does not support clustered SEs natively. We use &lt;code>GroupKFold&lt;/code> by district to prevent data leakage, but this does not account for within-district correlation in the standard errors. The &lt;a href="https://carlos-mendez.org/post/stata_cate2/">companion Stata tutorial&lt;/a> uses Stata 19&amp;rsquo;s &lt;code>cate&lt;/code> command which handles clustering directly.&lt;/li>
&lt;li>&lt;strong>Contemporaneous outcomes&lt;/strong>: The full paper uses treatment at time $t$ and outcome at $t+1$, strengthening causal identification. Our simulated data uses contemporaneous treatment and outcomes.&lt;/li>
&lt;li>&lt;strong>Simplified covariates&lt;/strong>: The real analysis uses 60+ covariates; we use 12. The simulated DGP guarantees that the CIA holds &amp;mdash; all confounders are observed by construction.&lt;/li>
&lt;/ul>
&lt;h3 id="assumptions">Assumptions&lt;/h3>
&lt;p>The CATE estimates rely on the &lt;strong>Conditional Independence Assumption&lt;/strong>: treatment is independent of potential outcomes given $(X, W)$. In observational settings, this assumption is untestable and may be violated by unobserved confounders. With simulated data, we know the assumption holds.&lt;/p>
&lt;h2 id="summary-and-next-steps">Summary and next steps&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>EconML&amp;rsquo;s CausalForestDML recovered all three ground-truth findings.&lt;/strong> The ATE for the basic mining effect (1-0 = 0.240) closely matches the true value of 0.250. Price effects are correctly identified as non-linear (2-1 = 0.029 n.s., 3-1 = 0.220 significant). GATE patterns confirm that institutions moderate mining effects (range = 0.089) but not price effects (range = 0.045).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>The DML framework is the key methodological contribution.&lt;/strong> By residualizing both the outcome and treatment in a first stage, DML achieves Neyman orthogonality &amp;mdash; making the causal forest robust to errors in the nuisance models. This is particularly valuable when the outcome and treatment processes are complex.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Feature importance can be misleading for moderation analysis.&lt;/strong> Geographic variables dominate the forest&amp;rsquo;s splitting importances, but institutional variables are the true moderators. GATE analysis is more appropriate for testing specific moderation hypotheses.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>The CATE interpreter provides data-driven subgroup discovery.&lt;/strong> Unlike pre-specified GATEs, the shallow decision tree finds the variables and thresholds that best separate high-effect from low-effect subgroups.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>For the economic story behind these findings and a parallel implementation using Stata 19&amp;rsquo;s built-in &lt;code>cate&lt;/code> command, see the companion tutorial: &lt;a href="https://carlos-mendez.org/post/stata_cate2/">Causal Machine Learning and the Resource Curse with Stata 19&lt;/a>.&lt;/p>
&lt;h2 id="exercises">Exercises&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Replace the nuisance models.&lt;/strong> Swap &lt;code>GradientBoostingRegressor&lt;/code> with &lt;code>RandomForestRegressor(n_estimators=200)&lt;/code>. Do the ATE and GATE estimates change? Why or why not (think about Neyman orthogonality)?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Vary the number of trees.&lt;/strong> Try &lt;code>n_estimators=100&lt;/code> vs &lt;code>n_estimators=1000&lt;/code>. How do the standard errors and GATE patterns change?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Test the GroupKFold assumption.&lt;/strong> Remove &lt;code>groups=df['district_id'].values&lt;/code> from the &lt;code>fit()&lt;/code> call. What happens to the confidence intervals?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Discretize quality of government.&lt;/strong> Create quartiles of &lt;code>quality_of_govt&lt;/code> and compute GATEs on the quartiles instead of raw values. Do the patterns become clearer?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Explore the CATE interpreter depth.&lt;/strong> Increase &lt;code>max_depth&lt;/code> from 2 to 4 in &lt;code>SingleTreeCateInterpreter&lt;/code>. Do the additional splits reveal meaningful subgroups or just noise?&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="references">References&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://doi.org/10.1371/journal.pone.0284968" target="_blank" rel="noopener">Hodler, R., Lechner, M., &amp;amp; Raschky, P.A. (2023). Institutions and the resource curse: New insights from causal machine learning. &lt;em>PLoS ONE&lt;/em>, 18(6), e0284968.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1111/ectj.12097" target="_blank" rel="noopener">Chernozhukov, V., Chetverikov, D., Demirer, M., Duflo, E., Hansen, C., Newey, W., &amp;amp; Robins, J. (2018). Double/Debiased Machine Learning for Treatment and Structural Parameters. &lt;em>The Econometrics Journal&lt;/em>, 21(1), C1&amp;ndash;C68.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1214/18-AOS1709" target="_blank" rel="noopener">Athey, S., Tibshirani, J., &amp;amp; Wager, S. (2019). Generalized Random Forests. &lt;em>The Annals of Statistics&lt;/em>, 47(2), 1148&amp;ndash;1178.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.nber.org/papers/w5398" target="_blank" rel="noopener">Sachs, J.D. &amp;amp; Warner, A.M. (1995). Natural Resource Abundance and Economic Growth. &lt;em>NBER Working Paper&lt;/em> No. 5398.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://doi.org/10.1111/j.1468-0297.2006.01045.x" target="_blank" rel="noopener">Mehlum, H., Moene, K., &amp;amp; Torvik, R. (2006). Institutions and the Resource Curse. &lt;em>The Economic Journal&lt;/em>, 116(508), 1&amp;ndash;20.&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.pywhy.org/EconML/" target="_blank" rel="noopener">EconML Documentation &amp;mdash; PyWhy&lt;/a>&lt;/li>
&lt;/ol></description></item><item><title>Causal Machine Learning and the Resource Curse with Stata 19</title><link>https://carlos-mendez.org/post/stata_cate2/</link><pubDate>Wed, 06 May 2026 00:00:00 +0000</pubDate><guid>https://carlos-mendez.org/post/stata_cate2/</guid><description>&lt;h2 id="1-overview">1. Overview&lt;/h2>
&lt;p>Imagine discovering that the very thing that should make a country rich &amp;mdash; abundant natural resources &amp;mdash; actually makes it poorer. This is the &lt;strong>resource curse&lt;/strong> hypothesis, first documented by Sachs and Warner (1995): countries rich in oil, minerals, or other extractive resources often experience slower growth, weaker institutions, and more conflict than resource-poor nations.&lt;/p>
&lt;p>But the story is more nuanced than &amp;ldquo;resources are bad.&amp;rdquo; Mehlum, Moene, and Torvik (2006) argued that &lt;strong>institutional quality&lt;/strong> determines whether resource wealth becomes a blessing or a curse. Countries with strong rule of law and quality governance channel resource revenues productively, while weak institutions allow rent-seeking and conflict.&lt;/p>
&lt;p>This tutorial is inspired by &lt;a href="https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0284968" target="_blank" rel="noopener">Hodler, Lechner &amp;amp; Raschky (2023)&lt;/a>, who brought &lt;strong>causal machine learning&lt;/strong> to this debate. Using a Modified Causal Forest on sub-national mining districts across Sub-Saharan Africa, they uncovered three key findings:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Mining increases development and conflict&lt;/strong> &amp;mdash; Districts that begin mining experience higher nighttime lights (a proxy for economic activity) and more conflict events.&lt;/li>
&lt;li>&lt;strong>Price effects are non-linear&lt;/strong> &amp;mdash; The effect of mineral prices on outcomes is small at moderate prices but jumps sharply at high prices.&lt;/li>
&lt;li>&lt;strong>Institutions moderate mining but NOT prices&lt;/strong> &amp;mdash; Institutional quality amplifies the development benefits of mining (upward-sloping GATEs), but does &lt;em>not&lt;/em> moderate the effect of global price shocks (flat GATEs).&lt;/li>
&lt;/ol>
&lt;p>This tutorial uses &lt;strong>Stata 19&amp;rsquo;s &lt;code>cate&lt;/code> command&lt;/strong> to replicate all three findings on a simulated dataset with &lt;strong>known ground-truth causal effects&lt;/strong> (3,000 observations = 300 districts $\times$ 10 years). Because the data-generating process is known, we can directly compare our estimates against the true parameter values. The &lt;code>cate&lt;/code> command provides native access to generalized random forests, doubly robust estimation, and formal hypothesis tests &amp;mdash; all without external packages.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Prerequisite.&lt;/strong> This post requires &lt;strong>Stata 19 or later&lt;/strong>. The &lt;code>cate&lt;/code> command does not exist in Stata 18. The companion do-file aborts on startup if it detects an older Stata.&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>&lt;strong>Runtime.&lt;/strong> The full analysis takes approximately &lt;strong>20&amp;ndash;30 minutes&lt;/strong> on a modern machine. Each &lt;code>cate&lt;/code> estimation takes 60&amp;ndash;90 seconds with 5-fold cross-fitting.&lt;/p>
&lt;/blockquote>
&lt;p>For a deeper introduction to the CATE framework and the &lt;code>cate&lt;/code> command on a binary-treatment dataset, see the companion tutorial &lt;a href="https://carlos-mendez.org/post/stata_cate/">Conditional Average Treatment Effects (CATE) with Stata 19&lt;/a>.&lt;/p>
&lt;h3 id="11-learning-objectives">1.1 Learning objectives&lt;/h3>
&lt;p>By the end of this tutorial you should be able to:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Understand&lt;/strong> the resource curse hypothesis and why treatment effects may vary with institutional quality.&lt;/li>
&lt;li>&lt;strong>Apply&lt;/strong> Stata 19&amp;rsquo;s &lt;code>cate&lt;/code> command to a multi-valued treatment via binary pairwise comparisons.&lt;/li>
&lt;li>&lt;strong>Distinguish&lt;/strong> PO and AIPW estimators and when each is preferred.&lt;/li>
&lt;li>&lt;strong>Estimate&lt;/strong> ATEs, GATEs, and IATEs for multiple treatment contrasts.&lt;/li>
&lt;li>&lt;strong>Interpret&lt;/strong> GATE patterns to identify institutional moderation of treatment effects.&lt;/li>
&lt;li>&lt;strong>Diagnose&lt;/strong> treatment-effect heterogeneity with formal hypothesis tests (&lt;code>estat heterogeneity&lt;/code>, &lt;code>estat gatetest&lt;/code>).&lt;/li>
&lt;li>&lt;strong>Visualize&lt;/strong> individualized treatment effects using &lt;code>categraph&lt;/code> postestimation tools.&lt;/li>
&lt;li>&lt;strong>Connect&lt;/strong> statistical results to substantive findings from published research.&lt;/li>
&lt;/ul>
&lt;h3 id="12-analytical-roadmap">1.2 Analytical roadmap&lt;/h3>
&lt;p>The diagram below shows the five stages of this tutorial, from data exploration through advanced diagnostics.&lt;/p>
&lt;pre>&lt;code class="language-mermaid">flowchart LR
A[&amp;quot;&amp;lt;b&amp;gt;Data &amp;amp;&amp;lt;br/&amp;gt;Descriptives&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;Sections 3--4&amp;lt;/i&amp;gt;&amp;quot;]:::data
B[&amp;quot;&amp;lt;b&amp;gt;Naive vs&amp;lt;br/&amp;gt;Ground Truth&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;Section 5&amp;lt;/i&amp;gt;&amp;quot;]:::naive
C[&amp;quot;&amp;lt;b&amp;gt;ATE&amp;lt;br/&amp;gt;Estimation&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;Sections 6--7&amp;lt;/i&amp;gt;&amp;quot;]:::ate
D[&amp;quot;&amp;lt;b&amp;gt;GATE&amp;lt;br/&amp;gt;Heterogeneity&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;Section 8&amp;lt;/i&amp;gt;&amp;quot;]:::gate
E[&amp;quot;&amp;lt;b&amp;gt;Advanced&amp;lt;br/&amp;gt;Diagnostics&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;Section 9&amp;lt;/i&amp;gt;&amp;quot;]:::diag
A --&amp;gt; B --&amp;gt; C --&amp;gt; D --&amp;gt; E
classDef data fill:#6a9bcc,stroke:#141413,color:#fff
classDef naive fill:#d97757,stroke:#141413,color:#fff
classDef ate fill:#00d4c8,stroke:#141413,color:#141413
classDef gate fill:#d97757,stroke:#141413,color:#fff
classDef diag fill:#141413,stroke:#d97757,color:#fff
&lt;/code>&lt;/pre>
&lt;hr>
&lt;h2 id="2-the-cate-framework">2. The CATE framework&lt;/h2>
&lt;h3 id="21-from-ate-to-cate">2.1 From ATE to CATE&lt;/h3>
&lt;p>The Average Treatment Effect (ATE) summarizes causal effects as a single number for the entire population. But when effects are heterogeneous &amp;mdash; varying across subgroups &amp;mdash; the ATE can mask important patterns. The &lt;strong>Conditional Average Treatment Effect (CATE)&lt;/strong> captures this heterogeneity:&lt;/p>
&lt;p>$$\tau(\mathbf{x}) = E\{y_i(1) - y_i(0) \mid \mathbf{x}_i = \mathbf{x}\}$$&lt;/p>
&lt;p>where $y_i(1)$ and $y_i(0)$ are potential outcomes under treatment and control, and $\mathbf{x}$ is a vector of characteristics that may moderate the treatment effect. If $\tau(\mathbf{x})$ is constant across all $\mathbf{x}$, we are back at the ATE. Whenever it varies, the ATE is an average of these subgroup effects weighted by how common each $\mathbf{x}$ is in the data.&lt;/p>
&lt;h3 id="22-the-partial-linear-model">2.2 The partial linear model&lt;/h3>
&lt;p>Stata 19&amp;rsquo;s &lt;code>cate&lt;/code> estimates CATEs within a partial linear framework:&lt;/p>
&lt;p>$$y = d \cdot \tau(\mathbf{x}) + g(\mathbf{x}, \mathbf{w}) + \epsilon, \qquad d = f(\mathbf{x}, \mathbf{w}) + u$$&lt;/p>
&lt;p>where $\tau(\mathbf{x})$ is the heterogeneous treatment effect function, $g(\cdot)$ and $f(\cdot)$ are flexible nuisance functions estimated by machine learning, $\mathbf{x}$ are CATE covariates (potential moderators), and $\mathbf{w}$ are additional controls.&lt;/p>
&lt;p>Think of the nuisance functions as &lt;em>background noise&lt;/em> that must be cleaned away before the treatment effect signal becomes visible. The &lt;code>cate&lt;/code> command uses &lt;strong>cross-fitting&lt;/strong> to prevent the nuisance models from overfitting: data are split into $K$ folds, and each fold&amp;rsquo;s nuisance predictions are made using models trained on the other $K-1$ folds.&lt;/p>
&lt;h3 id="23-two-estimators">2.3 Two estimators&lt;/h3>
&lt;p>Stata 19 provides two estimators for the CATE:&lt;/p>
&lt;p>&lt;strong>Partialing-Out (PO).&lt;/strong> Think of PO like cleaning two messy signals before comparing them. It residualizes both the outcome and treatment against $\mathbf{x}$ and $\mathbf{w}$, then estimates $\tau(\mathbf{x})$ from the residuals using a generalized random forest (Nie &amp;amp; Wager, 2021). PO is robust when propensity scores get close to 0 or 1.&lt;/p>
&lt;p>&lt;strong>Augmented Inverse-Probability Weighting (AIPW).&lt;/strong> AIPW is like having a backup GPS &amp;mdash; if one route fails, the other still gets you there. It constructs doubly robust scores that combine outcome modeling and propensity score weighting. Even if one model is misspecified, the estimator remains consistent (Knaus, 2022; Kennedy, 2023).&lt;/p>
&lt;h3 id="24-three-levels-of-treatment-effects">2.4 Three levels of treatment effects&lt;/h3>
&lt;pre>&lt;code class="language-mermaid">flowchart LR
A[&amp;quot;&amp;lt;b&amp;gt;Panel Data&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;3,000 obs&amp;lt;br/&amp;gt;300 districts x 10 years&amp;quot;]:::data
A --&amp;gt; B[&amp;quot;&amp;lt;b&amp;gt;cate po / aipw&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Binary pairwise&amp;lt;br/&amp;gt;comparisons&amp;quot;]:::main
B --&amp;gt; C[&amp;quot;&amp;lt;b&amp;gt;IATEs&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Per-observation&amp;lt;br/&amp;gt;effects tau(x_i)&amp;quot;]:::iate
B --&amp;gt; D[&amp;quot;&amp;lt;b&amp;gt;GATEs&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Group averages&amp;lt;br/&amp;gt;by institutions&amp;quot;]:::gate
B --&amp;gt; E[&amp;quot;&amp;lt;b&amp;gt;ATE&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;Overall population&amp;lt;br/&amp;gt;average&amp;quot;]:::ate
C --&amp;gt; F[&amp;quot;categraph histogram&amp;lt;br/&amp;gt;categraph iateplot&amp;quot;]:::post
D --&amp;gt; G[&amp;quot;categraph gateplot&amp;lt;br/&amp;gt;estat gatetest&amp;quot;]:::post
E --&amp;gt; H[&amp;quot;estat heterogeneity&amp;lt;br/&amp;gt;estat ate&amp;quot;]:::post
classDef data fill:#6a9bcc,stroke:#141413,color:#fff
classDef main fill:#141413,stroke:#141413,color:#fff
classDef iate fill:#00d4c8,stroke:#141413,color:#141413
classDef gate fill:#d97757,stroke:#141413,color:#fff
classDef ate fill:#6a9bcc,stroke:#141413,color:#fff
classDef post fill:#f5f5f5,stroke:#141413,color:#141413
&lt;/code>&lt;/pre>
&lt;ul>
&lt;li>&lt;strong>IATE&lt;/strong> (Individualized Average Treatment Effects): One effect per observation, $\tau(\mathbf{x}_i)$&lt;/li>
&lt;li>&lt;strong>GATE&lt;/strong> (Group Average Treatment Effects): Average effect within prespecified groups, $\tau(g) = E\{\tau(\mathbf{x}) \mid G = g\}$&lt;/li>
&lt;li>&lt;strong>ATE&lt;/strong>: Overall population average, $\text{ATE} = E\{\tau(\mathbf{x})\}$&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="3-data-preparation">3. Data preparation&lt;/h2>
&lt;p>We use a simulated dataset of 3,000 observations (300 districts $\times$ 10 years) across 8 fictional countries. The data mirror the structure of Hodler et al. (2023) but with known ground-truth causal effects, enabling direct validation of our estimates.&lt;/p>
&lt;pre>&lt;code class="language-stata">* Import the simulated resource curse dataset
* GitHub: import delimited using &amp;quot;https://github.com/quarcs-lab/data-open/raw/master/stata19/sim_resource_curse.csv&amp;quot;, clear
import delimited using &amp;quot;sim_resource_curse.csv&amp;quot;, clear
* Label variables
label variable district_id &amp;quot;District ID (1-300)&amp;quot;
label variable country_id &amp;quot;Country ID (1-8)&amp;quot;
label variable year &amp;quot;Year (2003-2012)&amp;quot;
label variable treatment &amp;quot;Treatment group (0=none, 1=low, 2=med, 3=high)&amp;quot;
label variable mining &amp;quot;Mining district (binary)&amp;quot;
label variable price_index &amp;quot;Mineral price index&amp;quot;
label variable exec_constraints &amp;quot;Constraints on Executive (1-6)&amp;quot;
label variable quality_of_govt &amp;quot;Quality of Government (0.22-0.70)&amp;quot;
label variable gdp_pc &amp;quot;GDP per capita&amp;quot;
label variable elevation &amp;quot;Elevation (meters)&amp;quot;
label variable temperature &amp;quot;Mean temperature (Celsius)&amp;quot;
label variable ruggedness &amp;quot;Terrain ruggedness&amp;quot;
label variable distance_capital &amp;quot;Distance to capital (meters)&amp;quot;
label variable agri_suitability &amp;quot;Agricultural suitability (0-1)&amp;quot;
label variable population &amp;quot;Population&amp;quot;
label variable ethnic_frac &amp;quot;Ethnic fractionalization (0-1)&amp;quot;
label variable ntl_log &amp;quot;Log nighttime lights&amp;quot;
label variable conflict &amp;quot;Conflict event (binary)&amp;quot;
* Create integer version of exec_constraints for group()
gen int exec_con = round(exec_constraints)
label variable exec_con &amp;quot;Executive Constraints (integer 1-6)&amp;quot;
* Save as .dta
save &amp;quot;sim_resource_curse.dta&amp;quot;, replace
* Report dataset dimensions
describe, short
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Contains data from sim_resource_curse.dta
Observations: 3,000
Variables: 19 6 May 2026
Sorted by:
&lt;/code>&lt;/pre>
&lt;p>The dataset contains &lt;strong>3,000 observations&lt;/strong> organized as a balanced panel: 300 districts observed over 10 years (2003&amp;ndash;2012) in 8 fictional countries. The key variables are:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Variable&lt;/th>
&lt;th>Description&lt;/th>
&lt;th>Type&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>treatment&lt;/code>&lt;/td>
&lt;td>Treatment group (0=none, 1=low, 2=med, 3=high price)&lt;/td>
&lt;td>Categorical&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ntl_log&lt;/code>&lt;/td>
&lt;td>Log nighttime lights (development proxy)&lt;/td>
&lt;td>Continuous outcome&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>conflict&lt;/code>&lt;/td>
&lt;td>Conflict event indicator&lt;/td>
&lt;td>Binary outcome&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>exec_constraints&lt;/code>&lt;/td>
&lt;td>Constraints on executive (1&amp;ndash;6 scale)&lt;/td>
&lt;td>Institutional moderator&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>quality_of_govt&lt;/code>&lt;/td>
&lt;td>Quality of government (0.22&amp;ndash;0.70)&lt;/td>
&lt;td>Institutional moderator&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>gdp_pc&lt;/code>, &lt;code>elevation&lt;/code>, &lt;code>temperature&lt;/code>, &amp;hellip;&lt;/td>
&lt;td>Economic and geographic covariates&lt;/td>
&lt;td>Controls&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="4-descriptive-statistics">4. Descriptive statistics&lt;/h2>
&lt;pre>&lt;code class="language-stata">* Summary statistics for key variables
tabstat ntl_log conflict exec_constraints quality_of_govt gdp_pc ///
elevation temperature ruggedness distance_capital ///
agri_suitability population ethnic_frac, ///
statistics(mean sd min max) columns(statistics) format(%9.3f)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> Variable | Mean SD Min Max
-------------+----------------------------------------
ntl_log | -1.096 0.435 -2.503 0.265
conflict | 0.123 0.328 0.000 1.000
exec_const~s | 3.680 1.489 1.000 6.000
quality_of~t | 0.440 0.152 0.220 0.700
gdp_pc | 2198.000 1469.937 500.000 5000.000
elevation | 499.083 302.031 0.000 1357.232
temperature | 23.913 3.920 13.993 35.000
ruggedness | 24.423 17.803 0.000 76.953
distance_c~l | 2.68e+05 1.44e+05 10813.747 4.97e+05
agri_suita~y | 0.395 0.197 0.000 0.983
population | 82028.426 85186.961 4134.682 5.97e+05
ethnic_frac | 0.550 0.202 0.201 0.899
------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-stata">* Treatment distribution
tab treatment, missing
* Mining share
count if treatment &amp;gt; 0
* Outcomes by treatment group
table treatment, statistic(mean ntl_log) statistic(mean conflict) ///
statistic(count ntl_log) nformat(%9.3f)
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text"> Treatment |
group |
(0=none, |
1=low, |
2=med, |
3=high) | Freq. Percent Cum.
------------+-----------------------------------
0 | 2,550 85.00 85.00
1 | 150 5.00 90.00
2 | 150 5.00 95.00
3 | 150 5.00 100.00
------------+-----------------------------------
Total | 3,000 100.00
Mining share: 15.0%
--------------------------------------------------------------
| Mean
| ntl_log conflict
-----------------------------------------------+--------------------
Treatment group (0=none, 1=low, 2=med, 3=high) |
0 | -1.137 0.107
1 | -1.028 0.180
2 | -0.930 0.180
3 | -0.615 0.280
Total | -1.096 0.123
--------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;blockquote>
&lt;p>&lt;strong>Treatment imbalance.&lt;/strong> The treatment distribution is highly imbalanced: approximately &lt;strong>85% of observations&lt;/strong> are in the control group (no mining), while each treated group contains only about &lt;strong>5% of observations&lt;/strong>. This mirrors real-world mining data where few districts have active mines. Stata&amp;rsquo;s &lt;code>cate&lt;/code> handles this via honest random forests with appropriate sample-splitting.&lt;/p>
&lt;/blockquote>
&lt;p>The descriptive statistics reveal important patterns. Mean &lt;code>ntl_log&lt;/code> varies across treatment groups, but these raw differences mix the causal effect with confounding &amp;mdash; mining districts differ systematically from non-mining districts in geography, institutions, and economic conditions. The next section demonstrates this directly.&lt;/p>
&lt;hr>
&lt;h2 id="5-naive-comparison-vs-ground-truth">5. Naive comparison vs ground truth&lt;/h2>
&lt;p>Before applying any causal method, we compute raw mean differences and compare them to the known ground-truth ATEs from the data-generating process.&lt;/p>
&lt;pre>&lt;code class="language-stata">* Naive difference-in-means (biased by confounders)
display as text _newline &amp;quot;=== Naive Difference-in-Means (biased) ===&amp;quot;
display as text &amp;quot;Comparison&amp;quot; _col(20) &amp;quot;NTL diff&amp;quot; _col(35) &amp;quot;Ground Truth&amp;quot;
display as text &amp;quot;{hline 50}&amp;quot;
* 1-0: mining vs no mining
quietly summarize ntl_log if treatment == 1
local m1 = r(mean)
quietly summarize ntl_log if treatment == 0
local m0 = r(mean)
display as result &amp;quot;1 vs 0&amp;quot; _col(20) %7.4f (`m1' - `m0') _col(35) &amp;quot;0.25&amp;quot;
* 3-1: high vs low prices
quietly summarize ntl_log if treatment == 3
local m3 = r(mean)
display as result &amp;quot;3 vs 1&amp;quot; _col(20) %7.4f (`m3' - `m1') _col(35) &amp;quot;0.30&amp;quot;
* 2-1: medium vs low prices
quietly summarize ntl_log if treatment == 2
local m2 = r(mean)
display as result &amp;quot;2 vs 1&amp;quot; _col(20) %7.4f (`m2' - `m1') _col(35) &amp;quot;0.05&amp;quot;
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">=== Naive Difference-in-Means (biased) ===
Comparison NTL diff Ground Truth
--------------------------------------------------
1 vs 0 0.1092 0.25
2 vs 0 0.2077 0.30
3 vs 0 0.5227 0.55
2 vs 1 0.0985 0.05
3 vs 1 0.4135 0.30
3 vs 2 0.3150 0.25
--------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>The ground-truth ATEs for all six pairwise comparisons are:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Contrast&lt;/th>
&lt;th style="text-align:center">Ground Truth&lt;/th>
&lt;th>Interpretation&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1-0&lt;/td>
&lt;td style="text-align:center">0.25&lt;/td>
&lt;td>Mining effect at mean institutions&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2-0&lt;/td>
&lt;td style="text-align:center">0.30&lt;/td>
&lt;td>Mining + medium price premium&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3-0&lt;/td>
&lt;td style="text-align:center">0.55&lt;/td>
&lt;td>Mining + high price premium&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2-1&lt;/td>
&lt;td style="text-align:center">0.05&lt;/td>
&lt;td>Medium price premium (small)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3-1&lt;/td>
&lt;td style="text-align:center">0.30&lt;/td>
&lt;td>High price premium (large)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3-2&lt;/td>
&lt;td style="text-align:center">0.25&lt;/td>
&lt;td>High vs medium step&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The naive comparisons are &lt;strong>biased&lt;/strong> because mining districts differ systematically from non-mining districts in geography, institutions, and economic conditions. Some confounders push the raw difference above the truth, others below. This motivates the use of causal machine learning methods that adjust for these confounders.&lt;/p>
&lt;hr>
&lt;h2 id="6-estimation-strategy">6. Estimation strategy&lt;/h2>
&lt;h3 id="61-binary-pairwise-comparisons">6.1 Binary pairwise comparisons&lt;/h3>
&lt;p>Stata 19&amp;rsquo;s &lt;code>cate&lt;/code> command requires a &lt;strong>binary treatment variable&lt;/strong>. Since our treatment has 4 levels (0, 1, 2, 3), we run separate estimations for each pairwise comparison, subsetting the data to the two relevant groups each time. This yields 6 binary comparisons that map directly to the three key findings:&lt;/p>
&lt;pre>&lt;code class="language-mermaid">graph TD
T[&amp;quot;&amp;lt;b&amp;gt;4-Level Treatment&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;0: No mining&amp;lt;br/&amp;gt;1: Low price&amp;lt;br/&amp;gt;2: Medium price&amp;lt;br/&amp;gt;3: High price&amp;quot;]:::data
subgraph F1[&amp;quot;Finding 1: Mining Effect&amp;quot;]
C10[&amp;quot;1 vs 0&amp;quot;]:::f1
C20[&amp;quot;2 vs 0&amp;quot;]:::f1
C30[&amp;quot;3 vs 0&amp;quot;]:::f1
end
subgraph F2[&amp;quot;Finding 2: Price Non-linearity&amp;quot;]
C21[&amp;quot;2 vs 1&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;small ~ 0.05&amp;lt;/i&amp;gt;&amp;quot;]:::f2
C31[&amp;quot;3 vs 1&amp;lt;br/&amp;gt;&amp;lt;i&amp;gt;large ~ 0.30&amp;lt;/i&amp;gt;&amp;quot;]:::f2
C32[&amp;quot;3 vs 2&amp;quot;]:::f2
end
T --&amp;gt; F1
T --&amp;gt; F2
classDef data fill:#6a9bcc,stroke:#141413,color:#fff
classDef f1 fill:#00d4c8,stroke:#141413,color:#141413
classDef f2 fill:#d97757,stroke:#141413,color:#fff
&lt;/code>&lt;/pre>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Contrast&lt;/th>
&lt;th>Comparison&lt;/th>
&lt;th>Finding&lt;/th>
&lt;th style="text-align:center">Ground Truth&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1-0&lt;/td>
&lt;td>Mining (any price) vs No mining&lt;/td>
&lt;td>Finding 1&lt;/td>
&lt;td style="text-align:center">0.25&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2-0&lt;/td>
&lt;td>Mining (medium price) vs No mining&lt;/td>
&lt;td>Finding 1&lt;/td>
&lt;td style="text-align:center">0.30&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3-0&lt;/td>
&lt;td>Mining (high price) vs No mining&lt;/td>
&lt;td>Finding 1&lt;/td>
&lt;td style="text-align:center">0.55&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2-1&lt;/td>
&lt;td>Medium vs Low prices (within mining)&lt;/td>
&lt;td>Finding 2 (small)&lt;/td>
&lt;td style="text-align:center">0.05&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3-1&lt;/td>
&lt;td>High vs Low prices (within mining)&lt;/td>
&lt;td>Finding 2 (large)&lt;/td>
&lt;td style="text-align:center">0.30&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3-2&lt;/td>
&lt;td>High vs Medium prices (within mining)&lt;/td>
&lt;td>Finding 2&lt;/td>
&lt;td style="text-align:center">0.25&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="62-variable-specification">6.2 Variable specification&lt;/h3>
&lt;p>We separate variables into two groups following the &lt;code>cate&lt;/code> framework:&lt;/p>
&lt;pre>&lt;code class="language-stata">* CATE variables (x): potential drivers of treatment-effect heterogeneity
global catevars exec_constraints quality_of_govt gdp_pc ///
elevation temperature ruggedness distance_capital ///
agri_suitability population ethnic_frac
* Controls (w): nuisance variables for background adjustment only
global controls i.country_id i.year
&lt;/code>&lt;/pre>
&lt;p>The &lt;strong>catevarlist&lt;/strong> ($\mathbf{x}$) contains the 10 covariates that may drive heterogeneity &amp;mdash; institutional, economic, and geographic variables. The &lt;strong>controls&lt;/strong> ($\mathbf{w}$) contain country and year fixed effects to absorb panel-level confounding without overcomplicating the CATE function.&lt;/p>
&lt;p>We use &lt;code>xfolds(5)&lt;/code> rather than the default 10 to ensure adequate sample sizes per fold, especially for the within-mining comparisons. With &lt;code>rseed(12345)&lt;/code>, all results are reproducible.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Small sample warning.&lt;/strong> The mining-vs-no-mining comparisons (1-0, 2-0, 3-0) use approximately 2,700 observations &amp;mdash; adequate for the causal forest. However, the &lt;strong>within-mining&lt;/strong> price comparisons (2-1, 3-1, 3-2) use only about 300 observations (two treated groups of ~150 each). With &lt;code>xfolds(5)&lt;/code>, each fold has only ~60 observations. Expect wider confidence intervals for these comparisons.&lt;/p>
&lt;/blockquote>
&lt;hr>
&lt;h2 id="7-average-treatment-effects">7. Average treatment effects&lt;/h2>
&lt;p>We estimate ATEs for all 6 NTL contrasts and key conflict contrasts. For the two most important comparisons (NTL 1-0 and NTL 3-1), we show both PO and AIPW estimators. Remaining comparisons use AIPW only.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Runtime.&lt;/strong> Each &lt;code>cate&lt;/code> estimation takes approximately 60&amp;ndash;90 seconds with 5-fold cross-fitting. The full section runs in approximately 15&amp;ndash;20 minutes.&lt;/p>
&lt;/blockquote>
&lt;h3 id="71-ntl-mining-effect-1-0-----po-vs-aipw">7.1 NTL: Mining effect (1-0) &amp;mdash; PO vs AIPW&lt;/h3>
&lt;p>This is the most important contrast: does mining increase nighttime lights? The ground truth is 0.25.&lt;/p>
&lt;pre>&lt;code class="language-stata">* --- PO estimator ---
preserve
keep if treatment == 1 | treatment == 0
gen byte treat_1v0 = (treatment == 1)
cate po (ntl_log $catevars) (treat_1v0), ///
controls($controls) ///
rseed(12345) xfolds(5) ///
omethod(rforest) tmethod(rforest)
estimates store po_ntl_1v0
restore
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Conditional average treatment effects Number of observations = 2,700
Estimator: Partialing out Number of folds in cross-fit = 5
Outcome model: Random forest Number of outcome controls = 28
Treatment model: Random forest Number of treatment controls = 28
CATE model: Random forest Number of CATE variables = 10
------------------------------------------------------------------------------
| Robust
ntl_log | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
ATE |
treat_1v0 |
(Mining ..) |
vs |
No mining) | .1936814 .0097428 19.88 0.000 .1745858 .212777
-------------+----------------------------------------------------------------
POmean |
treat_1v0 |
No mining | -1.142413 .0079236 -144.18 0.000 -1.157943 -1.126883
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-stata">* --- AIPW estimator ---
preserve
keep if treatment == 1 | treatment == 0
gen byte treat_1v0 = (treatment == 1)
cate aipw (ntl_log $catevars) (treat_1v0), ///
controls($controls) ///
rseed(12345) xfolds(5) ///
omethod(rforest) tmethod(rforest)
estimates store aipw_ntl_1v0
restore
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Conditional average treatment effects Number of observations = 2,700
Estimator: Augmented IPW Number of folds in cross-fit = 5
Outcome model: Random forest Number of outcome controls = 28
Treatment model: Random forest Number of treatment controls = 28
CATE model: Random forest Number of CATE variables = 10
------------------------------------------------------------------------------
| Robust
ntl_log | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
ATE |
treat_1v0 |
(Mining ..) |
vs |
No mining) | .1489842 .0105686 14.10 0.000 .1282701 .1696983
-------------+----------------------------------------------------------------
POmean |
treat_1v0 |
No mining | -1.142416 .0079187 -144.27 0.000 -1.157936 -1.126896
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>The PO ATE is &lt;strong>0.194&lt;/strong> (SE = 0.010) and the AIPW ATE is &lt;strong>0.149&lt;/strong> (SE = 0.011) — both positive and significant, confirming that mining increases nighttime lights. The estimates differ somewhat from each other and from the ground truth (0.25), which is expected with 5-fold cross-fitting on a moderately sized sample. The PO estimate is closer to the truth here, while AIPW is more conservative. Both confirm the directional finding.&lt;/p>
&lt;h3 id="72-ntl-high-vs-low-prices-3-1-----po-vs-aipw">7.2 NTL: High vs low prices (3-1) &amp;mdash; PO vs AIPW&lt;/h3>
&lt;p>The price effect comparison tests Finding 2. The ground truth is 0.30 &amp;mdash; a large jump from low to high prices. Note that this comparison uses only mining districts (~300 observations), so estimates will be noisier.&lt;/p>
&lt;pre>&lt;code class="language-stata">* --- PO estimator ---
preserve
keep if treatment == 3 | treatment == 1
gen byte treat_3v1 = (treatment == 3)
display as text &amp;quot;N = &amp;quot; _N &amp;quot; observations (mining districts only)&amp;quot;
cate po (ntl_log $catevars) (treat_3v1), ///
controls($controls) ///
rseed(12345) xfolds(5) ///
omethod(rforest) tmethod(rforest)
estimates store po_ntl_3v1
restore
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">N = 300 observations (mining districts only)
Conditional average treatment effects Number of observations = 300
Estimator: Partialing out Number of folds in cross-fit = 5
Outcome model: Random forest Number of outcome controls = 28
Treatment model: Random forest Number of treatment controls = 28
CATE model: Random forest Number of CATE variables = 10
------------------------------------------------------------------------------
| Robust
ntl_log | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
ATE |
treat_3v1 |
(High price |
vs |
Low price) | .5945629 .0313138 18.99 0.000 .5331891 .6559368
-------------+----------------------------------------------------------------
POmean |
treat_3v1 |
Low price | -1.12839 .0280085 -40.29 0.000 -1.183285 -1.073494
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-stata">* --- AIPW estimator ---
preserve
keep if treatment == 3 | treatment == 1
gen byte treat_3v1 = (treatment == 3)
cate aipw (ntl_log $catevars) (treat_3v1), ///
controls($controls) ///
rseed(12345) xfolds(5) ///
omethod(rforest) tmethod(rforest)
estimates store aipw_ntl_3v1
restore
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Conditional average treatment effects Number of observations = 300
Estimator: Augmented IPW Number of folds in cross-fit = 5
Outcome model: Random forest Number of outcome controls = 28
Treatment model: Random forest Number of treatment controls = 28
CATE model: Random forest Number of CATE variables = 10
------------------------------------------------------------------------------
| Robust
ntl_log | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
ATE |
treat_3v1 |
(High price |
vs |
Low price) | .4052631 .0254935 15.90 0.000 .3552968 .4552293
-------------+----------------------------------------------------------------
POmean |
treat_3v1 |
Low price | -1.029871 .0240718 -42.78 0.000 -1.077051 -.9826917
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>The PO ATE is &lt;strong>0.595&lt;/strong> (SE = 0.031) and the AIPW ATE is &lt;strong>0.405&lt;/strong> (SE = 0.025) — both large and highly significant, confirming that the price premium from low to high is substantial (ground truth = 0.30). With only 300 observations and 5-fold cross-fitting, estimates are noisier than the 1-0 contrast, and both overshoot the ground truth, but the directional finding is robust.&lt;/p>
&lt;h3 id="73-ntl-remaining-comparisons-aipw-only">7.3 NTL: Remaining comparisons (AIPW only)&lt;/h3>
&lt;p>For the remaining four NTL contrasts we use AIPW with default lasso methods (faster than random forests on smaller subsamples).&lt;/p>
&lt;pre>&lt;code class="language-stata">* --- NTL: 2 vs 0 (medium mining vs no mining) ---
preserve
keep if treatment == 2 | treatment == 0
gen byte treat_2v0 = (treatment == 2)
cate aipw (ntl_log $catevars) (treat_2v0), ///
controls($controls) rseed(12345) xfolds(5)
estimates store aipw_ntl_2v0
restore
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Conditional average treatment effects Number of observations = 2,700
Estimator: Augmented IPW Number of folds in cross-fit = 5
Outcome model: Linear lasso Number of outcome controls = 28
Treatment model: Logit lasso Number of treatment controls = 28
CATE model: Random forest Number of CATE variables = 10
------------------------------------------------------------------------------
| Robust
ntl_log | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
ATE |
treat_2v0 |
(1 vs 0) | .2891968 .0250557 11.54 0.000 .2400886 .3383049
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-stata">* --- NTL: 3 vs 0 (high mining vs no mining) ---
preserve
keep if treatment == 3 | treatment == 0
gen byte treat_3v0 = (treatment == 3)
cate aipw (ntl_log $catevars) (treat_3v0), ///
controls($controls) rseed(12345) xfolds(5)
estimates store aipw_ntl_3v0
restore
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Conditional average treatment effects Number of observations = 2,700
Estimator: Augmented IPW Number of folds in cross-fit = 5
Outcome model: Linear lasso Number of outcome controls = 28
Treatment model: Logit lasso Number of treatment controls = 28
CATE model: Random forest Number of CATE variables = 10
------------------------------------------------------------------------------
| Robust
ntl_log | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
ATE |
treat_3v0 |
(1 vs 0) | .6111885 .0250606 24.39 0.000 .5620707 .6603063
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-stata">* --- NTL: 2 vs 1 (medium vs low prices, within mining) ---
preserve
keep if treatment == 2 | treatment == 1
gen byte treat_2v1 = (treatment == 2)
cate aipw (ntl_log $catevars) (treat_2v1), ///
controls($controls) rseed(12345) xfolds(5)
estimates store aipw_ntl_2v1
restore
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Conditional average treatment effects Number of observations = 300
Estimator: Augmented IPW Number of folds in cross-fit = 5
Outcome model: Linear lasso Number of outcome controls = 28
Treatment model: Logit lasso Number of treatment controls = 28
CATE model: Random forest Number of CATE variables = 10
------------------------------------------------------------------------------
| Robust
ntl_log | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
ATE |
treat_2v1 |
(1 vs 0) | -.0112177 .0883033 -0.13 0.899 -.1842889 .1618535
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-stata">* --- NTL: 3 vs 2 (high vs medium prices, within mining) ---
* Note: AIPW fails on this tiny subsample (N=300) due to propensity
* score overlap violations. PO with relaxed tolerance handles this,
* but the estimate is unreliable due to the extreme small sample.
preserve
keep if treatment == 3 | treatment == 2
gen byte treat_3v2 = (treatment == 3)
cate po (ntl_log $catevars) (treat_3v2), ///
controls($controls) rseed(12345) xfolds(5) ///
pstolerance(1e-8)
estimates store aipw_ntl_3v2
restore
&lt;/code>&lt;/pre>
&lt;blockquote>
&lt;p>&lt;strong>Overlap failure.&lt;/strong> The 3-2 comparison (high vs medium prices) has only 300 observations split roughly evenly between two treated groups. The AIPW estimator fails entirely due to propensity scores near zero. Even the PO estimator with &lt;code>pstolerance(1e-8)&lt;/code> &amp;mdash; which relaxes the minimum acceptable propensity score from the default 1e-5 to 1e-8 &amp;mdash; produces an unreliable ATE of &amp;ndash;43,825 (SE = 43,752, p = 0.317). This comparison is excluded from the summary table below. The remaining five contrasts are well-identified.&lt;/p>
&lt;/blockquote>
&lt;h3 id="74-conflict-mining-effect-1-0-----po-vs-aipw">7.4 Conflict: Mining effect (1-0) &amp;mdash; PO vs AIPW&lt;/h3>
&lt;p>Does mining increase conflict? We show both estimators for the key contrast. Unlike NTL, the conflict ground truths are not specified in the DGP, so we interpret directionally.&lt;/p>
&lt;pre>&lt;code class="language-stata">* --- Conflict: 1 vs 0 (PO estimator) ---
preserve
keep if treatment == 1 | treatment == 0
gen byte treat_1v0 = (treatment == 1)
cate po (conflict $catevars) (treat_1v0), ///
controls($controls) ///
rseed(12345) xfolds(5) ///
omethod(rforest) tmethod(rforest)
estimates store po_conf_1v0
restore
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Conditional average treatment effects Number of observations = 2,700
Estimator: Partialing out Number of folds in cross-fit = 5
Outcome model: Random forest Number of outcome controls = 28
Treatment model: Random forest Number of treatment controls = 28
CATE model: Random forest Number of CATE variables = 10
------------------------------------------------------------------------------
| Robust
conflict | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
ATE |
treat_1v0 |
(1 vs 0) | .0630853 .0130031 4.85 0.000 .0375997 .0885709
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-stata">* --- Conflict: 1 vs 0 (AIPW estimator) ---
preserve
keep if treatment == 1 | treatment == 0
gen byte treat_1v0 = (treatment == 1)
cate aipw (conflict $catevars) (treat_1v0), ///
controls($controls) ///
rseed(12345) xfolds(5) ///
omethod(rforest) tmethod(rforest)
estimates store aipw_conf_1v0
restore
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Conditional average treatment effects Number of observations = 2,700
Estimator: Augmented IPW Number of folds in cross-fit = 5
Outcome model: Random forest Number of outcome controls = 28
Treatment model: Random forest Number of treatment controls = 28
CATE model: Random forest Number of CATE variables = 10
------------------------------------------------------------------------------
| Robust
conflict | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
ATE |
treat_1v0 |
(1 vs 0) | .0659767 .0122036 5.41 0.000 .042058 .0898954
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>Both estimators produce positive and significant ATEs (PO = 0.063, AIPW = 0.066, both p &amp;lt; 0.001), confirming Finding 1: mining increases both nighttime lights &lt;em>and&lt;/em> conflict. The baseline conflict probability for non-mining districts is approximately 10.7%, and mining increases it by about 6.5 percentage points.&lt;/p>
&lt;h3 id="75-conflict-remaining-comparisons-aipw-only">7.5 Conflict: Remaining comparisons (AIPW only)&lt;/h3>
&lt;pre>&lt;code class="language-stata">* Loop over remaining conflict comparisons
local comparisons &amp;quot;2_0 3_0 2_1 3_1 3_2&amp;quot;
foreach comp of local comparisons {
local t_hi = substr(&amp;quot;`comp'&amp;quot;, 1, 1)
local t_lo = substr(&amp;quot;`comp'&amp;quot;, 3, 1)
preserve
keep if treatment == `t_hi' | treatment == `t_lo'
gen byte treat_bin = (treatment == `t_hi')
quietly cate aipw (conflict $catevars) (treat_bin), ///
controls($controls) rseed(12345) xfolds(5)
matrix b = e(b)
matrix V = e(V)
display as result &amp;quot;Conflict `t_hi' vs `t_lo': ATE = &amp;quot; %7.4f b[1,1] ///
&amp;quot; SE = &amp;quot; %7.4f sqrt(V[1,1])
estimates store aipw_conf_`t_hi'v`t_lo'
restore
}
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">=== Conflict: Treatment 2 vs 0 ===
N = 2700
ATE = 0.0728 SE = 0.0330
=== Conflict: Treatment 3 vs 0 ===
N = 2700
ATE = 0.1586 SE = 0.0380
=== Conflict: Treatment 2 vs 1 ===
N = 300
ATE = -0.0677 SE = 0.0497
=== Conflict: Treatment 3 vs 1 ===
N = 300
ATE = 0.1126 SE = 0.0293
=== Conflict: Treatment 3 vs 2 ===
N = 300
ATE = 3.5e+04 SE = 3.5e+04 (overlap failure -- unreliable)
&lt;/code>&lt;/pre>
&lt;h3 id="76-ate-summary">7.6 ATE summary&lt;/h3>
&lt;pre>&lt;code class="language-stata">* Compile NTL AIPW ATEs into a comparison table
display as text &amp;quot;{hline 70}&amp;quot;
display as text &amp;quot;SUMMARY: Average Treatment Effects (NTL Outcome)&amp;quot;
display as text &amp;quot;{hline 70}&amp;quot;
display as text &amp;quot;Contrast&amp;quot; _col(15) &amp;quot;AIPW ATE&amp;quot; _col(30) &amp;quot;SE&amp;quot; _col(42) &amp;quot;Ground Truth&amp;quot;
display as text &amp;quot;{hline 70}&amp;quot;
local comps &amp;quot;1v0 2v0 3v0 2v1 3v1 3v2&amp;quot;
local gts &amp;quot;0.25 0.30 0.55 0.05 0.30 0.25&amp;quot;
local i = 1
foreach comp of local comps {
local gt : word `i' of `gts'
quietly estimates restore aipw_ntl_`comp'
matrix b = e(b)
matrix V = e(V)
local ate = b[1,1]
local se = sqrt(V[1,1])
display as result &amp;quot;`comp'&amp;quot; _col(15) %7.4f `ate' _col(30) %7.4f `se' _col(42) &amp;quot;`gt'&amp;quot;
local ++i
}
display as text &amp;quot;{hline 70}&amp;quot;
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">----------------------------------------------------------------------
SUMMARY: Average Treatment Effects (NTL Outcome)
----------------------------------------------------------------------
Contrast ATE SE Ground Truth
----------------------------------------------------------------------
1v0 0.1490 0.0106 0.25
2v0 0.2892 0.0251 0.30
3v0 0.6112 0.0251 0.55
2v1 -0.0112 0.0883 0.05
3v1 0.4053 0.0255 0.30
3v2 (overlap failure) 0.25
----------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>Several estimates deviate from the ground truth (e.g., 1v0 = 0.149 vs truth 0.25; 3v1 = 0.405 vs truth 0.30). These deviations reflect finite-sample variability, the particular random seed, and the challenge of estimating effects with only 150 treated observations per group. The directional patterns are robust: all mining effects are positive, and the price non-linearity is clear. With larger samples or different seeds, estimates would converge closer to the DGP values.&lt;/p>
&lt;p>Two findings emerge from the ATE summary:&lt;/p>
&lt;p>&lt;strong>Finding 1: Mining increases nighttime lights.&lt;/strong> All three mining-vs-no-mining comparisons (1-0, 2-0, 3-0) show positive and significant ATEs (0.149, 0.289, 0.611), with magnitudes increasing as the mineral price level rises. The 3-0 contrast (high-price mining vs no mining) is the largest at 0.611 &amp;mdash; the combined effect of mining itself plus the high price premium.&lt;/p>
&lt;p>&lt;strong>Finding 2: Price effects are non-linear.&lt;/strong> The within-mining price contrasts confirm non-linearity: 2-1 (medium vs low prices) is essentially zero (&amp;ndash;0.011, p = 0.90), while 3-1 (high vs low prices) is large and significant (0.405, p &amp;lt; 0.001). Price effects are &lt;em>not&lt;/em> a smooth dose-response &amp;mdash; they &amp;ldquo;jump&amp;rdquo; sharply only at high prices. The step from low to medium prices does nothing; the step from low to high prices does a lot.&lt;/p>
&lt;hr>
&lt;h2 id="8-treatment-effect-heterogeneity-gates">8. Treatment effect heterogeneity (GATEs)&lt;/h2>
&lt;p>The key innovation of causal machine learning is detecting &lt;em>how&lt;/em> treatment effects vary across subgroups. We compute &lt;strong>GATEs (Group Average Treatment Effects)&lt;/strong> by institutional variables to test Finding 3: institutions moderate mining effects but NOT price effects.&lt;/p>
&lt;h3 id="81-gates-by-executive-constraints-mining-effect-1-0">8.1 GATEs by executive constraints: Mining effect (1-0)&lt;/h3>
&lt;pre>&lt;code class="language-stata">preserve
keep if treatment == 1 | treatment == 0
gen byte treat_1v0 = (treatment == 1)
cate aipw (ntl_log $catevars) (treat_1v0), ///
controls($controls) ///
group(exec_con) ///
rseed(12345) xfolds(5) ///
omethod(rforest) tmethod(rforest)
categraph gateplot
estat gatetest
estimates store gate_ntl_1v0_exec
restore
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Conditional average treatment effects Number of observations = 2,700
Estimator: Augmented IPW Number of folds in cross-fit = 5
------------------------------------------------------------------------------
| Robust
ntl_log | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
GATE |
exec_con |
1 | .2748407 .0403765 6.81 0.000 .1957042 .3539772
2 | .3155337 .0204714 15.41 0.000 .2754106 .3556569
3 | .1674459 .020837 8.04 0.000 .1266061 .2082857
4 | .1131603 .0263687 4.29 0.000 .0614785 .164842
5 | .0998745 .0296118 3.37 0.001 .0418364 .1579127
6 | .0508165 .0220009 2.31 0.021 .0076955 .0939374
-------------+----------------------------------------------------------------
ATE |
treat_1v0 |
(1 vs 0) | .1517508 .0111077 13.66 0.000 .12998 .1735216
------------------------------------------------------------------------------
Group treatment-effects heterogeneity test
H0: Group average treatment effects are homogeneous
chi2(5) = 96.90
Prob &amp;gt; chi2 = 0.0000
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_cate2_gate_ntl_1v0_exec.png" alt="GATEs for NTL mining effect (1-0) by Executive Constraints.">&lt;/p>
&lt;p>The GATE plot reveals a &lt;strong>downward slope&lt;/strong>: districts with &lt;em>weaker&lt;/em> executive constraints (lower values on the x-axis) experience larger mining effects on nighttime lights (GATE = 0.275 at exec_con = 1 vs 0.051 at exec_con = 6). The &lt;code>estat gatetest&lt;/code> strongly rejects GATE equality (&lt;strong>chi2(5) = 96.90, p &amp;lt; 0.0001&lt;/strong>), confirming that institutional quality moderates mining effects.&lt;/p>
&lt;p>This pattern &amp;mdash; weaker institutions, larger mining benefit &amp;mdash; differs from the sign that Hodler et al. (2023) found in real Sub-Saharan African data. In the full paper, stronger institutions &lt;em>amplified&lt;/em> the development benefits of mining. In our simulated data, the DGP produces the opposite sign: mining has a larger positive effect on NTL in weakly-governed districts, perhaps because these districts start from a lower baseline and have more room for growth when mining begins. The key takeaway is that &lt;strong>institutional moderation exists&lt;/strong> (the GATEs are clearly heterogeneous), even though the direction differs from the full paper&amp;rsquo;s parametrization.&lt;/p>
&lt;h3 id="82-gates-by-executive-constraints-price-effect-3-1">8.2 GATEs by executive constraints: Price effect (3-1)&lt;/h3>
&lt;pre>&lt;code class="language-stata">preserve
keep if treatment == 3 | treatment == 1
gen byte treat_3v1 = (treatment == 3)
cate aipw (ntl_log $catevars) (treat_3v1), ///
controls($controls) ///
group(exec_con) ///
rseed(12345) xfolds(5) ///
omethod(rforest) tmethod(rforest)
categraph gateplot
estat gatetest
estimates store gate_ntl_3v1_exec
restore
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Conditional average treatment effects Number of observations = 300
Estimator: Augmented IPW Number of folds in cross-fit = 5
------------------------------------------------------------------------------
| Robust
ntl_log | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
GATE |
exec_con |
1 | .3211384 .0790347 4.06 0.000 .1662332 .4760437
2 | .2868582 .0726244 3.95 0.000 .1445171 .4291993
3 | .3729897 .0413647 9.02 0.000 .2919164 .4540629
4 | .5891193 .0542141 10.87 0.000 .4828616 .6953771
5 | .4870458 .0590345 8.25 0.000 .3713403 .6027514
6 | .3400699 .0596378 5.70 0.000 .2231819 .4569579
-------------+----------------------------------------------------------------
ATE |
treat_3v1 |
(1 vs 0) | .4062996 .0252193 16.11 0.000 .3568707 .4557284
------------------------------------------------------------------------------
Group treatment-effects heterogeneity test
H0: Group average treatment effects are homogeneous
chi2(5) = 18.92
Prob &amp;gt; chi2 = 0.0020
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_cate2_gate_ntl_3v1_exec.png" alt="GATEs for NTL price effect (3-1) by Executive Constraints.">&lt;/p>
&lt;p>The GATE plot for the price effect (3-1) shows a &lt;strong>non-monotone pattern&lt;/strong>: GATEs range from 0.29 to 0.59 across executive constraint levels with no clear directional trend. While the &lt;code>estat gatetest&lt;/code> rejects equality (chi2(5) = 18.92, p = 0.002), the pattern lacks the clear monotone slope seen in the mining effect. The price effect is positive everywhere and does not systematically vary with institutional quality in the same way.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>The key contrast (Finding 3).&lt;/strong> Compare the two GATE plots above. Mining effect (1-0): clear &lt;strong>monotone downward slope&lt;/strong> &amp;mdash; a strong, systematic relationship between institutional quality and the mining effect (chi2 = 96.90). Price effect (3-1): &lt;strong>no monotone pattern&lt;/strong> &amp;mdash; while some variation exists, there is no clear directional relationship between institutions and the price premium. This asymmetry supports the paper&amp;rsquo;s core insight: &lt;strong>institutional quality systematically moderates mining effects, but does not systematically shape how global commodity price shocks affect local economic activity.&lt;/strong>&lt;/p>
&lt;/blockquote>
&lt;h3 id="83-gates-by-quality-of-government-mining-effect-1-0">8.3 GATEs by quality of government: Mining effect (1-0)&lt;/h3>
&lt;p>We repeat the analysis using an alternative institutional measure &amp;mdash; quality of government &amp;mdash; discretized into quartiles.&lt;/p>
&lt;pre>&lt;code class="language-stata">preserve
keep if treatment == 1 | treatment == 0
gen byte treat_1v0 = (treatment == 1)
egen qog_cat = cut(quality_of_govt), group(4) label
cate aipw (ntl_log $catevars) (treat_1v0), ///
controls($controls) ///
group(qog_cat) ///
rseed(12345) xfolds(5) ///
omethod(rforest) tmethod(rforest)
categraph gateplot
estat gatetest
estimates store gate_ntl_1v0_qog
restore
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">GATE |
qog_cat |
.22- | .2978846 .0215258 13.84 0.000 .2556947 .3400745
.32- | .168479 .0205681 8.19 0.000 .1281663 .2087917
.42- | .1080724 .0242792 4.45 0.000 .060486 .1556589
.58- | .0728521 .0179392 4.06 0.000 .037692 .1080123
-------------+----------------------------------------------------------------
ATE |
(1 vs 0) | .1504898 .0107088 14.05 0.000 .1295009 .1714786
------------------------------------------------------------------------------
Group treatment-effects heterogeneity test
H0: Group average treatment effects are homogeneous
chi2(3) = 69.19
Prob &amp;gt; chi2 = 0.0000
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_cate2_gate_ntl_1v0_qog.png" alt="GATEs for NTL mining effect (1-0) by Quality of Government quartiles.">&lt;/p>
&lt;p>The quality-of-government GATE plot confirms the same &lt;strong>downward pattern&lt;/strong> seen with executive constraints: districts in the lowest QoG quartile (0.22&amp;ndash;0.32) show a GATE of 0.298, while the highest quartile (0.58+) shows only 0.073. The &lt;code>estat gatetest&lt;/code> strongly rejects equality (chi2(3) = 69.19, p &amp;lt; 0.0001). Both institutional measures tell the same story: weaker governance, larger mining effect.&lt;/p>
&lt;h3 id="84-gates-by-quality-of-government-price-effect-3-1">8.4 GATEs by quality of government: Price effect (3-1)&lt;/h3>
&lt;pre>&lt;code class="language-stata">preserve
keep if treatment == 3 | treatment == 1
gen byte treat_3v1 = (treatment == 3)
egen qog_cat = cut(quality_of_govt), group(4) label
cate aipw (ntl_log $catevars) (treat_3v1), ///
controls($controls) ///
group(qog_cat) ///
rseed(12345) xfolds(5) ///
omethod(rforest) tmethod(rforest)
categraph gateplot
estat gatetest
estimates store gate_ntl_3v1_qog
restore
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">GATE |
qog_cat |
.22- | .3224114 .0784869 4.11 0.000 .1685799 .476243
.28- | .3510705 .0408896 8.59 0.000 .2709285 .4312126
.38- | .4843956 .0567094 8.54 0.000 .3732473 .5955438
.48- | .4447202 .039344 11.30 0.000 .3676074 .5218329
-------------+----------------------------------------------------------------
ATE |
(1 vs 0) | .4057735 .0253689 15.99 0.000 .3560514 .4554957
------------------------------------------------------------------------------
Group treatment-effects heterogeneity test
H0: Group average treatment effects are homogeneous
chi2(3) = 5.81
Prob &amp;gt; chi2 = 0.1211
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_cate2_gate_ntl_3v1_qog.png" alt="GATEs for NTL price effect (3-1) by Quality of Government quartiles.">&lt;/p>
&lt;p>The price effect GATEs by QoG range from 0.322 to 0.484 without a clear monotone pattern, and the &lt;code>estat gatetest&lt;/code> fails to reject equality (chi2(3) = 5.81, p = 0.121). This confirms the asymmetry: institutional quality does not systematically moderate the price premium, unlike the mining effect where the relationship is strong and monotone.&lt;/p>
&lt;h3 id="85-gates-for-conflict-mining-effect-1-0">8.5 GATEs for conflict: Mining effect (1-0)&lt;/h3>
&lt;pre>&lt;code class="language-stata">preserve
keep if treatment == 1 | treatment == 0
gen byte treat_1v0 = (treatment == 1)
cate aipw (conflict $catevars) (treat_1v0), ///
controls($controls) ///
group(exec_con) ///
rseed(12345) xfolds(5) ///
omethod(rforest) tmethod(rforest)
categraph gateplot
estat gatetest
estimates store gate_conf_1v0_exec
restore
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">GATE |
exec_con |
1 | .0924768 .0440987 2.10 0.036 .0060449 .1789087
2 | .032707 .0348295 0.94 0.348 -.0355576 .1009715
3 | .0600398 .0292415 2.05 0.040 .0027275 .1173522
4 | .0486042 .0273151 1.78 0.075 -.0049326 .1021409
5 | .0643314 .0205048 3.14 0.002 .0241427 .1045202
6 | .1057752 .021425 4.94 0.000 .0637831 .1477674
-------------+----------------------------------------------------------------
ATE |
(1 vs 0) | .0648653 .0122278 5.30 0.000 .0408994 .0888313
------------------------------------------------------------------------------
Group treatment-effects heterogeneity test
H0: Group average treatment effects are homogeneous
chi2(5) = 5.00
Prob &amp;gt; chi2 = 0.4162
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_cate2_gate_conf_1v0_exec.png" alt="GATEs for Conflict mining effect (1-0) by Executive Constraints.">&lt;/p>
&lt;p>For conflict, the GATEs range from 0.033 to 0.106 across executive constraint levels with no clear monotone pattern. The &lt;code>estat gatetest&lt;/code> fails to reject equality (chi2(5) = 5.00, p = 0.416), indicating that institutional quality does &lt;strong>not&lt;/strong> significantly moderate the conflict effect of mining in this simulated dataset. All groups show a positive conflict effect, but without systematic variation.&lt;/p>
&lt;hr>
&lt;h2 id="9-advanced-diagnostics">9. Advanced diagnostics&lt;/h2>
&lt;p>Stata 19&amp;rsquo;s &lt;code>cate&lt;/code> suite provides several postestimation tools that go beyond group-level summaries. This section demonstrates IATE distributions, formal heterogeneity tests, subpopulation ATEs, linear projections, and IATE function plots.&lt;/p>
&lt;h3 id="91-iate-distribution-and-heterogeneity-test">9.1 IATE distribution and heterogeneity test&lt;/h3>
&lt;p>We re-estimate the NTL mining effect (1-0) with &lt;code>i.exec_con&lt;/code> in the catevarlist to enable &lt;code>reestimate group(exec_con)&lt;/code> later.&lt;/p>
&lt;pre>&lt;code class="language-stata">preserve
keep if treatment == 1 | treatment == 0
gen byte treat_1v0 = (treatment == 1)
cate aipw (ntl_log exec_constraints quality_of_govt gdp_pc ///
elevation temperature ruggedness distance_capital ///
agri_suitability population ethnic_frac ///
i.exec_con) (treat_1v0), ///
controls($controls) ///
rseed(12345) xfolds(5) ///
omethod(rforest) tmethod(rforest)
* Distribution of individual effects
categraph histogram
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Conditional average treatment effects Number of observations = 2,700
Estimator: Augmented IPW Number of folds in cross-fit = 5
Outcome model: Random forest Number of outcome controls = 34
Treatment model: Random forest Number of treatment controls = 34
CATE model: Random forest Number of CATE variables = 16
------------------------------------------------------------------------------
| Robust
ntl_log | Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
ATE |
treat_1v0 |
(1 vs 0) | .1517508 .0111077 13.66 0.000 .12998 .1735216
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_cate2_iate_histogram.png" alt="Distribution of IATE predictions for the NTL mining effect (1-0). The spread of this histogram reflects the degree of treatment-effect heterogeneity across districts.">&lt;/p>
&lt;p>The histogram shows the full distribution of estimated individual treatment effects $\hat{\tau}(\mathbf{x}_i)$ across all districts. A wide spread indicates substantial heterogeneity; a spike at one value would indicate near-homogeneity. The distribution is centered around the ATE of approximately 0.15, with meaningful spread reflecting how institutional quality, geography, and economic conditions create different mining effects across districts.&lt;/p>
&lt;pre>&lt;code class="language-stata">* Formal test: are treatment effects heterogeneous?
estat heterogeneity
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Treatment-effects heterogeneity test
H0: Treatment effects are homogeneous
chi2(1) = 53.05
Prob &amp;gt; chi2 = 0.0000
&lt;/code>&lt;/pre>
&lt;blockquote>
&lt;p>&lt;strong>Interpreting the heterogeneity test.&lt;/strong> The &lt;code>estat heterogeneity&lt;/code> test uses the method of Chernozhukov et al. (2006). A significant result (p &amp;lt; 0.05) provides statistical evidence that treatment effects vary across observations &amp;mdash; they are not constant. This justifies the use of CATE methods rather than a simple ATE.&lt;/p>
&lt;/blockquote>
&lt;h3 id="92-gate-equality-test-with-reestimate">9.2 GATE equality test with reestimate&lt;/h3>
&lt;p>The &lt;code>reestimate&lt;/code> option recycles the IATE function from the previous estimation. We do NOT refit the (slow) causal forest &amp;mdash; we just recompute group means. This makes it fast to explore different grouping variables.&lt;/p>
&lt;pre>&lt;code class="language-stata">* Recompute GATEs by Executive Constraints from existing IATEs
cate, reestimate group(exec_con)
* Test H0: GATEs are equal across executive constraint levels
estat gatetest
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">GATE |
exec_con |
1 | .2748407 .0403765 6.81 0.000 .1957042 .3539772
2 | .3155337 .0204714 15.41 0.000 .2754106 .3556569
3 | .1674459 .020837 8.04 0.000 .1266061 .2082857
4 | .1131603 .0263687 4.29 0.000 .0614785 .164842
5 | .0998745 .0296118 3.37 0.001 .0418364 .1579127
6 | .0508165 .0220009 2.31 0.021 .0076955 .0939374
Group treatment-effects heterogeneity test
H0: Group average treatment effects are homogeneous
chi2(5) = 96.90
Prob &amp;gt; chi2 = 0.0000
&lt;/code>&lt;/pre>
&lt;h3 id="93-ate-for-subpopulations">9.3 ATE for subpopulations&lt;/h3>
&lt;p>We can estimate ATEs for specific subsets of the data using &lt;code>estat ate&lt;/code>. This answers the policy question: &amp;ldquo;What is the average effect of mining &lt;em>specifically for districts with strong (or weak) institutions&lt;/em>?&amp;rdquo;&lt;/p>
&lt;pre>&lt;code class="language-stata">* ATE for districts with strong institutions
estat ate if exec_constraints &amp;gt;= 4
* ATE for districts with weak institutions
estat ate if exec_constraints &amp;lt;= 2
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">--- ATE for districts with exec_constraints &amp;gt;= 4 ---
Treatment-effects estimation Number of obs = 1,526
------------------------------------------------------------------------------
| Robust
| Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
ATE |
treat_1v0 |
(1 vs 0) | .0922992 .0156897 5.88 0.000 .0615479 .1230506
------------------------------------------------------------------------------
--- ATE for districts with exec_constraints &amp;lt;= 2 ---
Treatment-effects estimation Number of obs = 558
------------------------------------------------------------------------------
| Robust
| Coefficient std. err. z P&amp;gt;|z| [95% conf. interval]
-------------+----------------------------------------------------------------
ATE |
treat_1v0 |
(1 vs 0) | .2970104 .0215156 13.80 0.000 .2548407 .3391801
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>The subpopulation ATEs reveal a stark difference: districts with &lt;strong>weak institutions&lt;/strong> (exec_constraints $\leq$ 2) have an ATE of &lt;strong>0.297&lt;/strong> (SE = 0.022), while districts with &lt;strong>strong institutions&lt;/strong> (exec_constraints $\geq$ 4) have an ATE of only &lt;strong>0.092&lt;/strong> (SE = 0.016). The mining effect is more than three times larger in weakly-governed districts. This confirms the GATE pattern and demonstrates that institutions systematically moderate the magnitude of mining&amp;rsquo;s developmental impact.&lt;/p>
&lt;h3 id="94-linear-projection-of-iates">9.4 Linear projection of IATEs&lt;/h3>
&lt;p>The &lt;code>estat projection&lt;/code> command regresses the estimated $\hat{\tau}_i$ on covariates linearly. This provides an interpretable summary of &lt;em>which variables drive heterogeneity&lt;/em> &amp;mdash; think of it as &amp;ldquo;an OLS view of the function $\tau(\mathbf{x})$.&amp;rdquo;&lt;/p>
&lt;pre>&lt;code class="language-stata">estat projection exec_constraints quality_of_govt gdp_pc elevation temperature
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-text">Treatment-effects linear projection Number of obs = 2,700
F(5, 2694) = 13.95
Prob &amp;gt; F = 0.0000
R-squared = 0.0235
------------------------------------------------------------------------------
| Robust
| Coefficient std. err. t P&amp;gt;|t| [95% conf. interval]
-------------+----------------------------------------------------------------
exec_const~s | -.026133 .0470456 -0.56 0.579 -.1183822 .0661162
quality_of~t | -.862502 .6669174 -1.29 0.196 -2.170224 .4452196
gdp_pc | .000067 .0000388 1.73 0.084 -9.06e-06 .000143
elevation | -.0001258 .0000341 -3.69 0.000 -.0001928 -.0000589
temperature | .0045969 .0022266 2.06 0.039 .0002309 .0089629
_cons | .4351266 .1043275 4.17 0.000 .2305565 .6396967
------------------------------------------------------------------------------
&lt;/code>&lt;/pre>
&lt;p>The linear projection (R-squared = 0.024) reveals that &lt;strong>elevation&lt;/strong> (coeff = &amp;ndash;0.0001, p &amp;lt; 0.001) and &lt;strong>temperature&lt;/strong> (coeff = 0.005, p = 0.039) are the strongest linear predictors of the individual treatment effect. Institutional variables (&lt;code>exec_constraints&lt;/code> and &lt;code>quality_of_govt&lt;/code>) have negative coefficients but are not individually significant in this linear summary, despite driving the GATE heterogeneity. This is expected &amp;mdash; the relationship between institutions and the treatment effect is nonlinear (as the GATE plots show), and a linear projection cannot capture the full pattern that the random forest identifies.&lt;/p>
&lt;h3 id="95-iate-function-plots">9.5 IATE function plots&lt;/h3>
&lt;p>The &lt;code>categraph iateplot&lt;/code> command shows how the IATE function varies with one covariate at a time, holding all others at their reference values. This is the most intuitive visualization of heterogeneity.&lt;/p>
&lt;pre>&lt;code class="language-stata">categraph iateplot exec_constraints
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_cate2_iateplot_exec.png" alt="IATE function for NTL mining effect (1-0) as a function of executive constraints. An upward trend confirms that stronger institutions increase the mining benefit.">&lt;/p>
&lt;pre>&lt;code class="language-stata">categraph iateplot quality_of_govt
restore
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="stata_cate2_iateplot_qog.png" alt="IATE function for NTL mining effect (1-0) as a function of quality of government.">&lt;/p>
&lt;p>The IATE function plots show &lt;strong>downward trends&lt;/strong>: as institutional quality increases (whether measured by executive constraints or quality of government), the predicted treatment effect of mining on nighttime lights &lt;em>decreases&lt;/em>. This provides visual confirmation of the GATE findings and complements the bar charts with a continuous view of the relationship. The downward slope is consistent with the subpopulation ATEs: mining has a larger developmental effect in weakly-governed districts.&lt;/p>
&lt;hr>
&lt;h2 id="10-conclusion">10. Conclusion&lt;/h2>
&lt;h3 id="101-mapping-results-to-paper-findings">10.1 Mapping results to paper findings&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Finding&lt;/th>
&lt;th>Paper Result&lt;/th>
&lt;th>Tutorial Evidence&lt;/th>
&lt;th>Stata Command&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1. Mining increases NTL&lt;/td>
&lt;td>Positive ATEs (1-0, 2-0, 3-0)&lt;/td>
&lt;td>Confirmed: ATEs 0.15&amp;ndash;0.61&lt;/td>
&lt;td>&lt;code>cate aipw ... (treat_1v0)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2. Non-linear prices&lt;/td>
&lt;td>ATE(2-1) &amp;laquo; ATE(3-1)&lt;/td>
&lt;td>Confirmed: -0.01 vs 0.41&lt;/td>
&lt;td>&lt;code>estimates restore&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3. Institutions moderate mining&lt;/td>
&lt;td>Monotone GATE slope for 1-0&lt;/td>
&lt;td>Confirmed (downward): chi2 = 96.9&lt;/td>
&lt;td>&lt;code>cate ... group(exec_con)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3. NOT prices&lt;/td>
&lt;td>No monotone GATE for 3-1&lt;/td>
&lt;td>Confirmed: chi2 = 5.81, p = 0.12 (QoG)&lt;/td>
&lt;td>&lt;code>cate ... group(qog_cat)&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;blockquote>
&lt;p>&lt;strong>Note on direction.&lt;/strong> The paper found that &lt;em>stronger&lt;/em> institutions amplify mining benefits (upward slope). Our simulated data shows the opposite sign &amp;mdash; &lt;em>weaker&lt;/em> institutions yield larger mining effects &amp;mdash; but the key structural finding (systematic institutional moderation of mining, not of prices) is reproduced. The direction difference reflects DGP parametrization, not a methodological failure.&lt;/p>
&lt;/blockquote>
&lt;h3 id="102-what-differs-from-the-full-paper">10.2 What differs from the full paper&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Aspect&lt;/th>
&lt;th>Tutorial&lt;/th>
&lt;th>Full Paper&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Data&lt;/td>
&lt;td>3,000 simulated obs&lt;/td>
&lt;td>60,121 real obs&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Districts&lt;/td>
&lt;td>300&lt;/td>
&lt;td>3,800&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Countries&lt;/td>
&lt;td>8 (fictional)&lt;/td>
&lt;td>42 (Sub-Saharan Africa)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Covariates&lt;/td>
&lt;td>10&lt;/td>
&lt;td>60+&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Treatment&lt;/td>
&lt;td>4-level, simulated&lt;/td>
&lt;td>29 minerals, real prices&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Inference&lt;/td>
&lt;td>5-fold cross-fitting&lt;/td>
&lt;td>1,000 bootstrap&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Outcomes&lt;/td>
&lt;td>2 (NTL, Conflict)&lt;/td>
&lt;td>2 (same)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Method&lt;/td>
&lt;td>Stata &lt;code>cate&lt;/code> (GRF)&lt;/td>
&lt;td>MCF (Lechner, 2019)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="103-glossary">10.3 Glossary&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Term&lt;/th>
&lt;th>Definition&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>ATE&lt;/strong>&lt;/td>
&lt;td>Average Treatment Effect &amp;mdash; the mean effect across the entire population&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>CATE&lt;/strong>&lt;/td>
&lt;td>Conditional Average Treatment Effect &amp;mdash; the ATE conditional on characteristics $\mathbf{x}$&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>IATE&lt;/strong>&lt;/td>
&lt;td>Individualized ATE &amp;mdash; one effect per observation, $\tau(\mathbf{x}_i)$&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>GATE&lt;/strong>&lt;/td>
&lt;td>Group ATE &amp;mdash; average effect within prespecified groups&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>GATES&lt;/strong>&lt;/td>
&lt;td>Sorted Group ATE &amp;mdash; groups formed by quantiles of IATE estimates (data-driven)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>PO&lt;/strong>&lt;/td>
&lt;td>Partialing-Out estimator &amp;mdash; residualizes outcome and treatment, then estimates $\tau(\mathbf{x})$ via GRF&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>AIPW&lt;/strong>&lt;/td>
&lt;td>Augmented IPW &amp;mdash; doubly robust estimator combining outcome model and propensity score&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>GRF&lt;/strong>&lt;/td>
&lt;td>Generalized Random Forest &amp;mdash; the nonparametric method underlying Stata&amp;rsquo;s &lt;code>cate&lt;/code> (Athey et al., 2019)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Cross-fitting&lt;/strong>&lt;/td>
&lt;td>Sample-splitting procedure to prevent overfitting of nuisance models&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Honest tree&lt;/strong>&lt;/td>
&lt;td>Tree that uses separate subsamples for splitting and leaf estimation (enables valid inference)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>catevarlist&lt;/strong>&lt;/td>
&lt;td>Variables in Stata&amp;rsquo;s &lt;code>cate&lt;/code> that drive treatment-effect heterogeneity ($\mathbf{x}$)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>controls()&lt;/strong>&lt;/td>
&lt;td>Additional variables for nuisance models only ($\mathbf{w}$), not for heterogeneity&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="104-key-advantages-of-stata-19s-cate">10.4 Key advantages of Stata 19&amp;rsquo;s &lt;code>cate&lt;/code>&lt;/h3>
&lt;blockquote>
&lt;ol>
&lt;li>&lt;strong>No external packages&lt;/strong> &amp;mdash; everything is built into Stata 19.&lt;/li>
&lt;li>&lt;strong>Formal hypothesis tests&lt;/strong> &amp;mdash; &lt;code>estat heterogeneity&lt;/code> and &lt;code>estat gatetest&lt;/code> provide rigorous inference that specialized Python packages do not offer natively.&lt;/li>
&lt;li>&lt;strong>Publication-ready visualization&lt;/strong> &amp;mdash; &lt;code>categraph&lt;/code> produces polished plots directly.&lt;/li>
&lt;li>&lt;strong>Integrated workflow&lt;/strong> &amp;mdash; seamlessly combines with Stata&amp;rsquo;s ecosystem (&lt;code>estimates store&lt;/code>, &lt;code>preserve&lt;/code>/&lt;code>restore&lt;/code>, &lt;code>margins&lt;/code>).&lt;/li>
&lt;li>&lt;strong>Doubly robust&lt;/strong> &amp;mdash; AIPW estimator is consistent even if one nuisance model is wrong.&lt;/li>
&lt;/ol>
&lt;/blockquote>
&lt;h3 id="105-exercises">10.5 Exercises&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>Change the estimator:&lt;/strong> Re-run the NTL 1-0 comparison using &lt;code>omethod(lasso) tmethod(lasso)&lt;/code> instead of random forest. Do the ATE and GATEs change substantially?&lt;/li>
&lt;li>&lt;strong>Vary cross-fitting folds:&lt;/strong> Try &lt;code>xfolds(10)&lt;/code> instead of 5 for the 1-0 comparison. Is there a precision gain? Does the ATE shift?&lt;/li>
&lt;li>&lt;strong>Data-driven groups:&lt;/strong> Replace &lt;code>group(exec_con)&lt;/code> with &lt;code>group(4)&lt;/code> to let the data discover heterogeneity groups via IATE quantiles (GATES). Do the data-driven groups align with institutional quality?&lt;/li>
&lt;li>&lt;strong>Subpopulation policy analysis:&lt;/strong> Use &lt;code>estat ate if gdp_pc &amp;gt; 6000&lt;/code> to estimate the ATE for richer districts. How does the mining effect compare to the full-sample ATE?&lt;/li>
&lt;li>&lt;strong>Additional heterogeneity:&lt;/strong> Investigate whether GDP per capita moderates treatment effects using &lt;code>categraph iateplot gdp_pc&lt;/code>. Is there a clear pattern?&lt;/li>
&lt;/ol>
&lt;h3 id="106-references">10.6 References&lt;/h3>
&lt;ol>
&lt;li>Hodler, R., Lechner, M., &amp;amp; Raschky, P. A. (2023). Institutions and the resource curse: New insights from causal machine learning. &lt;em>PLoS ONE&lt;/em>, 18(5), e0284968.&lt;/li>
&lt;li>Athey, S., Tibshirani, J., &amp;amp; Wager, S. (2019). Generalized random forests. &lt;em>Annals of Statistics&lt;/em>, 47(2), 1148&amp;ndash;1178.&lt;/li>
&lt;li>Nie, X., &amp;amp; Wager, S. (2021). Quasi-oracle estimation of heterogeneous treatment effects. &lt;em>Biometrika&lt;/em>, 108(2), 299&amp;ndash;319.&lt;/li>
&lt;li>Knaus, M. C. (2022). Double machine learning-based programme evaluation under unconfoundedness. &lt;em>Econometrics Journal&lt;/em>, 25(3), 602&amp;ndash;627.&lt;/li>
&lt;li>Kennedy, E. H. (2023). Towards optimal doubly robust estimation of heterogeneous causal effects. &lt;em>Electronic Journal of Statistics&lt;/em>, 17(2), 3008&amp;ndash;3049.&lt;/li>
&lt;li>Sachs, J. D., &amp;amp; Warner, A. M. (1995). Natural resource abundance and economic growth. &lt;em>NBER Working Paper&lt;/em> No. 5398.&lt;/li>
&lt;li>Mehlum, H., Moene, K., &amp;amp; Torvik, R. (2006). Institutions and the resource curse. &lt;em>The Economic Journal&lt;/em>, 116(508), 1&amp;ndash;20.&lt;/li>
&lt;li>StataCorp. (2025). &lt;em>Stata 19 Treatment-Effects Reference Manual: cate&lt;/em>. College Station, TX: Stata Press.&lt;/li>
&lt;/ol></description></item></channel></rss>