RM_ANOVA
Repeated-measures ANOVA evaluates mean differences across within-subject conditions while accounting for subject-level dependence.
It partitions variability into within-condition effects and residual error after controlling for subject effects. Pingouin can also report sphericity corrections and effect sizes for the repeated effect.
This wrapper accepts long-format table data and returns the repeated-measures ANOVA results table.
Excel Usage
=RM_ANOVA(data, dv, within, subject, correction, detailed, effsize)
data(list[list], required): Input table where the first row contains column names.dv(str, required): Name of the dependent-variable column.within(str, required): Name of the within-subject factor column.subject(str, required): Name of the subject identifier column.correction(str, optional, default: “auto”): Sphericity correction option (for example auto, True, False).detailed(bool, optional, default: false): Whether to return a detailed ANOVA table.effsize(str, optional, default: “ng2”): Effect size metric (for example, ng2, np2).
Returns (list[list]): 2D table containing repeated-measures ANOVA results.
Example 1: Repeated measures with three time points
Inputs:
| data | dv | within | subject | ||
|---|---|---|---|---|---|
| subject | time | score | score | time | subject |
| S1 | T1 | 5.2 | |||
| S1 | T2 | 5.8 | |||
| S1 | T3 | 6.1 | |||
| S2 | T1 | 4.9 | |||
| S2 | T2 | 5.3 | |||
| S2 | T3 | 5.7 | |||
| S3 | T1 | 5.4 | |||
| S3 | T2 | 5.9 | |||
| S3 | T3 | 6.4 |
Excel formula:
=RM_ANOVA({"subject","time","score";"S1","T1",5.2;"S1","T2",5.8;"S1","T3",6.1;"S2","T1",4.9;"S2","T2",5.3;"S2","T3",5.7;"S3","T1",5.4;"S3","T2",5.9;"S3","T3",6.4}, "score", "time", "subject")
Expected output:
| Source | ddof1 | ddof2 | F | p_unc | ng2 | eps |
|---|---|---|---|---|---|---|
| time | 2 | 4 | 122 | 0.000260146 | 0.677778 | 1 |
Example 2: Detailed repeated measures output
Inputs:
| data | dv | within | subject | detailed | ||
|---|---|---|---|---|---|---|
| subject | phase | value | value | phase | subject | true |
| A | pre | 10.1 | ||||
| A | post | 11.2 | ||||
| B | pre | 9.8 | ||||
| B | post | 10.5 | ||||
| C | pre | 11 | ||||
| C | post | 12.3 |
Excel formula:
=RM_ANOVA({"subject","phase","value";"A","pre",10.1;"A","post",11.2;"B","pre",9.8;"B","post",10.5;"C","pre",11;"C","post",12.3}, "value", "phase", "subject", TRUE)
Expected output:
| Source | SS | DF | MS | F | p_unc | ng2 | eps |
|---|---|---|---|---|---|---|---|
| phase | 1.60167 | 1 | 1.60167 | 34.3214 | 0.0279218 | 0.3976 | 1 |
| Error | 0.0933333 | 2 | 0.0466667 |
Example 3: Apply sphericity correction flag true
Inputs:
| data | dv | within | subject | correction | ||
|---|---|---|---|---|---|---|
| subject | cond | y | y | cond | subject | true |
| P1 | C1 | 2.1 | ||||
| P1 | C2 | 2.4 | ||||
| P1 | C3 | 2.9 | ||||
| P2 | C1 | 1.9 | ||||
| P2 | C2 | 2.2 | ||||
| P2 | C3 | 2.8 | ||||
| P3 | C1 | 2.3 | ||||
| P3 | C2 | 2.6 | ||||
| P3 | C3 | 3 |
Excel formula:
=RM_ANOVA({"subject","cond","y";"P1","C1",2.1;"P1","C2",2.4;"P1","C3",2.9;"P2","C1",1.9;"P2","C2",2.2;"P2","C3",2.8;"P3","C1",2.3;"P3","C2",2.6;"P3","C3",3}, "y", "cond", "subject", TRUE)
Expected output:
| Source | ddof1 | ddof2 | F | p_unc | p_GG_corr | ng2 | eps | sphericity | W_spher | p_spher |
|---|---|---|---|---|---|---|---|---|---|---|
| cond | 2 | 4 | 147 | 0.000180172 | 0.00673408 | 0.844828 | 0.5 | true | 600 | 1 |
Example 4: Partial eta squared effect size
Inputs:
| data | dv | within | subject | effsize | ||
|---|---|---|---|---|---|---|
| id | trial | response | response | trial | id | np2 |
| U1 | A | 3 | ||||
| U1 | B | 3.4 | ||||
| U2 | A | 2.8 | ||||
| U2 | B | 3.2 | ||||
| U3 | A | 3.1 | ||||
| U3 | B | 3.6 |
Excel formula:
=RM_ANOVA({"id","trial","response";"U1","A",3;"U1","B",3.4;"U2","A",2.8;"U2","B",3.2;"U3","A",3.1;"U3","B",3.6}, "response", "trial", "id", "np2")
Expected output:
| Source | ddof1 | ddof2 | F | p_unc | np2 | eps |
|---|---|---|---|---|---|---|
| trial | 1 | 2 | 169 | 0.00586515 | 0.988304 | 1 |
Python Code
import pandas as pd
from pingouin import rm_anova as pg_rm_anova
def rm_anova(data, dv, within, subject, correction='auto', detailed=False, effsize='ng2'):
"""
Perform repeated-measures ANOVA on tabular data using Pingouin.
See: https://pingouin-stats.org/build/html/generated/pingouin.rm_anova.html
This example function is provided as-is without any representation of accuracy.
Args:
data (list[list]): Input table where the first row contains column names.
dv (str): Name of the dependent-variable column.
within (str): Name of the within-subject factor column.
subject (str): Name of the subject identifier column.
correction (str, optional): Sphericity correction option (for example auto, True, False). Default is 'auto'.
detailed (bool, optional): Whether to return a detailed ANOVA table. Default is False.
effsize (str, optional): Effect size metric (for example, ng2, np2). Default is 'ng2'.
Returns:
list[list]: 2D table containing repeated-measures ANOVA results.
"""
try:
def to2d(x):
return [[x]] if not isinstance(x, list) else x
def build_dataframe(table):
table = to2d(table)
if not isinstance(table, list) or not table or not all(isinstance(row, list) for row in table):
return None, "Error: data must be a non-empty 2D list"
if len(table) < 2:
return None, "Error: data must include a header row and at least one data row"
headers = [str(h).strip() for h in table[0]]
if any(h == "" for h in headers):
return None, "Error: header row contains empty column names"
if len(set(headers)) != len(headers):
return None, "Error: header row contains duplicate column names"
rows = []
for row in table[1:]:
if len(row) != len(headers):
return None, "Error: all data rows must match header width"
rows.append([None if cell == "" else cell for cell in row])
return pd.DataFrame(rows, columns=headers), None
def dataframe_to_2d(df):
out = [list(df.columns)]
for values in df.itertuples(index=False, name=None):
row = []
for value in values:
if pd.isna(value):
row.append("")
elif isinstance(value, bool):
row.append(value)
elif isinstance(value, (int, float)):
row.append(float(value))
else:
row.append(str(value))
out.append(row)
return out
frame, error = build_dataframe(data)
if error:
return error
for col_name, col_label in [(dv, "dv"), (within, "within"), (subject, "subject")]:
if col_name not in frame.columns:
return f"Error: {col_label} column '{col_name}' not found"
corr_value = correction
if isinstance(correction, str):
lower = correction.lower()
if lower == "true":
corr_value = True
elif lower == "false":
corr_value = False
result = pg_rm_anova(
data=frame,
dv=dv,
within=within,
subject=subject,
correction=corr_value,
detailed=bool(detailed),
effsize=effsize,
)
return dataframe_to_2d(result)
except Exception as e:
return f"Error: {str(e)}"Online Calculator
Input table where the first row contains column names.
Name of the dependent-variable column.
Name of the within-subject factor column.
Name of the subject identifier column.
Sphericity correction option (for example auto, True, False).
Whether to return a detailed ANOVA table.
Effect size metric (for example, ng2, np2).