14Bridging the Gap: The Point Biserial Correlation
The point biserial correlation is a statistical measure that quantifies the relationship between a continuous variable and a dichotomous variable. It is a special case of the Pearson product moment correlation coefficient, which is used to measure the strength and direction of the linear relationship between two continuous variables. Why is this so important? The point biserial correlation (\(r_{pb}\)) is the key to bridging the gap between continuous and dichotomous variables - a gap caused by the shift from one GLM model (MRC with continuous) to another (ANOVA with discrete or categorical predictors).
14.1 Learning Objectives
Understand the concept of the point biserial correlation.
Learn how to calculate the point biserial correlation.
Understand how to interpret the point biserial correlation.
Learn how MRC and ANOVA are fundamentally the same model.
14.2 The Trick: A Dichotomy Is Just 0 and 1
Here is the idea that makes this simple. A dichotomous variable (passed/failed, treatment/control, yes/no) looks categorical, but if we simply code it 0 and 1, we can drop it straight into a Pearson correlation as if it were a number. That is all the point-biserial correlation is: an ordinary Pearson \(r\) where one of the two variables happens to be a 0/1 indicator.
NoteWorking in SPSS, Julia, or Python?
The code tabs below assume this chapter’s data is already loaded. Grab the one-file setup for your language from Getting the Book’s Data, run it once, then load what you need by name - this chapter uses ch13-study. For example, book_data("ch13-study") in R, Julia, or Python, or !bookdata name = "ch13-study". in SPSS. Every language reads the same shipped files, so your numbers will match the ones printed here exactly.
library(tidyverse) # dplyr, ggplot2, purrr, tibble, readr, stringr, forcatslibrary(broom) # tidy(), glance(), augment() for model outputsource("_common.R") # book-wide helpers: round2(), fmt_p(), tidy2()set.seed(14)d <-tibble(studied =rbinom(50, 1, 0.5)) |># 0 = no study, 1 = studiedmutate(score =70+8* studied +rnorm(50, 0, 6)) # studiers score higherd |>summarise(point_biserial =cor(score, studied), # point-biserial: just Pearson r!# and the same number even if the dichotomy is stored as a factorsame_thing =cor(score, as.numeric(factor(studied)) -1) ) |>round2()
point_biserial
same_thing
0.54
0.54
INSERT FILE='data/sim/ch13-study.sps'.
* point-biserial correlation: just Pearson r with a 0/1 variable.
CORRELATIONS /VARIABLES=score studied.
Correlations
+---------------------------+-----+-------+
| |score|studied|
+---------------------------+-----+-------+
|score Pearson Correlation|1.000| .537|
| Sig. (2-tailed) | | .000|
| N | 50| 50|
+---------------------------+-----+-------+
|studied Pearson Correlation| .537| 1.000|
| Sig. (2-tailed) | .000| |
| N | 50| 50|
+---------------------------+-----+-------+
import pandas as pddf = pd.read_csv("data/sim/ch13-study.csv")df.score.corr(df.studied) # point-biserial: just Pearson r!
np.float64(0.5370145073323761)
No new formula, no new function; cor() did not even notice one variable was a dichotomy. The point-biserial correlation is just Pearson’s \(r\) computed with a 0/1 variable.
14.3 The Same Thing, Wearing Three Hats
Now the payoff. The relationship between a continuous outcome and a two-group predictor can be expressed as (a) a point-biserial correlation, (b) a two-sample t-test, or (c) a simple regression, and they are the identical analysis. Every one gives the same t statistic and p-value:
usingCSV, DataFramesdf = CSV.read("data/sim/ch13-study.csv", DataFrame)usingHypothesisTests, GLM# (a) test the point-biserial correlationCorrelationTest(df.score, df.studied)# (b) the classic two-sample t-test (pooled variance)EqualVarianceTTest(df.score[df.studied .==0], df.score[df.studied .==1])# (c) regression with the 0/1 predictorcoeftable(lm(@formula(score ~ studied), df))
Test for nonzero correlation
----------------------------
Population details:
parameter of interest: Correlation
value under h_0: 0.0
point estimate: 0.537015
95% confidence interval: (0.3041, 0.7093)
Test summary:
outcome with 95% confidence: reject h_0
two-sided p-value: <1e-04
Details:
number of observations: 50
number of conditional variables: 0
t-statistic: 4.41046
degrees of freedom: 48
Two sample t-test (equal variance)
----------------------------------
Population details:
parameter of interest: Mean difference
value under h_0: 0
point estimate: -6.76848
95% confidence interval: (-9.854, -3.683)
Test summary:
outcome with 95% confidence: reject h_0
two-sided p-value: <1e-04
Details:
number of observations: [20,30]
t-statistic: -4.4104607562171445
degrees of freedom: 48
empirical standard error: 1.5346431746816767
────────────────────────────────────────────────────────────────────────
Coef. Std. Error t Pr(>|t|) Lower 95% Upper 95%
────────────────────────────────────────────────────────────────────────
(Intercept) 70.0558 1.18873 58.93 <1e-45 67.6657 72.4459
studied 6.76848 1.53464 4.41 <1e-04 3.68288 9.85409
────────────────────────────────────────────────────────────────────────
import pandas as pddf = pd.read_csv("data/sim/ch13-study.csv")from scipy import statsimport statsmodels.formula.api as smf# (a) test the point-biserial correlationstats.pearsonr(df.score, df.studied)# (b) the classic two-sample t-test (pooled variance)stats.ttest_ind(df.score[df.studied ==0], df.score[df.studied ==1])# (c) regression with the 0/1 predictorsmf.ols("score ~ studied", data=df).fit().summary()
Three “different” techniques, one t statistic. The correlation, the group comparison, and the regression are the same GLM. And the regression coefficient tells you something the correlation does not show directly: the raw difference between the two group means:
means <- d |>summarise(mean =mean(score), .by = studied) |>arrange(studied)tibble(regression_slope = rg$estimate[2],mean_difference = means$mean[2] - means$mean[1]) |>round2()
regression_slope
mean_difference
6.77
6.77
The slope on a 0/1 predictor is the difference in group means. (This is the same dummy-coding logic from the ANOVA chapter, previewed here with the simplest possible category.)
14.4 From \(r\) to \(t\) and Back
Because they are the same analysis, the correlation and the t statistic are tied by a simple formula, handy for converting between an effect size (\(r_{pb}\)) and a significance test (\(t\)):
They match. This little equation is why you can always recover an effect size from a reported t (and its df), and vice versa, which is useful when reading other people’s papers.
ImportantWhy This Bridge Matters
MRC (regression with continuous predictors) and ANOVA (with categorical predictors) are often taught as if they were unrelated. The point-biserial correlation connects them: once you code a category as numbers, a group difference is a correlation and a t-test is a regression. Every categorical predictor is just a set of 0/1 indicators you can correlate. That idea is the key to the entire General Linear Model.
14.5 Challenge
TipDo One Yourself
Simulate a continuous outcome and a 0/1 predictor. Compute the point-biserial correlation with cor(), then confirm the t from cor.test(), t.test(var.equal = TRUE), and lm() are identical.
Show that the regression slope equals the difference in the two group means.
Recode the predictor as 1/2 instead of 0/1 and refit. What changes about the intercept, what stays the same about the slope and the p-value, and why?
14.6 Where We Go Next
We have the bridge and the engine. Now we build the machine in earnest, starting with the simplest continuous-predictor case: bivariate regression.