endogCopula corrects an endogenous regressor in a linear model when you have no instrument for it. The correction models the dependence between the regressor and the structural error with a Gaussian copula, then adds the result to the regression as an extra term. It is for researchers and analysts who suspect a regressor is correlated with the error term and have no instrument, or no instrument they trust, to run two-stage least squares with instead.
Installation
Install the development version from GitHub:
# install.packages("remotes")
remotes::install_github("ashgreat/endogCopula")Quick example
The package ships a simulated dataset, sim_endog, built so that z_endog is correlated with the structural error and x_exog is not. The true coefficient on z_endog is 2.
library(endogCopula)
data(sim_endog)
ols <- lm(y ~ z_endog + x_exog, data = sim_endog)
coef(ols)["z_endog"] z_endog
2.070418
# set.seed only for reproducible standard errors below; CopRegPG() does not set one itself
set.seed(1)
fit <- CopRegPG(y ~ z_endog | x_exog + w_instr, data = sim_endog, nboots = 199)
summary(fit)Copula endogeneity correction: PG (Park & Gupta 2012)
Call:
CopRegPG(formula = y ~ z_endog | x_exog + w_instr, data = sim_endog,
nboots = 199)
Residuals of the augmented regression (u = xi - C gamma):
Min 1Q Median 3Q Max
-3.073357 -0.691804 0.003566 0.656036 3.612771
Residuals of the structural model (xi = y - mu - P alpha - W beta):
Min 1Q Median 3Q Max
-4.60754 -1.01166 0.01541 0.92245 4.71045
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) 1.00473 0.03396 29.588 <2e-16 ***
z_endog 1.14235 0.62562 1.826 0.0679 .
x_exog 1.51360 0.02223 68.085 <2e-16 ***
w_instr -0.05375 0.02735 -1.965 0.0494 *
z_endog_cop 1.04021 0.68265 1.524 0.1276
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Endogeneity: rho(P*, xi*) is the correlation between the normal score
of an endogenous regressor and that of the structural error, xi* = xi / sigma.
Estimate Std. Error z value Pr(>|z|)
rho(z_endog*, xi*) 0.7237 0.4063 1.781 0.0749 .
Fit, on 1995 residual degrees of freedom:
augmented structural
Residual standard error 0.9884 1.4322
R-squared 0.8822 0.7526
Adjusted R-squared 0.8819 0.7521
Standard errors from 199 bootstrap replicates; cdf = "kde.silverman", ties = "max".
validity(fit)Validity check for PG (Park & Gupta 2012)
n = 2000, intercept: yes, target power 80%
Sources: Becker, Proksch & Ringle (2022); Yang, Qian & Xie (2025);
Qian, Koschmann & Xie (2025)
[1] Nonnormality of the endogenous regressors
skewness ex.kurtosis AD CvM KS p Yang ok Becker ok
z_endog 0.033 -0.009 0.493 0.086 0.505 FALSE FALSE
Becker et al. at n = 2000: |skewness| >= 0.774, or AD > 18.964, or CvM > 3.488
Yang et al.: KS p < .05
[2] Assumption: correlation of the copula transformation term
with the exogenous regressors
corr(W, CTT) p (Holm)
x_exog -0.0030 0.894
w_instr 0.5364 <1e-16
Joint test: R2 = 0.2877, F = 403.3, p = < 2.2e-16
=> violated. Park & Gupta is inconsistent here (Haschka, 2025).
[3] Exogenous regressors as identifying variation
(continuous, KS p < .001, first-stage F > 10)
Not available: this estimator has no first stage.
[4] Error term: structural residuals xi
skewness = 0.03907, excess kurtosis = -0.07838, AD = 0.3954 (p = 0.371)
Becker et al. (2022) find that with a nonnormal error the approach
is no longer consistent in models with an intercept. Yang et al.
(2025) and Qian et al. (2025) do permit a nonnormal error, but only
under the decomposition xi = U + V into a normally distributed
endogenous part U, which carries the entire dependence with the
regressors, and an independent nonnormal V. The residuals show xi,
not U, so their skewness neither establishes nor rules out a
violation; whether that decomposition holds has to be argued from
the suspected sources of endogeneity.
[5] ICON: standard error inflation relative to uncorrected OLS
SE (corrected) SE (uncorrected) ICON
(Intercept) 0.03396 0.01949 1.7419
z_endog 0.62562 0.02471 25.3137
x_exog 0.02223 0.02224 0.9997
w_instr 0.02735 0.02745 0.9964
=> largest ICON = 25.31, above the threshold of 6: weak
identification or a misspecified dependence model.
The corrected estimate for z_endog is 1.14, against 2.07 for OLS and a true value of 2, and its standard error is 25 times larger than the uncorrected one. validity() explains why. The copula term correlates with w_instr at 0.54, and Park and Gupta’s estimator needs that correlation to be zero for the correction to stay consistent. CopReg2sCOPE() and the later estimators in this package were built to relax exactly that requirement, which is why you check validity() before trusting a corrected estimate.
Estimators
Six of the eight estimators fit by augmented least squares. The copula term for each endogenous regressor enters the regression as an extra column, and the standard errors come from a bootstrap. CopRegPANEL() fits by maximum likelihood after a panel transformation, and CopRegBAYES() fits by MCMC.
| Function | Estimator | Reference | Package |
|---|---|---|---|
CopRegPG() |
PG | Park and Gupta (2012), Marketing Science 31(4), 567 to 586 | endogCopula |
CopReg2sCOPE() |
2sCOPE | Yang, Qian, and Xie (2025), Journal of Marketing Research 62(4), 601 to 623 | endogCopula |
CopReg2sCOPEnp() |
2sCOPE-np | Hu, Qian, and Xie (2025), NBER Working Paper 33607 | endogCopula |
CopRegIMA() |
IMA | Haschka (2025a), IMA Journal of Management Mathematics 36(1), 161 to 180 | endogCopula |
CopRegBMW() |
BMW | Breitung, Mayer, and Wied (2024), The Econometrics Journal 27(3), 362 to 383 | endogCopula |
CopRegJAMS() |
JAMS | Liengaard et al. (2025), Journal of the Academy of Marketing Science 53, 279 to 299 | endogCopula |
CopRegPANEL() |
PANEL | Haschka (2022), Journal of Marketing Research 59(4), 861 to 880 | endogCopulaPanel |
CopRegBAYES() |
BAYES | Haschka (2025b), Oxford Bulletin of Economics and Statistics | endogCopulaBayes |
The six cross-sectional estimators are also reachable through one generic entry point, copreg(), which rewrites the call and hands it to the estimator’s own function:
copreg(y ~ z_endog | x_exog + w_instr, data = sim_endog, method = "2scope")method is one of "pg", "2scope", "ima", "bmw", "jams", or "np". CopRegPANEL() and CopRegBAYES() are not reachable through copreg(), because the first needs a panel index and the second replaces bootstrap inference with a posterior.
The formula interface
Every cross-sectional estimator takes a two-part formula: y ~ endogenous | exogenous. Position around the bar decides what happens to a term, not its name. A term written before the bar is treated as endogenous and gets a copula correction added to the regression as an extra column. The same term written after the bar is treated as exogenous and enters as an ordinary control with no correction. Everything lm() accepts works on either side: transformations such as log(price), interactions, polynomials written with I(x^2), and factors. Use - 1 to drop the intercept.
Choosing the cdf argument
Every cross-sectional estimator needs an estimate of the marginal cumulative distribution function of each endogenous regressor before it can turn that regressor into a normal score. The cdf argument picks which estimator does that job. The literature disagrees on the right choice, so all seven are implemented, and each estimator already defaults to the one its own paper used.
| Value | What it estimates | Default for |
|---|---|---|
"kde.silverman" |
Kernel density integral, Silverman bandwidth | CopRegPG() |
"kde.cv" |
Kernel density integral, cross-validated bandwidth | |
"kde.plugin" |
Kernel density integral, plug-in bandwidth | |
"ecdf.fixed" |
Empirical CDF with the boundary values replaced | |
"ecdf.adj" |
Adjusted empirical CDF | CopRegJAMS() |
"rank.n" |
Rescaled empirical CDF, rank over n |
CopReg2sCOPE(), CopRegIMA()
|
"rank.n1" |
Rank over n plus 1 | CopRegBMW() |
CopReg2sCOPEnp() has no cdf argument. It estimates the conditional distribution of the endogenous regressor directly, and passing cdf to it is an error rather than something it silently ignores.
Checking validity
validity() takes a fitted model and walks the identifying assumptions its estimator depends on. It reports what the data show for each one, instead of asking you to take the correction on faith:
- whether the endogenous regressor is nonnormal enough for the copula transformation to carry information
- whether the copula term correlates with the exogenous regressors in a way the estimator does not allow for
- how far the standard errors have inflated relative to plain OLS
What gets checked depends on the estimator. BMW’s nonnormality requirement falls on the first-stage residuals rather than on the endogenous regressor itself, and 2sCOPE-np is checked against its own identifying assumption instead.
validity(fit, level = 0.05, power = 0.8)Read its output before you report a corrected estimate. The quick example above shows why. validity() flags a violated assumption for the PG estimator on that data, which is exactly the situation it exists to catch.
Companion packages
Two more estimators live in their own packages because they need a different model class or a different inference engine, and neither is reachable through copreg().
-
endogCopulaPanel provides
CopRegPANEL(), a linear panel model with fixed effects, fitted by maximum likelihood. It takes the same two-part formula plus anindexargument naming the panel identifier. -
endogCopulaBayes provides
CopRegBAYES(), a Bayesian version of the same idea. It samples the copula parameters together with the regression coefficients instead of bootstrapping them, so it has nocdf,ties, ornbootsargument to set.
The estimators in this package are ported from the R code that Rouven E. Haschka published alongside the papers above, at https://github.com/HashtagHaschka/Copula-based-endogeneity-corrections. Haschka is credited as a contributor and copyright holder in the package DESCRIPTION.
What changed in 0.2.0
Version 0.2.0 is a breaking release. CopRegML_par() and CopRegBayes() are gone from this package. The panel estimator is now CopRegPANEL() in endogCopulaPanel, and the Bayesian estimator is now CopRegBAYES() in endogCopulaBayes. The cdf argument now takes seven values instead of the old four ("kde", "ecdf", "resc.ecdf", "adj.ecdf"), and each estimator defaults to a different one of them than before. Code written against 0.1.0 will not run unchanged. See NEWS.md for the full list of changes.
Citation and license
If you use endogCopula in published work, cite it as:
Malshe, A. (2026). endogCopula: Endogeneity Corrections via Gaussian Copulas.
R package version 0.2.0. https://github.com/ashgreat/endogCopula
endogCopula is released under the MIT license. See the DESCRIPTION file for the full list of copyright holders, which includes Rouven E. Haschka for the reference implementation the estimators are ported from.
