Ling 204: session 01

Author
Affiliations

UC Santa Barbara

JLU Giessen

Published

07 Jan 2026 11-34-56

1 Session 01: Recap of fixef regression modeling

1.1 Linear modeling

To do a quick recap of linear modeling, we will look at a data set in _input/rts.csv involving reaction times as the response variable:

rm(list=ls(all.names=TRUE))
pkgs2load <- c("car", "effects", "emmeans", "magrittr", "multcomp")
suppressMessages(sapply(pkgs2load, library,
   character.only=TRUE, logical.return=TRUE, quietly=TRUE)); rm(pkgs2load)
     car  effects  emmeans magrittr multcomp
    TRUE     TRUE     TRUE     TRUE     TRUE 
summary(d <- read.delim(   # summarize d, the result of loading
   file="_input/rts.csv",  # this file
   stringsAsFactors=TRUE)) # change categorical variables into factors
      CASE            RT             LENGTH         LANGUAGE         GROUP
 Min.   :   1   Min.   : 271.0   Min.   :3.000   english:4023   english :3961
 1st Qu.:2150   1st Qu.: 505.0   1st Qu.:4.000   spanish:3977   heritage:4039
 Median :4310   Median : 595.0   Median :5.000
 Mean   :4303   Mean   : 661.5   Mean   :5.198
 3rd Qu.:6450   3rd Qu.: 732.0   3rd Qu.:6.000
 Max.   :8610   Max.   :4130.0   Max.   :9.000
                NA's   :248
      CORRECT        FREQPMW           ZIPFFREQ     SITE
 correct  :7749   Min.   :   1.00   Min.   :3.000   a:2403
 incorrect: 251   1st Qu.:  17.00   1st Qu.:4.230   b:2815
                  Median :  42.00   Median :4.623   c:2782
                  Mean   :  81.14   Mean   :4.591
                  3rd Qu.: 101.00   3rd Qu.:5.004
                  Max.   :1152.00   Max.   :6.061
                                                            

You can see what the variables/columns contain in the file _input/rts.r. Specifically, we are asking, does the (logged) reaction time to a word (in ms) vary as a function of

  • the Zipf frequency of that word (ZIPFFREQ);
  • the length of that word (LENGTH);
  • the language that word was presented in (LANGUAGE: spanish vs. english);
  • the speaker group that words was presented to (GROUP: english vs. heritage);
  • any pairwise interaction of these predictors? (Specifically, we are expecting that the effect of LENGTH will be stronger for when LANGUAGE is spanish.)

1.1.1 Exploration & preparation

As always, a decent amount of exploration is required to prepare the data for a hopefully unproblematic modeling process.

1.1.1.1 The response variable

We begin our exploration with the response variable RT. An initial histogram shows that we will probably want to transform the response given the long right tail. A log transformation seems to work well enough so that’s what we’ll go with:

par(mfrow=c(1, 2)) # make the plotting window have 1 rows & 2 columns
hist(d$RT, main="")
hist(log2(d$RT), main="")
par(mfrow=c(1, 1)) # reset to default
d$RT_log <- log2(d$RT)
Figure 1: The distribution of RT

1.1.1.2 Numeric predictors

Let’s now look at the numeric predictors. We begin with ZIPFFREQ, which looks ok:

par(mfrow=c(1, 2)) # make the plotting window have 1 rows & 2 columns
hist(d$ZIPFFREQ) # hist(d$ZIPFFREQ, breaks="FD") # usually, also check the bin width
plot(main="RT (in ms, logged) as a function of Zipf frequency",
   pch=16, col="#00000020",
   xlab="Zipf frequency"    , x=d$ZIPFFREQ,
   ylab="RT (in ms, logged)", y=d$RT_log); grid()
   abline(lm(d$RT_log ~ d$ZIPFFREQ), col="green", lwd=2)
par(mfrow=c(1, 1)) # reset to default
Figure 2: The distribution of ZIPFFREQ & RT

Let’s now look at LENGTH, which looks ok:

par(mfrow=c(1, 2)) # make the plotting window have 1 rows & 2 columns
hist(d$LENGTH) # hist(d$LENGTH, breaks="FD") # usually, also check the bin width
plot(main="RT (in ms, logged) as a function of Length",
   pch=16, col="#00000020",
   xlab="Length"            , x=jitter(d$LENGTH),
   ylab="RT (in ms, logged)", y=d$RT_log); grid()
   abline(lm(d$RT_log ~ d$LENGTH), col="green", lwd=2)
par(mfrow=c(1, 1)) # reset to default
Figure 3: The distribution of LENGTH & RT

1.1.1.3 Categorical predictors

Let’s now look at the categorical predictors. We begin with LANGUAGE, which looks ok:

qwe <- boxplot(main="RT (in ms, logged) as a function of stimulus language",
   d$RT_log ~ d$LANGUAGE, notch=TRUE,
   xlab="Stimulus language",
   ylab="RT (in ms, logged)"); grid()
Figure 4: The distribution of LANGUAGE & RT

Let’s now look at GROUP, which looks ok:

qwe <- boxplot(main="RT (in ms, logged) as a function of speaker group",
   d$RT_log ~ d$GROUP, notch=TRUE,
   xlab="Speaker group",
   ylab="RT (in ms, logged)"); grid()
Figure 5: The distribution of GROUP & RT

Do we have a good distribution for the interaction of the categorical variables? Yes:

table(d$LANGUAGE, d$GROUP)

          english heritage
  english    2005     2018
  spanish    1956     2021

1.1.1.4 Deviance

First, we’ll quickly compute the deviance of the null model:

deviance(m_00 <- lm(RT_log ~ 1, data=d)) # 1581.951
[1] 1581.951

1.1.2 Model selection

The initial model involves all predictors and all their interactions:

summary(m_01 <- lm(       # make/summarize the linear model m_01:
   RT_log ~ 1 +           # RT_log ~ an overall intercept (1)
   # & these predictors & their pairwise interactions
   (ZIPFFREQ+LANGUAGE+GROUP+LENGTH)^2,
   data=d,                # those vars are in d
   na.action=na.exclude)) # skip cases with NA/missing data (good habit)

Call:
lm(formula = RT_log ~ 1 + (ZIPFFREQ + LANGUAGE + GROUP + LENGTH)^2,
    data = d, na.action = na.exclude)

Residuals:
     Min       1Q   Median       3Q      Max
-1.25135 -0.27954 -0.06143  0.20982  2.55535

Coefficients:
                               Estimate Std. Error t value Pr(>|t|)
(Intercept)                    9.007342   0.213938  42.103  < 2e-16 ***
ZIPFFREQ                       0.006682   0.043985   0.152  0.87925
LANGUAGEspanish                0.308147   0.103832   2.968  0.00301 **
GROUPheritage                  0.102615   0.098841   1.038  0.29922
LENGTH                         0.106338   0.040699   2.613  0.00900 **
ZIPFFREQ:LANGUAGEspanish      -0.010640   0.018610  -0.572  0.56751
ZIPFFREQ:GROUPheritage        -0.001619   0.017674  -0.092  0.92702
ZIPFFREQ:LENGTH               -0.019935   0.008436  -2.363  0.01814 *
LANGUAGEspanish:GROUPheritage -0.206348   0.019821 -10.410  < 2e-16 ***
LANGUAGEspanish:LENGTH         0.018155   0.010490   1.731  0.08355 .
GROUPheritage:LENGTH           0.001545   0.009436   0.164  0.86999
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.4234 on 7741 degrees of freedom
  (248 observations deleted due to missingness)
Multiple R-squared:  0.1227,    Adjusted R-squared:  0.1216
F-statistic: 108.3 on 10 and 7741 DF,  p-value: < 2.2e-16
drop1(m_01,           # drop each droppable predictor at a time from m_01 &
   test="F") %>%      # test its significance w/ an F-test
   "["(order(.[,6]),) # order by p-values
Single term deletions

Model:
RT_log ~ 1 + (ZIPFFREQ + LANGUAGE + GROUP + LENGTH)^2
                  Df Sum of Sq    RSS    AIC  F value  Pr(>F)
LANGUAGE:GROUP     1   19.4296 1407.2 -13207 108.3753 < 2e-16 ***
ZIPFFREQ:LENGTH    1    1.0013 1388.8 -13310   5.5850 0.01814 *
LANGUAGE:LENGTH    1    0.5370 1388.3 -13312   2.9953 0.08355 .
ZIPFFREQ:LANGUAGE  1    0.0586 1387.9 -13315   0.3269 0.56751
GROUP:LENGTH       1    0.0048 1387.8 -13315   0.0268 0.86999
ZIPFFREQ:GROUP     1    0.0015 1387.8 -13315   0.0084 0.92702
<none>                         1387.8 -13313
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Let’s fit the 2nd model with that interaction ZIPFFREQ:GROUP deleted:

summary(m_02 <- update( # make m_02 an update of
   m_01, .~.            # m_01, namely all of it (.~.), but then
   - ZIPFFREQ:GROUP))   # remove this interaction

Call:
lm(formula = RT_log ~ ZIPFFREQ + LANGUAGE + GROUP + LENGTH +
    ZIPFFREQ:LANGUAGE + ZIPFFREQ:LENGTH + LANGUAGE:GROUP + LANGUAGE:LENGTH +
    GROUP:LENGTH, data = d, na.action = na.exclude)

Residuals:
     Min       1Q   Median       3Q      Max
-1.25164 -0.27940 -0.06149  0.20977  2.55564

Coefficients:
                               Estimate Std. Error t value Pr(>|t|)
(Intercept)                    9.011315   0.209483  43.017  < 2e-16 ***
ZIPFFREQ                       0.005864   0.043065   0.136  0.89170
LANGUAGEspanish                0.308391   0.103791   2.971  0.00297 **
GROUPheritage                  0.094727   0.048511   1.953  0.05089 .
LENGTH                         0.106293   0.040694   2.612  0.00902 **
ZIPFFREQ:LANGUAGEspanish      -0.010686   0.018602  -0.574  0.56567
ZIPFFREQ:LENGTH               -0.019933   0.008435  -2.363  0.01814 *
LANGUAGEspanish:GROUPheritage -0.206289   0.019810 -10.414  < 2e-16 ***
LANGUAGEspanish:LENGTH         0.018146   0.010489   1.730  0.08368 .
GROUPheritage:LENGTH           0.001621   0.009399   0.172  0.86311
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.4234 on 7742 degrees of freedom
  (248 observations deleted due to missingness)
Multiple R-squared:  0.1227,    Adjusted R-squared:  0.1217
F-statistic: 120.3 on 9 and 7742 DF,  p-value: < 2.2e-16
anova(m_01, m_02, # compare m_01 to m_02
   test="F")      # using an F-test
Analysis of Variance Table

Model 1: RT_log ~ 1 + (ZIPFFREQ + LANGUAGE + GROUP + LENGTH)^2
Model 2: RT_log ~ ZIPFFREQ + LANGUAGE + GROUP + LENGTH + ZIPFFREQ:LANGUAGE +
    ZIPFFREQ:LENGTH + LANGUAGE:GROUP + LANGUAGE:LENGTH + GROUP:LENGTH
  Res.Df    RSS Df  Sum of Sq      F Pr(>F)
1   7741 1387.8
2   7742 1387.8 -1 -0.0015043 0.0084  0.927
drop1(m_02,           # drop each droppable predictor at a time from m_02 &
   test="F") %>%      # test its significance w/ an F-test
   "["(order(.[,6]),) # order by p-values
Single term deletions

Model:
RT_log ~ ZIPFFREQ + LANGUAGE + GROUP + LENGTH + ZIPFFREQ:LANGUAGE +
    ZIPFFREQ:LENGTH + LANGUAGE:GROUP + LANGUAGE:LENGTH + GROUP:LENGTH
                  Df Sum of Sq    RSS    AIC  F value  Pr(>F)
LANGUAGE:GROUP     1   19.4391 1407.3 -13209 108.4418 < 2e-16 ***
ZIPFFREQ:LENGTH    1    1.0011 1388.8 -13312   5.5846 0.01814 *
LANGUAGE:LENGTH    1    0.5365 1388.3 -13314   2.9928 0.08368 .
ZIPFFREQ:LANGUAGE  1    0.0592 1387.9 -13317   0.3300 0.56567
GROUP:LENGTH       1    0.0053 1387.8 -13317   0.0297 0.86311
<none>                         1387.8 -13315
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Let’s fit the 3rd model with the ns interaction GROUP:LENGTH deleted:

summary(m_03 <- update( # make m_03 an update of
   m_02, .~.            # m_02, namely all of it (.~.), but then
   - GROUP:LENGTH))     # remove this interaction

Call:
lm(formula = RT_log ~ ZIPFFREQ + LANGUAGE + GROUP + LENGTH +
    ZIPFFREQ:LANGUAGE + ZIPFFREQ:LENGTH + LANGUAGE:GROUP + LANGUAGE:LENGTH,
    data = d, na.action = na.exclude)

Residuals:
     Min       1Q   Median       3Q      Max
-1.25124 -0.27896 -0.06132  0.20991  2.55518

Coefficients:
                               Estimate Std. Error t value Pr(>|t|)
(Intercept)                    9.006924   0.207917  43.320  < 2e-16 ***
ZIPFFREQ                       0.005935   0.043061   0.138  0.89038
LANGUAGEspanish                0.307637   0.103692   2.967  0.00302 **
GROUPheritage                  0.102765   0.013427   7.654 2.19e-14 ***
LENGTH                         0.107184   0.040362   2.656  0.00793 **
ZIPFFREQ:LANGUAGEspanish      -0.010645   0.018600  -0.572  0.56711
ZIPFFREQ:LENGTH               -0.019949   0.008434  -2.365  0.01804 *
LANGUAGEspanish:GROUPheritage -0.205486   0.019254 -10.673  < 2e-16 ***
LANGUAGEspanish:LENGTH         0.018175   0.010487   1.733  0.08313 .
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.4234 on 7743 degrees of freedom
  (248 observations deleted due to missingness)
Multiple R-squared:  0.1227,    Adjusted R-squared:  0.1218
F-statistic: 135.4 on 8 and 7743 DF,  p-value: < 2.2e-16
anova(m_02, m_03, # compare m_02 to m_03
   test="F")      # using an F-test
Analysis of Variance Table

Model 1: RT_log ~ ZIPFFREQ + LANGUAGE + GROUP + LENGTH + ZIPFFREQ:LANGUAGE +
    ZIPFFREQ:LENGTH + LANGUAGE:GROUP + LANGUAGE:LENGTH + GROUP:LENGTH
Model 2: RT_log ~ ZIPFFREQ + LANGUAGE + GROUP + LENGTH + ZIPFFREQ:LANGUAGE +
    ZIPFFREQ:LENGTH + LANGUAGE:GROUP + LANGUAGE:LENGTH
  Res.Df    RSS Df  Sum of Sq      F Pr(>F)
1   7742 1387.8
2   7743 1387.8 -1 -0.0053296 0.0297 0.8631
drop1(m_03,           # drop each droppable predictor at a time from m_03 &
   test="F") %>%      # test its significance w/ an F-test
   "["(order(.[,6]),) # order by p-values
Single term deletions

Model:
RT_log ~ ZIPFFREQ + LANGUAGE + GROUP + LENGTH + ZIPFFREQ:LANGUAGE +
    ZIPFFREQ:LENGTH + LANGUAGE:GROUP + LANGUAGE:LENGTH
                  Df Sum of Sq    RSS    AIC  F value  Pr(>F)
LANGUAGE:GROUP     1   20.4159 1408.2 -13206 113.9055 < 2e-16 ***
ZIPFFREQ:LENGTH    1    1.0028 1388.8 -13314   5.5948 0.01804 *
LANGUAGE:LENGTH    1    0.5383 1388.4 -13316   3.0035 0.08313 .
ZIPFFREQ:LANGUAGE  1    0.0587 1387.9 -13319   0.3276 0.56711
<none>                         1387.8 -13317
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Let’s fit the 4th model with the ns interaction ZIPFFREQ:LANGUAGE deleted:

summary(m_04 <- update(  # make m_04 an update of
   m_03, .~.             # m_03, namely all of it (.~.), but then
   - ZIPFFREQ:LANGUAGE)) # remove this interaction

Call:
lm(formula = RT_log ~ ZIPFFREQ + LANGUAGE + GROUP + LENGTH +
    ZIPFFREQ:LENGTH + LANGUAGE:GROUP + LANGUAGE:LENGTH, data = d,
    na.action = na.exclude)

Residuals:
     Min       1Q   Median       3Q      Max
-1.24964 -0.27871 -0.06224  0.21043  2.55420

Coefficients:
                               Estimate Std. Error t value Pr(>|t|)
(Intercept)                    9.002136   0.207740  43.334  < 2e-16 ***
ZIPFFREQ                       0.007370   0.042986   0.171  0.86387
LANGUAGEspanish                0.257516   0.055524   4.638 3.58e-06 ***
GROUPheritage                  0.102783   0.013426   7.655 2.16e-14 ***
LENGTH                         0.113334   0.038903   2.913  0.00359 **
ZIPFFREQ:LENGTH               -0.021360   0.008065  -2.648  0.00811 **
LANGUAGEspanish:GROUPheritage -0.205373   0.019252 -10.668  < 2e-16 ***
LANGUAGEspanish:LENGTH         0.018385   0.010480   1.754  0.07943 .
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.4233 on 7744 degrees of freedom
  (248 observations deleted due to missingness)
Multiple R-squared:  0.1227,    Adjusted R-squared:  0.1219
F-statistic: 154.7 on 7 and 7744 DF,  p-value: < 2.2e-16
anova(m_03, m_04, # compare m_03 to m_04
   test="F")      # using an F-test
Analysis of Variance Table

Model 1: RT_log ~ ZIPFFREQ + LANGUAGE + GROUP + LENGTH + ZIPFFREQ:LANGUAGE +
    ZIPFFREQ:LENGTH + LANGUAGE:GROUP + LANGUAGE:LENGTH
Model 2: RT_log ~ ZIPFFREQ + LANGUAGE + GROUP + LENGTH + ZIPFFREQ:LENGTH +
    LANGUAGE:GROUP + LANGUAGE:LENGTH
  Res.Df    RSS Df Sum of Sq      F Pr(>F)
1   7743 1387.8
2   7744 1387.9 -1 -0.058712 0.3276 0.5671
drop1(m_04,           # drop each droppable predictor at a time from m_04 &
   test="F") %>%      # test its significance w/ an F-test
   "["(order(.[,6]),) # order by p-values
Single term deletions

Model:
RT_log ~ ZIPFFREQ + LANGUAGE + GROUP + LENGTH + ZIPFFREQ:LENGTH +
    LANGUAGE:GROUP + LANGUAGE:LENGTH
                Df Sum of Sq    RSS    AIC  F value    Pr(>F)
LANGUAGE:GROUP   1   20.3956 1408.3 -13208 113.8018 < 2.2e-16 ***
ZIPFFREQ:LENGTH  1    1.2570 1389.1 -13314   7.0135  0.008106 **
LANGUAGE:LENGTH  1    0.5515 1388.4 -13318   3.0773  0.079429 .
<none>                       1387.9 -13319
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

We’re done now – why? Because remember that we said “we are expecting that the effect of LENGTH will be stronger for when LANGUAGE is spanish”, meaning if the coefficient for LANGUAGEspanish:LENGTH is positive – which it is – we can take half its p-value and then it is in fact significant! We call the final model m_final (for easy copying and pasting across analyses) and do some housekeeping:

m_final <- m_04; rm(m_04); invisible(gc())

Normally, we would do some regression diagnostics now – I have to skip those now to discuss how to interpret the model, but we will return to this later! For now, we compute the confidence intervals …

Confint(m_final)
                                  Estimate        2.5 %       97.5 %
(Intercept)                    9.002135551  8.594909888  9.409361213
ZIPFFREQ                       0.007369857 -0.076893532  0.091633246
LANGUAGEspanish                0.257516141  0.148673274  0.366359008
GROUPheritage                  0.102783095  0.076463530  0.129102661
LENGTH                         0.113334392  0.037073349  0.189595435
ZIPFFREQ:LENGTH               -0.021359653 -0.037170042 -0.005549264
LANGUAGEspanish:GROUPheritage -0.205372665 -0.243111112 -0.167634217
LANGUAGEspanish:LENGTH         0.018384612 -0.002159247  0.038928470

… , add the predictions of the model to the original data frame:

d$PRED <-   # make d$PRED the
   predict( # predictions for all cases
   m_final) # based on m_final
head(d) # check the result
  CASE   RT LENGTH LANGUAGE    GROUP CORRECT FREQPMW ZIPFFREQ SITE    RT_log
1    1  748      5  spanish heritage correct      19 4.278754    b  9.546894
2    2  408      5  spanish heritage correct      78 4.892095    b  8.672425
3    3  483      6  spanish heritage correct     233 5.367356    b  8.915879
4    4 1266      4  spanish heritage correct     133 5.123852    b 10.306062
5    5  636      6  spanish heritage correct      14 4.146128    b  9.312883
6    6  395      6  spanish heritage correct      67 4.826075    b  8.625709
      PRED
1 9.390227
2 9.329244
3 9.299064
4 9.283925
5 9.446573
6 9.364444

…, and generate an often super-useful kind of data frame that I always call nd (for new data) that contains all combinations of

  • all levels of all categorical predictors and
  • useful values of all numeric predictors (where ‘useful’ is usually 0, 1 and the mean):
nd <- expand.grid(
   ZIPFFREQ=c(0, 1, mean(d$ZIPFFREQ)),
   LENGTH  =c(0, 1, mean(d$LENGTH)),
   GROUP   =levels(d$GROUP),
   LANGUAGE=levels(d$LANGUAGE))

To that, we add the predictions the model makes for all these combinations and give it some nice(r) names:

nd <- cbind(nd,
   predict(m_final, newdata=nd, interval="confidence"))
names(nd)[5:7] <- c("PRED", "PRED_LOW", "PRED_UPP")
head(nd)
  ZIPFFREQ LENGTH   GROUP LANGUAGE     PRED PRED_LOW PRED_UPP
1 0.000000      0 english  english 9.002136 8.594910 9.409361
2 1.000000      0 english  english 9.009505 8.684218 9.334792
3 4.590539      0 english  english 9.035967 8.950325 9.121610
4 0.000000      1 english  english 9.115470 8.782580 9.448360
5 1.000000      1 english  english 9.101480 8.835596 9.367365
6 4.590539      1 english  english 9.051249 8.981904 9.120594

A maybe (!) nicer way to represent this might be this, but whether this is nicer or not is probably subjective:

with(nd,    # use the data in nd and
   tapply(  # apply
      PRED, # to the predicted reaction times
      # a grouping like this: the numerics in the rows & columns, the factors in the layers/slices
      list(round(ZIPFFREQ, 2), round(LENGTH, 2), "GROUP"=GROUP, "LANGUAGE"=LANGUAGE),
      # just list the values, but rounded to 2 decimals
      c)) %>% round(2)
, , GROUP = english, LANGUAGE = english


          0    1  5.2
  0    9.00 9.12 9.59
  1    9.01 9.10 9.49
  4.59 9.04 9.05 9.12

, , GROUP = heritage, LANGUAGE = english


          0    1  5.2
  0    9.10 9.22 9.69
  1    9.11 9.20 9.59
  4.59 9.14 9.15 9.22

, , GROUP = english, LANGUAGE = spanish


          0    1  5.2
  0    9.26 9.39 9.94
  1    9.27 9.38 9.84
  4.59 9.29 9.33 9.47

, , GROUP = heritage, LANGUAGE = spanish


          0    1  5.2
  0    9.16 9.29 9.84
  1    9.16 9.27 9.74
  4.59 9.19 9.22 9.37

1.1.3 Model evaluation & interpretation

Let’s look at the results.

1.1.3.1 LANGUAGE:GROUP

Let’s visualize the nature of the effect of LANGUAGE:GROUP based on the predictions from effects:

lagr_d <- data.frame(   # make lagr_d a data frame of
   lagr <- effect(      # the effect
      "LANGUAGE:GROUP", # of LANGUAGE:GROUP
      m_final))         # in m_final
plot(lagr,              # plot the effect of the interaction
   ylim=range(d$RT_log, na.rm=TRUE), # w/ these y-axis limits
   grid=TRUE)           # & w/ a grid
plot(lagr,              # plot the effect of the interaction
   ylim=range(d$RT_log, na.rm=TRUE), # w/ these y-axis limits
   x.var="GROUP",       # w/ this predictor on the x-axis
   grid=TRUE)           # & w/ a grid
Figure 6: The interaction LANGUAGE:GROUP
Figure 7: The interaction LANGUAGE:GROUP

And here’s a version (of the first of the two effects plots) with everything: observed values, predicted values, and the confidence intervals of the predictions:

# split data & predictions up by levels of GROUP:
d_split <- split(d, d$GROUP)
lagr_d_split <- split(lagr_d, lagr_d$GROUP, drop=TRUE)
par(mfrow=c(1, 2))
stripchart(main="RT ~ LANGUAGE for GROUP='english'",  # stripchart w/ main heading
   pch=16, col="#00000020", ylim=c(8, 12),            # filled semi-transparent grey circles, y limits
   d_split$english$RT_log ~ d_split$english$LANGUAGE, # data: RT_log ~ LANG (for eng)
   xlab="Language", ylab="RT (in ms, logged)",          # axis labels
   vertical=TRUE, method="jitter"); grid() # vertical plot & jittered observations, add grid
# add predicted means:
points(seq(lagr_d_split$english$fit), lagr_d_split$english$fit, pch="X", col=c("red", "blue"))
# add confidence intervals:
for (i in seq(lagr_d_split$english$fit)) {  # as many arrows as needed
   arrows(i, lagr_d_split$english$lower[i],          # each from here
          i, lagr_d_split$english$upper[i],          # to here
          code=3, angle=90, col=c("red", "blue")[i]) # both tips w/ 90 degrees
}
stripchart(main="RT ~ LANGUAGE for GROUP='heritage'",   # stripchart w/ main heading
   pch=16, col="#00000020", ylim=c(8, 12),              # filled semi-transparent grey circles, y limits
   d_split$heritage$RT_log ~ d_split$heritage$LANGUAGE, # data: RT_log ~ LANG (for her)
   xlab="Language", ylab="RT (in ms, logged)",          # axis labels
   vertical=TRUE, method="jitter"); grid()              # vertical plot & jittered observations, add grid
# add predicted means:
points(seq(lagr_d_split$heritage$fit), lagr_d_split$heritage$fit, pch="X", col=c("red", "blue"))
# add confidence intervals:
for (i in seq(lagr_d_split$heritage$fit)) {          # as many arrows as needed
   arrows(i, lagr_d_split$heritage$lower[i],         # each from here
          i, lagr_d_split$heritage$upper[i],         # to here
          code=3, angle=90, col=c("red", "blue")[i]) # both tips w/ 90 degrees
}
par(mfrow=c(1, 1))
Figure 8: The interaction LANGUAGE:GROUP

1.1.3.2 LANGUAGE:LENGTH

Let’s visualize the nature of the effect of LANGUAGE:LENGTH based on the predictions from effects:

lale_d <- data.frame(    # make lale_d a data frame of
   lale <- effect(       # the effect
      "LANGUAGE:LENGTH", # of LANGUAGE:LENGTH
      m_final,           # in m_final
      xlevels=list(      # for when
      LENGTH=3:9)))      # LENGTH is these values
lale_d <- lale_d[order(lale_d$LANGUAGE, lale_d$LENGTH),]; lale_d
   LANGUAGE LENGTH      fit          se    lower    upper
1   english      3 9.133232 0.018124463 9.097703 9.168760
3   english      4 9.148109 0.010668181 9.127197 9.169022
5   english      5 9.162987 0.006742156 9.149770 9.176203
7   english      6 9.177864 0.011110169 9.156085 9.199643
9   english      7 9.192742 0.018647994 9.156187 9.229297
11  english      8 9.207619 0.026802838 9.155079 9.260160
13  english      9 9.222497 0.035147819 9.153598 9.291396
2   spanish      3 9.341016 0.015864551 9.309917 9.372115
4   spanish      4 9.374278 0.010881934 9.352947 9.395610
6   spanish      5 9.407541 0.007359139 9.393115 9.421967
8   spanish      6 9.440803 0.007674979 9.425758 9.455848
10  spanish      7 9.474065 0.011517882 9.451487 9.496643
12  spanish      8 9.507327 0.016595958 9.474795 9.539860
14  spanish      9 9.540589 0.022072562 9.497321 9.583858
plot(lale,               # plot the effect of the interaction
   ylim=range(d$RT_log, na.rm=TRUE), # w/ these y-axis limits
   grid=TRUE)            # & w/ a grid
Figure 9: The interaction LANGUAGE:LENGTH

And here a version with everything: observed values, predicted values (red for LANGUAGE: english, blue for LANGUAGE: spanish), and the confidence intervals of the predictions (I’m building this up very stepwise, more so than I might do in actual research):

d_split <- split(d, d$LANGUAGE)
lale_d_split <- split(lale_d, lale_d$LANGUAGE, drop=TRUE)
plot(main="RT ~ LANGUAGE:LENGTH", type="n", # plot nothing
   xlab="Length"            , x=d$LENGTH,         # x-axis
   ylab="RT (in ms, logged)", y=d$RT_log); grid() # y-axis & grid
   # plot observed data points for LANGUAGE being eng:
points(jitter(d_split$eng$LENGTH), d_split$eng$RT_log, pch=16, col="#FF000010")
lines(lale_d_split$eng$LENGTH, lale_d_split$eng$fit, col="red")
# plot observed data points for LANGUAGE being spa:
points(jitter(d_split$spa$LENGTH), d_split$spa$RT_log, pch=16, col="#0000FF10")
lines(lale_d_split$spa$LENGTH, lale_d_split$spa$fit, col="blue")
# plot confidence band for LANGUAGE being eng:
polygon(col="#FF000030", border="red", # semi-transparent grey, for once w/ border
   x=c(   lale_d_split$eng$LENGTH,   # x-coordinates left to right, then
      rev(lale_d_split$eng$LENGTH)), # the same values back (in reverse order)
   y=c(   lale_d_split$eng$lower,    # y-coordinates lower boundary
      rev(lale_d_split$eng$upper)))  # y-coordinates upper boundary
# plot confidence band for LANGUAGE being spa:
polygon(col="#0000FF30", border="blue", # semi-transparent grey, for once w/ border
   x=c(   lale_d_split$spa$LENGTH,   # x-coordinates left to right, then
      rev(lale_d_split$spa$LENGTH)), # the same values back (in reverse order)
   y=c(   lale_d_split$spa$lower,    # y-coordinates lower boundary
      rev(lale_d_split$spa$upper)))  # y-coordinates upper boundary
# plot legend
legend(title="Language", bty="n",
   x=6, xjust=0.5, y=11.5, yjust=0.5, ncol=2,
   legend=levels(d$LANGUAGE), fill=c("red", "blue"))
Figure 10: The interaction LANGUAGE:LENGTH

1.1.3.3 ZIPFFREQ:LENGTH

Let’s visualize the nature of the effect of ZIPFFREQ:LENGTH based on the predictions from effects:

(zile_d <- data.frame(   # make zile_d a data frame of
   zile <- effect(       # the effect
      "ZIPFFREQ:LENGTH", # of ZIPFFREQ:LENGTH
      m_final,           # in m_final
      xlevels=list(      # for when
      ZIPFFREQ=seq(3, 6, 0.25), # ZIPFFREQ is these values
      LENGTH=seq(3, 9, 0.5))))) # LENGTH is these values
    ZIPFFREQ LENGTH      fit          se    lower    upper
1       3.00    3.0 9.325689 0.034081672 9.258879 9.392498
2       3.25    3.0 9.311511 0.029465917 9.253750 9.369273
3       3.50    3.0 9.297334 0.024992078 9.248343 9.346325
4       3.75    3.0 9.283157 0.020752145 9.242477 9.323837
5       4.00    3.0 9.268980 0.016922850 9.235806 9.302153
6       4.25    3.0 9.254802 0.013849115 9.227654 9.281950
7       4.50    3.0 9.240625 0.012120106 9.216866 9.264384
8       4.75    3.0 9.226448 0.012315833 9.202305 9.250590
9       5.00    3.0 9.212270 0.014357798 9.184125 9.240416
10      5.25    3.0 9.198093 0.017615243 9.163563 9.232624
11      5.50    3.0 9.183916 0.021543700 9.141684 9.226147
12      5.75    3.0 9.169739 0.025838906 9.119087 9.220390
13      6.00    3.0 9.155561 0.030345526 9.096076 9.215047
14      3.00    3.5 9.354793 0.028132740 9.299645 9.409941
15      3.25    3.5 9.337945 0.024317877 9.290276 9.385615
16      3.50    3.5 9.321098 0.020614716 9.280688 9.361509
17      3.75    3.5 9.304251 0.017096000 9.270738 9.337764
18      4.00    3.5 9.287404 0.013902490 9.260151 9.314656
19      4.25    3.5 9.270557 0.011313024 9.248380 9.292733
20      4.50    3.5 9.253709 0.009817796 9.234464 9.272955
21      4.75    3.5 9.236862 0.009924365 9.217408 9.256317
22      5.00    3.5 9.220015 0.011588625 9.197298 9.242732
23      5.25    3.5 9.203168 0.014275809 9.175183 9.231152
24      5.50    3.5 9.186320 0.017521431 9.151974 9.220667
25      5.75    3.5 9.169473 0.021068974 9.128172 9.210774
26      6.00    3.5 9.152626 0.024789152 9.104033 9.201219
27      3.00    4.0 9.383897 0.022669802 9.339458 9.428336
28      3.25    4.0 9.364380 0.019588537 9.325981 9.402778
29      3.50    4.0 9.344862 0.016591628 9.312338 9.377387
30      3.75    4.0 9.325345 0.013734406 9.298422 9.352268
31      4.00    4.0 9.305828 0.011125024 9.284020 9.327636
32      4.25    4.0 9.286311 0.008982143 9.268703 9.303918
33      4.50    4.0 9.266794 0.007705329 9.251689 9.281898
34      4.75    4.0 9.247277 0.007736004 9.232112 9.262441
35      5.00    4.0 9.227759 0.009060898 9.209998 9.245521
36      5.25    4.0 9.208242 0.011230960 9.186226 9.230258
37      5.50    4.0 9.188725 0.013854585 9.161566 9.215884
38      5.75    4.0 9.169208 0.016719601 9.136433 9.201983
39      6.00    4.0 9.149691 0.019721081 9.111032 9.188349
40      3.00    4.5 9.413001 0.018137448 9.377447 9.448555
41      3.25    4.5 9.390814 0.015661904 9.360112 9.421515
42      3.50    4.5 9.368627 0.013249556 9.342654 9.394599
43      3.75    4.5 9.346439 0.010942283 9.324990 9.367889
44      4.00    4.5 9.324252 0.008822907 9.306957 9.341548
45      4.25    4.5 9.302065 0.007062657 9.288220 9.315910
46      4.50    4.5 9.279878 0.005987143 9.268142 9.291614
47      4.75    4.5 9.257691 0.005978122 9.245972 9.269410
48      5.00    4.5 9.235504 0.007039696 9.221704 9.249303
49      5.25    4.5 9.213317 0.008792270 9.196081 9.230552
50      5.50    4.5 9.191129 0.010907704 9.169747 9.212512
51      5.75    4.5 9.168942 0.013212846 9.143042 9.194843
52      6.00    4.5 9.146755 0.015623954 9.116128 9.177382
53      3.00    5.0 9.442105 0.015381559 9.411953 9.472257
54      3.25    5.0 9.417248 0.013270737 9.391234 9.443262
55      3.50    5.0 9.392391 0.011214830 9.370407 9.414375
56      3.75    5.0 9.367534 0.009250523 9.349400 9.385667
57      4.00    5.0 9.342677 0.007450623 9.328071 9.357282
58      4.25    5.0 9.317820 0.005965838 9.306125 9.329514
59      4.50    5.0 9.292962 0.005080403 9.283003 9.302921
60      4.75    5.0 9.268105 0.005115623 9.258077 9.278133
61      5.00    5.0 9.243248 0.006055456 9.231378 9.255119
62      5.25    5.0 9.218391 0.007570160 9.203552 9.233231
63      5.50    5.0 9.193534 0.009385411 9.175136 9.211932
64      5.75    5.0 9.168677 0.011358010 9.146412 9.190942
65      6.00    5.0 9.143820 0.013418744 9.117515 9.170124
66      3.00    5.5 9.471209 0.015388175 9.441044 9.501374
67      3.25    5.5 9.443682 0.013272622 9.417664 9.469700
68      3.50    5.5 9.416155 0.011222616 9.394156 9.438154
69      3.75    5.5 9.388628 0.009281689 9.370433 9.406823
70      4.00    5.5 9.361101 0.007534616 9.346331 9.375871
71      4.25    5.5 9.333574 0.006148916 9.321520 9.345627
72      4.50    5.5 9.306047 0.005409799 9.295442 9.316651
73      4.75    5.5 9.278520 0.005580387 9.267581 9.289459
74      5.00    5.5 9.250993 0.006590414 9.238074 9.263912
75      5.25    5.5 9.223466 0.008132923 9.207523 9.239408
76      5.50    5.5 9.195939 0.009963601 9.176407 9.215470
77      5.75    5.5 9.168411 0.011950745 9.144985 9.191838
78      6.00    5.5 9.140884 0.014028018 9.113386 9.168383
79      3.00    6.0 9.500313 0.018154276 9.464726 9.535901
80      3.25    6.0 9.470116 0.015666695 9.439405 9.500827
81      3.50    6.0 9.439919 0.013269320 9.413908 9.465931
82      3.75    6.0 9.409722 0.011021175 9.388118 9.431327
83      4.00    6.0 9.379525 0.009034361 9.361815 9.397235
84      4.25    6.0 9.349328 0.007518974 9.334589 9.364067
85      4.50    6.0 9.319131 0.006797954 9.305805 9.332457
86      4.75    6.0 9.288934 0.007116973 9.274983 9.302885
87      5.00    6.0 9.258737 0.008357772 9.242354 9.275121
88      5.25    6.0 9.228540 0.010188981 9.208567 9.248513
89      5.50    6.0 9.198343 0.012350718 9.174132 9.222554
90      5.75    6.0 9.168146 0.014697859 9.139334 9.196958
91      6.00    6.0 9.137949 0.017154469 9.104322 9.171576
92      3.00    6.5 9.529417 0.022692240 9.484934 9.573900
93      3.25    6.5 9.496550 0.019594922 9.458139 9.534962
94      3.50    6.5 9.463683 0.016617932 9.431108 9.496259
95      3.75    6.5 9.430816 0.013839141 9.403688 9.457945
96      4.00    6.5 9.397949 0.011404362 9.375594 9.420305
97      4.25    6.5 9.365082 0.009579594 9.346304 9.383861
98      4.50    6.5 9.332216 0.008754955 9.315053 9.349378
99      4.75    6.5 9.299349 0.009203334 9.281308 9.317390
100     5.00    6.5 9.266482 0.010766845 9.245376 9.287588
101     5.25    6.5 9.233615 0.013050671 9.208032 9.259197
102     5.50    6.5 9.200748 0.015744415 9.169884 9.231611
103     5.75    6.5 9.167881 0.018671493 9.131279 9.204482
104     6.00    6.5 9.135014 0.021737851 9.092402 9.177626
105     3.00    7.0 9.558521 0.028158055 9.503324 9.613719
106     3.25    7.0 9.522985 0.024325078 9.475301 9.570668
107     3.50    7.0 9.487448 0.020644358 9.446979 9.527916
108     3.75    7.0 9.451911 0.017213841 9.418167 9.485654
109     4.00    7.0 9.416374 0.014215831 9.388507 9.444241
110     4.25    7.0 9.380837 0.011979571 9.357354 9.404320
111     4.50    7.0 9.345300 0.010980762 9.323775 9.366825
112     4.75    7.0 9.309763 0.011545166 9.287131 9.332395
113     5.00    7.0 9.274226 0.013477808 9.247806 9.300646
114     5.25    7.0 9.238689 0.016299034 9.206739 9.270640
115     5.50    7.0 9.203152 0.019629376 9.164673 9.241631
116     5.75    7.0 9.167615 0.023251087 9.122037 9.213194
117     6.00    7.0 9.132078 0.027047374 9.079058 9.185098
118     3.00    7.5 9.587626 0.034108541 9.520764 9.654488
119     3.25    7.5 9.549419 0.029473558 9.491643 9.607195
120     3.50    7.5 9.511212 0.025023516 9.462159 9.560265
121     3.75    7.5 9.473005 0.020877015 9.432080 9.513929
122     4.00    7.5 9.434798 0.017254298 9.400975 9.468621
123     4.25    7.5 9.396591 0.014551959 9.368065 9.425117
124     4.50    7.5 9.358384 0.013341508 9.332231 9.384537
125     4.75    7.5 9.320177 0.014014986 9.292704 9.347651
126     5.00    7.5 9.281970 0.016341098 9.249938 9.314003
127     5.25    7.5 9.243764 0.019744132 9.205060 9.282467
128     5.50    7.5 9.205557 0.023765894 9.158969 9.252144
129     5.75    7.5 9.167350 0.028142358 9.112183 9.222516
130     6.00    7.5 9.129143 0.032731553 9.064980 9.193306
131     3.00    8.0 9.616730 0.040329732 9.537672 9.695787
132     3.25    8.0 9.575853 0.034855483 9.507527 9.644179
133     3.50    8.0 9.534976 0.029599262 9.476953 9.592999
134     3.75    8.0 9.494099 0.024700652 9.445679 9.542519
135     4.00    8.0 9.453222 0.020418676 9.413196 9.493248
136     4.25    8.0 9.412345 0.017219661 9.378590 9.446101
137     4.50    8.0 9.371469 0.015776735 9.340542 9.402395
138     4.75    8.0 9.330592 0.016555611 9.298138 9.363045
139     5.00    8.0 9.289715 0.019288997 9.251903 9.327527
140     5.25    8.0 9.248838 0.023298859 9.203166 9.294510
141     5.50    8.0 9.207961 0.028042869 9.152990 9.262933
142     5.75    8.0 9.167084 0.033207866 9.101988 9.232181
143     6.00    8.0 9.126208 0.038625335 9.050491 9.201924
144     3.00    8.5 9.645834 0.046713598 9.554262 9.737405
145     3.25    8.5 9.602287 0.040377611 9.523136 9.681438
146     3.50    8.5 9.558740 0.034292991 9.491517 9.625964
147     3.75    8.5 9.515193 0.028620510 9.459089 9.571297
148     4.00    8.5 9.471647 0.023658496 9.425270 9.518024
149     4.25    8.5 9.428100 0.019944468 9.389003 9.467196
150     4.50    8.5 9.384553 0.018256662 9.348765 9.420341
151     4.75    8.5 9.341006 0.019138865 9.303489 9.378524
152     5.00    8.5 9.297459 0.022287960 9.253769 9.341150
153     5.25    8.5 9.253913 0.026919819 9.201142 9.306683
154     5.50    8.5 9.210366 0.032404733 9.146844 9.273888
155     5.75    8.5 9.166819 0.038378683 9.091586 9.242052
156     6.00    8.5 9.123272 0.044645788 9.035754 9.210790
157     3.00    9.0 9.674938 0.053201611 9.570648 9.779227
158     3.25    9.0 9.628721 0.045989467 9.538569 9.718873
159     3.50    9.0 9.582504 0.039062197 9.505932 9.659077
160     3.75    9.0 9.536288 0.032601901 9.472379 9.600196
161     4.00    9.0 9.490071 0.026946560 9.437248 9.542893
162     4.25    9.0 9.443854 0.022705830 9.399345 9.488364
163     4.50    9.0 9.397637 0.020765281 9.356932 9.438343
164     4.75    9.0 9.351421 0.021749564 9.308786 9.394056
165     5.00    9.0 9.305204 0.025319847 9.255570 9.354838
166     5.25    9.0 9.258987 0.030583497 9.199035 9.318939
167     5.50    9.0 9.212770 0.036821331 9.140591 9.284950
168     5.75    9.0 9.166554 0.043617371 9.081052 9.252055
169     6.00    9.0 9.120337 0.050747849 9.020857 9.219816
plot(zile,               # plot the effect of the interaction
   ylim=range(d$RT_log, na.rm=TRUE), # w/ these y-axis limits
   grid=TRUE)            # & w/ a grid
Figure 11: The interaction ZIPFFREQ:LENGTH

Not that great … There are two main ways we could try and plot this better. One would be with an actual 3-D plot. The simplest version of this might use rgl::plot3D like this, which would open a new window with a rotatable 3-D plot (but this wouldn’t be rendered into this HTML properly, which is why I am not executing it here):

rgl::plot3d(zile_d$ZIPFFREQ, zile_d$LENGTH, zile_d$fit)

Another option would be to use plotly::plot_ly:

library(plotly)
fig <- plot_ly(zile_d,
   x=~ZIPFFREQ, y=~LENGTH, z=~fit, color=~fit, colors=c("#FFFFFF20", "#00000020"))
fig <- fig %>% add_markers()
fig <- fig %>% layout(scene = list(
   xaxis = list(title = 'Zipf frequency'),
   yaxis = list(title = 'Length'),
   zaxis = list(title = 'Predicted RT')))
fig

While this is nice, it’s also hard to publish in print. Let’s do this differently with a numeric heatmap kind of plot that I often use. For that, the numeric predictions are first cut into 9 ordinal bins with lower/higher bin numbers reflecting lower/higher numeric predictions …

zile_d$fitcat <- as.numeric( # create a numeric column fitcat in zile_d
   cut(zile_d$fit,           # by cutting the predicted values up
       breaks=seq(           # at the values ranging from
          min(zile_d$fit),   # the smallest obs/pred RT to
          max(zile_d$fit),   # the largest obs/pred RT
          length.out=10),    # into 9 groups
       include.lowest=TRUE,  # include the lowest, and
       labels=1:9))          # use the numbers 1:9 as labels

… and then we plot them into a coordinate system:

plot(main="RT ~ ZIPFFREQ:LENGTH", type="n", # plot nothing, just
   xlab="Zipf frequency", xlim=range(zile_d$ZIPFFREQ), x=0, # get the coordinate
   ylab="Length"        , ylim=range(zile_d$LENGTH)  , y=0) # system set up
text(                                  # plot text
   x=zile_d$ZIPFFREQ, y=zile_d$LENGTH, # at these coordinates, namely
   labels=zile_d$fitcat,               # the 'categorical/ordinal' predictions
   # make the physical size of the numbers dependent on the values
   cex=0.5+zile_d$fitcat/7,
   # make the darkness of the grey reflect the predicted RT_log
   col=grey(level=zile_d$fitcat/10)) # see ?grey
Figure 12: The interaction ZIPFFREQ:LENGTH

However, there’s one thing that’s possibly problematic about this – what is that? The problem is that we are binning with cut on the basis of zile_d$fit (check the above code), but the range of those predictions is much smaller than the range of the actually observed reaction times in d$RT_log:

range(zile_d$fit)
[1] 9.120337 9.674938
range(d$RT_log)
[1] NA NA

That in turn means the binning steps based on zile_d$fit will be based on much smaller differences between predicted values (of only 40, check levels(cut(zile_d$fit, breaks=seq(min(zile_d$fit), max(zile_d$fit), length.out=10)))), meaning a difference of predictions of only 81 ms could lead to a difference in 3 bins – if we were to bin based on the actual value range, that would not happen, because there the binning steps are 429 ms wide (check levels(cut(d$RT_log, breaks=seq(min(d$RT_log), max(d$RT_log), length.out=10)))). Thus, the above plot exaggerates the effects somewhat.

Accordingly, we determine the range of the values to be plotted in a potentially more representative way, by using

  • as a minimum, the minimum of the predictions and the actual values combined;
  • as a maximum, the maximum of the predictions and the actual values combined:
# define the minimum of the range to be used for binning
min_of_obs_n_pred <- min(c(
   d$RT_log,
   zile_d$fit),
   na.rm=TRUE)
# define the maximum of the range to be used for binning
max_of_obs_n_pred <- max(c(
   d$RT_log,
   zile_d$fit),
   na.rm=TRUE)

We can see that the initial plot indeed exaggerated the range: the upper plot showed prediction bins from 1 to 9, but we now see that the predicctions are actually only in a really small range of values.

zile_d$fitcat2 <- as.numeric( # create a numeric column fitcat2 in zile_d
   cut(zile_d$fit,           # by cutting the predicted values up
       breaks=seq(           # at the values ranging from
          min_of_obs_n_pred, # the smallest obs/pred RT_log to   <---------
          max_of_obs_n_pred, # the largest obs/pred RT_log       <---------
          length.out=10),    # into 9 groups
       include.lowest=TRUE,  # include the lowest, and
       labels=1:9)) # use the numbers 1:9 as labels
table(zile_d$fitcat, zile_d$fitcat2)

     3  4
  1 26  0
  2 30  0
  3 31  0
  4 27  0
  5  9 11
  6  0 15
  7  0 10
  8  0  6
  9  0  4

Thus, we plot this again (with some additional info in the form of the decile-based grid), which here doesn’t help much:

plot(main="RT ~ ZIPFFREQ:LENGTH", type="n", # plot nothing, just
   xlab="Zipf frequency", xlim=range(zile_d$ZIPFFREQ), x=0, # get the coordinate
   ylab="Length"        , ylim=range(zile_d$LENGTH)  , y=0) # system set up
text(                                  # plot text
   x=zile_d$ZIPFFREQ, y=zile_d$LENGTH, # at these coordinates, namely
   labels=zile_d$fitcat2,              # the 'categorical/ordinal' predictions
   # make the physical size of the numbers dependent on the values
   cex=0.5+zile_d$fitcat2/7,
   # make the darkness of the grey reflect the predicted RT_log
   col=grey(level=zile_d$fitcat2/15)) # see ?grey
abline(h=quantile(d$LENGTH  , probs=seq(0, 1, 0.1)), lty=3, col="#00000020")
abline(v=quantile(d$ZIPFFREQ, probs=seq(0, 1, 0.1)), lty=3, col="#00000020")
Figure 13: The interaction ZIPFFREQ:LENGTH

1.1.4 Write-up

To determine whether the reaction time to a word (in ms) varies as a function of

  • the Zipf frequency of that word (ZIPFFREQ);
  • the length of that word (LENGTH);
  • the language that word was presented in (LANGUAGE: english vs. spanish);
  • the speaker group that words was presented to (GROUP: english vs. heritage);
  • any pairwise interaction of these predictors with the specific expectation that the effect of LENGTH will be stronger for when LANGUAGE is spanish

a linear model selection process was undertaken. In order to deal with the very long right tail of the response variable the response variable was logged; then a backwards stepwise model selection of all predictors and controls (based on significance testing) was applied. The final model resulting from the elimination of ns predictors contained the effects of ZIPFFREQ:LENGTH, LANGUAGE:GROUP, and LANGUAGE:LENGTH and was highly significant (F=154.7, df1=7, df2=7744, p<0.0001), but it explains only little of the response variable (mult. R2=0.123, adj. R2=0.122). The summary table of the model is shown here.

Estimate 95%-CI se t p2-tailed
Intercept (ZIPFFREQ=0, LENGTH=0, LANGUAGE=spanish, GROUP=english) 9 [8.595, 9.409] 0.208 43.33 <0.001
ZIPFFREQ0→1 0.007 [-0.077, 0.092] 0.043 0.171 0.864
LANGUAGEspanishenglish 0.258 [0.149, 0.366] 0.056 4.638 <0.001
GROUPenglishheritage 0.103 [0.076, 0.129] 0.013 7.655 <0.001
LENGTH0→1 0.113 [0.037, 0.19] 0.039 2.913 0.004
ZIPFFREQ0→1 + LENGTH0→1 -0.021 [-0.037 -0.006] 0.008 -2.648 0.008
LANGUAGEspanishenglish + GROUPenglishheritage -0.205 [-0.243, -0.168] 0.019 -10.668 <0.001
LANGUAGEspanishenglish + LENGTH0→1 0.018 [-0.002, 0.039] 0.01 1.754 0.079

The results indicate that

  • as hypothesized, the slowing-down effect of LENGTH is stronger for Spanish than for English words [show plot];
  • the interaction LANGUAGE:GROUP has the following effects [show at least one plot]:
    • reaction times are always higher when LANGUAGE is spanish than when LANGUAGE is english, but that difference is higher when GROUP is english than when GROUP is spanish;
    • when LANGUAGE is english, heritage speakers need more time than learners, but when LANGUAGE is spanish, heritage speakers need less time than learners;
    • all 4 means of LANGUAGE:GROUP are significantly different from each other (non-overlapping CIs);
  • the interaction LANGUAGE:LENGTH has the hypothesized effect that the effect of LENGTH is stronger – in fact, nearly 2.25 times as strong – when LANGUAGE is spanish than when LANGUAGE is english [show at least one plot];
  • the interaction of ZIPFFREQ:LENGTH is not strong: both variables behave as expected – LENGTH slows down, ZIPFFREQ speeds up – but
    • the effect of LENGTH is much stronger with rarer words than with frequent words;
    • the effect of ZIPFFREQ is much stronger with longer words than with shorter words.

1.2 Binary logistic regression modeling

To do a quick recap of linear modeling, we will look at a data set in _input/genitives.csv involving genitive choices as the response variable:

rm(list=ls(all.names=TRUE))
pkgs2load <- c("car", "effects", "emmeans", "magrittr", "multcomp")
suppressMessages(sapply(pkgs2load, library,
   character.only=TRUE, logical.return=TRUE, quietly=TRUE)); rm(pkgs2load)
     car  effects  emmeans magrittr multcomp
    TRUE     TRUE     TRUE     TRUE     TRUE 
source("_helpers/C.score.r"); source("_helpers/R2s.r");  # helper functions
summary(d <- read.delim(   # summarize d, the result of loading
   file="_input/genitives.csv", # this file
   stringsAsFactors=TRUE)) # change categorical variables into factors
      CASE      GENITIVE  SPEAKER       MODALITY      POR_LENGTH
 Min.   :   2   of:2720   nns:2666   spoken :1685   Min.   :  1.00
 1st Qu.:1006   s : 880   ns : 934   written:1915   1st Qu.:  8.00
 Median :2018                                       Median : 11.00
 Mean   :2012                                       Mean   : 14.58
 3rd Qu.:3017                                       3rd Qu.: 17.00
 Max.   :4040                                       Max.   :204.00
   PUM_LENGTH         POR_ANIMACY   POR_FINAL_SIB        POR_DEF
 Min.   :  2.00   animate   : 920   absent :2721   definite  :2349
 1st Qu.:  6.00   collective: 607   present: 879   indefinite:1251
 Median :  9.00   inanimate :1671
 Mean   : 10.35   locative  : 243
 3rd Qu.: 13.00   temporal  : 159
 Max.   :109.00                                                     

You can see what the variables/columns contain in the file _input/genitives.r. Specifically, we want to see whether the choice of a genitive varies as a function of

  • whether the speaker is a native speaker or not (SPEAKER);
  • the length difference between possessor and possessum and whether native and non-native speakers react to it in the same way;
  • the animacy of the possessor and whether native and non-native speakers react to it in the same way (POR_ANIMACY);
  • whether the possessor ends in a sibilant or not and whether native and non-native speakers react to it in the same way (POR_FINAL_SIB);
  • whether the modality is speaking or writing and whether native and non-native speakers react to it in the same way (MODALITY);
  • the interaction of POR_FINAL_SIB with MODALITY.

1.2.1 Exploration & preparation

1.2.1.1 All variables

As always, a decent amount of exploration is required to prepare the data for a hopefully unproblematic modeling process. We will only focus on the four variables of interest (and even there will take some didactically motivated shortcuts). We begin our exploration with the creation of the length difference predictor:

Let’s look at the numeric predictor first by exploring its ‘components’:

par(mfrow=c(1, 2)) # make the plotting window have 1 rows & 2 columns
hist(d$POR_LENGTH, main="")
hist(d$PUM_LENGTH, main="")
par(mfrow=c(1, 1)) # reset to default
Figure 14: The lengths of possessors/-ums

This doesn’t look all that great because it’s not likely that the long tails will magically cancel each other out, and they don’t:

par(mfrow=c(1, 2)) # make the plotting window have 1 rows & 2 columns
hist(d$POR_LENGTH - d$PUM_LENGTH, main="")
hist(d$POR_LENGTH - d$PUM_LENGTH, breaks="FD", main="")
par(mfrow=c(1, 1)) # reset to default
Figure 15: The difference of the logged lengths

What if we compute not the difference of the lengths, but the difference of the logged lengths?

hist(d$LEN_PORmPUM_LOG <- log2(d$POR_LENGTH)-log2(d$PUM_LENGTH), main="")
Figure 16: The difference of the logged lengths

That looks awesome – how this is related to the response? Very nicely:

spineplot(d$GENITIVE ~ d$LEN_PORmPUM_LOG)
Figure 17: The difference of the logged lengths and genitives

Then we cross-tabulate for all interactions:

cut(d$LEN_PORmPUM_LOG, 8) %>% table(d$SPEAKER) %>% addmargins

.                 nns   ns  Sum
  (-4.53,-3.37]     5    0    5
  (-3.37,-2.21]    54   14   68
  (-2.21,-1.06]   259   80  339
  (-1.06,0.0953]  744  272 1016
  (0.0953,1.25]   967  356 1323
  (1.25,2.4]      504  177  681
  (2.4,3.56]      113   28  141
  (3.56,4.72]      20    7   27
  Sum            2666  934 3600
d$POR_ANIMACY             %>% table(d$SPEAKER) %>% addmargins

.             nns   ns  Sum
  animate     678  242  920
  collective  452  155  607
  inanimate  1277  394 1671
  locative    151   92  243
  temporal    108   51  159
  Sum        2666  934 3600
d$POR_FINAL_SIB           %>% table(d$SPEAKER) %>% addmargins

.          nns   ns  Sum
  absent  2018  703 2721
  present  648  231  879
  Sum     2666  934 3600
d$MODALITY                %>% table(d$SPEAKER) %>% addmargins

.          nns   ns  Sum
  spoken  1261  424 1685
  written 1405  510 1915
  Sum     2666  934 3600

All looking good, but what if we add the response?

cut(d$LEN_PORmPUM_LOG, 8) %>% ftable(d$SPEAKER, d$GENITIVE)
                     of   s
.
(-4.53,-3.37]  nns    3   2
               ns     0   0
(-3.37,-2.21]  nns   12  42
               ns     4  10
(-2.21,-1.06]  nns  121 138
               ns    39  41
(-1.06,0.0953] nns  537 207
               ns   180  92
(0.0953,1.25]  nns  766 201
               ns   277  79
(1.25,2.4]     nns  452  52
               ns   161  16
(2.4,3.56]     nns  113   0
               ns    28   0
(3.56,4.72]    nns   20   0
               ns     7   0
    d$POR_ANIMACY         %>% ftable(d$SPEAKER, d$GENITIVE)
                  of    s
.
animate    nns   272  406
           ns     98  144
collective nns   314  138
           ns     94   61
inanimate  nns  1250   27
           ns    388    6
locative   nns   117   34
           ns     82   10
temporal   nns    71   37
           ns     34   17
    d$POR_FINAL_SIB       %>% ftable(d$SPEAKER, d$GENITIVE)
               of    s
.
absent  nns  1462  556
        ns    500  203
present nns   562   86
        ns    196   35
    d$MODALITY            %>% ftable(d$SPEAKER, d$GENITIVE)
               of    s
.
spoken  nns   914  347
        ns    318  106
written nns  1110  295
        ns    378  132

Not too bad. There are some low-frequency cells and for the numeric predictor even some empty cells, but that problem will not surface in the model because there we won’t use a factorized version of that predictor.

1.2.1.2 Deviance

First, we’ll quickly compute the deviance of the null model:

deviance(m_00 <- glm(GENITIVE ~ 1, family=binomial, data=d)) # 4004.273
[1] 4004.273

1.2.2 Model selection

Let’s fit our initial regression model:

summary(m_01 <- glm(      # make/summarize the gen. linear model m_01:
   GENITIVE ~ 1 +         # GENITIVE ~ an overall intercept (1)
   # these predictors & their interaction, as specified above:
   SPEAKER * (LEN_PORmPUM_LOG + POR_ANIMACY + POR_FINAL_SIB + MODALITY) +
   POR_FINAL_SIB:MODALITY,
   family=binomial,       # resp = binary
   data=d,                # those vars are in d
   na.action=na.exclude)) # skip cases with NA/missing data

Call:
glm(formula = GENITIVE ~ 1 + SPEAKER * (LEN_PORmPUM_LOG + POR_ANIMACY +
    POR_FINAL_SIB + MODALITY) + POR_FINAL_SIB:MODALITY, family = binomial,
    data = d, na.action = na.exclude)

Coefficients:
                                     Estimate Std. Error z value Pr(>|z|)
(Intercept)                           1.20153    0.12776   9.405  < 2e-16 ***
SPEAKERns                            -0.54297    0.22907  -2.370 0.017771 *
LEN_PORmPUM_LOG                      -0.73122    0.05581 -13.102  < 2e-16 ***
POR_ANIMACYcollective                -1.91467    0.15730 -12.172  < 2e-16 ***
POR_ANIMACYinanimate                 -4.48698    0.22364 -20.064  < 2e-16 ***
POR_ANIMACYlocative                  -2.48634    0.23428 -10.613  < 2e-16 ***
POR_ANIMACYtemporal                  -1.64632    0.24358  -6.759 1.39e-11 ***
POR_FINAL_SIBpresent                 -0.88314    0.21001  -4.205 2.61e-05 ***
MODALITYwritten                      -0.41258    0.13262  -3.111 0.001865 **
SPEAKERns:LEN_PORmPUM_LOG            -0.04120    0.11435  -0.360 0.718626
SPEAKERns:POR_ANIMACYcollective       0.85298    0.28698   2.972 0.002957 **
SPEAKERns:POR_ANIMACYinanimate       -0.29238    0.50100  -0.584 0.559490
SPEAKERns:POR_ANIMACYlocative        -0.52328    0.45501  -1.150 0.250118
SPEAKERns:POR_ANIMACYtemporal         0.24431    0.43390   0.563 0.573404
SPEAKERns:POR_FINAL_SIBpresent        0.12193    0.30279   0.403 0.687182
SPEAKERns:MODALITYwritten             0.86035    0.23713   3.628 0.000285 ***
POR_FINAL_SIBpresent:MODALITYwritten -0.26442    0.26642  -0.992 0.320958
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 4004.3  on 3599  degrees of freedom
Residual deviance: 2365.9  on 3583  degrees of freedom
AIC: 2399.9

Number of Fisher Scoring iterations: 7
pchisq(                                # compute the area under the chi-squared curve
   q=m_01$null.deviance-m_01$deviance, # for this chi-squared value: null - res. dev.
   df=m_01$df.null-m_01$df.residual,   # at this df: null - residual df
   lower.tail=FALSE)                   # only the right/upper tail/side
[1] 0
drop1(m_01,           # drop each droppable predictor at a time from m_01 &
   test="Chisq") %>%  # test its significance w/ a LRT
   "["(order(.[,4]),) # order by p-values
Single term deletions

Model:
GENITIVE ~ 1 + SPEAKER * (LEN_PORmPUM_LOG + POR_ANIMACY + POR_FINAL_SIB +
    MODALITY) + POR_FINAL_SIB:MODALITY
                        Df Deviance    AIC     LRT  Pr(>Chi)
SPEAKER:LEN_PORmPUM_LOG  1   2366.0 2398.0  0.1306 0.7177767
SPEAKER:POR_FINAL_SIB    1   2366.0 2398.0  0.1616 0.6876956
POR_FINAL_SIB:MODALITY   1   2366.9 2398.9  0.9849 0.3209917
SPEAKER:MODALITY         1   2379.2 2411.2 13.3199 0.0002626 ***
SPEAKER:POR_ANIMACY      4   2380.4 2406.4 14.5204 0.0058066 **
<none>                       2365.9 2399.9
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Let’s fit the 2nd model with the 2-way interaction SPEAKER:LEN_PORmPUM_LOG deleted:

summary(m_02 <- update( # make m_02 an update of
   m_01, .~.            # m_01, namely all of it (.~.), but then
   - SPEAKER:LEN_PORmPUM_LOG)) # remove this interaction

Call:
glm(formula = GENITIVE ~ SPEAKER + LEN_PORmPUM_LOG + POR_ANIMACY +
    POR_FINAL_SIB + MODALITY + SPEAKER:POR_ANIMACY + SPEAKER:POR_FINAL_SIB +
    SPEAKER:MODALITY + POR_FINAL_SIB:MODALITY, family = binomial,
    data = d, na.action = na.exclude)

Coefficients:
                                     Estimate Std. Error z value Pr(>|z|)
(Intercept)                           1.20656    0.12722   9.484  < 2e-16 ***
SPEAKERns                            -0.55734    0.22496  -2.477 0.013231 *
LEN_PORmPUM_LOG                      -0.74118    0.04871 -15.217  < 2e-16 ***
POR_ANIMACYcollective                -1.92276    0.15605 -12.322  < 2e-16 ***
POR_ANIMACYinanimate                 -4.49457    0.22298 -20.157  < 2e-16 ***
POR_ANIMACYlocative                  -2.49604    0.23310 -10.708  < 2e-16 ***
POR_ANIMACYtemporal                  -1.65316    0.24330  -6.795 1.09e-11 ***
POR_FINAL_SIBpresent                 -0.88063    0.21004  -4.193 2.76e-05 ***
MODALITYwritten                      -0.41417    0.13271  -3.121 0.001804 **
SPEAKERns:POR_ANIMACYcollective       0.87359    0.28033   3.116 0.001831 **
SPEAKERns:POR_ANIMACYinanimate       -0.26392    0.49366  -0.535 0.592917
SPEAKERns:POR_ANIMACYlocative        -0.49114    0.44521  -1.103 0.269949
SPEAKERns:POR_ANIMACYtemporal         0.26157    0.43008   0.608 0.543074
SPEAKERns:POR_FINAL_SIBpresent        0.11670    0.30167   0.387 0.698870
SPEAKERns:MODALITYwritten             0.85838    0.23634   3.632 0.000281 ***
POR_FINAL_SIBpresent:MODALITYwritten -0.26683    0.26630  -1.002 0.316343
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 4004.3  on 3599  degrees of freedom
Residual deviance: 2366.0  on 3584  degrees of freedom
AIC: 2398

Number of Fisher Scoring iterations: 6
anova(m_01, m_02, # compare m_01 to m_02
   test="Chisq")  # using a LRT
Analysis of Deviance Table

Model 1: GENITIVE ~ 1 + SPEAKER * (LEN_PORmPUM_LOG + POR_ANIMACY + POR_FINAL_SIB +
    MODALITY) + POR_FINAL_SIB:MODALITY
Model 2: GENITIVE ~ SPEAKER + LEN_PORmPUM_LOG + POR_ANIMACY + POR_FINAL_SIB +
    MODALITY + SPEAKER:POR_ANIMACY + SPEAKER:POR_FINAL_SIB +
    SPEAKER:MODALITY + POR_FINAL_SIB:MODALITY
  Resid. Df Resid. Dev Df Deviance Pr(>Chi)
1      3583     2365.9
2      3584     2366.0 -1 -0.13063   0.7178
drop1(m_02,           # drop each droppable predictor at a time from m_02 &
   test="Chisq") %>%  # test its significance w/ a LRT
   "["(order(.[,4]),) # order by p-values
Single term deletions

Model:
GENITIVE ~ SPEAKER + LEN_PORmPUM_LOG + POR_ANIMACY + POR_FINAL_SIB +
    MODALITY + SPEAKER:POR_ANIMACY + SPEAKER:POR_FINAL_SIB +
    SPEAKER:MODALITY + POR_FINAL_SIB:MODALITY
                       Df Deviance    AIC     LRT  Pr(>Chi)
SPEAKER:POR_FINAL_SIB   1   2366.2 2396.2   0.149 0.6993918
POR_FINAL_SIB:MODALITY  1   2367.0 2397.0   1.004 0.3163946
SPEAKER:MODALITY        1   2379.3 2409.3  13.338 0.0002601 ***
SPEAKER:POR_ANIMACY     4   2380.8 2404.8  14.804 0.0051263 **
LEN_PORmPUM_LOG         1   2647.5 2677.5 281.463 < 2.2e-16 ***
<none>                      2366.0 2398.0
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Let’s fit the 3rd model with the 2-way interaction SPEAKER:POR_FINAL_SIB deleted:

summary(m_03 <- update( # make m_03 an update of
   m_02, .~.            # m_02, namely all of it (.~.), but then
   - SPEAKER:POR_FINAL_SIB)) # remove this interaction

Call:
glm(formula = GENITIVE ~ SPEAKER + LEN_PORmPUM_LOG + POR_ANIMACY +
    POR_FINAL_SIB + MODALITY + SPEAKER:POR_ANIMACY + SPEAKER:MODALITY +
    POR_FINAL_SIB:MODALITY, family = binomial, data = d, na.action = na.exclude)

Coefficients:
                                     Estimate Std. Error z value Pr(>|z|)
(Intercept)                            1.1969     0.1246   9.606  < 2e-16 ***
SPEAKERns                             -0.5280     0.2121  -2.490 0.012782 *
LEN_PORmPUM_LOG                       -0.7412     0.0487 -15.218  < 2e-16 ***
POR_ANIMACYcollective                 -1.9154     0.1547 -12.384  < 2e-16 ***
POR_ANIMACYinanimate                  -4.4886     0.2223 -20.191  < 2e-16 ***
POR_ANIMACYlocative                   -2.4870     0.2318 -10.729  < 2e-16 ***
POR_ANIMACYtemporal                   -1.6462     0.2424  -6.790 1.12e-11 ***
POR_FINAL_SIBpresent                  -0.8508     0.1951  -4.360 1.30e-05 ***
MODALITYwritten                       -0.4150     0.1327  -3.127 0.001765 **
SPEAKERns:POR_ANIMACYcollective        0.8598     0.2786   3.086 0.002027 **
SPEAKERns:POR_ANIMACYinanimate        -0.2838     0.4915  -0.577 0.563682
SPEAKERns:POR_ANIMACYlocative         -0.5180     0.4401  -1.177 0.239274
SPEAKERns:POR_ANIMACYtemporal          0.2375     0.4259   0.558 0.577020
SPEAKERns:MODALITYwritten              0.8637     0.2364   3.654 0.000258 ***
POR_FINAL_SIBpresent:MODALITYwritten  -0.2587     0.2654  -0.975 0.329726
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 4004.3  on 3599  degrees of freedom
Residual deviance: 2366.2  on 3585  degrees of freedom
AIC: 2396.2

Number of Fisher Scoring iterations: 7
anova(m_02, m_03, # compare m_02 to m_03
   test="Chisq")  # using a LRT
Analysis of Deviance Table

Model 1: GENITIVE ~ SPEAKER + LEN_PORmPUM_LOG + POR_ANIMACY + POR_FINAL_SIB +
    MODALITY + SPEAKER:POR_ANIMACY + SPEAKER:POR_FINAL_SIB +
    SPEAKER:MODALITY + POR_FINAL_SIB:MODALITY
Model 2: GENITIVE ~ SPEAKER + LEN_PORmPUM_LOG + POR_ANIMACY + POR_FINAL_SIB +
    MODALITY + SPEAKER:POR_ANIMACY + SPEAKER:MODALITY + POR_FINAL_SIB:MODALITY
  Resid. Df Resid. Dev Df Deviance Pr(>Chi)
1      3584     2366.0
2      3585     2366.2 -1  -0.1491   0.6994
drop1(m_03,           # drop each droppable predictor at a time from m_03 &
   test="Chisq") %>%  # test its significance w/ a LRT
   "["(order(.[,4]),) # order by p-values
Single term deletions

Model:
GENITIVE ~ SPEAKER + LEN_PORmPUM_LOG + POR_ANIMACY + POR_FINAL_SIB +
    MODALITY + SPEAKER:POR_ANIMACY + SPEAKER:MODALITY + POR_FINAL_SIB:MODALITY
                       Df Deviance    AIC     LRT  Pr(>Chi)
POR_FINAL_SIB:MODALITY  1   2367.1 2395.1   0.950 0.3298005
SPEAKER:MODALITY        1   2379.6 2407.6  13.484 0.0002406 ***
SPEAKER:POR_ANIMACY     4   2380.9 2402.9  14.731 0.0052937 **
LEN_PORmPUM_LOG         1   2647.8 2675.8 281.601 < 2.2e-16 ***
<none>                      2366.2 2396.2
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Let’s fit the 4th model with the 2-way interaction POR_FINAL_SIB:MODALITY deleted:

summary(m_04 <- update( # make m_04 an update of
   m_03, .~.            # m_03, namely all of it (.~.), but then
   - POR_FINAL_SIB:MODALITY)) # remove this interaction

Call:
glm(formula = GENITIVE ~ SPEAKER + LEN_PORmPUM_LOG + POR_ANIMACY +
    POR_FINAL_SIB + MODALITY + SPEAKER:POR_ANIMACY + SPEAKER:MODALITY,
    family = binomial, data = d, na.action = na.exclude)

Coefficients:
                                Estimate Std. Error z value Pr(>|z|)
(Intercept)                       1.2238     0.1218  10.044  < 2e-16 ***
SPEAKERns                        -0.5373     0.2123  -2.532 0.011357 *
LEN_PORmPUM_LOG                  -0.7405     0.0487 -15.206  < 2e-16 ***
POR_ANIMACYcollective            -1.9231     0.1547 -12.430  < 2e-16 ***
POR_ANIMACYinanimate             -4.4891     0.2224 -20.185  < 2e-16 ***
POR_ANIMACYlocative              -2.4903     0.2321 -10.727  < 2e-16 ***
POR_ANIMACYtemporal              -1.6542     0.2423  -6.827 8.70e-12 ***
POR_FINAL_SIBpresent             -0.9879     0.1373  -7.197 6.16e-13 ***
MODALITYwritten                  -0.4632     0.1233  -3.756 0.000172 ***
SPEAKERns:POR_ANIMACYcollective   0.8732     0.2782   3.139 0.001694 **
SPEAKERns:POR_ANIMACYinanimate   -0.2717     0.4912  -0.553 0.580082
SPEAKERns:POR_ANIMACYlocative    -0.4963     0.4393  -1.130 0.258579
SPEAKERns:POR_ANIMACYtemporal     0.2568     0.4252   0.604 0.545832
SPEAKERns:MODALITYwritten         0.8609     0.2361   3.646 0.000267 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 4004.3  on 3599  degrees of freedom
Residual deviance: 2367.1  on 3586  degrees of freedom
AIC: 2395.1

Number of Fisher Scoring iterations: 7
anova(m_03, m_04, # compare m_03 to m_04
   test="Chisq")  # using a LRT
Analysis of Deviance Table

Model 1: GENITIVE ~ SPEAKER + LEN_PORmPUM_LOG + POR_ANIMACY + POR_FINAL_SIB +
    MODALITY + SPEAKER:POR_ANIMACY + SPEAKER:MODALITY + POR_FINAL_SIB:MODALITY
Model 2: GENITIVE ~ SPEAKER + LEN_PORmPUM_LOG + POR_ANIMACY + POR_FINAL_SIB +
    MODALITY + SPEAKER:POR_ANIMACY + SPEAKER:MODALITY
  Resid. Df Resid. Dev Df Deviance Pr(>Chi)
1      3585     2366.2
2      3586     2367.1 -1 -0.94968   0.3298
drop1(m_04,           # drop each droppable predictor at a time from m_04 &
   test="Chisq") %>%  # test its significance w/ a LRT
   "["(order(.[,4]),) # order by p-values
Single term deletions

Model:
GENITIVE ~ SPEAKER + LEN_PORmPUM_LOG + POR_ANIMACY + POR_FINAL_SIB +
    MODALITY + SPEAKER:POR_ANIMACY + SPEAKER:MODALITY
                    Df Deviance    AIC     LRT  Pr(>Chi)
SPEAKER:MODALITY     1   2380.5 2406.5  13.419 0.0002491 ***
SPEAKER:POR_ANIMACY  4   2382.0 2402.0  14.867 0.0049847 **
POR_FINAL_SIB        1   2422.3 2448.3  55.194 1.092e-13 ***
LEN_PORmPUM_LOG      1   2648.4 2674.4 281.288 < 2.2e-16 ***
<none>                   2367.1 2395.1
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

It seems like we’re done:

m_final <- m_04; rm(m_04); invisible(gc())

Here are the confidence intervals; in the interest of time, I am skipping nd:

Confint(m_final)
                                  Estimate      2.5 %     97.5 %
(Intercept)                      1.2237618  0.9875054  1.4653615
SPEAKERns                       -0.5373533 -0.9532095 -0.1204319
LEN_PORmPUM_LOG                 -0.7405007 -0.8373898 -0.6464154
POR_ANIMACYcollective           -1.9230937 -2.2301212 -1.6233581
POR_ANIMACYinanimate            -4.4891032 -4.9458935 -4.0714096
POR_ANIMACYlocative             -2.4902940 -2.9576044 -2.0458645
POR_ANIMACYtemporal             -1.6542441 -2.1376028 -1.1859007
POR_FINAL_SIBpresent            -0.9878511 -1.2597876 -0.7214424
MODALITYwritten                 -0.4631802 -0.7060025 -0.2224345
SPEAKERns:POR_ANIMACYcollective  0.8732028  0.3273953  1.4184405
SPEAKERns:POR_ANIMACYinanimate  -0.2717511 -1.3214325  0.6335101
SPEAKERns:POR_ANIMACYlocative   -0.4962674 -1.3939910  0.3391914
SPEAKERns:POR_ANIMACYtemporal    0.2568132 -0.5859427  1.0850090
SPEAKERns:MODALITYwritten        0.8608539  0.3994571  1.3255667

1.2.3 Model evaluation & interpretation: numerical & visual

We first assess the predictive power of the model in various ways. For that, we first compute all numeric and categorical predictions (as well as PRED_PP_OBS, which is the predicted probability of the level of the response variable that should have been predicted) …

d$PRED_PP_2 <- predict( # make d$PRED_PP_2 the result of predicting
   m_final,             # from m_final
   type="response")     # predicted probabilities of "s"
d$PRED_LO_2 <- predict( # make d$PRED_LO_2 the result of predicting
   m_final)             # from m_final -- this time the log odds
d$PRED_CAT <- factor(         # d$PRED_CAT is determined by ifelse
   ifelse(d$PRED_PP_2>=0.5,   # if the predicted prob. of "s" is >=0.5
      levels(d$GENITIVE)[2],  # predict "s"
      levels(d$GENITIVE)[1]), # otherwise, predict "of"
   levels=levels(d$GENITIVE)) # make sure both levels are registered
d$PRED_PP_OBS <-   # d$PRED_PP_OBS is determined by ifelse
   ifelse(d$GENITIVE==levels(d$GENITIVE)[2], # if the obs gen = 2nd level of GENITIVE
      d$PRED_PP_2, # take its predicted prob.
    1-d$PRED_PP_2) # otherwise take 1 minus its predicted probability
head(d)
  CASE GENITIVE SPEAKER MODALITY POR_LENGTH PUM_LENGTH POR_ANIMACY
1    2       of     nns   spoken         13          7  collective
2    3       of     nns   spoken         22          7     animate
3    4       of     nns   spoken         11          8     animate
4    5       of     nns   spoken         26          4  collective
5    6        s     nns   spoken          8          4     animate
6    7        s     nns   spoken          7          3     animate
  POR_FINAL_SIB  POR_DEF LEN_PORmPUM_LOG  PRED_PP_2     PRED_LO_2 PRED_CAT
1        absent definite       0.8930848 0.20413276 -1.3606618015       of
2        absent definite       1.6520767 0.50009947  0.0003978715        s
3       present definite       0.4594316 0.47394893 -0.1042987321       of
4        absent definite       2.7004397 0.06303183 -2.6990094172       of
5        absent definite       1.0000000 0.61851765  0.4832611329        s
6        absent definite       1.2223924 0.57897800  0.3185793854        s
  PRED_PP_OBS
1   0.7958672
2   0.4999005
3   0.5260511
4   0.9369682
5   0.6185176
6   0.5789780

… and then we evaluate the confusion matrix:

(c_m <- table(        # confusion matrix: cross-tabulate
   OBS  =d$GENITIVE,  # observed choices in the rows
   PREDS=d$PRED_CAT)) # predicted choices in the columns
    PREDS
OBS    of    s
  of 2485  235
  s   323  557
c( # evaluate the confusion matrix
   "Class. acc."=mean(d$GENITIVE==d$PRED_CAT, na.rm=TRUE), # (tp+tn) / (tp+tn+fp+fn)
   "Prec. for s"     =c_m["s","s"]   / sum(c_m[    ,"s" ]),
   "Acc./rec. for s" =c_m["s","s"]   / sum(c_m["s" ,    ]),
   "Prec. for of"    =c_m["of","of"] / sum(c_m[    ,"of"]),
   "Acc./rec. for of"=c_m["of","of"] / sum(c_m["of",    ]))
     Class. acc.      Prec. for s  Acc./rec. for s     Prec. for of
       0.8450000        0.7032828        0.6329545        0.8849715
Acc./rec. for of
       0.9136029 

Also, we compute R2-values, Cohen’s κ, and C:

c(R2s(m_final), "Cohen's kappa"=cohens.kappa(c_m)[[1]],
  "C"=C.score(cv.f=d$GENITIVE, d$PRED_PP_2))
  McFadden R-squared Nagelkerke R-squared        Cohen's kappa
           0.4088545            0.5444074            0.5656912
                   C
           0.9034022 

Let’s now look at the effects/interactions one by one.

1.2.3.1 Main effect 1: LEN_PORmPUM_LOG

Here’s the effects object for the first interaction, LEN_PORmPUM_LOG:

ld_d <- data.frame(ld <- effect(
   "LEN_PORmPUM_LOG", m_final,
   xlevels=list(LEN_PORmPUM_LOG=seq(-5, 5, 1))))
# we add the predicted log odds as well:
ld_d$fit.lo <- qlogis(ld_d$fit)
# for later, we add the proportions of values in the data:
ld_d$PROP <- d$LEN_PORmPUM_LOG                        %>%
   cut(breaks=seq(-5.5, 5.5, 1), include.lowest=TRUE) %>%
   table %>% prop.table %>% as.numeric
ld_d %>% round(4)
   LEN_PORmPUM_LOG    fit     se  lower  upper  fit.lo   PROP
1               -5 0.8543 0.0317 0.7806 0.9062  1.7687 0.0003
2               -4 0.7366 0.0407 0.6495 0.8084  1.0282 0.0011
3               -3 0.5714 0.0410 0.4899 0.6493  0.2877 0.0125
4               -2 0.3887 0.0307 0.3305 0.4503 -0.4528 0.0453
5               -1 0.2327 0.0180 0.1993 0.2697 -1.1933 0.1619
6                0 0.1263 0.0100 0.1079 0.1474 -1.9338 0.3092
7                1 0.0645 0.0064 0.0531 0.0782 -2.6743 0.2900
8                2 0.0318 0.0042 0.0245 0.0413 -3.4148 0.1383
9                3 0.0154 0.0027 0.0110 0.0217 -4.1553 0.0325
10               4 0.0074 0.0016 0.0048 0.0114 -4.8958 0.0081
11               5 0.0036 0.0009 0.0021 0.0060 -5.6363 0.0008

Let’s visualize:

# plot(ld, grid=TRUE)
plot(ld, type="response", ylim=c(0, 1), grid=TRUE)
Figure 18: The effect of length differences

I might plot that effect like this:

op <- par(mar=c(4, 4, 4, 4)) # make the plotting margins big enough
plot(pch=16, col="#00000060", type="b", yaxp=c(0.1, 0.9, 4),
     cex=0.5 + (10 * ld_d$PROP),
   xlab="Difference of logged lengths (or-um)"              , x=ld_d$LEN_PORmPUM_LOG,
   ylab="Pred. prob. of s-genitives"          , ylim=c(0, 1), y=ld_d$fit)
abline(h=seq(0.1, 0.9, 0.2), lty=3, col="#00000030")
abline(v=quantile(d$LEN_PORmPUM_LOG, prob=seq(0, 1, 0.1)), lty=3, col="#00000030")
polygon(border=NA, col="#00000020",
   c(ld_d$LEN_PORmPUM_LOG, rev(ld_d$LEN_PORmPUM_LOG)),
   c(ld_d$lower          , rev(ld_d$upper)))
axis(4, at=seq(0.1, 0.9, 0.2), labels=round(qlogis(seq(0.1, 0.9, 0.2)), 2))
mtext("Pred. log odds", 4, 2.5)
par(op) # reset to defaults
Figure 19: The effect of length differences

Interpretation: a very clear short-before-long effect: when the possessor is longer than the possessum (on the right), s-genitives, which would have the long possessor in the front, are very unlikely and the other way round.

1.2.3.2 Main effect 2: POR_FINAL_SIB

Here’s the effects object for the second main effect, POR_FINAL_SIB:

fs_d <- data.frame(fs <- effect(
   "POR_FINAL_SIB", m_final))
# we add the predicted log odds as well:
fs_d$fit.lo <- qlogis(fs_d$fit)
# for later, we add the proportions of values in the data:
fs_d$PROP <- d$POR_FINAL_SIB %>% table %>% prop.table %>% as.numeric
fs_d
  POR_FINAL_SIB        fit          se      lower      upper    fit.lo
1        absent 0.11993689 0.010138717 0.10144322 0.14127200 -1.993028
2       present 0.04829721 0.006828568 0.03654244 0.06358365 -2.980879
       PROP
1 0.7558333
2 0.2441667

Let’s visualize:

# plot(fs, grid=TRUE)
plot(fs, type="response", ylim=c(0, 1), grid=TRUE)
Figure 20: The effect of final sibilants of the possessor

I would probably plot that effect like this:

op <- par(mar=c(4, 4, 4, 4)) # make the plotting margins big enough
plot(pch=16, col="#00000060", axes=FALSE,
     cex=4 * fs_d$PROP,
   xlab="Sibilant at end of possessor"            , x=1:2,
   ylab="Pred. prob. of s-genitives", ylim=c(0, 1), y=fs_d$fit)
abline(h=seq(0.1, 0.9, 0.2), lty=3, col="#00000030")
axis(1, at=1:2, labels=levels(d$POR_FINAL_SIB))
axis(2, at=seq(0.1, 0.9, 0.2)); abline(h=0.5, lty=3)
axis(4, at=seq(0.1, 0.9, 0.2), labels=round(qlogis(seq(0.1, 0.9, 0.2)), 2))
mtext("Pred. log odds", 4, 2.5)
for (i in seq(nrow(fs_d))) {
   arrows(i, fs_d$lower[i], i, fs_d$upper[i], angle=90, code=3)
}
par(op)
Figure 21: The effect of final sibilants of the possessor

Interpretation: a clear articulatory effect: when the possessor ends in a sibilant, s-genitives are less likely.

1.2.3.3 Interaction 1: SPEAKER:POR_ANIMACY

Here’s the effects object for the first interaction, SPEAKER:POR_ANIMACY:

span_d <- data.frame(span <- effect(
   "SPEAKER:POR_ANIMACY", m_final))
# we add the predicted log odds as well:
span_d$fit.lo <- qlogis(span_d$fit)
# for later, we add the proportions of values in the data:
span_d$PROP <- d$SPEAKER %>% table(d$POR_ANIMACY) %>% prop.table %>% as.numeric
span_d
   SPEAKER POR_ANIMACY        fit          se       lower      upper     fit.lo
1      nns     animate 0.60724788 0.020898039 0.565624076 0.64736973  0.4357584
2       ns     animate 0.58815212 0.034551554 0.519181196 0.65382621  0.3563316
3      nns  collective 0.18432202 0.018731617 0.150392613 0.22388893 -1.4873353
4       ns  collective 0.33324173 0.041692049 0.257055932 0.41926491 -0.6935594
5      nns   inanimate 0.01706783 0.003411133 0.011522547 0.02521373 -4.0533448
6       ns   inanimate 0.01207437 0.004990149 0.005354518 0.02699862 -4.4045228
7      nns    locative 0.11359488 0.021464411 0.077819929 0.16291073 -2.0545356
8       ns    locative 0.06721803 0.022111475 0.034843498 0.12575379 -2.6302299
9      nns    temporal 0.22820304 0.039580743 0.159903424 0.31474577 -1.2184857
10      ns    temporal 0.26093792 0.062288047 0.157872381 0.39937877 -1.0410994
         PROP
1  0.18833333
2  0.06722222
3  0.12555556
4  0.04305556
5  0.35472222
6  0.10944444
7  0.04194444
8  0.02555556
9  0.03000000
10 0.01416667

Let’s visualize:

plot(span, type="response", ylim=c(0, 1), grid=TRUE,
   multiline=TRUE, confint=list(style="auto"))
# the next plot is less useful, I think, but you should still have plotted it:
# plot(span, type="response", ylim=c(0, 1), grid=TRUE,
#      x.var="SPEAKER", multiline=TRUE, confint=list(style="auto"))
Figure 22: The effect of Speaker interacting with Possessor animacy

I would probably plot that effect like this:

x.coords <- rep(1:5, each=2)+c(-0.05, 0.05)
y.cols <- rainbow(2, alpha=0.5)[span_d$SPEAKER]
op <- par(mar=c(4, 4, 4, 4)) # make the plotting margins big enough
plot(pch=16, axes=FALSE,
     col=y.cols,
     cex=0.5+(5*span_d$PROP),
   xlab="Possessor animacy"         , xlim=c(0.9, 5.1), x=x.coords,
   ylab="Pred. prob. of s-genitives", ylim=c(0  , 1)  , y=span_d$fit)
abline(h=seq(0.1, 0.9, 0.2), lty=3, col="#00000030")
axis(1, at=1:5, labels=levels(d$POR_ANIMACY))
axis(2, at=seq(0.1, 0.9, 0.2)); abline(h=0.5, lty=2)
axis(4, at=seq(0.1, 0.9, 0.2), labels=round(qlogis(seq(0.1, 0.9, 0.2)), 2))
mtext("Pred. log odds", 4, 2.5)
for (i in seq(nrow(span_d))) {
   arrows(x.coords[i], span_d$lower[i],
          x.coords[i], span_d$upper[i],
          angle=90, code=3, col=y.cols[i])
}
legend(title="Speaker", bty="n",
   x=3, xjust=0.5, y=0.8, yjust=0.5, ncol=2,
   legend=levels(d$SPEAKER), fill=unique(y.cols))
par(op)
Figure 23: The effect of Speaker interacting with Possessor animacy

Interpretation: Both NS and NNS are most likely to use s-genitives with animate possessors, followed by collective/temporal possessors, then locative, and finally inanimate ones (e.g., concrete objects), but the only significant difference in the interaction is that NS are significantly more likely to use s-genitives with collectives than NNS.

1.2.3.4 Interaction 2: SPEAKER:MODALITY

Here’s the effects object for the second interaction, SPEAKER:MODALITY:

spmo_d <- data.frame(spmo <- effect(
   "SPEAKER:MODALITY", m_final))
# we add the predicted log odds as well:
spmo_d$fit.lo <- qlogis(spmo_d$fit)
# for later, we add the proportions of values in the data:
spmo_d$PROP <- d$SPEAKER %>% table(d$MODALITY) %>% prop.table %>% as.numeric
spmo_d
  SPEAKER MODALITY        fit          se      lower     upper    fit.lo
1     nns   spoken 0.12271581 0.012627520 0.10003348 0.1496859 -1.966960
2      ns   spoken 0.07548225 0.016373503 0.04902120 0.1145068 -2.505375
3     nns  written 0.08090306 0.009111243 0.06474884 0.1006538 -2.430140
4      ns  written 0.10835057 0.021102993 0.07338475 0.1571511 -2.107701
       PROP
1 0.3502778
2 0.1177778
3 0.3902778
4 0.1416667

Let’s visualize:

plot(spmo, type="response", ylim=c(0, 1), grid=TRUE,
   multiline=TRUE, confint=list(style="auto"))
# the next plot is not any more useful, but you should still have plotted it:
# plot(spmo, type="response", ylim=c(0, 1), grid=TRUE,
#      x.var="MODALITY", multiline=TRUE, confint=list(style="auto"))
Figure 24: The effect of Speaker interacting with Modality

I would probably plot that effect like this:

x.coords <- rep(1:2, each=2)+c(-0.05, 0.05)
y.cols <- rainbow(2, alpha=0.5)[span_d$SPEAKER]
op <- par(mar=c(4, 4, 4, 4)) # make the plotting margins big enough
plot(pch=16, axes=FALSE,
     cex=0.5+(5*spmo_d$PROP),
     col=y.cols,
   xlab="Modality"                  , xlim=c(0.9, 2.1), x=x.coords,
   ylab="Pred. prob. of s-genitives", ylim=c(0  , 1)  , y=spmo_d$fit)
abline(h=seq(0.1, 0.9, 0.2), lty=3, col="#00000030")
axis(1, at=1:2, labels=levels(d$MODALITY))
axis(2, at=seq(0.1, 0.9, 0.2)); abline(h=0.5, lty=2)
axis(4, at=seq(0.1, 0.9, 0.2), labels=round(qlogis(seq(0.1, 0.9, 0.2)), 2))
mtext("Pred. log odds", 4, 2.5)
for (i in seq(nrow(spmo_d))) {
   arrows(x.coords[i], spmo_d$lower[i],
          x.coords[i], spmo_d$upper[i],
          angle=90, code=3, col=y.cols[i])
}
legend(title="Speaker", bty="n",
   x=1.5, xjust=0.5, y=0.8, yjust=0.5, ncol=2,
   legend=levels(d$SPEAKER), fill=unique(y.cols))
par(op)
Figure 25: The effect of Speaker interacting with Modality

Interpretation: NS are more likely to use s-genitives in writing than they are in speaking and than NNS are in writing. NNS are less likely to use s-genitives in writing than they are in speaking and than NS are in writing. The highest predicted probability of s-genitives is found for NNS in speaking and the lowest predicted probability of s-genitives is found for NS in speaking.

1.2.4 Write-up

To determine whether the choice of a genitive varies as a function of

  • whether the speaker is a native speaker or not;
  • the length difference between possessor and possessum and whether native and non-native speakers react to it in the same way;
  • the animacy of the possessor and whether native and non-native speakers react to it in the same way;
  • whether the possessor ends in a sibilant or not and whether native and non-native speakers react to it in the same way;
  • whether the modality is speaking or writing and whether native and non-native speakers react to it in the same way;
  • the interaction of POR_FINAL_SIB with MODALITY.

a generalized linear model selection process was undertaken (using backwards stepwise model selection of all predictors and controls based on significance testing). The final model resulting from the elimination of ns predictors contained the effects of LEN_PORmPUM_LOG:POR_ANIMACY and SPEAKER:POR_ANIMACY and was highly significant (LR=1596.2, df=14, p<0.0001) with a good amount of explanatory/predictive power (McFadden’s R2=0.397, Nagelkerke’s R2=0.534, C=0.899). The summary table of the model is shown here.

Estimate 95%-CI se z p2-tailed
(Intercept (LEN_PORmPUM_LOG=0, POR_ANIMACY=animate, SPEAKER=nns) 1.22 [0.99, 1.47] 0.12 10.04 <0.001
SPEAKERnnsns -0.54 [-0.95, -0.12] 0.21 -2.53 0.011
LEN_PORmPUM_LOG0→1 -0.74 [-0.84, -0.65] 0.05 -15.21 <0.001
POR_ANIMACYanimatecollective -1.92 [-2.23, -1.62] 0.15 -12.43 <0.001
POR_ANIMACYanimateinanimate -4.49 [-4.95, -4.07] 0.22 -20.19 <0.001
POR_ANIMACYanimatelocative -2.49 [-2.96, -2.05] 0.23 -10.73 <0.001
POR_ANIMACYanimatetemporal -1.65 [-2.14, -1.19] 0.24 -6.83 <0.001
POR_FINAL_SIBabsentpresent -0.99 [-1.26, -0.72] 0.14 -7.2 <0.001
MODALITYspokenwritten -0.46 [-0.71, -0.22] 0.12 -3.76 0.0002
SPEAKERnnsns:POR_ANIMACYanimatecollective 0.87 [0.33, 1.42] 0.28 3.14 0.0017
SPEAKERnnsns:POR_ANIMACYanimateinanimate -0.27 [-1.32, 0.63] 0.49 -0.55 0.581
SPEAKERnnsns:POR_ANIMACYanimatelocative -0.5 [-1.39, 0.34] 0.44 -1.13 0.286
SPEAKERnnsns:POR_ANIMACYanimatetemporal 0.26 [-0.59, 1.09] 0.43 0.6 0.546
SPEAKERnnsns:MODALITYspokenwritten 0.86 [0.4, 1.33] 0.24 3.65 0.0003

The results indicate that

  • there is a clear main effect of LEN_PORmPUM_LOG amounting to a short-before-long effect;
  • there is a clear main effect of POR_FINAL_SIB: when the possessor ends in a sibilant, s-genitives are less likely;
  • the interaction SPEAKER:POR_ANIMACY only has the effect that native speakers are significantly more likely to use s genitives with collectives than learners are – all other speaker differences within each animacy level are ns. [Show plot(s)]
  • the interaction SPEAKER:MODALITY has the effect that NS are more likely to use s-genitives in writing than they are in speaking and than NNS are in writing. NNS are less likely to use s-genitives in writing than they are in speaking and than NS are in writing. [Show plot(s)]

2 Session info

sessionInfo()
R version 4.5.2 (2025-10-31)
Platform: x86_64-pc-linux-gnu
Running under: Pop!_OS 22.04 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so;  LAPACK version 3.10.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C
 [9] LC_ADDRESS=C               LC_TELEPHONE=C
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C

time zone: America/Los_Angeles
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices utils     datasets  compiler  methods
[8] base

other attached packages:
 [1] plotly_4.11.0   ggplot2_4.0.1   multcomp_1.4-29 TH.data_1.1-5
 [5] MASS_7.3-65     survival_3.8-3  mvtnorm_1.3-3   emmeans_2.0.1
 [9] effects_4.2-4   car_3.1-3       carData_3.0-5   STGmisc_1.04
[13] Rcpp_1.1.0      magrittr_2.0.4

loaded via a namespace (and not attached):
 [1] gtable_0.3.6       xfun_0.55          htmlwidgets_1.6.4  insight_1.4.4
 [5] lattice_0.22-7     crosstalk_1.2.2    vctrs_0.6.5        tools_4.5.2
 [9] Rdpack_2.6.4       generics_0.1.4     sandwich_3.1-1     tibble_3.3.0
[13] pkgconfig_2.0.3    Matrix_1.7-4       data.table_1.18.0  RColorBrewer_1.1-3
[17] S7_0.2.1           lifecycle_1.0.4    farver_2.1.2       mitools_2.4
[21] codetools_0.2-20   survey_4.4-8       htmltools_0.5.9    lazyeval_0.2.2
[25] yaml_2.3.12        Formula_1.2-5      tidyr_1.3.2        pillar_1.11.1
[29] nloptr_2.2.1       reformulas_0.4.3   boot_1.3-32        abind_1.4-8
[33] nlme_3.1-168       tidyselect_1.2.1   digest_0.6.39      purrr_1.2.0
[37] dplyr_1.1.4        splines_4.5.2      fastmap_1.2.0      grid_4.5.2
[41] colorspace_2.1-2   cli_3.6.5          withr_3.0.2        scales_1.4.0
[45] estimability_1.5.1 httr_1.4.7         rmarkdown_2.30     otel_0.2.0
[49] nnet_7.3-20        lme4_1.1-38        zoo_1.8-15         coda_0.19-4.1
[53] evaluate_1.0.5     knitr_1.51         rbibutils_2.4      viridisLite_0.4.2
[57] rlang_1.1.6        xtable_1.8-4       glue_1.8.0         DBI_1.2.3
[61] rstudioapi_0.17.1  minqa_1.2.8        jsonlite_2.0.0     R6_2.6.1