There are several definitions of $R^2$ that are equivalent for in-sample OLS linear regression.
The squared correlation between the $x$ and $y$ in simple linear regression
The squared correlation between the predictions $\hat y$ and truth $y$
The proportion of variance explained
A comparison of the model performance to the performance of a naïve model that always predicts $\bar y$, no matter what values the features take
The final one is the one that makes the most sense to me.
When you get to out-of-sample $R^2$, the first three present issues.
Typically, an out-of-sample metric is of interest to problems that require more complicated feature spaces than just one variable, so this is out.
Out-of-sample, all bets are off. If you've badly overfit, you could be in a position where high values of $y$ correspond to low values of $\hat y$, and low values of $y$ correspond to high values of $\hat y$, the extreme of which is $\hat y = -y$. This puts you in a position where $cor(y, \hat y)<0$, and when you square that value, you miss that the predictions are terrible. Further, this approach misses systematic bias (consistently predicting too high or too low by $k$) and predicting multiples of the true values. That is, if $\hat y = a+b y$ for $(a,b)\ne(0,1)$, $cor(y, \hat y)$ is oblivious to the poor predictions. That this metric misses such critical information is, to me, a dealbreaker.
This one is appealing, but the "proportion of variance explained" interpretation of $R^2$ breaks down in most situations. Out-of-sample is one such situation, as the coefficients that result in the orthogonality needed for this interpretation to hold out-of-sample are unlikely to be the coefficients estimated in-sample, even for an OLS linear regression.
Finally, this one makes sense. We have a model and are interested in the square loss. As minimizing square loss corresponds to finding the conditional mean, a good benchmark is to see if the predictions are better than a model that always predicts the conditional mean to be the pooled mean.
$$
\dfrac{
\sum_{i=1}^n\big(
y_i - \hat y_i
\big)^2
}{
\sum_{i=1}^n\big(
y_i - \bar y
\big)^2
}
$$
If the numerator is smaller than the denominator, it means that our predictions beat the predictions made by out baseline model that naïvely predicts $\bar y$ every time. If the numerator is larger than the denominator, then all of our statistics and machine learning efforts are doing worse than we would do by predicting AVERAGE(A:A)
(to use some Excel terminology). That is, our model is doing a poor job of predicting.
It is typical to subtract this quantity from $1$ in order to align with the other three ways of defining in-sample $R^2$.
$$R^2 = 1-
\dfrac{
\sum_{i=1}^n\big(
y_i - \hat y_i
\big)^2
}{
\sum_{i=1}^n\big(
y_i - \bar y
\big)^2
}
$$
This idea of comparing to a baseline model exists for other metrics. For logistic regressions, there are two named metrics that do exactly this: Efron's and McFadden's pseudo $R^2$, as discussed on this UCLA page.
For evaluating an out-of-sample $R^2$, I would use the following:
$$R^2_{oos}=1-
\dfrac{
\sum_{i=1}^n\big(
y_i - \hat y_i
\big)^2
}{
\sum_{i=1}^n\big(
y_i - \bar y_{train}
\big)^2
}
$$
This compares the out-of-sample performance of your model to the out-of-sample performance you would get from a model trained just to predict the mean every time (the naïve baseline model).
Irritatingly, the popular Python machine learning package sklearn
has an out-of-sample $R^2$ function, sklearn.metrics.r2_score
, that uses the $\bar y$ from whatever you input as the truth values. This is fine for in-sample $R^2$, but for out-of-sample $R^2$, it results in the following formula:
$$R^2_{oos}1-
\dfrac{
\sum_{i=1}^n\big(
y_i - \hat y_i
\big)^2
}{
\sum_{i=1}^n\big(
y_i - \bar y_{test}
\big)^2
}
$$
The denominator is now based on the square loss of an intercept-only linear model that has been trained on the test data. We should never have access to this model, as it requires us to train on the test data, and I disagree with the sklearn
implementation. Fortunately, however, this function does not fall for the traps that just the $cor(y, \hat y)$ does.
np.random.seed(2022)
N = 100
y = np.random.uniform(0, 1, N)
yhat1 = y + 4
plt.scatter(y, yhat1, label = "Observed Predictions")
plt.plot([0, 1], [0, 1], label = "Perfect Predictions")
plt.legend()
plt.show()
plt.close()
print("sklearn R^2: ", r2_score(y, yhat1))
print("Squared correlation between observations and predictions: ", np.corrcoef(y, yhat1)[1, 1]**2)
sklearn R^2: -157.49362110734847
Squared correlation between observations and predictions: 1.0
yhat2 = y * 3
plt.scatter(y, yhat2, label = "Observed Predictions")
plt.plot([0, 1], [0, 1], label = "Perfect Predictions")
plt.legend()
plt.show()
plt.close()
print("sklearn R^2: ", r2_score(y, yhat2))
print("Squared correlation between observations and predictions: ", np.corrcoef(y, yhat2)[1, 1]**2)
sklearn R^2: -12.341142004503533
Squared correlation between observations and predictions: 1.0
yhat3 = -y
plt.scatter(y, yhat3, label = "Observed Predictions")
plt.plot([0, 1], [0, 1], label = "Perfect Predictions")
plt.legend()
plt.show()
plt.close()
print("sklearn R^2: ", r2_score(y, yhat3))
print("Squared correlation between observations and predictions: ", np.corrcoef(y, yhat3)[1, 1]**2)
sklearn R^2: -12.341142004503533
Squared correlation between observations and predictions: 1.0
yhat4 = 2 + 3*y
plt.scatter(y, yhat4, label = "Observed Predictions")
plt.plot([0, 1], [0, 1], label = "Perfect Predictions")
plt.legend()
plt.show()
plt.close()
print("sklearn R^2: ", r2_score(y, yhat4))
print("Squared correlation between observations and predictions: ", np.corrcoef(y, yhat4)[1, 1]**2)
sklearn R^2: -90.44196171593748
Squared correlation between observations and predictions: 1.0
In all of these situations, the squared correlation between the predictions and the truth is a perfect-looking $1$, yet the sklearn.metrics.r2_score
correctly indicates that the predictions are terrible, as is evident in all four plots.
Finally, for evaluating $R^2$ on the combined data (training and testing), it is unclear what this would tell you. If I had to do that calculation, I would be inclined to use the in-sample $\bar y$ in the denominator and just do the sums over all $n+p$ values. You might also be interested in bootstrap validation that uses all of the observations.