Seemingly Unrelated Regression (SUR) in R: systemfit Package
Seemingly unrelated regression (SUR) fits a system of linear equations jointly when their error terms are correlated within the same observation. In R, the systemfit package estimates SUR with feasible generalised least squares in one line. SUR gives you smaller standard errors than running each equation through separate lm() calls, and it lets you test hypotheses that span both equations.
When should you use seemingly unrelated regression?
You reach for SUR when you have two or more regression equations whose predictors may differ, but whose errors share a common shock per observation. Households, firms, and plants react to the same unobserved jolt in the same year, row, or region, so the residuals end up correlated even though the equations look independent. systemfit() turns that correlation into an efficiency gain. Here is the whole workflow on iris, with two flower-width equations sharing petal predictors.
The off-diagonal entry, 0.32, is the cross-equation residual correlation. That number is the licence to use SUR. When two observations land above their predicted Sepal.Length, they also tend to land above their predicted Sepal.Width. Separate lm() calls would ignore that pattern; SUR uses it to sharpen both equations at once. If the off-diagonal had been near zero, SUR and OLS would return essentially the same numbers and you could skip the extra machinery.
systemfit() fits each equation by OLS, estimates the residual covariance matrix from those fits, then re-fits the whole system with generalised least squares using that covariance. You see a single systemfit object, but two rounds of estimation happened.Try it: Fit a SUR system on mtcars with two equations, mpg ~ wt + hp and qsec ~ wt + hp. Save the fit to ex_fit1 and print the coefficient of wt in the first equation.
Click to reveal solution
Explanation: systemfit() takes a named list of formulas; the name eq1 gets prefixed onto every coefficient from that equation, which is why we ask for eq1_wt.
How do you specify a system of equations for systemfit()?
Every SUR model is a list of standard R formulas. You write each one exactly as you would for lm(), then bundle them into a named list. systemfit() stitches them into a single block-diagonal design matrix behind the scenes, but the formula API stays familiar.
Every coefficient is prefixed by its equation label. That keeps eq_sepal_Petal.Length clearly distinct from eq_petal_Petal.Length even though they come from the same predictor column. You will need the exact prefixed names later when testing cross-equation restrictions, so name your equations something short and memorable.
eq1, eq2. Name them after the outcome (eq_wage, eq_hours) and every piece of downstream output reads like a sentence.Try it: Redefine the iris system so equation 2 uses Petal.Length + Species instead of the petal-width predictor. Fit and print the coefficient table for equation 2 only.
Click to reveal solution
Explanation: R automatically one-hot encodes Species into two dummy columns, and each appears in its own row of the coefficient table.
How much efficiency does SUR gain over separate OLS?
SUR is only worth the extra setup when it produces tighter standard errors than running each equation separately. Let us measure the gain directly by fitting the same iris system two ways, equation-by-equation OLS and joint SUR, then comparing their coefficient standard errors side by side.
The reduction is exactly zero on this particular iris system because both equations share an identical set of regressors. That is the first of the two cases where SUR mathematically collapses to OLS. The cross-equation correlation exists (0.32, from the earlier block) but there is nothing left to squeeze out of it once the regressors match. To see a non-zero gain, you would need equations with different predictors, as the earlier "Species in equation 2 only" example had.
If you are comfortable skipping the math, jump to the next section, the practical code above is all you need. Otherwise, the SUR estimator is:
$$\hat{\beta}_{\text{SUR}} = \left(X^\top (\hat{\Sigma}^{-1} \otimes I_n) X\right)^{-1} X^\top (\hat{\Sigma}^{-1} \otimes I_n) y$$
Where:
- $\hat{\beta}_{\text{SUR}}$ is the stacked coefficient vector across all equations
- $\hat{\Sigma}$ is the estimated cross-equation residual covariance matrix
- $\otimes$ is the Kronecker product, which expands $\hat{\Sigma}$ to act on every observation
- $n$ is the number of observations per equation
- $X$ is the block-diagonal matrix of regressors
When $\hat{\Sigma}$ is diagonal (no cross-equation correlation) or every block of $X$ is identical, the sandwich collapses and the formula reduces to the usual OLS normal equations.
Try it: Fit the iris SUR model with identical regressors in both equations (as in the payoff code) and confirm that the SUR coefficients equal the OLS coefficients to 10 decimal places.
Click to reveal solution
Explanation: The 3.55e-15 is floating-point noise; mathematically the two vectors are identical because the identical-regressors condition forces SUR to reduce to OLS.
How do you test hypotheses across SUR equations?
A large reason to fit the two equations jointly, rather than separately, is to test whether a predictor has the same effect in both. The car::linearHypothesis() function reads a systemfit object directly, so you write the restriction as a string and R does the rest.
The chi-square statistic is huge (121.17) and the p-value is effectively zero, so we reject the null that Petal.Length has the same slope in both equations. That makes biological sense: petal length positively predicts sepal length but negatively predicts sepal width. The cross-equation test gives you one clean p-value for that difference, which is exactly the thing two separate lm() fits cannot do.
names(coef(fit)) when you write restrictions. A typo in the restriction string does not throw an error, it silently tests a different hypothesis. linearHypothesis() treats any unrecognised string as a fresh coefficient name and may report a meaningless result.Try it: Test whether the two iris intercepts are equal. Save the test object to ex_test1 and print the chi-square statistic.
Click to reveal solution
Explanation: The backtick-free name inside the restriction string has to match names(coef(fit)) exactly, including the (Intercept) parentheses.
What assumptions and diagnostics should you check?
SUR inherits every OLS assumption and adds one more: errors may be correlated across equations within the same observation, but must be independent across observations. If you suspect endogenous regressors, SUR will not save you, the estimates are biased for the same reasons OLS is.
- Errors are independent across observations (no serial correlation, no clustering).
- Errors within an observation may be correlated across equations.
- Regressors are exogenous and not correlated with the error term.
- Each equation is correctly specified, with a linear functional form and no omitted confounders.
The first two assumptions you check with the residual correlation matrix; the other two you check with the same diagnostics you would run on any lm() fit (residual plots, VIF, reset tests).
An off-diagonal of 0.32 is a middling correlation, meaningful but not dominant. That is enough to make SUR worth running when your regressors differ across equations. If this value had come back near zero (say |r| < 0.05), you could safely fall back on separate lm() calls and lose nothing. If it came back near one, you should also check whether the two outcomes are really measuring different things or just renamed copies of each other.
Try it: Compute the residual correlation matrix for the iris SUR fit and extract only the off-diagonal value.
Click to reveal solution
Explanation: Subscripting [1, 2] pulls the row-1 column-2 entry, which on a symmetric 2x2 correlation matrix is the only unique off-diagonal cell.
Practice Exercises
Exercise 1: Fit a SUR system on mtcars with different regressors per equation
Build a SUR system on mtcars where equation 1 is mpg ~ wt + hp and equation 2 is qsec ~ wt + hp + cyl. Save the fit to my_fit and print the residual correlation between the two equations.
Click to reveal solution
Explanation: The residual correlation is -0.20, a moderate negative dependence. Cars whose fuel efficiency overshoots the model tend to undershoot on quarter-mile time, which the two equations capture jointly via SUR.
Exercise 2: Test whether wt has the same effect in both mtcars equations
Using the my_fit object from Exercise 1, test $H_0:\ \beta_{\text{wt, mpg\_eq}} = \beta_{\text{wt, qsec\_eq}}$. Save the test to my_test and print the chi-square statistic plus p-value.
Click to reveal solution
Explanation: The chi-square of 40.9 rejects the null at any reasonable significance level. Weight has substantially different effects on fuel efficiency and quarter-mile time, which is exactly the cross-equation story separate lm() fits cannot quantify.
Complete Example
Here is the full SUR workflow from start to finish on a fresh iris fit, so you can copy it as a template.
Residual correlation of 0.38 says there is real cross-equation dependence worth exploiting. The chi-square of 70.5 tells you the petal-length slopes differ strongly between the two equations. Together, those two numbers justify joint estimation and answer a scientific question that separate lm() calls could not reach in one step.
Summary
| Question | Answer |
|---|---|
| When does SUR help over OLS? | Errors correlated across equations and regressors differ across equations |
| When does SUR collapse to OLS? | Residual correlation near zero, or identical regressors in every equation |
| Main function | systemfit(formula = list(eq1, eq2), method = "SUR", data = df) |
| Cross-equation tests | car::linearHypothesis(fit, "eq1_x - eq2_x = 0", test = "Chisq") |
| Key diagnostic | cor(residuals(fit)), off-diagonal element is the signal |
| Extra assumption vs OLS | Errors independent across observations; correlated within observations |
References
- Zellner, A. (1962). An Efficient Method of Estimating Seemingly Unrelated Regressions and Tests for Aggregation Bias. Journal of the American Statistical Association, 57(298), 348-368. Link
- Henningsen, A., and Hamann, J. D. (2007). systemfit: A Package for Estimating Systems of Simultaneous Equations in R. Journal of Statistical Software, 23(4). Link
- systemfit CRAN vignette. Link
- UCLA OARC Statistical Consulting. How can I perform seemingly unrelated regression in R? Link
- Greene, W. H. (2018). Econometric Analysis (8th ed.), Chapter 10. Pearson.
- Wikipedia. Seemingly unrelated regressions. Link
Continue Learning
- Multiple Regression in R, the parent post covering single-equation multiple regression, the natural starting point before moving to systems.
- Linear Regression Assumptions in R, every assumption SUR inherits from OLS, plus how to diagnose violations.
- Regression Diagnostics in R, residual plots, leverage, influence measures that apply equation-by-equation inside a SUR fit.