import numpy as np
from numpy.linalg import pinv
X = np.array([
[1, 2104, 5, 2104, 1, 45],
[1, 1416, 3, 2104, 2, 40],
[1, 1534, 3, 2104, 2, 30],
[1, 852, 2, 2104, 1, 36],
])
y = np.array([
[460],
[232],
[315],
[178],
])
XT = np.transpose(X)
pinv(XT @ X) @ XT @ y
array([[ 5.09353825e-05], [ 2.12425850e-01], [ 2.27277367e+01], [ 1.07168045e-01], [-6.53624164e+01], [-5.79337497e+00]])
Q: How do we calculate the inverse of $X^TX$ if it is non-invertible (singular/degenerate)?
A: pinv
will do it for you (pseudo-inverse)
A = np.zeros((2, 2))
Inverse = pinv(A)
Inverse
array([[0., 0.], [0., 0.]])
Q: When would it be non-invertible?
A: A few causes: