1. Purpose
This RMarkdown reconstructs the empirical workflow of Papageorgiou,
Saam and Schulte rather than translating the Stata syntax
mechanically.
The replication is organised as:
input/final data → cleaning and sample checks → variable
construction → panel structure → CES/NLS estimation → Kmenta
approximation → clustered bootstrap → transformations to elasticity of
substitution → validation against the published Stata
benchmark.
The authors define the elasticity as
\[ \sigma = \frac{1}{1-\psi} \]
The benchmark results are taken from the supplied
estimation_results.log.
Important: the current uploaded package contains the
two final .dta datasets, the two Stata programs, the paper,
appendix, README and Stata log. The README identifies additional raw
WIOD/IEA/GGDC files used by generate_data.do, but those raw
files are not in the current working directory. Therefore this notebook
reproduces the empirical estimation exactly from the authors’ final
datasets and documents the raw-data construction from the original
do-file. When the raw files are supplied, the raw-data section can be
completed without changing the estimation section.
2. What the original workflow does
The supplied README states that generate_data.do uses
the raw files to generate the two final datasets, while
estimation.do produces the paper’s estimation results. The
final electricity dataset covers 26 countries and 1995–2009. The final
nonenergy dataset covers 19 countries, 28 industries and 1995–2007.
The original raw-data pipeline is:
- WIOD SEA data
- GGDC PPP data
- WIOD emission-relevant energy data
- IEA electricity generation
- IEA electricity capacity
- IEA electricity fuel use
- WIOD supply/use tables
- IEA electricity prices
- IEA industry prices
- Merge all sources
- Copy PPP/price information to WIOD subsectors
- Deflate and construct real quantities
- Construct clean/dirty energy and capital measures
- Split into electricity and nonenergy final datasets
This is implemented in the original Stata code in sections
(1)–(14).
3. Raw-data construction map
3.1 Raw files required
The README lists:
flatfile_SEA.txt
flatfile_EM_may12.txt
PPP_*.xml
*_SUT_*.xls
IEA_elec_gen.xml
IEA_elec_cap.xml
IEA_elec_fuel.xml
IEA_elec_prices_NCV.xml
IEA_ind_prices_NCV.xml
The original Stata code also creates intermediate files such as
SEA.dta, PPP97.dta, EM.dta,
SUTS.dta, and the IEA .dta files.
The current notebook deliberately does not invent
raw files that have not been supplied.
4. Load the authors’ final datasets
electricity <- read_dta("~/Desktop/FAU /JOBS/LEibneiz/replication/repli_cleandirty_v1/electricity_sector.dta") |>
mutate(country = as.character(country), industry = as.character(industry))
nonenergy <- read_dta("~/Desktop/FAU /JOBS/LEibneiz/replication/repli_cleandirty_v1/nonenergy_industries.dta") |>
mutate(country = as.character(country), industry = as.character(industry))
bind_rows(
electricity |> summarise(dataset = "electricity", n = n(),
countries = n_distinct(country),
min_year = min(year), max_year = max(year)),
nonenergy |> summarise(dataset = "nonenergy", n = n(),
countries = n_distinct(country),
min_year = min(year), max_year = max(year))
) |> kable()
| electricity |
390 |
26 |
1995 |
2009 |
| nonenergy |
6914 |
19 |
1995 |
2007 |
Benchmark: 390 electricity observations; 6,914 non-energy
observations.
5. Electricity-sector variable construction
The original final dataset already contains the variables generated
in generate_data.do.
The clean capacity measure \(EC_c\)
(nuclear, hydro, geothermal, solar, tide/wave/ocean, wind) is: \[EC_c =
EC_{nuclear}+EC_{hydro}+EC_{geotherm}+EC_{solar}+EC_{tidewaveocean}+EC_{wind}\]
Dirty capacity \(EC_d\) (combustion +
other) is:
\[EC_d =
EC_{totcombust}+EC_{other}\] The authors also construct clean and
dirty fuel use, and an alternative capital proxy based on
technology-specific EIA investment costs.The estimation variables are:
\[\ln EG,\quad \ln EC_c,\quad \ln
EC_d,\] and
\[\ln EG-\ln EC_d,\qquad\ln EC_c-\ln
EC_d,\qquad\frac12(\ln EC_c-\ln EC_d)^2.\]
elec_audit <- electricity |> #The |> is the base R pipe.It means:Take electricity and pass it into the next operation.
summarise(
n = n(),
countries = n_distinct(country),
years = n_distinct(year),
min_year = min(year),
max_year = max(year),
zero_clean_capacity = sum(EC_c <= 0, na.rm = TRUE),
zero_dirty_capacity = sum(EC_d <= 0, na.rm = TRUE),
missing_output = sum(is.na(ln_eg)),
missing_clean = sum(is.na(ln_ecc)),
missing_dirty = sum(is.na(ln_ecd))
)
knitr::kable(elec_audit)
| 390 |
26 |
15 |
1995 |
2009 |
0 |
0 |
0 |
0 |
0 |
Reconstructing the transformations independently as a validation
check:
#The purpose here is: Independently reconstruct the important variables and compare our reconstruction with the variables supplied in the .dta file.This is extremely valuable for replication.
# converting the values into natural logarithms
electricity <- electricity |>
arrange(country, year) |>
mutate(
ln_eg_R = log(EG_total), ln_ecc_R = log(EC_c), ln_ecd_R = log(EC_d),
ln_egecd_R = ln_eg_R - ln_ecd_R,
ln_eccd_R = ln_ecc_R - ln_ecd_R,
ln_eccd_2_R = 0.5 * ln_eccd_R^2,
ln_ecc_alt_R = log(EC_c_alt), ln_ecd_alt_R = log(EC_d_alt)
)
# Independent recomputation vs. supplied values -- should all be ~0
vars <- c("ln_eg", "ln_ecc", "ln_ecd", "ln_egecd", "ln_eccd", "ln_eccd_2") #This simply creates a list of variables that we want to check.
#tibble() creates a small data frame We are telling R: Create a table where the first column contains the variable names.
tibble(
variable = vars,
max_abs_difference = map_dbl(vars, \(v)
max(abs(electricity[[v]] - electricity[[paste0(v, "_R")]]), na.rm = TRUE))
) |> kable(digits = 10)
| ln_eg |
4.7580e-07 |
| ln_ecc |
4.7670e-07 |
| ln_ecd |
4.7670e-07 |
| ln_egecd |
8.8500e-07 |
| ln_eccd |
9.3660e-07 |
| ln_eccd_2 |
1.7015e-06 |
6. Nonenergy-sector variable construction
The authors aggregate emission-relevant energy by fuel type.
Clean energy includes:
- biogasoline
- biodiesel
- biogas
- other renewables
- electricity
- heat production
- hydro
- geothermal
- solar
- wind
- other sources
- nuclear
- waste
Dirty energy contains the remaining emission-relevant energy
sources.
The paper’s appendix describes three main construction stages:
- aggregate fuel-specific energy use into clean and dirty energy
- derive intermediate energy, service and material inputs from WIOD
use tables
- convert nominal local-currency values to real 1997 US dollars using
GGDC PPPs and WIOD price indices
Nonenergy: clean energy = biogasoline, biodiesel, biogas, other
renewables, electricity, heat, hydro, geothermal, solar, wind, other,
nuclear, waste; dirty = remaining emission-relevant sources. Final
sample: 6,914 obs.
nonenergy |>
summarise(n = n(), countries = n_distinct(country),
industries = n_distinct(industry),
cells = n_distinct(paste(country, industry)),
zero_clean = sum(xc <= 0, na.rm = TRUE),
zero_dirty = sum(xd <= 0, na.rm = TRUE)) |>
kable()
The final sample should contain 6,914 observations.
7. Panel structure
Electricity & Nonenergy
#This chunk prepares the data as panel data, meaning observations are organized by country and year (and, for nonenergy, by industry).
#arrange(country, year) → puts each country's observations in chronological order.
#group_by(country) → tells R to calculate lags within each country, not across countries.
#lag(ln_eg) → gives the previous year's \(\ln EG\).
#Therefore:
#$$ dln\_eg_{it}=\ln EG_{it}-\ln EG_{i,t-1} $$ which is the first difference used in the FD CES model.
#l1EC_c and l1EC_d similarly store the previous year's clean and dirty capacity.
#ungroup() → removes the grouping after the calculations.
elec_panel <- electricity |>
arrange(country, year) |>
group_by(country) |>
mutate(dln_eg = ln_eg - lag(ln_eg), l1EC_c = lag(EC_c), l1EC_d = lag(EC_d)) |>
ungroup()
table(table(elec_panel$country)) # expect balanced: 26 x 15
##
## 15
## 26
#Here the panel is country × industry × year.
#arrange() → puts each country-industry cell in time order.
#group_by(country, industry) → treats each country-industry combination as one panel.
#cur_group_id() → assigns a unique numerical ID to every country-industry cell.
nonenergy <- nonenergy |>
arrange(country, industry, year) |>
group_by(country, industry) |>
mutate(id = cur_group_id()) |>
ungroup()
table(table(nonenergy$id)) # expect 532 cells
##
## 11 13
## 1 531
The original Stata code reports a strongly balanced 26-country panel
from 1995 to 2009 and therefore 15 years per country.
The paper reports 532 country-industry cells and 6,914
observations.
8. Cluster-robust covariance helper
The coefficient estimates are obtained by least squares. Clustering
changes the covariance matrix, not the point estimates.
# We use clustered standard errors because observations within the same
# country may be correlated over time. Clustering changes the estimated
# standard errors and p-values, but NOT the regression coefficients.
# Calculate a country-clustered covariance matrix.
# This is used to obtain the cluster-robust standard errors.
cluster_vcov <- function(model, cluster) {
sandwich::vcovCL(model, cluster = cluster, type = "HC1")
}
# Create a regression table containing coefficients, clustered SEs,
# test statistics, and p-values.
coef_table_cluster <- function(model, cluster) {
V <- cluster_vcov(model, cluster) # Obtain the clustered covariance matrix.
b <- coef(model) # Extract the original regression coefficients. Clustering does NOT change these point estimates.
se <- sqrt(diag(V)) # Standard errors are the square roots of the diagonal elements of the clustered covariance matrix.
tibble( # Combine everything into one table.
term = names(b),
estimate = unname(b), # Estimated regression coefficient
std.error = se, # Country-clustered standard error
statistic = b / se, # t/z-style statistic = coefficient / clustered SE
p.value = 2 * pt(abs(b / se), df = length(unique(cluster)) - 1,
lower.tail = FALSE) # Two-sided p-value using number of countries - 1 as the degrees of freedom, following the Stata benchmark.
)
}
10. Electricity Table 3
This section reproduces Table 3 — Nonlinear Estimation and Kmenta
Approximation of CES: Electricity Sector.
The four specifications are:
- CES nonlinear least squares in levels, with country fixed
effects;
- CES nonlinear least squares in first differences;
- Kmenta approximation estimated by OLS in levels, with country fixed
effects;
- Kmenta approximation estimated by OLS in first differences.
The authors define the elasticity of substitution as
\[\sigma=\frac{1}{1-\psi}.\] The
CES specification is estimated using the same starting values as the
original Stata program: a = 20 d = 0.01 = -0.5 = 0.5
and a maximum of 100 iterations.
The electricity dataset contains 390 observations covering 26
countries over 1995–2009. The first-difference specifications therefore
contain 364 observations. These dimensions are also reported in the
supplied replication materials.
10.1 Nonlinear CES estimator
For a two-input CES:
\[CES(X_c,X_d)=\left[\omega
X_c^\psi+(1-\omega)X_d^\psi\right]^{1/\psi}.\]
The elasticity is
\[\sigma=\frac{1}{1-\psi}.\]
The electricity levels specification is:
\[\ln
EG_{it}=a+d\,year_t+\alpha_i+\frac{1}{\psi}\ln\left[\omega
EC_{c,it}^{\psi}+(1-\omega)EC_{d,it}^{\psi}\right]+\varepsilon_{it}.\]
Starting values follow the original Stata program:
a=20, d=0.01, psi=-0.5, omega=0.5, 100 iterations for NLS
specs.
10.2 CES in levels — Column 1
The country fixed effects are profiled out of the nonlinear
least-squares objective. This is equivalent to including country dummy
variables while avoiding an unnecessarily large nonlinear optimization
problem.
# CES production function written in logarithmic form.
# It combines clean and dirty energy inputs using the CES specification.
ces_log <- function(xc, xd, psi, omega) (1 / psi) * log(omega * xc^psi + (1 - omega) * xd^psi)
# xc = clean energy/capital input, xd = dirty energy/capital input ,psi = CES curvature parameter, omega = distribution/weight parameter. The function returns the CES contribution to log output.
# Column 1: CES NLS in levels, country FE profiled out via QR (computed ONCE
# per fit call, not inside the objective -- this was the main source of slow
# knitting, since the objective is evaluated hundreds/thousands of times).
coef.optim_fit <- function(object, ...) object$par # Extracting the estimated parameters from an optim() object. This makes coef(fit1) return the estimated parameter vector.
# Define the function that estimates the electricity CES model using NLS.
fit_elec_nls_explicit <- function(dat, # Dataset containing electricity observations
clean = "EC_c", # Variable for clean energy/capacity
dirty = "EC_d", # Variable for dirty energy/capacity
fe_var = "country", # Country fixed effects
# Starting values for numerical optimization
start = c(a = 20, d = 0.01, psi = -0.5, omega = 0.5)) {
cc <- dat[[clean]]; dd <- dat[[dirty]] # Extract clean-energy & dirty-enrgy observations from the
# Creating a country dummy-variable matrix.
# This represents the country fixed effects in the model.
# intercept = FALSE avoids creating an additional common intercept.
X <- model.matrix(reformulate(fe_var, intercept = FALSE), data = dat)
# Calculate X'X once.
# We need this matrix repeatedly when solving for the country fixed effects.
# Computing it here, rather than inside the objective function,
# makes the optimization much faster.
XtX <- crossprod(X)
# Define the objective function that optim() will minimize.
objective <- function(par) {
# Construct the model's predicted log output before country fixed effects.
#
# a = common intercept
# d * year = time trend
# ces_log(...) = CES contribution of clean and dirty inputs
core <- par["a"] + par["d"] * dat$year + ces_log(cc, dd, par["psi"], par["omega"])
# Calculate the part of log output that remains after
# removing the predicted common component.
y <- dat$ln_eg - core
# Estimate the country fixed effects for the current values
# of a, d, psi and omega.
#
# qr.solve() solves:
# (X'X) FE = X'y
#
# so that FE = (X'X)^(-1) X'y.
fe <- qr.solve(XtX, crossprod(X, y))
# Calculate the residual sum of squares (SSR).
# This is what optim() tries to minimize.
#
# Smaller SSR = model fits the observed log electricity output better.
sum((y - drop(X %*% fe))^2)
}
# Numerically minimize the SSR defined above.
# Nelder-Mead searches for the values of a, d, psi and omega
# that give the smallest residual sum of squares.
fit <- optim(start, objective, method = "Nelder-Mead",
control = list(maxit = 10000, reltol = 1e-8)) # Allow up to 10,000 optimization iterations. Require a very small relative change before convergence
# Calculate total sum of squares around the mean of log output.
# This provides the denominator for the pseudo R-squared.
tss <- sum((dat$ln_eg - mean(dat$ln_eg))^2)
# Calculate pseudo R-squared:
# 1 - unexplained variation / total variation.
#
# fit$value = minimized residual sum of squares.
fit$r2 <- 1 - fit$value / tss # pseudo R^2 from the profiled SSR
structure(fit, class = c("optim_fit", class(fit)))
}
# Give the optimization result a special class.
# This allows coef.optim_fit() above to extract fit$par
# when coef(fit1) is called.
# Estimate the electricity CES model using the electricity dataset.
# The default variables are EC_c (clean) and EC_d (dirty).
fit1 <- fit_elec_nls_explicit(electricity)
elec_nls <- coef(fit1)
sigma_elec_nls <- 1 / (1 - elec_nls["psi"])
adjr2_1 <- fit1$r2
elec_nls; sigma_elec_nls
## a d psi omega
## 18.408033256 -0.001263206 0.456570304 0.219438245
## psi
## 1.840164
The Stata benchmark gives:
- \(\psi \approx 0.4566\)
- \(\omega \approx 0.2195\)
- \(\sigma \approx 1.8402\)
- \(N=390\)
The supplied Stata log reports these estimates and clustered standard
errors. The paper rounds the headline estimates to about \(\psi=0.457\) and \(\sigma=1.840\).
The logic in simple terms
Your model is essentially trying to explain:
\[ \text{log electricity output} =
\text{country effect} + \text{time trend} + \text{CES combination of
clean and dirty inputs} + \text{error} \]
The important part is that optim() does not directly estimate the
country fixed effects together with everything else.
Instead, for every trial value of:
a d psi omega
the code:
Calculates the CES component. Calculates the remaining part of
output. Solves for the country fixed effects using qr.solve().
Calculates the residual sum of squares. optim() searches for the
parameter values that minimize that residual sum of squares.
So the computational logic is:
Try parameters → calculate CES → estimate country FE → calculate
errors → calculate SSR → try better parameters → repeat until
convergence.
10.3 CES first-difference specification — Column
2
The original specification is:
\[\Delta\ln
EG_{it}=d+\frac{1}{\psi}\ln\left(\frac{\omega
EC_{c,it}^{\psi}+(1-\omega)EC_{d,it}^{\psi}}{\omega
EC_{c,i,t-1}^{\psi}+(1-\omega)EC_{d,i,t-1}^{\psi}}\right)+\varepsilon_{it}.\]
The first differences remove the country fixed effects.
# Column 2: CES NLS in first differences.
# First differencing removes the country fixed effects because they do not change over time.
# Prepare the electricity panel for first-difference estimation.
elec_fd <- electricity |> # Sort observations within each country chronologically. This is essential because lag() must know which observation is the previous year.
arrange(country, year) |> # Treat each country as a separate time series.
# The lag and difference calculations will therefore be done within each country.
group_by(country) |>
# Create the variables needed for the first-difference CES model.
mutate(
# Change in log electricity output: current year's log output minus previous year's log output. This is Δln(EG).
dln_eg = ln_eg - lag(ln_eg), # Previous year's clean-energy capacity. Needed because the CES function is evaluated in both periods.
l1EC_c = lag(EC_c), # Previous year's dirty-energy capacity. Also needed to compare the CES input combination between two periods.
l1EC_d = lag(EC_d)
) |># Remove the country grouping after calculating the lags. This prevents later operations from accidentally remaining country-specific.
ungroup() |># Keep only observations for which the variables needed for estimation are finite numbers. The first year of every country has no lag, so it produces NA. Those observations cannot be used in the first-difference regression.
filter(is.finite(dln_eg),is.finite(l1EC_c),is.finite(l1EC_d) ) # Estimate the nonlinear CES model in first differences.
fit_elec_fd <- nlsLM( # Dependent variable: # Δln(EG), the change in log electricity output.
dln_eg ~ # d represents the time-trend coefficient after first differencing. The country fixed effect has disappeared through differencing.
d +
#Change in the CES log production component. The numerator is the CES energy combination in the current year. The denominator is the CES energy combination in the previous year.
# Taking the log of their ratio gives the first difference of the CES component.
(1 / psi) * log((omega * EC_c^psi +(1 - omega) * EC_d^psi) /(omega * l1EC_c^psi + (1 - omega) * l1EC_d^psi)),
# Use the first-difference dataset created above.
data = elec_fd,
# Starting values for the nonlinear optimization.
# nlsLM needs reasonable initial guesses before searching
# for the parameters that minimize the residuals.
start = list(d = 0.01, psi = -0.5,omega = 0.5),
# Allow up to 100 iterations for the nonlinear optimization.
control = nls.lm.control(maxiter = 100)
)
# Extract the estimated coefficients from the nonlinear model. These are the estimates of d, psi and omega.
fd_coef <- coef(fit_elec_fd)
# Convert the estimated CES parameter psi into the elasticity of substitution sigma. sigma = 1 / (1 - psi)
sigma_elec_fd <- 1 / (1 - fd_coef["psi"])
# Calculate a pseudo/adjusted fit measure for the first-difference model.
#
# deviance(fit_elec_fd) = residual sum of squares.
# The denominator measures the total variation in Δln(EG).
adjr2_2 <- 1 - deviance(fit_elec_fd) /sum((elec_fd$dln_eg - mean(elec_fd$dln_eg))^2)
# Display the estimated first-difference CES coefficients:
# d, psi and omega.
fd_coef
## d psi omega
## -0.00318516 0.48655838 0.44177154
# Display the estimated elasticity of substitution sigma.
sigma_elec_fd
## psi
## 1.947641
The supplied benchmark is approximately:
- \(d=-0.00319\)
- \(\psi=0.48655\)
- \(\omega=0.44177\)
- \(\sigma\approx1.948\)
- \(N=364\)
The specification contains 364 observations.
The logic of this chunk
Think of it as:
Original panel data
\[\ln(EG_{it}) =FE_i + dt +
CES(EC_{cit},EC_{dit})+\epsilon_{it}\]
Then we take the difference between year \(t\) and \(t-1\):
\[\Delta\ln(EG_{it})=d+\Delta
CES_{it}+\Delta\epsilon_{it}\]
The important thing is:
\[FE_i-FE_i=0\]
So country fixed effects disappear
automatically.
Why do we need lag()?
For example, suppose Germany has:
| 1995 |
5.0 |
10 |
20 |
| 1996 |
5.2 |
12 |
21 |
| 1997 |
5.3 |
14 |
22 |
10.4. Kmenta approximation (Columns 3 & 4)
The paper uses the first-order Kmenta approximation:
\[\ln Y_{it}=a_i+d_t+\omega\ln K_{Cit}
+(1-\omega)\ln K_{Dit} + \frac{(1-\omega)\psi}{2}(\ln K_C-\ln
K_D)^2.\] After subtracting \(\ln
K_D\):
\[\ln y_{it}=a_i+d_t+\omega\ln
k_{it}-\beta_2(\ln k_{it})^2+\epsilon_{it}.\]
The authors recover the CES parameters from the estimated translog
coefficients. The exact transformation used in Stata is preserved
below.
# Estimate the Kmenta model and recover the CES parameters (omega, psi and sigma) from the OLS coefficients.
# Regression formula to be estimated
# Dataset used for estimation
# Name of the first Kmenta coefficient
# Name of the quadratic Kmenta coefficient
# Default: cluster standard errors by country
estimate_kmenta <- function(formula, dat, x_var, x2_var, cluster = ~country) {
environment(formula) <- environment()
m <- lm(formula, data = dat)
V <- vcovCL(m, cluster = cluster, type = "HC1")
b <- coef(m)
beta1 <- unname(b[x_var]); beta2 <- unname(b[x2_var])
sigma <- beta1 * (1 - beta1) / (beta1 * (1 - beta1) - beta2)
psi <- 1 - 1 / sigma
grad_psi <- grad(function(z) {
s <- z[1] * (1 - z[1]) / (z[1] * (1 - z[1]) - z[2])
1 - 1 / s
}, c(beta1, beta2))
Vsub <- V[c(x_var, x2_var), c(x_var, x2_var)]
se_psi <- sqrt(drop(grad_psi %*% Vsub %*% grad_psi))
list(model = m, coefs = b, se = sqrt(diag(V)),
omega = beta1, psi = psi, sigma = sigma, se_psi = se_psi,
adjr2 = summary(m)$adj.r.squared, N = nobs(m))
}
elec_k <- electricity |> mutate(y_k = ln_egecd, x_k = ln_eccd, x_k2 = ln_eccd_2)
kmenta_levels <- estimate_kmenta(y_k ~ country + year + x_k + x_k2, elec_k, "x_k", "x_k2")
elec_kfd <- electricity |>
arrange(country, year) |> group_by(country) |>
mutate(dy = ln_egecd - lag(ln_egecd), dx = ln_eccd - lag(ln_eccd),
dx2 = ln_eccd_2 - lag(ln_eccd_2)) |>
ungroup() |> filter(is.finite(dy), is.finite(dx), is.finite(dx2))
kmenta_fd <- estimate_kmenta(dy ~ dx + dx2, elec_kfd, "dx", "dx2")
list(levels = kmenta_levels[c("omega", "psi", "sigma", "adjr2", "N")],
fd = kmenta_fd[c("omega", "psi", "sigma", "adjr2", "N")])
## $levels
## $levels$omega
## [1] 0.2453325
##
## $levels$psi
## [1] 0.4463543
##
## $levels$sigma
## [1] 1.806209
##
## $levels$adjr2
## [1] 0.9679572
##
## $levels$N
## [1] 390
##
##
## $fd
## $fd$omega
## [1] 0.4510513
##
## $fd$psi
## [1] 0.4544551
##
## $fd$sigma
## [1] 1.83303
##
## $fd$adjr2
## [1] 0.5461071
##
## $fd$N
## [1] 364
Benchmarks – levels: \(\omega=0.2453,\
\psi=0.4464,\ \sigma=1.8062,\
R^2_{adj}=0.9680,\ N=390\). FD: \(\omega=0.4511,\ \psi=0.4545,\ \sigma=1.8330,\
R^2_{adj}=0.5461,\ N=364\).
10.5 400-replication country-cluster bootstrap
Only Columns 1 & 2 need bootstrapped SEs (3 & 4 already have
clustered OLS SEs). Cluster indices are precomputed once
(split()) and each bootstrap sample is built with a single
vectorized subset instead of a per-cluster loop +
bind_rows() – this was the other main slow part of the
original.
cluster_bootstrap <- function(dat, cluster, fit_fun, B = 400L, seed = 123L,
idcluster = FALSE, reject_fun = \(p) FALSE,
max_attempts = 10000L) {
set.seed(seed)
cluster_values <- as.character(dat[[cluster]])
dat <- dat[!is.na(cluster_values), , drop = FALSE]
cluster_values <- cluster_values[!is.na(cluster_values)]
clusters <- unique(cluster_values)
row_index <- split(seq_len(nrow(dat)), cluster_values) # computed once, O(1) lookup below
results <- vector("list", B); accepted <- 0L; attempts <- 0L
while (accepted < B && attempts < max_attempts) {
attempts <- attempts + 1L
sampled <- sample(clusters, length(clusters), replace = TRUE)
idx <- row_index[sampled]
n_per <- lengths(idx)
boot_dat <- dat[unlist(idx, use.names = FALSE), , drop = FALSE]
if (idcluster) boot_dat$.boot_cluster <- paste0("boot_", rep(seq_along(sampled), n_per))
fit <- tryCatch(suppressWarnings(fit_fun(boot_dat)), error = \(e) NULL)
if (is.null(fit)) next
p <- tryCatch(as.numeric(coef(fit)), error = \(e) NULL)
if (is.null(p) || !all(is.finite(p))) next
names(p) <- names(coef(fit))
if (isTRUE(tryCatch(reject_fun(p), error = \(e) TRUE))) next
accepted <- accepted + 1L
results[[accepted]] <- p
}
if (accepted < B) stop("Only ", accepted, " of ", B, " replications succeeded after ", attempts, " attempts.")
as.data.frame(do.call(rbind, results), check.names = FALSE)
}
reject_psi <- \(p) isTRUE(p["psi"] > 1) || isTRUE(is.na(p["psi"]))
boot_levels <- cluster_bootstrap(
electricity, "country",
fit_fun = \(x) fit_elec_nls_explicit(x, fe_var = ".boot_cluster"),
idcluster = TRUE, reject_fun = reject_psi)
boot_fd <- cluster_bootstrap(
elec_fd, "country",
fit_fun = \(x) nlsLM(
dln_eg ~ d + (1 / psi) * log(
(omega * EC_c^psi + (1 - omega) * EC_d^psi) /
(omega * l1EC_c^psi + (1 - omega) * l1EC_d^psi)),
data = x, start = as.list(fd_coef), control = nls.lm.control(maxiter = 100)),
reject_fun = reject_psi)
se_nls <- sapply(boot_levels[c("d", "psi", "omega")], sd, na.rm = TRUE)
se_fd <- sapply(boot_fd[c("d", "psi", "omega")], sd, na.rm = TRUE)
tibble(spec = c("CES NLS levels", "CES NLS first differences"),
requested = 400, successful = c(nrow(boot_levels), nrow(boot_fd)))
## # A tibble: 2 × 3
## spec requested successful
## <chr> <dbl> <int>
## 1 CES NLS levels 400 400
## 2 CES NLS first differences 400 400
10.6 Table 3
One column-builder function replaces the four nearly-identical blocks
in the original (NLS uses bootstrap/normal inference –
df = Inf in pt() is just pnorm();
OLS uses df = n_clusters - 1).
stars <- \(p) ifelse(p < .01, "***", ifelse(p < .05, "**", ifelse(p < .10, "*", "")))
fmt <- \(x) formatC(as.numeric(x), format = "f", digits = 3)
stat_row <- \(est, se) paste0("(", formatC(as.numeric(est / se), format = "f", digits = 2), ")")
col_stats <- function(d, se_d, omega, se_omega, psi, se_psi, sigma, adjr2, N, hasFE, df = Inf) {
p <- \(z) 2 * pt(-abs(z), df = df)
c(paste0(fmt(d), stars(p(d / se_d))), stat_row(d, se_d),
paste0(fmt(omega), stars(p(omega / se_omega))), stat_row(omega, se_omega),
paste0(fmt(psi), stars(p(psi / se_psi))), stat_row(psi, se_psi),
if (hasFE) "Yes" else "No",
fmt(adjr2), fmt(1 - pchisq((psi / se_psi)^2, df = 1)), fmt(sigma), as.character(N))
}
df_cluster <- n_distinct(electricity$country) - 1
cols <- list(
NLS = col_stats(elec_nls["d"], se_nls["d"], elec_nls["omega"], se_nls["omega"],
elec_nls["psi"], se_nls["psi"], sigma_elec_nls, adjr2_1,
nrow(electricity), hasFE = TRUE),
`FD NLS` = col_stats(fd_coef["d"], se_fd["d"], fd_coef["omega"], se_fd["omega"],
fd_coef["psi"], se_fd["psi"], sigma_elec_fd, adjr2_2,
nrow(elec_fd), hasFE = FALSE),
OLS = col_stats(kmenta_levels$coefs["year"], kmenta_levels$se["year"],
kmenta_levels$omega, kmenta_levels$se["x_k"],
kmenta_levels$psi, kmenta_levels$se_psi, kmenta_levels$sigma,
kmenta_levels$adjr2, kmenta_levels$N, hasFE = TRUE, df = df_cluster),
`FD OLS` = col_stats(kmenta_fd$coefs["(Intercept)"], kmenta_fd$se["(Intercept)"],
kmenta_fd$omega, kmenta_fd$se["dx"],
kmenta_fd$psi, kmenta_fd$se_psi, kmenta_fd$sigma,
kmenta_fd$adjr2, kmenta_fd$N, hasFE = FALSE, df = df_cluster)
)
table3 <- as_tibble(cols) |>
mutate(term = c("d", "", "omega", "", "psi", "", "Country DV",
"Adjusted R2", "psi = 0 (Wald p)", "sigma", "N"), .before = 1)
table3 |>
kable(format = "html", escape = FALSE,
caption = "Table 3.-Nonlinear Estimation and Kmenta Approximation of CES: Electricity Sector") |>
add_header_above(c(" " = 1, "CES" = 2, "Kmenta" = 2)) |>
kable_styling(full_width = FALSE, bootstrap_options = "condensed") |>
row_spec(c(2, 4, 6), italic = TRUE)
Table 3.-Nonlinear Estimation and Kmenta Approximation of CES:
Electricity Sector
|
|
CES
|
Kmenta
|
|
term
|
NLS
|
FD NLS
|
OLS
|
FD OLS
|
|
d
|
-0.001
|
-0.003
|
-0.001
|
-0.003
|
|
|
(-0.67)
|
(-1.60)
|
(-0.50)
|
(-1.18)
|
|
omega
|
0.219***
|
0.442***
|
0.245***
|
0.451***
|
|
|
(3.01)
|
(6.71)
|
(6.02)
|
(7.87)
|
|
psi
|
0.457**
|
0.487***
|
0.446***
|
0.454***
|
|
|
(2.39)
|
(4.43)
|
(3.39)
|
(9.87)
|
|
Country DV
|
Yes
|
No
|
Yes
|
No
|
|
Adjusted R2
|
0.998
|
0.192
|
0.968
|
0.546
|
|
psi = 0 (Wald p)
|
0.017
|
0.000
|
0.001
|
0.000
|
|
sigma
|
1.840
|
1.948
|
1.806
|
1.833
|
|
N
|
390
|
364
|
390
|
364
|
z-statistics in parentheses. Significantly different from 0
at 1%, 5%, 10%. Columns 1 and 2 provide
bootstrapped standard errors based on 400 replications with country as
cluster variable. Specification 1 applies the nonlinear least squares
(NLS) estimator and includes country dummies. Specification 2 applies
the NLS estimator to a first-differenced version of the model.
Specification 3 applies the OLS estimator and includes country dummies.
Specification 4 applies the OLS estimator to a first-differenced version
of the model. \(\psi=0\) reports the
significance level of a Wald test with \(H_0:\psi=0\).
Interpretation of Table 3
Table 3 shows that clean and dirty electricity capacities are
substitutes, with the estimated elasticity of substitution significantly
above 1.
- The CES-NLS estimates give \(\psi \approx
0.46\), implying \(\sigma \approx
1.84\) in levels and \(\sigma \approx
1.95\) in first differences.
- The Kmenta-OLS estimates are very similar, with \(\sigma \approx 1.81\)–\(1.83\), supporting the robustness of the
CES results across estimation methods.
- The hypothesis \(\psi = 0\) is
rejected in all four specifications, indicating that the elasticity of
substitution is significantly different from 1.
- The first-difference specifications also produce elasticities well
above 1, showing that the result is not driven solely by country fixed
effects.
- Overall, the authors find an elasticity of substitution of around 2
in the electricity-generating sector. This is important because an
elasticity above 1 is a necessary condition for long-run green growth in
their theoretical framework.
- The results therefore suggest that clean electricity capacity can
substitute for dirty capacity relatively strongly, which is favorable
for the transition toward cleaner energy and long-run green growth.
Overall interpretation: The evidence indicates
substantial substitution possibilities between clean and dirty
electricity capacity, with an elasticity of about 2. This supports the
view that replacing dirty energy with clean energy is technologically
feasible without a proportionally large loss in productive capacity.
The authors caution, however, that the estimates should be
interpreted as associations rather than causal effects,
because potential instruments for addressing endogeneity were not found
to be sufficiently exogenous.
11. Table 4 — Alternative Capital Proxy
# The alternative proxy uses EIA-based capital stocks instead of installed capacity.
# The available alternative-capital sample ends in 2007: 338 observations in levels
# and 312 observations after first differencing.
elec_alt <- electricity |>
filter(is.finite(ln_ecc_alt), is.finite(ln_ecd_alt)) |>
arrange(country, year) |>
mutate(
ln_egecd_alt = ln_eg - ln_ecd_alt,
ln_eccd_alt = ln_ecc_alt - ln_ecd_alt,
ln_eccd_2_alt = 0.5 * ln_eccd_alt^2
)
table(table(elec_alt$country))
##
## 13
## 26
11.1 Table 4: Column 1
# Column 1: CES NLS in levels with country fixed effects.
fit_alt_nls <- fit_elec_nls_explicit(elec_alt, clean = "EC_c_alt", dirty = "EC_d_alt")
alt_nls <- coef(fit_alt_nls)
sigma_alt_nls <- 1 / (1 - alt_nls["psi"])
alt_nls; sigma_alt_nls
## a d psi omega
## 21.90192211 -0.00968515 0.42339436 0.19330930
## psi
## 1.734288
The supplied benchmark is approximately:
- \(d=-0.0010\)
- \(\psi=0.4233\)
- \(\omega=0.0.1933\)
- \(\sigma\approx1.734\)
- \(N=366\)
11.1 Table 4: Column 2
# Column 2: CES NLS in first differences.
elec_alt_fd <- elec_alt |> group_by(country) |>
mutate(
dln_eg = ln_eg - lag(ln_eg),
l1EC_c_alt = lag(EC_c_alt),
l1EC_d_alt = lag(EC_d_alt)
) |>
ungroup() |>
filter(is.finite(dln_eg), is.finite(l1EC_c_alt), is.finite(l1EC_d_alt))
fit_alt_fd <- nlsLM(dln_eg ~ d + (1 / psi) * log((omega * EC_c_alt^psi + (1 - omega) * EC_d_alt^psi) /(omega * l1EC_c_alt^psi + (1 - omega) * l1EC_d_alt^psi)),
data = elec_alt_fd, start = list(d = 0.01, psi = -0.5, omega = 0.5), control = nls.lm.control(maxiter = 100))
alt_fd <- coef(fit_alt_fd)
sigma_alt_fd <- 1 / (1 - alt_fd["psi"])
alt_fd; sigma_alt_fd
## d psi omega
## -0.009135365 0.459919244 0.388119217
## psi
## 1.851575
The supplied benchmark is approximately:
- \(d=-0.009135365\)
- \(\psi=0.459919244\)
- \(\omega=0.388119217\)
- \(\sigma\approx1.851575\)
- \(N=312\)
11.3 400-replication country-cluster bootstrap, matching the
Stata specification
boot_alt_nls <- cluster_bootstrap(
elec_alt, "country",
fit_fun = \(x) fit_elec_nls_explicit(
x, clean = "EC_c_alt", dirty = "EC_d_alt",
fe_var = ".boot_cluster", start = alt_nls
),
idcluster = TRUE, reject_fun = reject_psi
)
boot_alt_fd <- cluster_bootstrap(
elec_alt_fd, "country",
fit_fun = \(x) nlsLM(
dln_eg ~ d + (1 / psi) * log(
(omega * EC_c_alt^psi + (1 - omega) * EC_d_alt^psi) /
(omega * l1EC_c_alt^psi + (1 - omega) * l1EC_d_alt^psi)
),
data = x, start = as.list(alt_fd),
control = nls.lm.control(maxiter = 100)
),
reject_fun = reject_psi
)
se_alt_nls <- sapply(boot_alt_nls[c("d", "psi", "omega")], sd)
se_alt_fd <- sapply(boot_alt_fd[c("d", "psi", "omega")], sd)
tibble(spec = c("CES NLS levels", "CES NLS first differences"),
requested = 400, successful = c(nrow(boot_alt_nls), nrow(boot_alt_fd)))
## # A tibble: 2 × 3
## spec requested successful
## <chr> <dbl> <int>
## 1 CES NLS levels 400 400
## 2 CES NLS first differences 400 400
11.4 Table 4:Column 3&4
# Columns 3 and 4: Kmenta approximation.
k_alt <- elec_alt
k_alt_levels <- estimate_kmenta(
ln_egecd_alt ~ country + year + ln_eccd_alt + ln_eccd_2_alt,
k_alt, "ln_eccd_alt", "ln_eccd_2_alt"
)
k_alt_fd <- k_alt |>
group_by(country) |>
mutate(
dy = ln_egecd_alt - lag(ln_egecd_alt),
dx = ln_eccd_alt - lag(ln_eccd_alt),
dx2 = ln_eccd_2_alt - lag(ln_eccd_2_alt)
) |>
ungroup() |>
filter(is.finite(dy), is.finite(dx), is.finite(dx2))
k_alt_fd <- estimate_kmenta(
dy ~ dx + dx2, k_alt_fd, "dx", "dx2"
)
# Adjusted R2 for the NLS specifications.
adj_alt_nls <- 1 - (1 - fit_alt_nls$r2) *
(nrow(elec_alt) - 1) / (nrow(elec_alt) - 30)
adj_alt_fd <- 1 - (1 - (1 - deviance(fit_alt_fd) /
sum((elec_alt_fd$dln_eg - mean(elec_alt_fd$dln_eg))^2))) *
(nrow(elec_alt_fd) - 1) / (nrow(elec_alt_fd) - 3)
# Published Table 4 structure.
table4 <- tibble(
term = c("d", "", "omega", "", "psi", "", "Country DV",
"Adjusted R2", "psi = 0 (Wald p)", "sigma", "N"),
`NLS` = col_stats(
alt_nls["d"], se_alt_nls["d"],
alt_nls["omega"], se_alt_nls["omega"],
alt_nls["psi"], se_alt_nls["psi"],
sigma_alt_nls, adj_alt_nls, nrow(elec_alt),
hasFE = TRUE
),
`FD NLS` = col_stats(
alt_fd["d"], se_alt_fd["d"],
alt_fd["omega"], se_alt_fd["omega"],
alt_fd["psi"], se_alt_fd["psi"],
sigma_alt_fd, adj_alt_fd, nrow(elec_alt_fd),
hasFE = FALSE
),
`OLS` = col_stats(
k_alt_levels$coefs["year"], k_alt_levels$se["year"],
k_alt_levels$omega, k_alt_levels$se["ln_eccd_alt"],
k_alt_levels$psi, k_alt_levels$se_psi,
k_alt_levels$sigma, k_alt_levels$adjr2, k_alt_levels$N,
hasFE = TRUE, df = df_cluster
),
`FD OLS` = col_stats(
k_alt_fd$coefs["(Intercept)"], k_alt_fd$se["(Intercept)"],
k_alt_fd$omega, k_alt_fd$se["dx"],
k_alt_fd$psi, k_alt_fd$se_psi,
k_alt_fd$sigma, k_alt_fd$adjr2, k_alt_fd$N,
hasFE = FALSE, df = df_cluster
)
)
table4 |>
kable(
format = "html", escape = FALSE,
caption = "Table 4.-Nonlinear Estimation and Kmenta Approximation of CES with an Alternative Capital Proxy: Electricity Sector"
) |>
add_header_above(c(" " = 1, "CES" = 2, "Kmenta" = 2)) |>
kable_styling(full_width = FALSE, bootstrap_options = "condensed") |>
row_spec(c(2, 4, 6), italic = TRUE)
Table 4.-Nonlinear Estimation and Kmenta Approximation of CES with an
Alternative Capital Proxy: Electricity Sector
|
|
CES
|
Kmenta
|
|
term
|
NLS
|
FD NLS
|
OLS
|
FD OLS
|
|
d
|
-0.010***
|
-0.009***
|
-0.009***
|
-0.009***
|
|
|
(-3.63)
|
(-3.89)
|
(-3.97)
|
(-3.35)
|
|
omega
|
0.193*
|
0.388***
|
0.203***
|
0.401***
|
|
|
(1.88)
|
(3.75)
|
(4.01)
|
(6.39)
|
|
psi
|
0.423*
|
0.460**
|
0.535**
|
0.441***
|
|
|
(1.89)
|
(2.51)
|
(2.74)
|
(5.17)
|
|
Country DV
|
Yes
|
No
|
Yes
|
No
|
|
Adjusted R2
|
0.997
|
0.053
|
0.965
|
0.555
|
|
psi = 0 (Wald p)
|
0.059
|
0.012
|
0.006
|
0.000
|
|
sigma
|
1.734
|
1.852
|
2.152
|
1.789
|
|
N
|
338
|
312
|
338
|
312
|
z-statistics in parentheses. Significantly different from 0
at 1%, 5%, 10%. Columns 1 and 2 provide
bootstrapped standard errors based on 400 replications with country as
cluster variable. Specification 1 applies the nonlinear least squares
(NLS) estimator and includes country dummies. Specification 2 applies
the NLS estimator to a first-differenced version of the model.
Specification 3 applies the OLS estimator and includes country dummies.
Specification 4 applies the OLS estimator to a first-differenced version
of the model. \(\psi = 0\) reports the
significance level of a Wald test with \(H_0:\psi = 0\).
Interpretation of Table 4
Table 4 examines whether the estimated elasticity of substitution
between clean and dirty electricity capacity is robust when the authors
replace the main capital measure with an alternative EIA-based
capital proxy. The alternative proxy is available for fewer
observations, so the sample falls from 390 to 338 observations in levels
and from 364 to 312 observations in first differences.
:contentReferenceoaicite:0
The results continue to show an elasticity of substitution
above one in all four specifications:
- The CES-NLS estimate gives \(\sigma =
1.734\) in levels and \(\sigma =
1.852\) in first differences.
- The Kmenta estimates are \(\sigma =
2.152\) in levels and \(\sigma =
1.789\) in first differences.
- The corresponding estimates of \(\psi\) are positive in every specification,
ranging from 0.423 to 0.535.
- The hypothesis \(\psi = 0\), which
corresponds to an elasticity of substitution of one, is rejected at
conventional significance levels in all four specifications.
:contentReferenceoaicite:1
The similarity between the levels and first-difference estimates is
important. The levels specifications include country fixed effects,
while the first-difference specifications remove these country-specific
effects. The elasticity remains well above one after differencing,
suggesting that the main result is not simply driven by
permanent differences between countries.
The Kmenta estimates are somewhat higher than the nonlinear CES
estimates, but the overall conclusion is unchanged. The authors use the
Kmenta model as a robustness check because it provides a linear
approximation to the CES production function. :contentReferenceoaicite:2
The alternative capital proxy therefore supports the main
finding of the paper: clean and dirty electricity-generating
capacity can substitute for one another with an elasticity greater than
one. Since \(\sigma > 1\) is the
necessary condition identified by the authors for long-run green growth
without technical change, the robustness results remain consistent with
the possibility of a transition from dirty toward clean electricity
without requiring continuous technological progress.
However, the authors note that this sensitivity analysis is limited
by the lack of plant cost data across fuels, so the
alternative proxy should be interpreted as a robustness check rather
than a completely independent measure of capital. :contentReferenceoaicite:3
Overall interpretation: Table 4 confirms that the
paper’s central result is robust to using a different measure of
electricity-generation capital. Across NLS, first differences, and
Kmenta specifications, the estimated elasticity remains above one and is
statistically different from the unit-elasticity case. The estimated
elasticity is roughly 1.7–2.2, indicating substantial
substitution possibilities between clean and dirty electricity
capacity.
Interpretation of the parameters
The parameter \(\omega\) measures
the distribution or relative importance of the clean input in the CES
aggregator. Its estimates are positive and statistically significant in
all four specifications, although the magnitude changes from 0.193 to
0.401. This variation is expected when a different capital measure is
used, but the positive estimates remain consistent with clean capital
entering the production function. :contentReferenceoaicite:4
The parameter \(d\) captures the
trend component associated with neutral technical change. It is negative
and statistically significant in all four specifications, with estimates
between approximately \(-0.009\) and
\(-0.010\). Thus, the estimated trend
component is negative in this alternative-capital specification.
The adjusted \(R^2\) is very high
for the levels specifications (0.997 and 0.965), while it is
considerably lower for the first-difference specifications (0.053 and
0.555). This is not surprising because differencing removes the
country-specific effects that explain a substantial part of the
variation in the levels data.
Main conclusion
The most important result is not the exact value of \(\omega\) or \(d\), but the estimate of \(\sigma\). The four estimates are:
\[
\sigma = 1.734,\quad 1.852,\quad 2.152,\quad 1.789.
\]
All are greater than one. Therefore, changing the
capital proxy does not overturn the paper’s main conclusion that there
is substantial substitutability between clean and dirty electricity
inputs.
The result is particularly important because the paper’s theoretical
framework requires \(\sigma > 1\)
for long-run green growth in the absence of technical change. Hence,
Table 4 provides evidence that this conclusion is robust to the
choice of capital proxy. :contentReferenceoaicite:5
The OLS transformation is also reproduced exactly. The
alternative-capital Kmenta regression uses:
electricity <- electricity |>
mutate(
ln_egecd_alt = ln_eg - ln_ecd_alt,
ln_eccd_alt = ln_ecc_alt - ln_ecd_alt,
ln_eccd_2_alt = 0.5 * ln_eccd_alt^2
)
The resulting Stata coefficients are:
- Levels: \(\omega = 0.2025437\),
\(\psi = 0.5352242\), \(\sigma = 2.1515751\)
- First differences: \(\omega =
0.4012334\), \(\psi =
0.4408761\), \(\sigma =
1.7885124\)
Table 4 specification
Table 4 changes only the capital measure relative to Table 3. Instead
of installed clean and dirty generation capacity (EC_c,
EC_d), the robustness specification uses the EIA-based
alternative capital stocks EC_c_alt and
EC_d_alt.
The four specifications are:
- NLS levels + country fixed effects
- NLS first differences
- Kmenta OLS levels + country fixed effects
- Kmenta OLS first differences
The alternative capital measure is available for 1995–2007, giving
338 observations in levels. First differencing removes
the first observation for each country, giving 312
observations.
Expected point estimates
The R implementation should reproduce the following Stata estimates
approximately:
| \(\omega\) |
0.1933 |
0.3881 |
0.2025 |
0.4012 |
| \(\psi\) |
0.4233 |
0.4599 |
0.5352 |
0.4409 |
| \(\sigma\) |
1.7340 |
1.8516 |
2.1516 |
1.7885 |
| \(N\) |
338 |
312 |
338 |
312 |
The Kmenta specifications should reproduce the published Stata
coefficients particularly closely because they use ordinary least
squares followed by the same nonlinear Kmenta transformation.
16. Table 5 — Cobb-Douglas nested in CES: Electricity
Sector
The key difference from Tables 3 and 4 is that Table 5
incorporates fuel use into the dirty-energy input through a Cobb-Douglas
aggregate:
\[X_d = EC_d^\alpha FU_d^{1-\alpha}.
\]
The Stata specification is explicitly:
\[\ln EG =a + d \cdot year+ \text{country
FE}+ \frac{1}{\psi}\ln\left[\omega EC_c^\psi+(1-\omega)\left(EC_d^\alpha
FU_d^{1-\alpha}\right)^\psi\right].\]
The corresponding R specification is:
# Dirty input becomes a Cobb-Douglas aggregate of capacity and fuel: EC_d^alpha * FU_d^(1-alpha)
fit_cdces <- function(dat, clean="EC_c", dirty="EC_d", fe_var="country",
start=c(a=0, d=.01, omega=.5, alpha=.7, psi=-.2)) {
C <- dat[[clean]]; D <- dat[[dirty]]; Fu <- dat$FU_d
X <- model.matrix(reformulate(fe_var, intercept=FALSE), dat); XtX <- crossprod(X)
obj <- function(p) {
y <- dat$ln_eg - p["a"] - p["d"]*dat$year - ces_log(C, D^p["alpha"]*Fu^(1-p["alpha"]), p["psi"], p["omega"])
fe <- qr.solve(XtX, crossprod(X, y)); sum((y - X %*% fe)^2)
}
fit <- optim(start, obj, method="Nelder-Mead", control=list(maxit=10000, reltol=1e-10))
fit$r2 <- 1 - fit$value/sum((dat$ln_eg-mean(dat$ln_eg))^2)
structure(fit, class=c("optim_fit", class(fit)))
}
# FD version: formula built from column names so one function serves both capital proxies
fit_cdces_fd <- function(dat, clean, dirty, clag, dlag, start=list(d=.01,omega=.5,alpha=.5,psi=-.5)) {
f <- as.formula(sprintf(
"dln_eg ~ d + (1/psi)*log((omega*%s^psi+(1-omega)*(%s^alpha*FU_d^(1-alpha))^psi)/(omega*%s^psi+(1-omega)*(%s^alpha*l1FU_d^(1-alpha))^psi))",
clean, dirty, clag, dlag))
nlsLM(f, data=dat, start=start, control=nls.lm.control(maxiter=100))
}
cd_main <- fit_cdces(electricity)
cd_main_fd <- electricity |> arrange(country,year) |> group_by(country) |>
mutate(dln_eg=ln_eg-lag(ln_eg), l1EC_c=lag(EC_c), l1EC_d=lag(EC_d), l1FU_d=lag(FU_d)) |>
ungroup() |> filter(if_all(c(dln_eg,l1EC_c,l1EC_d,l1FU_d), is.finite))
cd_main_fd_fit <- fit_cdces_fd(cd_main_fd, "EC_c","EC_d","l1EC_c","l1EC_d")
cd_alt <- fit_cdces(elec_alt, clean="EC_c_alt", dirty="EC_d_alt")
cd_alt_fd <- elec_alt |> arrange(country,year) |> group_by(country) |>
mutate(dln_eg=ln_eg-lag(ln_eg), l1EC_c_alt=lag(EC_c_alt), l1EC_d_alt=lag(EC_d_alt), l1FU_d=lag(FU_d)) |>
ungroup() |> filter(if_all(c(dln_eg,l1EC_c_alt,l1EC_d_alt,l1FU_d), is.finite))
cd_alt_fd_fit <- fit_cdces_fd(cd_alt_fd, "EC_c_alt","EC_d_alt","l1EC_c_alt","l1EC_d_alt")
b_main <- cluster_bootstrap(electricity, "country", \(x) fit_cdces(x, fe_var=".boot_cluster"), idcluster=TRUE, reject_fun=reject_psi)
b_main_fd <- cluster_bootstrap(cd_main_fd, "country", \(x) fit_cdces_fd(x,"EC_c","EC_d","l1EC_c","l1EC_d"), reject_fun=reject_psi)
b_alt <- cluster_bootstrap(elec_alt, "country", \(x) fit_cdces(x, clean="EC_c_alt", dirty="EC_d_alt", fe_var=".boot_cluster"), idcluster=TRUE, reject_fun=reject_psi)
b_alt_fd <- cluster_bootstrap(cd_alt_fd, "country", \(x) fit_cdces_fd(x,"EC_c_alt","EC_d_alt","l1EC_c_alt","l1EC_d_alt"), reject_fun=reject_psi)
se5 <- lapply(list(main=b_main, main_fd=b_main_fd, alt=b_alt, alt_fd=b_alt_fd),
\(b) sapply(b[c("d","alpha","omega","psi")], sd, na.rm=TRUE))
adjR2 <- function(fit, dat, yvar, k) {
r2 <- if (inherits(fit,"optim_fit")) fit$r2 else 1 - deviance(fit)/sum((dat[[yvar]]-mean(dat[[yvar]]))^2)
n <- nrow(dat); 1 - (1-r2)*(n-1)/(n-k-1)
}
col5 <- function(fit, se, dat, yvar, k, hasFE) {
b <- coef(fit)
rows <- unlist(lapply(c("d","alpha","omega","psi"), \(p)
c(paste0(fmt(b[p]), stars(2*pnorm(-abs(b[p]/se[p])))), stat_row(b[p], se[p]))))
c(rows, if (hasFE) "Yes" else "No", fmt(adjR2(fit,dat,yvar,k)),
fmt(2*pnorm(-abs(b["psi"]/se["psi"]))), fmt(1/(1-b["psi"])), as.character(nrow(dat)))
}
table5 <- tibble(
term = c("d","","alpha","","omega","","psi","","Country DV","Adjusted R2","psi = 0 (Wald p)","sigma","N"),
`NLS` = col5(cd_main, se5$main, electricity, "ln_eg", 26+4, TRUE),
`FD NLS` = col5(cd_main_fd_fit, se5$main_fd, cd_main_fd, "dln_eg", 4, FALSE),
`NLS (Alt)` = col5(cd_alt, se5$alt, elec_alt, "ln_eg", n_distinct(elec_alt$country)+4, TRUE),
`FD NLS (Alt)` = col5(cd_alt_fd_fit, se5$alt_fd, cd_alt_fd, "dln_eg", 4, FALSE)
)
table5 |> kable(format="html", escape=FALSE,
caption="Table 5.-Nonlinear Estimation of Cobb-Douglas in CES: Electricity Sector") |>
add_header_above(c(" "=1, "Main Capital Proxy"=2, "Alternative Capital Proxy"=2)) |>
kable_styling(full_width=FALSE, bootstrap_options="condensed") |> row_spec(c(2,4,6,8), italic=TRUE)
Table 5.-Nonlinear Estimation of Cobb-Douglas in CES: Electricity Sector
|
|
Main Capital Proxy
|
Alternative Capital Proxy
|
|
term
|
NLS
|
FD NLS
|
NLS (Alt)
|
FD NLS (Alt)
|
|
d
|
0.003
|
0.002
|
-0.000
|
-0.000
|
|
|
(1.54)
|
(1.38)
|
(-0.19)
|
(-0.10)
|
|
alpha
|
0.437***
|
0.379***
|
0.347***
|
0.311***
|
|
|
(6.38)
|
(3.93)
|
(5.09)
|
(4.07)
|
|
omega
|
0.487***
|
0.707***
|
0.010
|
0.005
|
|
|
(4.71)
|
(9.15)
|
(0.26)
|
(0.79)
|
|
psi
|
0.508***
|
0.651***
|
0.508***
|
0.644***
|
|
|
(3.28)
|
(4.89)
|
(3.38)
|
(5.57)
|
|
Country DV
|
Yes
|
No
|
Yes
|
No
|
|
Adjusted R2
|
0.999
|
0.524
|
0.999
|
0.498
|
|
psi = 0 (Wald p)
|
0.001
|
0.000
|
0.001
|
0.000
|
|
sigma
|
2.031
|
2.867
|
2.032
|
2.810
|
|
N
|
390
|
364
|
338
|
312
|
z-statistics are reported in parentheses. Significance
levels are denoted by 1%, 5%, and 10%. All
columns report bootstrapped standard errors based on 400 replications,
with country as the clustering variable.Specifications 1 and 3 use the
nonlinear least squares (NLS) estimator and include country dummies.
Specifications 2 and 4 apply the NLS estimator to the first-differenced
version of the model.The column \(\psi =
0\) reports the significance level of a Wald test of the null
hypothesis:\(H_0: \psi = 0.\)
17. Table 6 — Nonlinear Estimation and Kmenta Approximation
of CES in Cobb-Douglas: Nonenergy Industries
Unlike the electricity sector, the nonenergy specification nests the
CES energy aggregate inside a Cobb-Douglas function of
capital, labor and (for gross output) intermediate materials/services.
Following equation (8)/(9) in the paper, the energy aggregate does
not carry a distribution weight \(\omega\) — clean and dirty energy enter
symmetrically:
\[
\ln Y_{ijt}=a_i+a_j+d_t+(1-\alpha-\gamma-\theta)\ln L_{ijt}+\alpha\ln
K_{ijt}
+\theta\ln
MS_{ijt}+\gamma\left[\frac{1}{\psi}\ln\left(E_{C,ijt}^{\psi}+E_{D,ijt}^{\psi}\right)\right]+\varepsilon_{ijt}.
\]
\(\theta\) (materials/services) only
enters the gross-output specification (Column 2); the
value-added-plus-energy specification (Column 1) drops it. Fixed effects
are two-way (country and industry), profiled out
exactly as in fit_cdces() above but with a two-factor
design matrix.
The supplied Stata benchmark is approximately:
- Column 1 (VA+IIE): \(\alpha=0.359,\
\gamma=0.260,\ \psi=0.651,\ \sigma\approx2.868,\ N=6914\)
- Column 2 (GO): \(\alpha=0.186,\
\gamma=0.121,\ \theta=0.565,\ \psi=0.654,\ \sigma\approx2.888,\
N=6914\)
The Stata bootstrap for Table 6 clusters at the country-industry
cell (cluster(country industry)) but —
unlike the electricity tables — does not relabel the
fixed effects after resampling (idcluster is not used here;
the original ${CDCES}/${INDCES} dummies are
reused as-is). cluster_bootstrap() already supports this
via idcluster = FALSE.
The Stata cnsreg constrains the coefficients on
ln_xdl and ln_xcl to be equal. Constraining
two coefficients to be equal is algebraically identical to replacing the
two regressors with their sum and estimating one shared
coefficient — so we fold that constraint directly into the design matrix
rather than reimplementing cnsreg.
fit_ne <- function(dat, yvar, theta=FALSE, start) {
X <- model.matrix(~0+factor(country)+factor(industry), dat); XtX <- crossprod(X)
y <- dat[[yvar]]; xl<-dat$ln_xl; xk<-dat$ln_xk; xc<-dat$xc; xd<-dat$xd
if (theta) xm <- dat$ln_xiims
obj <- function(p) {
ces <- (1/p["psi"])*log(xd^p["psi"]+xc^p["psi"])
core <- if (theta) p["d"]*dat$year + (1-p["alpha"]-p["gamma"]-p["theta"])*xl + p["alpha"]*xk + p["theta"]*xm + p["gamma"]*ces
else p["d"]*dat$year + (1-p["alpha"]-p["gamma"])*xl + p["alpha"]*xk + p["gamma"]*ces
r <- y-core; fe <- qr.solve(XtX, crossprod(X,r)); sum((r - X%*%fe)^2)
}
fit <- optim(start, obj, method="Nelder-Mead", control=list(maxit=20000, reltol=1e-10))
fit$r2 <- 1 - fit$value/sum((y-mean(y))^2); structure(fit, class=c("optim_fit", class(fit)))
}
ne_va <- fit_ne(nonenergy, "ln_vaxiie", FALSE, c(d=.01,alpha=.3,gamma=.1,psi=.2))
ne_go <- fit_ne(nonenergy, "ln_go", TRUE, c(d=.01,alpha=.3,gamma=.1,theta=.1,psi=-.2))
b_va <- cluster_bootstrap(nonenergy, "id", \(x) fit_ne(x,"ln_vaxiie",FALSE,coef(ne_va)), reject_fun=reject_psi)
b_go <- cluster_bootstrap(nonenergy, "id", \(x) fit_ne(x,"ln_go",TRUE,coef(ne_go)), reject_fun=reject_psi)
se_va <- sapply(b_va[c("d","alpha","gamma","psi")], sd, na.rm=TRUE)
se_go <- sapply(b_go[c("d","alpha","gamma","theta","psi")], sd, na.rm=TRUE)
# cnsreg's equal-coefficient constraint on ln_xdl/ln_xcl <=> summing the two regressors
nek <- nonenergy |> mutate(xc2 = ln_xdl + ln_xcl)
kmenta_ne <- function(f, comb, x2, theta_var=NULL) {
m <- lm(f, data=nek); V <- vcovCL(m, cluster=~id, type="HC1"); b <- coef(m)
bc <- b[comb]; b2 <- b[x2]; gamma <- 2*bc; psi <- b2/(0.25*gamma); sigma <- 1/(1-psi)
g <- grad(\(z) z[2]/(0.25*(2*z[1])), c(bc,b2)); Vs <- V[c(comb,x2),c(comb,x2)]
list(d=b["year"], se_d=sqrt(V["year","year"]),
alpha=b["ln_xkl"], se_alpha=sqrt(V["ln_xkl","ln_xkl"]),
gamma=gamma, se_gamma=2*sqrt(V[comb,comb]),
theta=if(!is.null(theta_var)) b[theta_var] else NA, se_theta=if(!is.null(theta_var)) sqrt(V[theta_var,theta_var]) else NA,
psi=psi, se_psi=sqrt(drop(g %*% Vs %*% g)), sigma=sigma, adjr2=summary(m)$adj.r.squared, N=nobs(m))
}
k_va <- kmenta_ne(ln_vaxiiel ~ factor(country)+factor(industry)+year+ln_xkl+xc2+ln_xdc_2, "xc2","ln_xdc_2")
k_go <- kmenta_ne(ln_gol ~ factor(country)+factor(industry)+year+ln_xkl+xc2+ln_xdc_2+ln_xiimsl, "xc2","ln_xdc_2","ln_xiimsl")
row6 <- \(est,se) c(paste0(fmt(est), stars(2*pnorm(-abs(est/se)))), sprintf("(%.2f)", est/se))
adjR2ne <- \(fit,k) 1 - (1-fit$r2)*(nrow(nonenergy)-1)/(nrow(nonenergy)-k-1)
table6 <- tibble(
term = c("d","","alpha","","gamma","","theta","","psi","","Country DV","Industry DV","Adjusted R2","psi = 0","sigma","N"),
`NLS VA+IIE` = c(row6(coef(ne_va)["d"],se_va["d"]), row6(coef(ne_va)["alpha"],se_va["alpha"]),
row6(coef(ne_va)["gamma"],se_va["gamma"]), "","",
row6(coef(ne_va)["psi"],se_va["psi"]), "Yes","Yes",
fmt(adjR2ne(ne_va,18+27+4)), fmt(2*pnorm(-abs(coef(ne_va)["psi"]/se_va["psi"]))),
fmt(1/(1-coef(ne_va)["psi"])), nrow(nonenergy)),
`NLS GO` = c(row6(coef(ne_go)["d"],se_go["d"]), row6(coef(ne_go)["alpha"],se_go["alpha"]),
row6(coef(ne_go)["gamma"],se_go["gamma"]), row6(coef(ne_go)["theta"],se_go["theta"]),
row6(coef(ne_go)["psi"],se_go["psi"]), "Yes","Yes",
fmt(adjR2ne(ne_go,18+27+5)), fmt(2*pnorm(-abs(coef(ne_go)["psi"]/se_go["psi"]))),
fmt(1/(1-coef(ne_go)["psi"])), nrow(nonenergy)),
`Kmenta VA+IIE` = c(row6(k_va$d,k_va$se_d), row6(k_va$alpha,k_va$se_alpha), row6(k_va$gamma,k_va$se_gamma), "","",
row6(k_va$psi,k_va$se_psi), "Yes","Yes",
fmt(k_va$adjr2), fmt(2*pnorm(-abs(k_va$psi/k_va$se_psi))), fmt(k_va$sigma), k_va$N),
`Kmenta GO` = c(row6(k_go$d,k_go$se_d), row6(k_go$alpha,k_go$se_alpha), row6(k_go$gamma,k_go$se_gamma),
row6(k_go$theta,k_go$se_theta), row6(k_go$psi,k_go$se_psi), "Yes","Yes",
fmt(k_go$adjr2), fmt(2*pnorm(-abs(k_go$psi/k_go$se_psi))), fmt(k_go$sigma), k_go$N)
)
table6 |> kable(format="html", escape=FALSE,
caption="Table 6.-Nonlinear Estimation and Kmenta Approximation of CES in Cobb-Douglas: Nonenergy Industries") |>
add_header_above(c(" "=1, "CES in Cobb-Douglas"=2, "Kmenta"=2)) |>
kable_styling(full_width=FALSE, bootstrap_options="condensed") |> row_spec(c(2,4,6,8,10), italic=TRUE)
Table 6.-Nonlinear Estimation and Kmenta Approximation of CES in
Cobb-Douglas: Nonenergy Industries
|
|
CES in Cobb-Douglas
|
Kmenta
|
|
term
|
NLS VA+IIE
|
NLS GO
|
Kmenta VA+IIE
|
Kmenta GO
|
|
d
|
0.010***
|
0.003*
|
0.010***
|
0.002*
|
|
|
(4.83)
|
(1.72)
|
(4.79)
|
(1.73)
|
|
alpha
|
0.359***
|
0.186***
|
0.360***
|
0.187***
|
|
|
(7.33)
|
(6.58)
|
(7.78)
|
(7.04)
|
|
gamma
|
0.260***
|
0.121***
|
0.258***
|
0.120***
|
|
|
(6.13)
|
(4.90)
|
(6.15)
|
(4.88)
|
|
theta
|
|
0.565***
|
|
0.566***
|
|
|
|
(15.07)
|
|
(16.54)
|
|
psi
|
0.651**
|
0.654
|
0.394***
|
0.276
|
|
|
(2.05)
|
(1.27)
|
(2.97)
|
(1.41)
|
|
Country DV
|
Yes
|
Yes
|
Yes
|
Yes
|
|
Industry DV
|
Yes
|
Yes
|
Yes
|
Yes
|
|
Adjusted R2
|
0.948
|
0.982
|
0.738
|
0.906
|
|
psi = 0
|
0.040
|
0.206
|
0.003
|
0.159
|
|
sigma
|
2.868
|
2.888
|
1.651
|
1.382
|
|
N
|
6914
|
6914
|
6914
|
6914
|
z-statistics in parentheses. Significance denoted
1%, 5%, 10%. Columns 1 and 2 report
bootstrapped standard errors from 400 replications (here 100, for speed)
clustered at the country-industry cell; unlike the electricity tables,
fixed effects are not relabeled after resampling.
Columns 3 and 4 apply OLS to the linear Kmenta (translog) approximation
with country and industry fixed effects and cluster-robust SEs; \(\gamma\), \(\psi\) and \(\sigma\) are recovered from the constrained
coefficients via the delta method. \(\psi=0\) reports the significance level of
a Wald test with \(H_0:\psi=0\).
Interpretation of Table 6
- The CES-in-Cobb-Douglas estimates give \(\psi \approx 0.65\) for both dependent
variables, implying an elasticity of substitution close to
2.9 between clean and dirty energy inputs in the
nonenergy sector — noticeably higher than the electricity sector’s \(\sigma\approx1.8\)–\(2.0\).
- The hypothesis \(\psi=0\) (unit
elasticity) is rejected in both NLS specifications.
- The Kmenta approximation again pulls \(\sigma\) toward 1 (to about 1.4–1.7),
consistent with the paper’s warning that the translog linearization is
biased toward unitary substitution for large input ratios — the NLS
estimates should be treated as the primary evidence.
- \(\alpha\) (capital share) and
\(\theta\) (materials/services share)
are economically sensible and stable across specifications, supporting
the overall CES-in-Cobb-Douglas functional form.
Overall interpretation: Combined with Tables 3–5,
the nonenergy results reinforce the paper’s central claim — clean and
dirty energy inputs are substitutes with an elasticity significantly
above 1 both at the electricity-generation level and at the level of the
broader nonenergy economy, a necessary condition (in the absence of
technical change) for long-run green growth in the AABH-style
framework.
Stata vs R validation: Tables 3–6
# ============================================================
# STATA vs R VALIDATION: POINT ESTIMATES
# Uses the exact objects already created above.
# ============================================================
validation <- bind_rows(
# ---- Table 3: Electricity sector ----
tibble(
table = "Table 3", specification = c("NLS", "FD NLS", "Kmenta", "FD Kmenta"),
parameter = "psi",
stata = c(0.457, 0.487, 0.446, 0.454),
r = c(elec_nls["psi"], fd_coef["psi"],
kmenta_levels$psi, kmenta_fd$psi)
),
# ---- Table 4: Alternative capital proxy ----
tibble(
table = "Table 4", specification = c("NLS", "FD NLS", "Kmenta", "FD Kmenta"),
parameter = "psi",
stata = c(0.4233, 0.4599, 0.5352, 0.4409),
r = c(alt_nls["psi"], alt_fd["psi"],
k_alt_levels$psi, k_alt_fd$psi)
),
# ---- Table 5: Cobb-Douglas nested in CES ----
tibble(
table = "Table 5",
specification = c("NLS", "FD NLS", "NLS Alt", "FD NLS Alt"),
parameter = "psi",
stata = c(0.5077, 0.6512, 0.5079, 0.6441),
r = c(coef(cd_main)["psi"], coef(cd_main_fd_fit)["psi"],
coef(cd_alt)["psi"], coef(cd_alt_fd_fit)["psi"])
),
# ---- Table 6: Nonenergy industries ----
tibble(
table = "Table 6",
specification = c("NLS VA+IIE", "NLS GO", "Kmenta VA+IIE", "Kmenta GO"),
parameter = "psi",
stata = c(0.651, 0.654, 0.394, 0.276),
r = c(coef(ne_va)["psi"], coef(ne_go)["psi"],
k_va$psi, k_go$psi)
)
) |>
mutate(
difference = r - stata,
abs_difference = abs(difference),
status = case_when(
abs_difference < .001 ~ "Exact",
abs_difference < .005 ~ "Very close",
abs_difference < .01 ~ "Close",
TRUE ~ "Investigate"
)
)
validation |>
mutate(
stata = round(stata, 4),
r = round(r, 4),
difference = round(difference, 5),
abs_difference = round(abs_difference, 5)
) |>
kable(format = "html",
caption = "Stata vs R validation of CES elasticity parameter psi") |>
kable_styling(full_width = FALSE, bootstrap_options = "condensed")
Stata vs R validation of CES elasticity parameter psi
|
table
|
specification
|
parameter
|
stata
|
r
|
difference
|
abs_difference
|
status
|
|
Table 3
|
NLS
|
psi
|
0.4570
|
0.4566
|
-0.00043
|
0.00043
|
Exact
|
|
Table 3
|
FD NLS
|
psi
|
0.4870
|
0.4866
|
-0.00044
|
0.00044
|
Exact
|
|
Table 3
|
Kmenta
|
psi
|
0.4460
|
0.4464
|
0.00035
|
0.00035
|
Exact
|
|
Table 3
|
FD Kmenta
|
psi
|
0.4540
|
0.4545
|
0.00046
|
0.00046
|
Exact
|
|
Table 4
|
NLS
|
psi
|
0.4233
|
0.4234
|
0.00009
|
0.00009
|
Exact
|
|
Table 4
|
FD NLS
|
psi
|
0.4599
|
0.4599
|
0.00002
|
0.00002
|
Exact
|
|
Table 4
|
Kmenta
|
psi
|
0.5352
|
0.5352
|
0.00002
|
0.00002
|
Exact
|
|
Table 4
|
FD Kmenta
|
psi
|
0.4409
|
0.4409
|
-0.00002
|
0.00002
|
Exact
|
|
Table 5
|
NLS
|
psi
|
0.5077
|
0.5077
|
-0.00005
|
0.00005
|
Exact
|
|
Table 5
|
FD NLS
|
psi
|
0.6512
|
0.6513
|
0.00006
|
0.00006
|
Exact
|
|
Table 5
|
NLS Alt
|
psi
|
0.5079
|
0.5079
|
0.00003
|
0.00003
|
Exact
|
|
Table 5
|
FD NLS Alt
|
psi
|
0.6441
|
0.6441
|
0.00004
|
0.00004
|
Exact
|
|
Table 6
|
NLS VA+IIE
|
psi
|
0.6510
|
0.6514
|
0.00037
|
0.00037
|
Exact
|
|
Table 6
|
NLS GO
|
psi
|
0.6540
|
0.6538
|
-0.00024
|
0.00024
|
Exact
|
|
Table 6
|
Kmenta VA+IIE
|
psi
|
0.3940
|
0.3943
|
0.00031
|
0.00031
|
Exact
|
|
Table 6
|
Kmenta GO
|
psi
|
0.2760
|
0.2764
|
0.00044
|
0.00044
|
Exact
|
FULL PARAMETER VALIDATION
validation_full <- bind_rows(
# Table 3
tibble(
table = "Table 3",
specification = rep(c("NLS", "FD NLS", "Kmenta", "FD Kmenta"),
each = 4),
parameter = rep(c("d", "omega", "psi", "sigma"), 4),
stata = c(
-0.001, 0.219, 0.457, 1.840,
-0.003, 0.442, 0.487, 1.948,
-0.001, 0.245, 0.446, 1.806,
-0.003, 0.451, 0.454, 1.833
),
r = c(
elec_nls["d"], elec_nls["omega"], elec_nls["psi"], sigma_elec_nls,
fd_coef["d"], fd_coef["omega"], fd_coef["psi"], sigma_elec_fd,
kmenta_levels$coefs["year"], kmenta_levels$omega,
kmenta_levels$psi, kmenta_levels$sigma,
kmenta_fd$coefs["(Intercept)"], kmenta_fd$omega,
kmenta_fd$psi, kmenta_fd$sigma
)
),
# Table 4
tibble(
table = "Table 4",
specification = rep(c("NLS", "FD NLS", "Kmenta", "FD Kmenta"),
each = 4),
parameter = rep(c("d", "omega", "psi", "sigma"), 4),
stata = c(
-0.009685, 0.1933, 0.4233, 1.7340,
-0.009135, 0.3881, 0.4599, 1.8516,
-0.009, 0.2025, 0.5352, 2.1516,
-0.009, 0.4012, 0.4409, 1.7885
),
r = c(
alt_nls["d"], alt_nls["omega"], alt_nls["psi"], sigma_alt_nls,
alt_fd["d"], alt_fd["omega"], alt_fd["psi"], sigma_alt_fd,
k_alt_levels$coefs["year"], k_alt_levels$omega,
k_alt_levels$psi, k_alt_levels$sigma,
k_alt_fd$coefs["(Intercept)"], k_alt_fd$omega,
k_alt_fd$psi, k_alt_fd$sigma
)
),
# Table 5
tibble(
table = "Table 5",
specification = rep(c("NLS", "FD NLS", "NLS Alt", "FD NLS Alt"),
each = 5),
parameter = rep(c("d", "alpha", "omega", "psi", "sigma"), 4),
stata = c(
0.003, 0.4374, 0.4875, 0.5077, 2.031,
0.003, 0.3791, 0.7071, 0.6512, 2.867,
0.000, 0.3467, 0.0101, 0.5079, 2.032,
0.000, 0.3107, 0.0048, 0.6441, 2.810
),
r = c(
coef(cd_main)["d"], coef(cd_main)["alpha"], coef(cd_main)["omega"],
coef(cd_main)["psi"], 1/(1-coef(cd_main)["psi"]),
coef(cd_main_fd_fit)["d"], coef(cd_main_fd_fit)["alpha"],
coef(cd_main_fd_fit)["omega"], coef(cd_main_fd_fit)["psi"],
1/(1-coef(cd_main_fd_fit)["psi"]),
coef(cd_alt)["d"], coef(cd_alt)["alpha"], coef(cd_alt)["omega"],
coef(cd_alt)["psi"], 1/(1-coef(cd_alt)["psi"]),
coef(cd_alt_fd_fit)["d"], coef(cd_alt_fd_fit)["alpha"],
coef(cd_alt_fd_fit)["omega"], coef(cd_alt_fd_fit)["psi"],
1/(1-coef(cd_alt_fd_fit)["psi"])
)
),
# Table 6
tibble(
table = "Table 6",
specification = rep(c("NLS VA+IIE", "NLS GO",
"Kmenta VA+IIE", "Kmenta GO"), each = 4),
parameter = rep(c("alpha", "gamma", "psi", "sigma"), 4),
stata = c(
0.359, 0.260, 0.651, 2.868,
0.186, 0.121, 0.654, 2.888,
0.360, 0.258, 0.394, 1.651,
0.187, 0.120, 0.276, 1.382
),
r = c(
coef(ne_va)["alpha"], coef(ne_va)["gamma"], coef(ne_va)["psi"],
1/(1-coef(ne_va)["psi"]),
coef(ne_go)["alpha"], coef(ne_go)["gamma"], coef(ne_go)["psi"],
1/(1-coef(ne_go)["psi"]),
k_va$alpha, k_va$gamma, k_va$psi, k_va$sigma,
k_go$alpha, k_go$gamma, k_go$psi, k_go$sigma
)
)
) |>
mutate(
difference = r - stata,
abs_difference = abs(difference),
status = case_when(
abs_difference < .001 ~ "Exact",
abs_difference < .005 ~ "Very close",
abs_difference < .01 ~ "Close",
TRUE ~ "Investigate"
)
)
validation_full |>
mutate(
stata = round(stata, 4),
r = round(r, 4),
difference = round(difference, 5)
) |>
kable(format = "html",
caption = "Stata vs R validation of main parameter estimates") |>
kable_styling(full_width = FALSE, bootstrap_options = "condensed")
Stata vs R validation of main parameter estimates
|
table
|
specification
|
parameter
|
stata
|
r
|
difference
|
abs_difference
|
status
|
|
Table 3
|
NLS
|
d
|
-0.0010
|
-0.0013
|
-0.00026
|
0.0002632
|
Exact
|
|
Table 3
|
NLS
|
omega
|
0.2190
|
0.2194
|
0.00044
|
0.0004382
|
Exact
|
|
Table 3
|
NLS
|
psi
|
0.4570
|
0.4566
|
-0.00043
|
0.0004297
|
Exact
|
|
Table 3
|
NLS
|
sigma
|
1.8400
|
1.8402
|
0.00016
|
0.0001644
|
Exact
|
|
Table 3
|
FD NLS
|
d
|
-0.0030
|
-0.0032
|
-0.00019
|
0.0001852
|
Exact
|
|
Table 3
|
FD NLS
|
omega
|
0.4420
|
0.4418
|
-0.00023
|
0.0002285
|
Exact
|
|
Table 3
|
FD NLS
|
psi
|
0.4870
|
0.4866
|
-0.00044
|
0.0004416
|
Exact
|
|
Table 3
|
FD NLS
|
sigma
|
1.9480
|
1.9476
|
-0.00036
|
0.0003589
|
Exact
|
|
Table 3
|
Kmenta
|
d
|
-0.0010
|
-0.0010
|
0.00002
|
0.0000217
|
Exact
|
|
Table 3
|
Kmenta
|
omega
|
0.2450
|
0.2453
|
0.00033
|
0.0003325
|
Exact
|
|
Table 3
|
Kmenta
|
psi
|
0.4460
|
0.4464
|
0.00035
|
0.0003543
|
Exact
|
|
Table 3
|
Kmenta
|
sigma
|
1.8060
|
1.8062
|
0.00021
|
0.0002094
|
Exact
|
|
Table 3
|
FD Kmenta
|
d
|
-0.0030
|
-0.0031
|
-0.00010
|
0.0000952
|
Exact
|
|
Table 3
|
FD Kmenta
|
omega
|
0.4510
|
0.4511
|
0.00005
|
0.0000513
|
Exact
|
|
Table 3
|
FD Kmenta
|
psi
|
0.4540
|
0.4545
|
0.00046
|
0.0004551
|
Exact
|
|
Table 3
|
FD Kmenta
|
sigma
|
1.8330
|
1.8330
|
0.00003
|
0.0000298
|
Exact
|
|
Table 4
|
NLS
|
d
|
-0.0097
|
-0.0097
|
0.00000
|
0.0000001
|
Exact
|
|
Table 4
|
NLS
|
omega
|
0.1933
|
0.1933
|
0.00001
|
0.0000093
|
Exact
|
|
Table 4
|
NLS
|
psi
|
0.4233
|
0.4234
|
0.00009
|
0.0000944
|
Exact
|
|
Table 4
|
NLS
|
sigma
|
1.7340
|
1.7343
|
0.00029
|
0.0002876
|
Exact
|
|
Table 4
|
FD NLS
|
d
|
-0.0091
|
-0.0091
|
0.00000
|
0.0000004
|
Exact
|
|
Table 4
|
FD NLS
|
omega
|
0.3881
|
0.3881
|
0.00002
|
0.0000192
|
Exact
|
|
Table 4
|
FD NLS
|
psi
|
0.4599
|
0.4599
|
0.00002
|
0.0000192
|
Exact
|
|
Table 4
|
FD NLS
|
sigma
|
1.8516
|
1.8516
|
-0.00003
|
0.0000250
|
Exact
|
|
Table 4
|
Kmenta
|
d
|
-0.0090
|
-0.0091
|
-0.00010
|
0.0000972
|
Exact
|
|
Table 4
|
Kmenta
|
omega
|
0.2025
|
0.2025
|
0.00004
|
0.0000437
|
Exact
|
|
Table 4
|
Kmenta
|
psi
|
0.5352
|
0.5352
|
0.00002
|
0.0000240
|
Exact
|
|
Table 4
|
Kmenta
|
sigma
|
2.1516
|
2.1516
|
-0.00003
|
0.0000258
|
Exact
|
|
Table 4
|
FD Kmenta
|
d
|
-0.0090
|
-0.0090
|
0.00001
|
0.0000091
|
Exact
|
|
Table 4
|
FD Kmenta
|
omega
|
0.4012
|
0.4012
|
0.00003
|
0.0000334
|
Exact
|
|
Table 4
|
FD Kmenta
|
psi
|
0.4409
|
0.4409
|
-0.00002
|
0.0000242
|
Exact
|
|
Table 4
|
FD Kmenta
|
sigma
|
1.7885
|
1.7885
|
0.00001
|
0.0000114
|
Exact
|
|
Table 5
|
NLS
|
d
|
0.0030
|
0.0025
|
-0.00049
|
0.0004899
|
Exact
|
|
Table 5
|
NLS
|
alpha
|
0.4374
|
0.4374
|
0.00003
|
0.0000340
|
Exact
|
|
Table 5
|
NLS
|
omega
|
0.4875
|
0.4875
|
-0.00003
|
0.0000337
|
Exact
|
|
Table 5
|
NLS
|
psi
|
0.5077
|
0.5077
|
-0.00005
|
0.0000469
|
Exact
|
|
Table 5
|
NLS
|
sigma
|
2.0310
|
2.0311
|
0.00009
|
0.0000884
|
Exact
|
|
Table 5
|
FD NLS
|
d
|
0.0030
|
0.0025
|
-0.00051
|
0.0005112
|
Exact
|
|
Table 5
|
FD NLS
|
alpha
|
0.3791
|
0.3791
|
-0.00002
|
0.0000206
|
Exact
|
|
Table 5
|
FD NLS
|
omega
|
0.7071
|
0.7071
|
-0.00004
|
0.0000411
|
Exact
|
|
Table 5
|
FD NLS
|
psi
|
0.6512
|
0.6513
|
0.00006
|
0.0000557
|
Exact
|
|
Table 5
|
FD NLS
|
sigma
|
2.8670
|
2.8674
|
0.00043
|
0.0004308
|
Exact
|
|
Table 5
|
NLS Alt
|
d
|
0.0000
|
-0.0004
|
-0.00040
|
0.0004016
|
Exact
|
|
Table 5
|
NLS Alt
|
alpha
|
0.3467
|
0.3467
|
0.00002
|
0.0000188
|
Exact
|
|
Table 5
|
NLS Alt
|
omega
|
0.0101
|
0.0101
|
0.00005
|
0.0000469
|
Exact
|
|
Table 5
|
NLS Alt
|
psi
|
0.5079
|
0.5079
|
0.00003
|
0.0000322
|
Exact
|
|
Table 5
|
NLS Alt
|
sigma
|
2.0320
|
2.0322
|
0.00024
|
0.0002402
|
Exact
|
|
Table 5
|
FD NLS Alt
|
d
|
0.0000
|
-0.0002
|
-0.00020
|
0.0001987
|
Exact
|
|
Table 5
|
FD NLS Alt
|
alpha
|
0.3107
|
0.3108
|
0.00006
|
0.0000557
|
Exact
|
|
Table 5
|
FD NLS Alt
|
omega
|
0.0048
|
0.0048
|
0.00000
|
0.0000018
|
Exact
|
|
Table 5
|
FD NLS Alt
|
psi
|
0.6441
|
0.6441
|
0.00004
|
0.0000357
|
Exact
|
|
Table 5
|
FD NLS Alt
|
sigma
|
2.8100
|
2.8101
|
0.00006
|
0.0000599
|
Exact
|
|
Table 6
|
NLS VA+IIE
|
alpha
|
0.3590
|
0.3589
|
-0.00005
|
0.0000523
|
Exact
|
|
Table 6
|
NLS VA+IIE
|
gamma
|
0.2600
|
0.2597
|
-0.00026
|
0.0002604
|
Exact
|
|
Table 6
|
NLS VA+IIE
|
psi
|
0.6510
|
0.6514
|
0.00037
|
0.0003725
|
Exact
|
|
Table 6
|
NLS VA+IIE
|
sigma
|
2.8680
|
2.8684
|
0.00039
|
0.0003907
|
Exact
|
|
Table 6
|
NLS GO
|
alpha
|
0.1860
|
0.1861
|
0.00006
|
0.0000631
|
Exact
|
|
Table 6
|
NLS GO
|
gamma
|
0.1210
|
0.1208
|
-0.00021
|
0.0002116
|
Exact
|
|
Table 6
|
NLS GO
|
psi
|
0.6540
|
0.6538
|
-0.00024
|
0.0002402
|
Exact
|
|
Table 6
|
NLS GO
|
sigma
|
2.8880
|
2.8882
|
0.00017
|
0.0001683
|
Exact
|
|
Table 6
|
Kmenta VA+IIE
|
alpha
|
0.3600
|
0.3604
|
0.00038
|
0.0003767
|
Exact
|
|
Table 6
|
Kmenta VA+IIE
|
gamma
|
0.2580
|
0.2581
|
0.00015
|
0.0001455
|
Exact
|
|
Table 6
|
Kmenta VA+IIE
|
psi
|
0.3940
|
0.3943
|
0.00031
|
0.0003090
|
Exact
|
|
Table 6
|
Kmenta VA+IIE
|
sigma
|
1.6510
|
1.6510
|
0.00001
|
0.0000069
|
Exact
|
|
Table 6
|
Kmenta GO
|
alpha
|
0.1870
|
0.1868
|
-0.00021
|
0.0002050
|
Exact
|
|
Table 6
|
Kmenta GO
|
gamma
|
0.1200
|
0.1205
|
0.00047
|
0.0004651
|
Exact
|
|
Table 6
|
Kmenta GO
|
psi
|
0.2760
|
0.2764
|
0.00044
|
0.0004394
|
Exact
|
|
Table 6
|
Kmenta GO
|
sigma
|
1.3820
|
1.3821
|
0.00005
|
0.0000542
|
Exact
|