MIXED_ANOVA

Mixed ANOVA models one within-subject factor and one between-subject factor in a single design.

It tests the main effects and the interaction between factors while accounting for repeated measurements within subjects. The output includes F statistics, p-values, and effect sizes for each tested term.

This wrapper takes long-format tabular data and returns the mixed ANOVA result table.

Excel Usage

=MIXED_ANOVA(data, dv, within, subject, between, correction, 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.
  • between (str, required): Name of the between-subject factor column.
  • correction (str, optional, default: “auto”): Sphericity correction option (for example auto, True, False).
  • effsize (str, optional, default: “np2”): Effect size metric (for example, np2, ng2).

Returns (list[list]): 2D table containing mixed ANOVA results.

Example 1: Mixed design with two groups and two times

Inputs:

data dv within subject between
subject group time score score time subject group
S1 G1 T1 5
S1 G1 T2 5.7
S2 G1 T1 4.8
S2 G1 T2 5.4
S3 G2 T1 5.3
S3 G2 T2 6.1
S4 G2 T1 5.1
S4 G2 T2 5.9

Excel formula:

=MIXED_ANOVA({"subject","group","time","score";"S1","G1","T1",5;"S1","G1","T2",5.7;"S2","G1","T1",4.8;"S2","G1","T2",5.4;"S3","G2","T1",5.3;"S3","G2","T2",6.1;"S4","G2","T1",5.1;"S4","G2","T2",5.9}, "score", "time", "subject", "group")

Expected output:

Source SS DF1 DF2 MS F p_unc np2 eps
group 0.28125 1 2 0.28125 5.4878 0.143905 0.732899
time 1.05125 1 2 1.05125 841 0.00118694 0.997628 1
Interaction 0.01125 1 2 0.01125 9 0.095466 0.818182
Example 2: Partial eta squared output

Inputs:

data dv within subject between effsize
id cohort session y y session id cohort np2
A C1 pre 2.1
A C1 post 2.9
B C1 pre 2
B C1 post 2.7
C C2 pre 2.4
C C2 post 3.2
D C2 pre 2.3
D C2 post 3.1

Excel formula:

=MIXED_ANOVA({"id","cohort","session","y";"A","C1","pre",2.1;"A","C1","post",2.9;"B","C1","pre",2;"B","C1","post",2.7;"C","C2","pre",2.4;"C","C2","post",3.2;"D","C2","pre",2.3;"D","C2","post",3.1}, "y", "session", "id", "cohort", "np2")

Expected output:

Source SS DF1 DF2 MS F p_unc np2 eps
cohort 0.21125 1 2 0.21125 13 0.0690507 0.866667
session 1.20125 1 2 1.20125 961 0.00103896 0.997923 1
Interaction 0.00125 1 2 0.00125 1 0.42265 0.333333
Example 3: Generalized eta squared output

Inputs:

data dv within subject between effsize
sid grp stage metric metric stage sid grp ng2
P1 A one 11.1
P1 A two 11.9
P2 A one 10.8
P2 A two 11.6
P3 B one 12
P3 B two 12.8
P4 B one 11.7
P4 B two 12.6

Excel formula:

=MIXED_ANOVA({"sid","grp","stage","metric";"P1","A","one",11.1;"P1","A","two",11.9;"P2","A","one",10.8;"P2","A","two",11.6;"P3","B","one",12;"P3","B","two",12.8;"P4","B","one",11.7;"P4","B","two",12.6}, "metric", "stage", "sid", "grp", "ng2")

Expected output:

Source SS DF1 DF2 MS F p_unc ng2 eps
grp 1.71125 1 2 1.71125 22.4426 0.0417851 0.916946
stage 1.36125 1 2 1.36125 1089 0.000917011 0.897774 1
Interaction 0.00125 1 2 0.00125 1 0.42265 0.008
Example 4: Correction value set true

Inputs:

data dv within subject between correction
sid grp wave val val wave sid grp true
U1 X w1 1.1
U1 X w2 1.4
U2 X w1 1
U2 X w2 1.3
U3 Y w1 1.5
U3 Y w2 1.9
U4 Y w1 1.4
U4 Y w2 1.8

Excel formula:

=MIXED_ANOVA({"sid","grp","wave","val";"U1","X","w1",1.1;"U1","X","w2",1.4;"U2","X","w1",1;"U2","X","w2",1.3;"U3","Y","w1",1.5;"U3","Y","w2",1.9;"U4","Y","w1",1.4;"U4","Y","w2",1.8}, "val", "wave", "sid", "grp", TRUE)

Expected output:

Source SS DF1 DF2 MS F p_unc np2 eps
grp 0.405 1 2 0.405 40.5 0.0238129 0.952941
wave 0.245 1 2 0.245 8827060000000000 1.13288e-16 1 1
Interaction 0.005 1 2 0.005 180144000000000 5.55112e-15 1

Python Code

import pandas as pd
from pingouin import mixed_anova as pg_mixed_anova

def mixed_anova(data, dv, within, subject, between, correction='auto', effsize='np2'):
    """
    Perform mixed ANOVA with within- and between-subject factors using Pingouin.

    See: https://pingouin-stats.org/build/html/generated/pingouin.mixed_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.
        between (str): Name of the between-subject factor column.
        correction (str, optional): Sphericity correction option (for example auto, True, False). Default is 'auto'.
        effsize (str, optional): Effect size metric (for example, np2, ng2). Default is 'np2'.

    Returns:
        list[list]: 2D table containing mixed 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"), (between, "between")]:
            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_mixed_anova(
            data=frame,
            dv=dv,
            within=within,
            subject=subject,
            between=between,
            correction=corr_value,
            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.
Name of the between-subject factor column.
Sphericity correction option (for example auto, True, False).
Effect size metric (for example, np2, ng2).