STATS_PAIRED – Paired-samples t-test for related data
What it does
Paired-samples t-test for two related measurements (e.g., pre vs post).
Only rows with a number in both columns are used.
With more columns than the test needs, the first two are used; the others are dropped before anything is computed (they do not remove rows and cannot cause an error).
Syntax
=STATS_PAIRED(columns, colFilter, ciLevel, alternative)Parameters
columns — required
Cell range with data, e.g., A1:D100 or A:D.
Usually the first row should contain column names.
You can also add labels as the first row and column names as the second row.
Labels should use formats like "male:1,female:2" or "low:1:3,mid:3:5,high:5:7".
Examples:
A1:D100— use 4 columnsA1:C200— use 3 columnsA:F— use 6 columnsA1:D100 where row 1 contains labels and row 2 contains column names— use labels from the sheet
If every non-empty cell of the first row holds at least two label rules ("low:1,high:2"), it is the labels row, and the next row holds the column names; an empty cell there is a column without labels.
One rule in a cell ("yes:1") is read as a name, so a header such as "T:1", "Ratio 1:2" or "Note:" stays the names row. Give such a column a second rule, or pass one label in the labels parameter, where one rule is valid.
If the first row contains ordinary text, it is treated as the column names row as before.
A number, a date or a checkbox in the names row is a name: 2025, 2025-01-01, TRUE.
Column names must be unique: two identical headers are an error "Duplicate column name: x".
Every column with values needs a name: an empty header cell over values is an error "The name of column 2 is empty: a column with values needs a name", even when colFilter does not select that column. A column with neither a name nor values (the empty right edge of a range such as A:Z) is dropped without an error.
Column names that start with "#" are reserved/unsupported in filters, because "#" is used for index-based colFilter rules (for example #1, #2-#5).
What counts as a number (the same in every function):
- A column is numeric when it holds more numbers than text; with as many texts as numbers, or more, it is a text column.
- Every cell is typed by itself: a number written as text ('10', '007') is the number, a checkbox is 1 (checked) or 0 (unchecked). The texts 'TRUE'/'FALSE' stay text.
- A cell that does not match the type of its own column (text in a numeric column, a number in a text column) is missing for calculations and is counted in missing.
- An empty cell is a missing answer; the sign "." means "not applicable" — it is not a missing answer, not a category and not counted in missing.
- A text column with value labels (a labels row above the names) is analysed by the codes of its labels. A text column without labels is not analysed as numbers: lists of columns skip it with a note, tests on a pair of columns and models show an error.
An empty tail of the range (rows below the data) is dropped — only blank cells make it: a row of "." at the end stays and counts as not applicable, as in the middle. A column with neither a name nor values is dropped; a named empty column stays.
colFilter — optional, default "*"
Comma-separated column selector.
Use names ("age,score"), negative rules ("-id,-note"), a regex ("/^q/"), or index rules ("#N", "#A-#B", "-#N", "-#A-#B").
Case-sensitive; flags are not supported.
A name that does not exist is an error: "Column nope does not exist" (excluding it, "-nope", is ignored). Empty — all columns ("*").
Examples:
"age,score"— Names only (include only "age" and "score")"-id,-group"— Exclude some (include all except "id" and "group")"/^q/"— Regex only (all starts with "q")"#1,#3-#5"— Index include rules (first, and range 3..5)"-#1,-#3-#4"— Index exclude rules (drop first and 3..4)"age,#2-#4,-#3"— Mixed name/index rules with deterministic order"age,/^q/,-q5"— Mixed (include age, all starts with "q" except "q5")"/^x1{1,2}$/"— Regex with a comma inside: x1 and x11
Choose which columns from columns to use - a comma-separated string of rules.
⚙️ Four rule types (you can mix them):
- By name - exact header text (case-sensitive)
- "age,score,total"
- Negative rules - exclude by name (prefix with
-)- "-id,-notes" (include all except
idandnotes)
- "-id,-notes" (include all except
- Regex - a regular expression between slashes
/.../- "/^q/" (all headers starting with
q), "/(score|age)/", "/^x1{1,2}$/" (commas inside /.../ do not split the filter) - ❗— case-sensitive
- ❗— flags are not supported.
- "/^q/" (all headers starting with
- Index rules - position-based rules with
#- "#1" (first selected column), "#2-#5" (range), "-#1", "-#2-#4"
"*" alone selects every column; it cannot be combined with other rules ("*,-id" is an error) — write "-id" instead.
Rules are applied in the written order, and the selected columns keep that order ("/^q/,-q5,age" puts age last).
Index rules count the columns of the range: "#1" is its first column.
A name that does not exist is an error: "Column nope does not exist" — also in colName, yName, steps, rowCol, colCol and colNameForSplit. Excluding a name that does not exist ("-nope") is ignored.
A filter that selects no column — a regex without a match ("/^Q/" where the columns are q1, q2), index rules past the end, exclusions that remove every column — is an error that names the filter and the columns of the range: colFilter "/^Q/" selects no columns (columns: q1, q2, t). Empty and "*" select every column.
In STATS_ANALYZE, a regex with brackets or | needs the new syntax: PEARSON('/(score|age)/').
A colFilter saved in Settings applies to every formula without its own filter: on a range without those columns the formula shows the error above.
# prefix is reserved for index syntax and is not treated as a column-name filter token.
colFilter applies to all expressions in query. It does not create separate groups per expression.
ciLevel — optional, default 0.95
Confidence level for intervals in tests/bootstraps: a number strictly between 0 and 1 (0.95, not 95). A number written as text ("0.9") is accepted.
Another value is an error: ciLevel must be between 0 and 1 (e.g. 0.95), got 95. Empty — the level from Settings.
Should be a number between 0 and 1
Examples:
0.90.950.8
alternative — optional, default "two-sided"
Alternative hypothesis for two-sample/one-sample tests and correlations: "two-sided", "greater" or "less"; 1 and -1 (number or text) mean greater and less.
Case and spaces around do not matter ("Greater", " less "). Another value is an error: alternative must be two-sided, greater or less (or 1, -1), got "grater". Empty — the default from Settings.
A one-sided hypothesis gives a one-sided interval: [lower, ∞) for greater, (−∞, upper] for less (for correlations [lower, 1] and [−1, upper]).
Allowed values: "two-sided", "greater", "less"
Examples:
"two-sided""greater""less"
Examples
- — the sample data (50 rows) all examples use
- —
=STATS_PAIRED(Data!A1:T51, "before,after",0.95,"two-sided") - —
=STATS_PAIRED(Data!A1:T51, "before,after") - —
=STATS_PAIRED(Data!A1:T51, "q1,q2", 0.99, "greater")
What the result contains
Flattened 2D report that spills below the formula cell.
Title "Paired T-Test (two-sided) for before,after" (the hypothesis is named once), then t, df, p, the mean and sd of the differences, se, n, Cohen's d (cohenD, dz) and the confidence interval of the difference.
How to read the result
Start with p: below 0.05 — the two measurements differ.
meanDelta says by how much and in which direction: the first column minus the second, so a positive value means the first is higher. The interval says how precisely.
The report has only the test of the differences (the SPSS Paired Samples Test): the mean and SD of each measurement come from STATS_DESCRIPTIVE. The effect size for paired data: d_z = meanDelta / sdDelta (about 0.2 small, 0.5 medium, 0.8 large).
In a report: "Scores after the course did not differ significantly from before (mean difference before − after = 0.16, 95% CI [−0.37, 0.69]), t(49) = 0.61, p = .547."
What each number means
t— t of the testBeyond about ±2 the difference is usually significant; read p. In a report t goes with df: t(49) = 2.35, p = .023.
df— Degrees of freedom of the t testWritten in brackets after t: t(49) = 0.69.
p— p of the testBelow 0.05 — the two measurements differ beyond chance. In a report: "Scores did not change significantly, t(49) = 0.61, p = .547."
meanDelta— Mean differenceHow much the first measurement is higher than the second on average: 0.16 means "before" is 0.16 points above "after". The sign follows the order of the columns in the formula.
sdDelta— Standard deviation of the differencesHow much the change differs from person to person. A small sdDelta next to a clear meanDelta means nearly everyone changed the same way.
se— Standard error of the mean differenceThe precision of the difference: t is the difference divided by it, and the interval is built around it. Rarely reported on its own.
n— Number of valuesA row with one of the two answers missing is left out — compare n with your sample.
cohenD— Cohen's dHow big the difference is, not only whether it exists: p says "not chance", d says "how much". Rough guide (Cohen): 0.2 small, 0.5 medium, 0.8 large; the sign follows the order of the columns.
APA asks for it next to the test: t(98) = 2.10, p = .038, d = 0.42.
Confidence interval
Level— Confidence level0.95 means that in 95 samples of 100 an interval built this way would contain the true value.
Lower— Lower bound of the differenceTogether with the other bound: a range of plausible values for the true difference. An interval that does not contain 0 matches p below 0.05 for a 95% interval; its width shows how precisely the difference is known.
Upper— Upper bound of the differenceTogether with the other bound: a range of plausible values for the true difference. An interval that does not contain 0 matches p below 0.05 for a 95% interval; its width shows how precisely the difference is known.
How it is computed
The first two selected columns are paired row by row; rows where either is empty are left out.
The differences d = first − second are tested: t = d̄ / (s_d / √n) with n − 1 degrees of freedom, as SPSS T-TEST PAIRS.
p is two-sided or one-sided by alternative (greater: the first column is larger); the confidence interval is that of the mean difference.
Errors
A column that does not exist: "Column nope does not exist".
Fewer than 2 complete pairs: "Paired T-Test: need at least 2 observations".
About STATS_PAIRED
STATS_PAIRED compares two related measurements of the same people — for example, scores before and after a course. Name the two columns in the filter, such as "before,after"; only rows with a number in both columns are used. The report spills below the formula cell and recalculates by itself when new responses arrive from Google Forms.
The test works on the differences, first column minus second: t = mean difference / (sd of the differences / √n) with n − 1 degrees of freedom, as SPSS T-TEST PAIRS. The report gives t, df, p, the mean and standard deviation of the differences, the standard error, n, the confidence interval of the mean difference and Cohen's d (dz) — an effect size that shows how large the change is, not only whether it is significant.
The test is two-sided by default; with "greater" it asks whether the first column is larger. With fewer than two complete pairs the formula shows a message instead of the report. Every number of the report is explained on this page in plain words.