Lecture notes. Written from the material of Berkeley Stat 153, licensed CC BY 4.0. These are notes, not a transcript: the material has been reorganised and rewritten. This adaptation carries the same licence, and the original is linked above.
130. Finger Tapping Regression Exercise¶
What this covers¶
This chapter works through an in-class data-collection exercise from Stat153/248: each student taps a finger for 60 seconds while software logs every tap, and the class fits ordinary least squares (OLS) models to the results. It assumes the reader already has least squares regression and the normal equations, and shows how a categorical predictor becomes a numeric one in a design matrix, how to compare competing encodings of the same experimental factor, and how a stream of event times gets turned into a time series by binning — the same move needed whenever a time series has to be built out of irregularly spaced events.
The experiment and the raw data¶
Each of 101 subjects tapped one finger — index or pinky — with one hand, for 60 seconds, while an
app recorded the time of every tap, t_seconds. From consecutive tap times the notebook computes
the inter-tap gap, dt_seconds. Two more variables describe each subject: handedness, which hand
they consider dominant, and hand, which hand they actually tapped with; a derived boolean,
dominant_hand, records whether the two agree.
The first two plots in the notebook set up the question the rest of the lecture answers numerically:
- a line plot of
t_secondsagainsttap_index, split by finger, shows how fast each finger accumulates taps over the minute; - a violin plot of
dt_secondsagainstdominant_hand, split by finger, shows the distribution of the gap between taps for each of the four combinations of hand dominance and finger.
Both plots raise the same question: do finger and hand dominance actually change how many taps someone can produce, or are the visual differences just noise? That is what the regression models below answer.
The subject-level summary collapses each of the 101 sessions into one row: subject id, hand,
finger, handedness, dominant_hand, and the total tap count in 60 seconds, ntaps — ranging, in the
sample shown, from the high 200s to the high 300s.
Coding a categorical predictor: dummy variables and the design matrix¶
OLS needs a numeric design matrix, so a categorical predictor like finger (index/pinky) or hand
(left/right) has to become one or more 0/1 columns before it can enter \(X\). With \(k\) levels this
takes \(k-1\) indicator columns once one level is fixed as the reference; pd.get_dummies(...,
drop_first=True) does exactly this, turning finger into a single column finger_pinky (1 if
pinky, 0 if index) and hand into hand_right. Adding a constant column gives the design matrix
against which ntaps is regressed:
The reference case — the row where every indicator is 0 — is tapping with the index finger on the non-dominant hand; \(\beta_0\) is the predicted tap count for that case, and each \(\beta_j\) is the shift in tap count from switching that one indicator on.
Three ways to fit the same model¶
The notebook fits this model three separate ways and gets the identical answer each time, which is worth pausing on because it is the reason a formula interface can be trusted rather than treated as a black box.
- By the normal equations directly, \(\hat\beta = (X^\top X)^{-1}X^\top y\), computed with
np.linalg.inv. - By
sm.OLS(y, X).fit()on the same hand-built dummy-coded matrix. - By the formula interface,
smf.ols("ntaps ~ C(dominant_hand) + C(finger)", data=summary_df), which builds the same dummy-coded matrix from the raw categorical columns internally —C()marks a column as categorical, and the reference level is whichever one sorts first (indexbeforepinky,FalsebeforeTrue).
All three return
i.e. the OLS estimator does not care how the design matrix was assembled, only what it contains — the formula interface is a convenience for building exactly the matrix in step 1, not a different computation.
Reading the fitted model: on the index finger with the non-dominant hand, it predicts about 323.5 taps in 60 seconds. Using the pinky costs about 28.25 taps (\(p = 0.004\)); using the dominant hand instead of the non-dominant one gains about 46.7 taps (\(p < 0.001\)). Both effects are large relative to their standard errors, and together they account for about a quarter of the variance in tap counts (\(R^2 = 0.262\)).
Which encoding of hand dominance fits better?¶
The notebook then compares three specifications that differ only in how hand dominance enters:
| Model | Predictors | \(R^2\) |
|---|---|---|
| A | C(dominant_hand) alone |
0.196 |
| B | C(dominant_hand) + C(finger) |
0.262 |
| C | C(hand) + C(finger) |
0.306 |
Model C replaces the derived indicator "was this your dominant hand" with the raw fact of which hand was used, and fits noticeably better: tapping with the right hand adds about 51.9 taps, the pinky penalty is about 30.9, and both are significant at \(p < 0.005\). B and C use different predictors for the same underlying idea, so this is not a nested-model comparison; it is a reminder that two variables can tell "the same story" — hand dominance — and still not carry the same information once a line is actually fit through the data, because whichever hand happens to be more common in the sample can absorb variation that the more abstract dominance label does not.
Bringing in outside covariates¶
A second, independent dataset comes from a self-reported Google Form (65 respondents), carrying the
same finger/hand/handedness fields plus hours of sleep, whether the respondent plays video games,
and a sport variable. Three specifications, each adding one extra predictor to
C(finger) + C(dominant_hand), are fit in turn:
| Added predictor | \(R^2\) | coefficient | \(p\)-value |
|---|---|---|---|
sleep_hrs |
0.326 | \(-9.40\) per hour | 0.134 |
sport |
0.308 | \(+1.20\) | 0.405 |
C(gamer) |
0.384 | \(+58.56\) (Yes) | 0.005 |
Only the gaming indicator clears conventional significance in this sample of 65; sleep and sport,
despite showing sizeable-looking coefficients, do not. With three separate candidate additions
tried and only one significant, that is a result to hold loosely rather than treat as established —
unlike finger and dominant_hand, which come out significant, in the same direction, in both this
dataset and the first one.
From an event stream to a time series: binning taps into windows¶
The subject-level totals throw away when within the minute each tap happened. The full per-tap
table, df_all, keeps every tap's timestamp for all 101 subjects (34,113 taps in total) and lets
the notebook ask a different question: does the tapping rate change over the course of the 60
seconds?
To answer it, each tap's time is assigned to a 10-second window, \(\text{time\_bin} = \lfloor
t_{\text{seconds}} / 10 \rfloor\), and taps are counted within each (subject, window) pair to give
taps_bin. This is the general move needed whenever irregular event times — taps, arrivals, spikes
— have to become a regularly spaced count series before a regression, or later a proper time series
model, can be fit to them.
Fitting a trend to the binned counts¶
With taps_bin as the response and time_bin (0 through 5, the six 10-second windows in a minute)
as the predictor, the simplest model,
gives \(\hat\beta_0 = 60.46\) and \(\hat\beta_1 = -1.56\) (\(p < 0.001\)): the average subject taps about 1.56 fewer times in each successive 10-second window. Over the five steps from the first bin to the last, that is a predicted decline of about 7.8 taps — roughly 13% of the starting rate. \(R^2\) is small here (0.062) because this model ignores every between-subject difference — finger, hand — established earlier.
Adding those back in, with an interaction between the time trend and finger,
raises \(R^2\) to 0.282. The main effects repeat the earlier story — pinky taps about 6.3 fewer per bin (\(p<0.001\)), the dominant hand taps about 8.3 more (\(p<0.001\)) — and the time trend for the index finger is \(-1.75\) per bin (\(p<0.001\)). The interaction term, \(+0.45\), is not significant (\(p = 0.305\)): there is no detectable evidence in this data that the pinky fatigues at a different rate than the index finger — it is simply slower throughout, at roughly the same rate of decline.
Sources¶
- Notebook
TippyTaps.md(converted fromTippyTaps.ipynb), Stat153/248, Berkeley, Spring 2026, "Lecture 7 and 8 Finger Tapping Exercise" — the sole input for this chapter. Every coefficient, \(p\)-value, \(R^2\), and sample size above is read directly from the code and printedstatsmodelsoutput in that notebook. - The notebook is explicitly marked "in progress" and carries no narrative markdown beyond its title; the exposition above is built from the code, the variable names, and the regression output, not from a lecturer's commentary — no transcript was supplied for this lecture.
- Four plots in the notebook (
t_secondsvs.tap_indexby finger;dt_secondsbydominant_handand finger;handcounts bydominant_hand;ntapsbydominant_handand gamer, as a boxplot) are referenced above by their axes only — the rendered images were not part of the converted source. - No transcript, written notes, or problem set were supplied for this lecture; no exercises section follows as a result.