Skip to contents

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 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 an index argument 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 no cdf, ties, or nboots argument 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.