Contrast Coding of Visual Attention Effects

Authors

Phillip Alday

Reinhold Kliegl

Published

2026-06-21

After working through this page you will be able to:

  • specify and compare a range of contrast schemes (sequential-difference, hypothesis, dummy, effects, Helmert, and others) in Julia;
  • map theoretically motivated contrasts onto a four-level factor;
  • read off the hypothesis tested by each coding from the model output.
NoteBefore you start

Prerequisites: Transformations of the predictors and the response.

Datasets used: kwdyz11 (see the dataset catalog).

Code
using AlgebraOfGraphics
using CairoMakie
using Chain
using DataFrames
using MixedModels
using SMLP2026: dataset
using StatsBase
using StatsModels
progress = false

1 A word of caution

Many researchers have pointed out that contrasts should be “tested instead of, rather than as a supplement to, the ordinary ‘omnibus’ F test” (Hays, 1973, p. 601),

For a (quasi-)experimental set of data, there is (or should be) a clear a priori theoretical commitment to specific hypotheses about differences between factor levels and how these differences enter in interactions with other factors. This specification should be used in the first LMM and reported, regardless of the outcome. If alternative theories lead to alternative a priori contrast specifications, both analyses are justified. If the observed means render the specification completely irrelevant, the comparisons originally planned could still be reported in a Supplement.

In this script, we are working through a large number of different contrasts for the same data. The purpose is to introduce both the preprogrammed (“canned”) and the general options to specify hypotheses about main effects and interactions. Obviously, we do not endorse generating a plot of the means and specifying the contrasts accordingly. This is known as the Texas sharpshooter fallacy.

Irrespective of how results turn out, there is nothing wrong with specifying a set of post-hoc contrasts to gain a better understanding of what the data are trying to tell us. Of course, in an article or report about the study, the a priori and post-hoc nature of contrast specifications must be made clear. Some kind of multiple comparisons correction (e.g., Bonferroni) may be called for, too. And, of course, there are grey zones.

There is quite a bit of statistical literature on contrasts. Two “local” references are Brehm & Alday (2022) and Schad et al. (2020).

For further readings see “Further Readings” in Schad et al. (2020).

2 Example data

We take the KWDYZ dataset from Kliegl et al. (2011). This is an experiment looking at three effects of visual cueing under four different cue-target relations (CTRs). Two horizontal rectangles are displayed above and below a central fixation point or they displayed in vertical orientation to the left and right of the fixation point. Subjects react to the onset of a small visual target occurring at one of the four ends of the two rectangles. The target is cued validly on 70% of trials by a brief flash of the corner of the rectangle at which it appears; it is cued invalidly at the three other locations 10% of the trials each.

We specify three contrasts for the four-level factor CTR that are derived from spatial, object-based, and attractor-like features of attention. They map onto sequential differences between appropriately ordered factor levels.

We also have a dataset from a replication and extension of this study (Kliegl et al., 2015).

3 Preprocessing

dat1 = DataFrame(dataset(:kwdyz11))
cellmeans = combine(
  groupby(dat1, [:CTR]),
  :rt => mean,
  :rt => std,
  :rt => length,
  :rt => (x -> std(x) / sqrt(length(x))) => :rt_se,
)
4×5 DataFrame
Row CTR rt_mean rt_std rt_length rt_se
String Float32 Float32 Int64 Float64
1 val 358.032 83.4579 20141 0.588067
2 sod 391.267 92.662 2863 1.73177
3 dos 405.146 92.6892 2843 1.73836
4 dod 402.3 95.3913 2863 1.78278

4 Julia contrast options

We use the same formula for all analyses

form = @formula(rt ~ 1 + CTR + (1 + CTR | Subj))

This is the default order of factor levels.

show(StatsModels.levels(dat1.CTR))
["val", "sod", "dos", "dod"]

Controlling the ordering of levels for contrasts:

  1. kwarg levels to order the levels
  2. The first level is set as the baseline; with kwarg base a different level can be specified.

4.1 Sequential difference coding

The SeqDiffCoding contrast corresponds to MASS::contr.sdif() in R.

m1 = let levels = ["val", "sod", "dos", "dod"]
  contrasts = Dict(
    :CTR => SeqDiffCoding(; levels),
  )
  fit(MixedModel, form, dat1; contrasts, progress)
end
Est. SE z p σ_Subj
(Intercept) 389.7336 7.0914 54.96 <1e-99 55.2006
CTR: sod 33.7817 3.2874 10.28 <1e-24 23.2487
CTR: dos 13.9852 2.3057 6.07 <1e-08 10.7540
CTR: dod -2.7470 2.2143 -1.24 0.2148 9.5109
Residual 69.8348

What does the intercept represent?

mean(dat1.rt)
370.42606f0
mean(cellmeans.rt_mean)
389.18622f0

Grand Mean is mean of condition means.

4.2 Hypothesis coding

HypothesisCoding is the most general option available. We can implement all “canned” contrasts ourselves. The next example reproduces the test statistics from SeqDiffCoding, with a minor modification illustrating the flexibility of going beyond the default version.

m1b = let levels = ["val", "sod", "dos", "dod"]
  contrasts = Dict(
    :CTR => HypothesisCoding(
      [
        -1  1 0  0
         0 -1 1  0
         0  0 1 -1
      ];
      levels,
      labels=["spt", "obj", "grv"],
    ),
  )
  fit(MixedModel, form, dat1; contrasts, progress)
end
Est. SE z p σ_Subj
(Intercept) 389.7336 7.0911 54.96 <1e-99 55.1977
CTR: spt 33.7817 3.2875 10.28 <1e-24 23.2490
CTR: obj 13.9852 2.3059 6.06 <1e-08 10.7567
CTR: grv 2.7469 2.2146 1.24 0.2148 9.5157
Residual 69.8348

The difference to the preprogrammed SeqDiffCoding is that for the third contrast we changed the direction of the contrast such that the sign of the effect is positive when the result is in agreement with theoretical expectation, that is we subtract the fourth level from the third, not the third level from the fourth.

4.3 Dummy coding

This contrast corresponds to contr.treatment() in R

m2 = let
  contrasts = Dict(:CTR => DummyCoding(; base="val"))
  fit(MixedModel, form, dat1; contrasts, progress)
end
Est. SE z p σ_Subj
(Intercept) 358.0914 6.1539 58.19 <1e-99 47.9099
CTR: dod 45.0200 4.3636 10.32 <1e-24 32.2915
CTR: dos 47.7669 3.5566 13.43 <1e-40 25.5369
CTR: sod 33.7817 3.2875 10.28 <1e-24 23.2491
Residual 69.8348

The DummyCoding contrast has the disadvantage that the intercept returns the mean of the level specified as base, default is the first level, not the GM.

4.4 YCHYCAEIT coding

The contrasts returned by DummyCoding may be exactly what we want. Can’t we have them, but also have the intercept estimate the GM, rather than the mean of the base level? Yes, we can! We call this “You can have your cake and it eat, too”-Coding (YCHYCAEIT coding). And we use HypothesisCoding to achieve this outcome.

m2b = let levels = ["val", "sod", "dos", "dod"]
  contrasts = Dict(
    :CTR => HypothesisCoding(
      [
        -1 1 0 0
        -1 0 1 0
        -1 0 0 1
      ];
      levels,
      labels=levels[2:end],
    )
  )
  fit(MixedModel, form, dat1; contrasts, progress)
end
Est. SE z p σ_Subj
(Intercept) 389.7336 7.0916 54.96 <1e-99 55.2019
CTR: sod 33.7817 3.2875 10.28 <1e-24 23.2494
CTR: dos 47.7669 3.5566 13.43 <1e-40 25.5372
CTR: dod 45.0200 4.3637 10.32 <1e-24 32.2921
Residual 69.8348

We can simply move the column with -1s for a different base.

m2c = let levels = ["val", "sod", "dos", "dod"]
  contrasts = Dict(
    :CTR => HypothesisCoding(
      [
       1 -1  0  0
       0 -1  1  0
       0 -1  0  1
      ];
      levels,
      labels=["val", "dos", "dod"],
    )
  )
  fit(MixedModel, form, dat1; contrasts, progress)
end
Est. SE z p σ_Subj
(Intercept) 389.7336 7.0926 54.95 <1e-99 55.2101
CTR: val -33.7817 3.2874 -10.28 <1e-24 23.2481
CTR: dos 13.9852 2.3061 6.06 <1e-08 10.7589
CTR: dod 11.2382 2.3589 4.76 <1e-05 11.4695
Residual 69.8348

We can simply relevel the factor with a different base.

m2d = let levels = ["sod", "val", "dos", "dod"]
  contrasts = Dict(
    :CTR => HypothesisCoding(
      [
        -1 1 0 0
        -1 0 1 0
        -1 0 0 1
      ];
      levels,
      labels=levels[2:end],
    )
  )
  fit(MixedModel, form, dat1; contrasts)
end

4.5 Effects coding

EffectsCoding estimates the difference between the Grand Mean and three of the four levels. The difference of the fourth levels can be computed from the Grand Mean and these three differences.

m3 = let levels = ["val", "sod", "dos",  "dod"]
  contrasts = Dict(:CTR => EffectsCoding(; levels, base="val"))
  fit(MixedModel, form, dat1; contrasts, progress)
end
Est. SE z p σ_Subj
(Intercept) 389.7336 7.1020 54.88 <1e-99 55.2836
CTR: sod 2.1404 1.3396 1.60 0.1101 6.0863
CTR: dos 16.1244 1.4337 11.25 <1e-28 7.2510
CTR: dod 13.3774 1.9359 6.91 <1e-11 12.4968
Residual 69.8344

This contrast corresponds almost to contr.sum() in R. The “almost” qualification refers to the fact that EffectsCoding uses the first level as default base; contr.sum() uses the last factor level.

m3b = let levels = ["val", "sod", "dos",  "dod"]
  contrasts = Dict(:CTR => EffectsCoding(; levels, base = "dod"))
  fit(MixedModel, form, dat1; contrasts, progress)
end
Est. SE z p σ_Subj
(Intercept) 389.7336 7.0919 54.95 <1e-99 55.2045
CTR: val -31.6422 2.6424 -11.97 <1e-32 19.9509
CTR: sod 2.1396 1.3338 1.60 0.1087 6.0079
CTR: dos 16.1248 1.4404 11.19 <1e-28 7.3317
Residual 69.8348

How could we achieve the default result with HypothesisCoding?

m3c = let levels = ["val", "sod", "dos",  "dod"]
  contrasts = Dict(
    :CTR => HypothesisCoding(
      [
         -1/4   3/4 -1/4  -1/4   # b - GM = b - (a+b+c+d)/4 =>  -1/4*a + 3/4*b - 1/4*c - 1/4*d
         -1/4  -1/4  3/4  -1/4   # c - GM = c - (a+b+c+d)/4 =>  -1/4*a - 1/4*b + 3/4*c - 1/4*d
         -1/4  -1/4 -1/4   3/4   # d - GM = d - (a+b+c+d)/4 =>  -1/4*a - 1/4*b - 1/4*c + 3/4*d
      ];
      levels,
      labels=levels[2:end],
    )
  )
  fit(MixedModel, form, dat1; contrasts, progress)
end
Est. SE z p σ_Subj
(Intercept) 389.7336 7.0897 54.97 <1e-99 55.1873
CTR: sod 2.1395 1.3332 1.60 0.1085 5.9995
CTR: dos 16.1248 1.4406 11.19 <1e-28 7.3345
CTR: dod 13.3779 1.9314 6.93 <1e-11 12.4546
Residual 69.8349

4.6 Helmert coding

HelmertCoding codes each level as the difference from the average of the lower levels. With the default order of CTR levels we get the following test statistics. These contrasts are orthogonal.

m4 = let levels = ["val", "sod", "dos",  "dod"]
  contrasts = Dict(:CTR => HelmertCoding(; levels))
  fit(MixedModel, form, dat1; contrasts, progress)
end
Est. SE z p σ_Subj
(Intercept) 389.7336 7.0927 54.95 <1e-99 55.2104
CTR: sod 16.8909 1.6438 10.28 <1e-24 11.6254
CTR: dos 10.2920 0.8354 12.32 <1e-34 5.2574
CTR: dod 4.4593 0.6442 6.92 <1e-11 4.1548
Residual 69.8348
+ HeC1: (b - a)/2           # (391 - 358)/2 = 16.5
+ HeC2: (c - (b+a)/2)/3     # (405 - (391 + 358)/2)/3 = 10.17 
+ HeC3: (d - (c+b+a)/3)/4   # (402 - (405 + 391 + 358)/3)/4 = 4.33

We can reconstruct the estimates, but they are scaled by the number of levels involved. With HypothesisCoding we can estimate the “unscaled” differences. Also the labeling of the contrasts is not as informative as they could be.

m4b = let levels = ["val", "sod", "dos",  "dod"]
  contrasts = Dict(
    :CTR => HypothesisCoding(
      [
          -1    1    0   0
         -1/2 -1/2   1   0
         -1/3 -1/3 -1/3  1

      ];
      levels,
      labels= ["2-1", "3-21", "4-321"]
    )
  )
  fit(MixedModel, form, dat1; contrasts, progress)
end
Est. SE z p σ_Subj
(Intercept) 389.7336 7.0915 54.96 <1e-99 55.2009
CTR: 2-1 33.7817 3.2874 10.28 <1e-24 23.2484
CTR: 3-21 30.8761 2.5062 12.32 <1e-34 15.7722
CTR: 4-321 17.8371 2.5765 6.92 <1e-11 16.6183
Residual 69.8348

4.7 Reverse Helmert coding

Reverse HelmertCoding codes each level as the difference from the average of the higher levels. To estimate these effects we simply reverse the order of factor levels. Of course, the contrasts are also orthogonal.

m4c = let levels = reverse(StatsModels.levels(dat1.CTR))
  contrasts = Dict(:CTR => HelmertCoding(; levels))
  fit(MixedModel, form, dat1; contrasts, progress)
end
Est. SE z p σ_Subj
(Intercept) 389.7336 7.0919 54.95 <1e-99 55.2046
CTR: dos 1.3735 1.1071 1.24 0.2147 4.7543
CTR: sod -4.2039 0.6842 -6.14 <1e-09 3.3482
CTR: val -10.5474 0.8807 -11.98 <1e-32 6.6492
Residual 69.8348
+ HeC1:(c - d)/2            # (405 - 402)/2 = 1.5
+ HeC2:(b - (c+d)/2)/3      # (391 - (405 + 402)/2)/3 = -4.17
+ HeC3:(a - (b+c+d)/3/4     # (356  -(391 + 405 + 402)/3)/4 = -10.83

… and the unscaled-by-number-of-levels estimates.

m4d = let levels = ["val", "sod", "dos",  "dod"]
  contrasts = Dict(
    :CTR => HypothesisCoding(
      [
        0    0     1   -1
        0    1   -1/2 -1/2
        1  -1/3  -1/3 -1/3
      ];
      levels,
      labels= ["3-4", "2-34", "1-234"]
    )
  )
  fit(MixedModel, form, dat1; contrasts)
end
Est. SE z p σ_Subj
(Intercept) 389.7336 7.0907 54.96 <1e-99 55.1947
CTR: 3-4 2.7469 2.2141 1.24 0.2147 9.5076
CTR: 2-34 -12.6117 2.0529 -6.14 <1e-09 10.0485
CTR: 1-234 -42.1895 3.5227 -11.98 <1e-32 26.5978
Residual 69.8348

5 Other orthogonal contrasts

For factors with more than four levels there are many options for specifying orthogonal contrasts as long as one proceeds in a top-down strictly hierarchical fashion.

Suppose you have a factor with seven levels and let’s ignore shifting columns. In this case, you have six options for the first contrast, that is 6 vs. 1, 5 vs.2 , 4 vs. 3, 3 vs. 4, 2 vs. 5, and 1 vs. 6 levels. Then, you specify orthogonal contrasts for partitions with more than 2 elements and so on. That is, you don’t specify a contrast that crosses an earlier partition line.

In the following example, after an initial 4 vs 3 partitioning of levels, we specify AnovaCoding for the left and HelmertCoding for the right partition.

contrasts = Dict(
  :CTR => HypothesisCoding(
    [
      -1/4 -1/4 -1/4 -1/4 +1/3 +1/3 +1/3
      -1/2 -1/2 +1/2 +1/2    0    0    0
      -1/2 +1/2 -1/2 +1/2    0    0    0
      +1/2 -1/2 -1/2 +1/2    0    0    0
         0    0    0    0   -1   +1    0
         0    0    0    0 -1/2 -1/2    1
    ];
    levels=["A1", "A2", "A3", "A4", "A5", "A6", "A7"],
    labels=["c567.1234", "B", "C", "BxC", "c6.5", "c6.56"],
  ),
);

There are two rules that hold for all orthogonal contrasts:

  1. The weights within rows sum to zero.
  2. For all pairs of rows, the sum of the products of weights in the same columns sums to zero.

5.1 Anova Coding

Factorial designs (i.e., lab experiments) are traditionally analyzed with analysis of variance. The test statistics of main effects and interactions are based on an orthogonal set of contrasts. We specify them with HypothesisCoding.

5.1.1 A(2) x B(2)

An A(2) x B(2) design can be recast as an F(4) design with the levels (A1-B1, A1-B2, A2-B1, A2-B2). The following contrast specification returns estimates for the main effect of A, the main effect of B, and the interaction of A and B. In a figure With A on the x-axis and the levels of B shown as two lines, the interaction tests the null hypothesis that the two lines are parallel. A positive coefficient implies overadditivity (diverging lines toward the right) and a negative coefficient underadditivity (converging lines).

m5 = let levels = ["val", "sod", "dos", "dod"]
  contrasts = Dict(
    :CTR => HypothesisCoding(
      [
        -1 -1 +1 +1          # A
        -1 +1 -1 +1          # B
        +1 -1 -1 +1          # A x B
      ];
      levels,
      labels=["A", "B", "AxB"],
    ),
  )
  fit(MixedModel, form, dat1; contrasts, progress)
end
Est. SE z p σ_Subj
(Intercept) 389.7336 7.0918 54.96 <1e-99 55.2033
CTR: A 59.0052 5.1831 11.38 <1e-29 36.2122
CTR: B 31.0348 4.6755 6.64 <1e-10 31.7183
CTR: AxB -36.5287 3.0930 -11.81 <1e-31 16.0087
Residual 69.8348

It is also helpful to see the corresponding layout of the four means for the interaction of A and B (i.e., the third contrast)

        B1     B2
   A1   +1     -1
   A2   -1     +1

Thus, interaction tests whether the difference between main diagonal and minor diagonal is different from zero.

5.1.2 A(2) x B(2) x C(2)

Going beyond the four level factor; it is also helpful to see the corresponding layout of the eight means for the interaction of A and B and C.

          C1              C2
      B1     B2        B1     B2
 A1   +1     -1   A1   -1     +1
 A2   -1     +1   A2   +1     -1

5.2 Nested coding

Nested contrasts are often specified as follow up as post-hoc tests for ANOVA interactions. They are orthogonal. We specify them with HypothesisCoding.

An A(2) x B(2) design can be recast as an F(4) design with the levels (A1-B1, A1-B2, A2-B1, A2-B2). The following contrast specification returns an estimate for the main effect of A and the effects of B nested in the two levels of A. In a figure With A on the x-axis and the levels of B shown as two lines, the second contrast tests whether A1-B1 is different from A1-B2 and the third contrast tests whether A2-B1 is different from A2-B2.

m6 = let levels = ["val", "sod", "dos", "dod"]
  contrasts = Dict(
    :CTR => HypothesisCoding(
      [
        -1 -1 +1 +1
        -1 +1  0  0
         0  0 +1 -1
      ];
      levels,
      labels=["do_so", "spt", "grv"],
    ),
  )
  fit(MixedModel, form, dat1; contrasts, progress)
end
Est. SE z p σ_Subj
(Intercept) 389.7336 7.0913 54.96 <1e-99 55.1998
CTR: do_so 59.0052 5.1824 11.39 <1e-29 36.2064
CTR: spt 33.7817 3.2873 10.28 <1e-24 23.2479
CTR: grv 2.7470 2.2142 1.24 0.2148 9.5100
Residual 69.8348

The three contrasts for one main effect and two nested contrasts are orthogonal. There is no test of the interaction (parallelism).

6 An Example for a complex example: Group(2) x A(2) x B(2)

Three factors:

  • G(roup): G1, G2 - between subjects
  • A: A1, A2, - within subjects
  • B: B1, B2, B3 - within subjects

2 x 3 = 6 measures / subject

dat2 = dataset(:exp_2x2x3)
Arrow.Table with 396 rows, 5 columns, and schema:
 :Subj   String
 :Group  String
 :A      String
 :B      String
 :dv     Float64

We select an LMM supported by the data.

cntrst2 = Dict(
    :Group => SeqDiffCoding(; levels=["G1", "G2"]),
    :A => SeqDiffCoding(; levels=["A1", "A2"]),
    :B => SeqDiffCoding(; levels=["B1", "B2", "B3"]),
  )

f6_cpx = @formula(dv ~ 1 + Group*A*B   + (1 + A+B | Subj));
f6_zcp = @formula(dv ~ 1 + Group*A*B   + zerocorr(1 + A+B | Subj));
f6_ovi = @formula(dv ~ 1 + Group*A*B   +  (1 | Subj));
m6_cpx = fit(MixedModel, f6_cpx, dat2; contrasts=cntrst2)
Est. SE z p σ_Subj
(Intercept) 29.5404 1.0697 27.62 <1e-99 8.5500
Group: G2 -6.3605 2.1394 -2.97 0.0029
A: A2 -0.0074 0.3796 -0.02 0.9844 0.3183
B: B2 0.1856 0.5132 0.36 0.7175 1.8073
B: B3 -0.9345 0.4665 -2.00 0.0451 0.5001
Group: G2 & A: A2 0.2392 0.7591 0.32 0.7527
Group: G2 & B: B2 1.9668 1.0263 1.92 0.0553
Group: G2 & B: B3 0.0872 0.9330 0.09 0.9256
A: A2 & B: B2 3.5336 0.9248 3.82 0.0001
A: A2 & B: B3 0.5232 0.9248 0.57 0.5715
Group: G2 & A: A2 & B: B2 2.8335 1.8496 1.53 0.1255
Group: G2 & A: A2 & B: B3 -2.3042 1.8496 -1.25 0.2128
Residual 3.7548
issingular(m6_cpx)
true
m6_zcp = fit(MixedModel, f6_zcp, dat2; contrasts=cntrst2)
Est. SE z p σ_Subj
(Intercept) 29.5404 1.0698 27.61 <1e-99 8.5483
Group: G2 -6.3605 2.1395 -2.97 0.0030
A: A2 -0.0074 0.3805 -0.02 0.9845 0.0000
B: B2 0.1856 0.4992 0.37 0.7100 1.4527
B: B3 -0.9345 0.4660 -2.01 0.0449 0.0000
Group: G2 & A: A2 0.2392 0.7610 0.31 0.7533
Group: G2 & B: B2 1.9668 0.9984 1.97 0.0488
Group: G2 & B: B3 0.0872 0.9321 0.09 0.9255
A: A2 & B: B2 3.5336 0.9321 3.79 0.0001
A: A2 & B: B3 0.5232 0.9321 0.56 0.5745
Group: G2 & A: A2 & B: B2 2.8335 1.8641 1.52 0.1285
Group: G2 & A: A2 & B: B3 -2.3042 1.8641 -1.24 0.2164
Residual 3.7843
issingular(m6_zcp)
true
m6_ovi = fit(MixedModel, f6_ovi, dat2; contrasts=cntrst2)
Est. SE z p σ_Subj
(Intercept) 29.5404 1.0698 27.61 <1e-99 8.5428
Group: G2 -6.3605 2.1395 -2.97 0.0030
A: A2 -0.0074 0.3879 -0.02 0.9848
B: B2 0.1856 0.4751 0.39 0.6960
B: B3 -0.9345 0.4751 -1.97 0.0492
Group: G2 & A: A2 0.2392 0.7758 0.31 0.7579
Group: G2 & B: B2 1.9668 0.9502 2.07 0.0385
Group: G2 & B: B3 0.0872 0.9502 0.09 0.9269
A: A2 & B: B2 3.5336 0.9502 3.72 0.0002
A: A2 & B: B3 0.5232 0.9502 0.55 0.5819
Group: G2 & A: A2 & B: B2 2.8335 1.9004 1.49 0.1360
Group: G2 & A: A2 & B: B3 -2.3042 1.9004 -1.21 0.2253
Residual 3.8580
issingular(m6_ovi)
false
MixedModels.likelihoodratiotest(m6_ovi, m6_zcp, m6_cpx)
model-dof -2 logLik χ² χ²-dof P(>χ²)
dv ~ 1 + Group + A + B + Group & A + Group & B + A & B + Group & A & B + (1 | Subj) 14 -2419
dv ~ 1 + Group + A + B + Group & A + Group & B + A & B + Group & A & B + zerocorr(1 + A + B | Subj) 17 -2418 1 3 0.8302
dv ~ 1 + Group + A + B + Group & A + Group & B + A & B + Group & A & B + (1 + A + B | Subj) 23 -2416 1 6 0.9597

There is a significant interaction between A and the first contrast of B (i.e., B2 - B1). The interaction is not significant for A and the second contrast of B (i.e., B3 - B2). This implies that the left pair of lines in the following figure is statistically not parallel and that we do not have sufficient evidence that the right pair of lines is not parallel.

DataFrame(coeftable(m6_ovi))[9:10, :]
2×5 DataFrame
Row Name Coef. Std. Error z Pr(>|z|)
String Float64 Float64 Float64 Float64
1 A: A2 & B: B2 3.53363 0.950202 3.71882 0.000200157
2 A: A2 & B: B3 0.523243 0.950202 0.550665 0.581863
using Chain
tbl1 = @chain DataFrame(dat2) begin
  groupby(_, [:Subj, :A, :B])
  combine(_, nrow => :n, :dv => mean => :dv)
  groupby(_, [:A, :B])
  combine(_, 
          :dv => mean => :dv_M,
          :dv => std => :dv_SD,
          :dv => sem => :dv_SE)
end 

fig = Figure(; size=(300, 300))
plt = data(tbl1) * mapping(:B, :dv_M; color=:A) * (visual(Lines) + visual(Scatter))
draw!(fig, plt)
fig

7 Exercises

  1. Intercept meaning. Fit the model once with DummyCoding and once with EffectsCoding for the four-level CTR factor. What does the intercept estimate in each case?

With DummyCoding the intercept is the mean of the reference level; with EffectsCoding it is the grand mean (the unweighted average across levels). The slopes change meaning accordingly — differences from the reference level versus deviations from the grand mean.

  1. Custom contrasts. Define a HypothesisCoding that encodes the spatial, object-based, and attraction comparisons described in the text, and verify that the resulting coefficients match those comparisons.

Supply a 3×4 hypothesis matrix whose rows correspond to the three theoretically motivated comparisons (in the appropriately ordered factor levels). After fitting, check that each estimated coefficient equals the corresponding difference of cell means — a good habit for confirming a custom coding does what you intended.


References

Brehm, L., & Alday, P. M. (2022). Contrast coding choices in a decade of mixed models. Journal of Memory and Language, 125, 104334. https://doi.org/10.1016/j.jml.2022.104334
Hays, W. L. (1973). Statistics for the social sciences (Vol. 410). Holt Rinehart; Winston.
Kliegl, R., Kushela, J., & Laubrock, J. (2015). Object orientation and target size modulate the speed of visual attention. Department of Psychology, University of Potsdam.
Kliegl, R., Wei, P., Dambacher, M., Yan, M., & Zhou, X. (2011). Experimental effects and individual differences in linear mixed models: Estimating the relationship between spatial, object, and attraction effects in visual attention. Frontiers in Psychology. https://doi.org/10.3389/fpsyg.2010.00238
Schad, D. J., Vasishth, S., Hohenstein, S., & Kliegl, R. (2020). How to capitalize on a priori contrasts in linear (mixed) models: A tutorial. Journal of Memory and Language, 110, 104038. https://doi.org/10.1016/j.jml.2019.104038

This page was rendered from git revision e563d55 using Quarto 1.9.38 and Julia 1.12.6.

Back to top