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.