Moore-Penrose Pseudoinverse in R: MASS::ginv() for Rank-Deficient Systems
The Moore-Penrose pseudoinverse, written A+, is a generalized inverse that exists for every matrix, including non-square and rank-deficient ones. In R, MASS::ginv(A) computes it via singular value decomposition and returns the unique matrix that gives the minimum-norm least-squares solution to A x = b, the case where solve() throws a singular-matrix error.
How does ginv() solve a rank-deficient system?
The fastest way to feel what ginv() does is to watch solve() fail first. Build a 4x3 design matrix A whose third column is exactly the sum of the first two. The normal equations A^T A x = A^T b are now singular, so solve() refuses. MASS::ginv() does not. It uses singular value decomposition under the hood, ignores the zero singular value, and returns a clean coefficient vector. Run the block and read off x_hat.
Three things to read off this output. First, solve() gave up because column 3 carries no information beyond columns 1 and 2, the matrix has rank 2 not 3. Second, ginv() still returned a 3-vector. Third, that vector is not arbitrary, it is the unique solution among the infinitely many least-squares solutions that has the smallest Euclidean norm. We will verify this property in the next section.
A is rank deficient, A x = b has either no exact solution or infinitely many least-squares solutions. ginv(A) %*% b selects the unique solution from that infinite set with the smallest ||x||, which is the most stable choice when columns of A are collinear.Try it: Build the rank-deficient 3x3 system below where row 3 equals row 1 plus row 2, then compute ex_x with ginv().
Click to reveal solution
Explanation: ex_A has rank 2 (row 3 equals row 1 plus row 2), so solve(ex_A, ex_b) errors. ginv() walks around the rank deficiency and returns the minimum-norm solution that still satisfies ex_A x = ex_b exactly.
What is the Moore-Penrose pseudoinverse, mathematically?
The pseudoinverse is defined by four conditions that pin it down uniquely. Given any matrix A, there exists exactly one matrix A^+ satisfying:
$$A A^+ A = A, \quad A^+ A A^+ = A^+, \quad (A A^+)^T = A A^+, \quad (A^+ A)^T = A^+ A.$$
The first condition says that sandwiching A around A^+ recovers A. The second says the same about A^+. The third and fourth force the products A A^+ and A^+ A to be symmetric, which is what makes them orthogonal projectors. Penrose proved in 1955 that exactly one matrix satisfies all four for any A, including rectangular and singular A.
The cleanest construction uses singular value decomposition. If A = U Σ V^T, then
$$A^+ = V \Sigma^+ U^T,$$
where Σ^+ is Σ^T with each non-zero singular value replaced by its reciprocal and zeros left as zeros. That is exactly what MASS::ginv() does internally. Let's verify the four conditions hold for the rank-deficient A from Section 1.
All four conditions return TRUE to numeric tolerance, even though A is rank deficient. This is the property no other generalized inverse has: uniqueness across all matrices. We can also build the pseudoinverse by hand from svd() and confirm it matches ginv().
The two pseudoinverses agree exactly, confirming that ginv() is doing nothing more exotic than SVD with a reciprocal-and-threshold step.
ginv() give a deterministic answer to a problem with infinitely many candidate solutions.Try it: For a fresh random 4x3 matrix ex_M, verify Penrose condition 3, that ex_M %*% ginv(ex_M) is symmetric.
Click to reveal solution
Explanation: The product ex_M %*% ginv(ex_M) is the orthogonal projector onto the column space of ex_M. Orthogonal projectors are symmetric, so the transpose check returns TRUE regardless of whether ex_M is full-rank or rank deficient.
How does ginv() find the minimum-norm least-squares solution?
For a rank-deficient or underdetermined system, infinitely many vectors x minimise ||A x - b||^2. The Moore-Penrose solution x_pinv = ginv(A) %*% b is the unique one with the smallest ||x||. Among all least-squares minimisers, it sits closest to the origin. That property is what makes it a sensible default for collinear design matrices: the coefficients do not blow up.
To see this, take the system from Section 1 and construct two alternative least-squares solutions by adding any vector from the null space of A to x_hat. Their residuals will be identical, but their norms will be larger.
All three vectors are exact solutions in the least-squares sense, the residual is zero. The ginv() solution wins on norm. This is the formal statement of the minimum-norm property: among the affine subspace of valid solutions, ginv() lands on the point closest to the origin.
The function exposes one tunable parameter, tol, that controls when a singular value is treated as zero. The default is sqrt(.Machine$double.eps) * max(dim(A)), roughly 1e-7 for typical sizes. If you tighten tol further, near-zero singular values are kept rather than discarded, which can produce wildly inflated coefficients on ill-conditioned matrices.
For an exactly rank-deficient matrix the smallest singular value is below machine epsilon, so changing tol has no effect. On near-singular matrices, however, a too-aggressive tol can change the answer dramatically, the inflated reciprocal of a tiny singular value blows up the corresponding row of Σ^+. The default is calibrated to drop only those singular values that are numerically indistinguishable from zero.
Try it: Use ginv(ex_A, tol = 0.5) on the rank-deficient ex_A from the first exercise and see how an aggressive cutoff changes ex_x.
Click to reveal solution
Explanation: With tol = 0.5 even the second-largest singular value is suppressed, so the pseudoinverse keeps only the leading singular direction. The solution still has small norm but no longer reproduces ex_b exactly.
How do overdetermined and underdetermined systems differ?
Two extremes bracket what ginv() is doing. An overdetermined system has more rows than columns, more equations than unknowns. There is usually no exact solution. ginv() returns the ordinary least-squares fit and agrees with lm.fit() to machine precision.
The two coefficient vectors agree because, for full column-rank A, the pseudoinverse formula A^+ = (A^T A)^{-1} A^T is exactly the closed-form OLS estimator. There is nothing exotic happening, ginv() is just a more general path to the same answer.
An underdetermined system has fewer rows than columns, more unknowns than equations. Now infinitely many vectors satisfy A x = b exactly. ginv() picks the one with smallest norm.
The residual is zero, so x_under is an exact solution. Any other exact solution differs from x_under by a vector in the null space of A_under (which is two-dimensional here), and adding any non-zero null-space vector strictly increases the L2 norm. That is the geometric content of the minimum-norm property.
ginv() to estimate regression coefficients on a deliberately rank-deficient design without thinking. When two predictors are perfectly collinear, the minimum-norm split is mathematically unique but statistically arbitrary, the load is divided across collinear columns purely by the norm criterion, not by any meaningful contrast. Drop a column or use a regularised method (ridge, lasso) instead.Try it: Build a 3x5 underdetermined system ex_A_und and ex_b_und of your choice, compute ex_x_und with ginv(), and verify the residual is essentially zero.
Click to reveal solution
Explanation: A 3x5 matrix has at most rank 3, so the equations are consistent and ginv() returns an exact solution. The residual is at machine epsilon, not exactly zero only because of floating-point arithmetic.
When should you use ginv() vs pracma::pinv() or qr.solve()?
Three R functions compute or use a generalized inverse. They are not interchangeable. MASS::ginv() uses SVD and handles every case, including rank-deficient A. pracma::pinv() is a near-clone, also SVD-based, with the same numerical behaviour. qr.solve(A, b) uses QR decomposition, which is faster for full-rank tall systems but errors out the moment A is singular.
For a full column-rank matrix, all three agree to machine precision. The difference shows up at the boundary: drop the rank by one and qr.solve() quits.
| Situation | ginv() |
pracma::pinv() |
qr.solve() |
solve() |
|---|---|---|---|---|
| Square, full rank | works | works | works | works (fastest) |
| Tall, full column rank | works | works | works (fast) | errors |
| Rank deficient | works | works | errors | errors |
| Square, singular | works | works | errors | errors |
| Wide (underdetermined) | works (min-norm) | works (min-norm) | errors | errors |
The rule of thumb: reach for ginv() whenever you cannot guarantee A is full rank. If you need raw speed and you know the matrix is well-conditioned, qr.solve() or even solve(crossprod(A), crossprod(A, b)) is faster, but neither saves you when the matrix is singular.
Try it: Confirm ginv() and pracma::pinv() agree on the rank-deficient A from Section 1.
Click to reveal solution
Explanation: Both functions use SVD with the same default tolerance, so they return identical pseudoinverses. The choice between them is mostly a matter of which package you already have loaded.
Practice Exercises
Exercise 1: Rank-deficient regression normal equations
You have a design matrix X and response y where two predictors are perfectly collinear. lm() returns NA for one coefficient. Use ginv() on the normal equations to get a minimum-norm coefficient vector that splits the load. Compare the fitted values from ginv() to those from lm().
Click to reveal solution
Explanation: lm() drops the redundant column and returns NA for x3. ginv() distributes the coefficient mass across x1, x2, and x3 to satisfy the minimum-norm criterion. Both produce identical fitted values, which is what matters for prediction; the individual coefficients are not separately interpretable when columns are collinear.
Exercise 2: Build a pseudoinverse from svd()
Write ex_pinv_via_svd(M) that takes a matrix and returns its Moore-Penrose pseudoinverse using only svd() and basic matrix operations. Threshold singular values below 1e-10 to zero. Verify against ginv() on three test matrices: a square invertible one, a rank-deficient one, and a wide underdetermined one.
Click to reveal solution
Explanation: The function reproduces ginv() because ginv() is itself thin-SVD plus reciprocation. The diag(d_plus, nrow = length(d_plus)) form handles the edge case where there is only one singular value, in which diag() would otherwise build an identity matrix.
Complete Example
A practical end-to-end use of ginv() is fitting a regression with multicollinear predictors. We simulate a design where x3 = 2 * x1 - x2 exactly, fit with lm() (which drops the redundant column), and fit again with ginv() on the normal equations.
lm() and ginv() give different coefficient vectors but identical predictions. The lm() vector has a hard NA on x3. The ginv() vector spreads the same total fit across all four coefficients in the minimum-norm direction. For prediction either is fine, for inference on individual coefficients neither is well-defined when columns are exactly collinear, the underlying problem is unidentified.
Summary
| Function | Decomposition | Handles rank-deficient? | Returns |
|---|---|---|---|
MASS::ginv() |
SVD | Yes | Pseudoinverse A^+ |
pracma::pinv() |
SVD | Yes | Pseudoinverse (same as ginv()) |
qr.solve() |
QR | No | Errors on singular A |
solve() |
LU | No | Errors on singular A |
Key takeaways:
ginv(A) %*% breturns the unique minimum-norm least-squares solution toA x = b.- The Moore-Penrose pseudoinverse is the only matrix satisfying all four Penrose conditions, regardless of whether
Ais square, tall, wide, full rank, or singular. - For full column-rank
A,ginv()agrees withlm.fit(),qr.solve(), and the closed-form OLS estimator. - For underdetermined or rank-deficient
A, onlyginv()andpracma::pinv()succeed. - Trust the default
tol. Lower it only when small singular values carry real signal; raise it when you want to suppress noisy directions. - Use
ginv()for prediction with collinear designs, but treat individual coefficients as not separately interpretable.
References
- Penrose, R. (1955). A generalized inverse for matrices. Proceedings of the Cambridge Philosophical Society, 51(3), 406-413.
- Wikipedia. Moore-Penrose inverse. Link
- Venables, W. N., & Ripley, B. D. (2002). Modern Applied Statistics with S, 4th ed. Springer. (MASS package author reference.)
- MASS package documentation,
ginv()reference. Link - pracma package documentation,
pinv()reference. Link - Golub, G. H., & Van Loan, C. F. (2013). Matrix Computations, 4th ed. Johns Hopkins University Press, Chapter 5.
- R Core Team.
svd()reference. Link - Strang, G. (2016). Introduction to Linear Algebra, 5th ed. Wellesley-Cambridge Press, Chapter 7.
Continue Learning
- Solving Linear Systems in R, the parent tutorial covers
solve(),qr.solve(), and least-squares fits with full-rank designs. - Singular Value Decomposition in R, the SVD that powers
ginv(), with deeper treatment of singular values and the four fundamental subspaces. - QR Decomposition in R, the alternative factorisation
qr.solve()uses, faster than SVD when the matrix is full rank.