The Colonial Origins of Comparative Development: An Empirical Investigation
Daron Acemoglu, Simon Johnson, and James A. Robinson (2001)
We replicate tables 4, 5. The data is available under https://economics.mit.edu/people/faculty/daron-acemoglu/data-archive. We download directly from the dropbox folders supplied. We start with table 4. In all specifications a single endogenous variable is instrumented by a single instrument, so the LIML and TSLS estimators are equal, as are the Anderson-Rubin, (conditional) likelihood-ratio, and Lagrange multiplier tests.
[1]:
from io import BytesIO
from zipfile import ZipFile
import numpy as np
import pandas as pd
import requests
url4 = "https://www.dropbox.com/scl/fi/3yuv9j514zuajzjfluoc1/maketable4.zip?rlkey=pq9l7bxktw1iqxe6fmoh26g79&e=1&dl=1"
content4 = requests.get(url4).content
with ZipFile(BytesIO(content4)).open("maketable4.dta") as file:
df4 = pd.read_stata(file)
df4 = df4[lambda x: x["baseco"] == 1]
df4["other_continent"] = df4["shortnam"].isin(["AUS", "MLT", "NZL"]).astype(int)
df4.head()
[1]:
shortnam | africa | lat_abst | rich4 | avexpr | logpgp95 | logem4 | asia | loghjypl | baseco | other_continent | |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | AGO | 1.0 | 0.136667 | 0.0 | 5.363636 | 7.770645 | 5.634789 | 0.0 | -3.411248 | 1.0 | 0 |
3 | ARG | 0.0 | 0.377778 | 0.0 | 6.386364 | 9.133459 | 4.232656 | 0.0 | -0.872274 | 1.0 | 0 |
5 | AUS | 0.0 | 0.300000 | 1.0 | 9.318182 | 9.897972 | 2.145931 | 0.0 | -0.170788 | 1.0 | 1 |
11 | BFA | 1.0 | 0.144444 | 0.0 | 4.454545 | 6.845880 | 5.634789 | 0.0 | -3.540459 | 1.0 | 0 |
12 | BGD | 0.0 | 0.266667 | 0.0 | 5.136364 | 6.877296 | 4.268438 | 1.0 | -2.063568 | 1.0 | 0 |
[2]:
from ivmodels import KClass
from ivmodels.tests import wald_test, anderson_rubin_test, rank_test
endogenous = ["avexpr"] # average protection against expropriation risk
instruments = ["logem4"] # log european settler mortality
for column, (outcome, exogenous, filter) in enumerate([
# *Columns 1 - 2 (Base Sample)
# ivreg logpgp95 (avexpr=logem4), first
("logpgp95", [], None),
# ivreg logpgp95 lat_abst (avexpr=logem4), first
("logpgp95", ["lat_abst"], None),
# *Columns 3 - 4 (Base Sample w/o Neo-Europes)
# ivreg logpgp95 (avexpr=logem4) if rich4!=1, first
("logpgp95", [], "rich4!=1"),
# ivreg logpgp95 lat_abst (avexpr=logem4) if rich4!=1, first
("logpgp95", ["lat_abst"], "rich4!=1"),
# *Columns 5 - 6 (Base Sample w/o Africa)
# ivreg logpgp95 (avexpr=logem4) if africa!=1, first
("logpgp95", [], "africa!=1"),
# ivreg logpgp95 lat_abst (avexpr=logem4) if africa!=1, first
("logpgp95", ["lat_abst"], "africa!=1"),
# *Columns 7 - 8 (Base Sample with continent dummies)
# ivreg logpgp95 (avexpr=logem4) africa asia other_cont, first
("logpgp95", ["africa", "asia", "other_continent"], None),
# ivreg logpgp95 lat_abst (avexpr=logem4) africa asia other_cont, first
("logpgp95", ["lat_abst", "africa", "asia", "other_continent"], None),
# *Column 9 (Base Sample, log GDP per worker)
# ivreg loghjypl (avexpr=logem4), first
("loghjypl", [], None),
]):
data = df4 if filter is None else df4.copy().query(filter)
data = data[lambda x: x[outcome].notna()]
X = data[endogenous]
Z = data[instruments]
C = data[exogenous]
y = data[outcome]
estimator = KClass(kappa="tsls").fit(Z=Z, X=X, C=C, y=y)
wald, wald_p = wald_test(X=X, y=y, Z=Z, C=C, beta=np.zeros(1))
std_error = np.abs(estimator.coef_[0]) / np.sqrt(wald)
ar, ar_p = anderson_rubin_test(X=X, y=y, Z=Z, C=C, beta=np.zeros(1))
f, f_p = rank_test(X=X, Z=Z, C=C)
print(f"Column ({column + 1}): {estimator.coef_[0]:.4f} ({std_error:.4f})")
print(f"wald: {wald:.4f} ({wald_p:.2g}), ar: {ar:.4f} ({ar_p:2g}), f: {f:.2g} ({f_p:.2g})")
Column (1): 0.9443 (0.1565)
wald: 36.3941 (1.6e-09), ar: 56.6029 (5.32907e-14), f: 23 (1.7e-06)
Column (2): 0.9957 (0.2217)
wald: 20.1744 (7.1e-06), ar: 36.2442 (1.74077e-09), f: 13 (0.0003)
Column (3): 1.2813 (0.3585)
wald: 12.7735 (0.00035), ar: 34.3118 (4.69521e-09), f: 8.6 (0.0033)
Column (4): 1.2117 (0.3543)
wald: 11.6997 (0.00063), ar: 28.1328 (1.13272e-07), f: 7.8 (0.0051)
Column (5): 0.5780 (0.0981)
wald: 34.6971 (3.9e-09), ar: 24.2217 (8.5858e-07), f: 31 (3.3e-08)
Column (6): 0.5757 (0.1173)
wald: 24.0864 (9.2e-07), ar: 16.9777 (3.7822e-05), f: 22 (3.3e-06)
Column (7): 0.9822 (0.2995)
wald: 10.7573 (0.001), ar: 19.3383 (1.09486e-05), f: 6.2 (0.013)
Column (8): 1.1071 (0.4636)
wald: 5.7032 (0.017), ar: 13.5579 (0.000231311), f: 3.5 (0.063)
Column (9): 0.9808 (0.1709)
wald: 32.9327 (9.5e-09), ar: 106.2383 ( 0), f: 24 (1.1e-06)
Identification is weak, with f-statistics dropping below the heuristic “weak instrument threshold” 10, but the causal effect of average protection against expropriation risk on log-gdp is still significant at level 0.001 using the weak-instrument-robust Anderson-Rubint test.
We continue with table 5.
[3]:
url5 = "https://www.dropbox.com/scl/fi/qiqyoc34vtr5tvgo4ew7n/maketable5.zip?rlkey=7xn2e59lqn8skf1psv0f7lkr0&e=1&dl=1"
content5 = requests.get(url5).content
with ZipFile(BytesIO(content5)).open("maketable5.dta") as file:
df5 = pd.read_stata(file)
df5 = df5[lambda x: x["baseco"] == 1]
outcome = 'logpgp95'
endogenous = ["avexpr"]
instruments = ["logem4"]
for column, (exogenous, filter) in enumerate([
# *--Columns 1 and 2 (British and French colony dummies)
# ivreg logpgp95 (avexpr=logem4) f_brit f_french, first
(["f_brit", "f_french"], None),
# ivreg logpgp95 lat_abst (avexpr=logem4) f_brit f_french, first
(["lat_abst", "f_brit", "f_french"], None),
# *--Columns 3 and 4 (British colonies only)
# ivreg logpgp95 (avexpr=logem4) if f_brit==1, first
([], "f_brit==1"),
# ivreg logpgp95 lat_abst (avexpr=logem4) if f_brit==1, first
(["lat_abst"], "f_brit==1"),
# *--Columns 5 and 6 (Control for French legel origin)
# ivreg logpgp95 (avexpr=logem4) sjlofr, first
(["sjlofr"], None),
# ivreg logpgp95 lat_abst (avexpr=logem4) sjlofr, first
(["lat_abst", "sjlofr"], None),
# *--Columns 7 and 8 (Religion dummies)
# ivreg logpgp95 (avexpr=logem4) catho80 muslim80 no_cpm80, first
(["catho80", "muslim80", "no_cpm80"], None),
# ivreg logpgp95 lat_abst (avexpr=logem4) catho80 muslim80 no_cpm80, first
(["lat_abst", "catho80", "muslim80", "no_cpm80"], None),
# *--Columns 9 (Multiple controls)
# ivreg logpgp95 lat_abst (avexpr=logem4) f_french sjlofr catho80 muslim80 no_cpm80, first
(["lat_abst", "f_french", "sjlofr", "catho80", "muslim80", "no_cpm80"], None)
]):
data = df5 if filter is None else df5.copy().query(filter)
data = data[lambda x: x[outcome].notna()]
X = data[endogenous]
Z = data[instruments]
C = data[exogenous]
y = data[outcome]
estimator = KClass(kappa="tsls").fit(Z=Z, X=X, C=C, y=y)
wald, wald_p = wald_test(X=X, y=y, Z=Z, C=C, beta=np.zeros(1))
std_error = np.abs(estimator.coef_[0]) / np.sqrt(wald)
ar, ar_p = anderson_rubin_test(X=X, y=y, Z=Z, C=C, beta=np.zeros(1))
f, f_p = rank_test(X=X, Z=Z, C=C)
print(f"Column ({column + 1}): {estimator.coef_[0]:.4f} ({std_error:.4f})")
print(f"wald: {wald:.4f} ({wald_p:.2g}), ar: {ar:.4f} ({ar_p:2g}), f: {f:.2g} ({f_p:.2g})")
Column (1): 1.0778 (0.2176)
wald: 24.5314 (7.3e-07), ar: 44.8089 (2.17235e-11), f: 15 (0.00013)
Column (2): 1.1553 (0.3372)
wald: 11.7400 (0.00061), ar: 26.1369 (3.18051e-07), f: 7.3 (0.0069)
Column (3): 1.0662 (0.2443)
wald: 19.0473 (1.3e-05), ar: 34.7046 (3.83717e-09), f: 9.8 (0.0017)
Column (4): 1.3391 (0.5164)
wald: 6.7227 (0.0095), ar: 20.4897 (5.99543e-06), f: 3.9 (0.049)
Column (5): 1.0800 (0.1912)
wald: 31.9153 (1.6e-08), ar: 55.7875 (8.07132e-14), f: 18 (1.9e-05)
Column (6): 1.1811 (0.2910)
wald: 16.4715 (4.9e-05), ar: 36.1450 (1.83171e-09), f: 9.9 (0.0017)
Column (7): 0.9174 (0.1467)
wald: 39.1013 (4e-10), ar: 51.2727 (8.03801e-13), f: 20 (8.4e-06)
Column (8): 1.0062 (0.2517)
wald: 15.9767 (6.4e-05), ar: 27.0786 (1.95353e-07), f: 8.6 (0.0033)
Column (9): 1.2122 (0.3949)
wald: 9.4226 (0.0021), ar: 23.7357 (1.10512e-06), f: 5.3 (0.022)
This is not a perfect replicates of the results presented by Acemoglu et al. (2001) in Table 4. However, it does match the results when running their Stata code (maketable5.do
in the archive).
As for table 4, identification is weak, but the causal effect of average protection against expropriation risk on log-gdp is still significant at level 0.001 using the weak-instrument-robust Anderson-Rubint test.