Binary Outcome GLM Effects Plots

This tutorial draws aims at making various binary outcome GLM models interpretable through the use of plots. As such, it begins by setting up some data (involving a few covariates) and then generating various versions of an outcome based upon data-generating proceses with and without interaction. The aim of the tutorial is to both highlight the use of predicted probability plots for demonstrating effects and demonstrate the challenge - even then - of clearly communicating the results of these types of models.

Let's begin by generating our covariates:

set.seed(1)
n <- 200
x1 <- rbinom(n, 1, 0.5)
x2 <- runif(n, 0, 1)
x3 <- runif(n, 0, 5)

Now, we'll build several models. Each model has an outcome that is a transformed linear function the covariates (i.e., we calculate a y variable that is a linear function of the covariates, then rescale that outcome [0,1], and use the rescaled version as a probability model in generating draws from a binomial distribution).

# Simple multivariate model (no interaction):
y1 <- 2 * x1 + 5 * x2 + rnorm(n, 0, 3)
y1s <- rbinom(n, 1, (y1 - min(y1))/(max(y1) - min(y1)))  # the math here is just to rescale to [0,1]
# Simple multivariate model (with interaction):
y2 <- 2 * x1 + 5 * x2 + 2 * x1 * x2 + rnorm(n, 0, 3)
y2s <- rbinom(n, 1, (y2 - min(y2))/(max(y2) - min(y2)))
# Simple multivariate model (with interaction and an extra term):
y3 <- 2 * x1 + 5 * x2 + 2 * x1 * x2 + x3 + rnorm(n, 0, 3)
y3s <- rbinom(n, 1, (y2 - min(y2))/(max(y2) - min(y2)))

We thus have three outcomes (y1s, y2s, and y3s) that are binary outcomes, but each is constructed as a slightly different function of our three covariates. We can then build models of each outcome. We'll build two versions of y2s and y3s (one version a that does not model the interaction and another version b that does model it):

m1 <- glm(y1s ~ x1 + x2, family = binomial(link = "probit"))
m2a <- glm(y2s ~ x1 + x2, family = binomial(link = "probit"))
m2b <- glm(y2s ~ x1 * x2, family = binomial(link = "probit"))
m3a <- glm(y1s ~ x1 + x2 + x3, family = binomial(link = "probit"))
m3b <- glm(y1s ~ x1 * x2 + x3, family = binomial(link = "probit"))

We can look at the outcome of one of our models, e.g. m3b (for y3s modelled with an interaction), but we know that the coefficients are not directly interpretable:

summary(m3b)
## 
## Call:
## glm(formula = y1s ~ x1 * x2 + x3, family = binomial(link = "probit"))
## 
## Deviance Residuals: 
##    Min      1Q  Median      3Q     Max  
## -1.554  -1.141   0.873   1.011   1.365  
## 
## Coefficients:
##             Estimate Std. Error z value Pr(>|z|)
## (Intercept)  -0.1591     0.2769   -0.57     0.57
## x1            0.4963     0.3496    1.42     0.16
## x2            0.3212     0.4468    0.72     0.47
## x3           -0.0315     0.0625   -0.50     0.61
## x1:x2        -0.0794     0.6429   -0.12     0.90
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 274.83  on 199  degrees of freedom
## Residual deviance: 266.59  on 195  degrees of freedom
## AIC: 276.6
## 
## Number of Fisher Scoring iterations: 4

Instead we need to look at fitted values (specifically, the predicted probability of observing y==1 in each model. We can see these fitted values for our actual data using the predict function:

p3b.fitted <- predict(m3b, type = "response", se.fit = TRUE)
p3b.fitted
## $fit
##      1      2      3      4      5      6      7      8      9     10 
## 0.4298 0.4530 0.6225 0.6029 0.4015 0.6364 0.6609 0.5970 0.6545 0.4695 
##     11     12     13     14     15     16     17     18     19     20 
## 0.4973 0.4273 0.6569 0.5082 0.6642 0.4463 0.6614 0.6982 0.5151 0.6141 
##     21     22     23     24     25     26     27     28     29     30 
## 0.6302 0.4258 0.6220 0.4929 0.5332 0.4765 0.4642 0.3856 0.6203 0.4908 
##     31     32     33     34     35     36     37     38     39     40 
## 0.4228 0.6397 0.4608 0.4837 0.6609 0.6472 0.6508 0.5043 0.6425 0.4370 
##     41     42     43     44     45     46     47     48     49     50 
## 0.6572 0.6667 0.6839 0.6086 0.6539 0.6261 0.4685 0.3956 0.6660 0.7103 
##     51     52     53     54     55     56     57     58     59     60 
## 0.4737 0.6890 0.4736 0.5032 0.4953 0.4100 0.4323 0.6188 0.6302 0.5521 
##     61     62     63     64     65     66     67     68     69     70 
## 0.6694 0.4475 0.4720 0.5097 0.6794 0.3998 0.4009 0.6893 0.4861 0.6063 
##     71     72     73     74     75     76     77     78     79     80 
## 0.4162 0.5845 0.4409 0.4336 0.4674 0.6353 0.6039 0.4327 0.6459 0.6608 
##     81     82     83     84     85     86     87     88     89     90 
## 0.3937 0.6701 0.4960 0.4253 0.6011 0.4232 0.6499 0.4563 0.3995 0.4565 
##     91     92     93     94     95     96     97     98     99    100 
## 0.4641 0.3956 0.7014 0.6519 0.6543 0.6663 0.4225 0.4199 0.5856 0.7099 
##    101    102    103    104    105    106    107    108    109    110 
## 0.6602 0.4064 0.4584 0.6347 0.6382 0.5027 0.4342 0.4874 0.5928 0.6369 
##    111    112    113    114    115    116    117    118    119    120 
## 0.5924 0.6219 0.5437 0.4831 0.4262 0.4882 0.6009 0.4480 0.5396 0.6509 
##    121    122    123    124    125    126    127    128    129    130 
## 0.6434 0.4324 0.4667 0.5559 0.6854 0.5004 0.6580 0.4891 0.4115 0.6425 
##    131    132    133    134    135    136    137    138    139    140 
## 0.6845 0.4723 0.4666 0.6879 0.6321 0.6621 0.6502 0.6263 0.6739 0.6822 
##    141    142    143    144    145    146    147    148    149    150 
## 0.6963 0.5908 0.4650 0.4652 0.6807 0.4638 0.4344 0.6252 0.4487 0.6544 
##    151    152    153    154    155    156    157    158    159    160 
## 0.6247 0.6036 0.5363 0.4336 0.6748 0.4489 0.7010 0.4441 0.4986 0.4451 
##    161    162    163    164    165    166    167    168    169    170 
## 0.4102 0.6543 0.5224 0.6137 0.6317 0.5000 0.4308 0.4861 0.6780 0.4361 
##    171    172    173    174    175    176    177    178    179    180 
## 0.6620 0.6581 0.6992 0.4602 0.4137 0.6391 0.6228 0.6851 0.5977 0.6332 
##    181    182    183    184    185    186    187    188    189    190 
## 0.4698 0.4494 0.6532 0.7151 0.6177 0.4415 0.6977 0.6755 0.5844 0.6767 
##    191    192    193    194    195    196    197    198    199    200 
## 0.6125 0.4399 0.5062 0.6395 0.4052 0.6253 0.4909 0.6311 0.4578 0.6187 
## 
## $se.fit
##       1       2       3       4       5       6       7       8       9 
## 0.06090 0.07493 0.07213 0.07595 0.08505 0.05446 0.05024 0.08497 0.08736 
##      10      11      12      13      14      15      16      17      18 
## 0.08458 0.11678 0.07902 0.07268 0.10658 0.07666 0.05544 0.05338 0.08647 
##      19      20      21      22      23      24      25      26      27 
## 0.10526 0.06544 0.06385 0.06895 0.05878 0.07211 0.10434 0.05489 0.07976 
##      28      29      30      31      32      33      34      35      36 
## 0.09837 0.06283 0.09679 0.07274 0.09728 0.05479 0.06119 0.06930 0.06898 
##      37      38      39      40      41      42      43      44      45 
## 0.06506 0.08250 0.04903 0.06905 0.08040 0.05357 0.08155 0.07871 0.05722 
##      46      47      48      49      50      51      52      53      54 
## 0.07044 0.05457 0.08967 0.06453 0.09121 0.09065 0.08373 0.05496 0.07556 
##      55      56      57      58      59      60      61      62      63 
## 0.07928 0.07808 0.06067 0.07489 0.05318 0.12015 0.06053 0.05486 0.08607 
##      64      65      66      67      68      69      70      71      72 
## 0.08180 0.06173 0.08619 0.08555 0.07004 0.06089 0.07659 0.08207 0.09648 
##      73      74      75      76      77      78      79      80      81 
## 0.05605 0.06985 0.07491 0.07977 0.07469 0.06774 0.04755 0.07062 0.09192 
##      82      83      84      85      86      87      88      89      90 
## 0.06098 0.09897 0.07266 0.09254 0.07215 0.06856 0.09395 0.08548 0.07833 
##      91      92      93      94      95      96      97      98      99 
## 0.08700 0.08957 0.09002 0.06974 0.04870 0.05586 0.07756 0.07460 0.09843 
##     100     101     102     103     104     105     106     107     108 
## 0.09045 0.05616 0.08076 0.05338 0.05133 0.05233 0.11964 0.06895 0.09019 
##     109     110     111     112     113     114     115     116     117 
## 0.09251 0.05045 0.08785 0.07194 0.11253 0.07388 0.06328 0.06781 0.08778 
##     118     119     120     121     122     123     124     125     126 
## 0.05248 0.11138 0.05489 0.06888 0.05946 0.05557 0.12468 0.07418 0.11247 
##     127     128     129     130     131     132     133     134     135 
## 0.08133 0.08255 0.07904 0.09024 0.09377 0.08286 0.05628 0.07110 0.10181 
##     136     137     138     139     140     141     142     143     144 
## 0.07580 0.05096 0.08026 0.06026 0.09215 0.09064 0.08885 0.05537 0.05696 
##     145     146     147     148     149     150     151     152     153 
## 0.06283 0.07662 0.07562 0.07680 0.05194 0.06802 0.06453 0.09005 0.10586 
##     154     155     156     157     158     159     160     161     162 
## 0.06816 0.09976 0.05952 0.08116 0.09688 0.09872 0.06212 0.07796 0.07138 
##     163     164     165     166     167     168     169     170     171 
## 0.09198 0.06561 0.07026 0.08818 0.08680 0.06974 0.06071 0.07222 0.06292 
##     172     173     174     175     176     177     178     179     180 
## 0.04906 0.08438 0.08074 0.07343 0.04938 0.05798 0.07527 0.08185 0.05499 
##     181     182     183     184     185     186     187     188     189 
## 0.05549 0.07398 0.05759 0.09607 0.06677 0.06207 0.07768 0.07232 0.09640 
##     190     191     192     193     194     195     196     197     198 
## 0.09458 0.07770 0.07854 0.10283 0.05069 0.08024 0.05617 0.06491 0.05762 
##     199     200 
## 0.05557 0.07847 
## 
## $residual.scale
## [1] 1

We can even draw a small plot showing the predicted values separately for levels of x1 (recall that x1 is a binary/indicator variable):

plot(NA, xlim = c(0, 1), ylim = c(0, 1), xlab = "x2", ylab = "Predicted Probability of y=1")
points(x2[x1 == 0], p3b.fitted$fit[x1 == 0], col = rgb(1, 0, 0, 0.5))
points(x2[x1 == 1], p3b.fitted$fit[x1 == 1], col = rgb(0, 0, 1, 0.5))

plot of chunk unnamed-chunk-6

But this graph doesn't show the fit of the model to all values of x1 and x2 (or x3) and doesn't communicate any of our uncertainty.

Predicted Probability Plots

To get a better grasp on our models, we'll create some fake data representing the full scales of x1, x2, and x3:

newdata1 <- expand.grid(x1 = 0:1, x2 = seq(0, 1, length.out = 10))
newdata2 <- expand.grid(x1 = 0:1, x2 = seq(0, 1, length.out = 10), x3 = seq(0, 
    5, length.out = 25))

We can then use these new fake data to generate predicted probabilities of each outcome at each combination of covarites:

p1 <- predict(m1, newdata1, type = "response", se.fit = TRUE)
p2a <- predict(m2a, newdata1, type = "response", se.fit = TRUE)
p2b <- predict(m2b, newdata1, type = "response", se.fit = TRUE)
p3a <- predict(m3a, newdata2, type = "response", se.fit = TRUE)
p3b <- predict(m3b, newdata2, type = "response", se.fit = TRUE)

We can look at one of these objects, e.g. p3b, to see that we have predicted probabilities and associated standard errors:

p3b
## $fit
##      1      2      3      4      5      6      7      8      9     10 
## 0.4368 0.6320 0.4509 0.6421 0.4650 0.6521 0.4792 0.6619 0.4935 0.6717 
##     11     12     13     14     15     16     17     18     19     20 
## 0.5077 0.6814 0.5219 0.6909 0.5361 0.7003 0.5503 0.7096 0.5644 0.7187 
##     21     22     23     24     25     26     27     28     29     30 
## 0.4342 0.6295 0.4483 0.6396 0.4624 0.6496 0.4766 0.6595 0.4909 0.6693 
##     31     32     33     34     35     36     37     38     39     40 
## 0.5051 0.6790 0.5193 0.6886 0.5335 0.6980 0.5477 0.7073 0.5618 0.7165 
##     41     42     43     44     45     46     47     48     49     50 
## 0.4316 0.6271 0.4457 0.6372 0.4598 0.6472 0.4740 0.6571 0.4882 0.6670 
##     51     52     53     54     55     56     57     58     59     60 
## 0.5025 0.6767 0.5167 0.6863 0.5309 0.6957 0.5451 0.7051 0.5592 0.7143 
##     61     62     63     64     65     66     67     68     69     70 
## 0.4291 0.6246 0.4431 0.6347 0.4572 0.6448 0.4714 0.6547 0.4856 0.6646 
##     71     72     73     74     75     76     77     78     79     80 
## 0.4999 0.6743 0.5141 0.6839 0.5283 0.6934 0.5425 0.7028 0.5566 0.7120 
##     81     82     83     84     85     86     87     88     89     90 
## 0.4265 0.6221 0.4405 0.6323 0.4546 0.6423 0.4688 0.6523 0.4830 0.6622 
##     91     92     93     94     95     96     97     98     99    100 
## 0.4972 0.6719 0.5115 0.6816 0.5257 0.6911 0.5399 0.7005 0.5540 0.7098 
##    101    102    103    104    105    106    107    108    109    110 
## 0.4239 0.6196 0.4379 0.6298 0.4520 0.6399 0.4662 0.6499 0.4804 0.6598 
##    111    112    113    114    115    116    117    118    119    120 
## 0.4946 0.6696 0.5089 0.6792 0.5231 0.6888 0.5373 0.6982 0.5514 0.7075 
##    121    122    123    124    125    126    127    128    129    130 
## 0.4213 0.6171 0.4354 0.6273 0.4494 0.6374 0.4636 0.6475 0.4778 0.6574 
##    131    132    133    134    135    136    137    138    139    140 
## 0.4920 0.6672 0.5062 0.6769 0.5205 0.6865 0.5347 0.6959 0.5488 0.7053 
##    141    142    143    144    145    146    147    148    149    150 
## 0.4188 0.6146 0.4328 0.6248 0.4468 0.6350 0.4610 0.6450 0.4752 0.6550 
##    151    152    153    154    155    156    157    158    159    160 
## 0.4894 0.6648 0.5036 0.6745 0.5179 0.6842 0.5321 0.6936 0.5462 0.7030 
##    161    162    163    164    165    166    167    168    169    170 
## 0.4162 0.6121 0.4302 0.6223 0.4443 0.6325 0.4584 0.6426 0.4726 0.6525 
##    171    172    173    174    175    176    177    178    179    180 
## 0.4868 0.6624 0.5010 0.6722 0.5153 0.6818 0.5295 0.6913 0.5436 0.7007 
##    181    182    183    184    185    186    187    188    189    190 
## 0.4137 0.6096 0.4276 0.6198 0.4417 0.6300 0.4558 0.6401 0.4700 0.6501 
##    191    192    193    194    195    196    197    198    199    200 
## 0.4842 0.6600 0.4984 0.6698 0.5126 0.6795 0.5269 0.6890 0.5410 0.6985 
##    201    202    203    204    205    206    207    208    209    210 
## 0.4111 0.6071 0.4251 0.6173 0.4391 0.6275 0.4532 0.6377 0.4674 0.6477 
##    211    212    213    214    215    216    217    218    219    220 
## 0.4816 0.6576 0.4958 0.6674 0.5100 0.6771 0.5242 0.6867 0.5384 0.6962 
##    221    222    223    224    225    226    227    228    229    230 
## 0.4086 0.6045 0.4225 0.6148 0.4365 0.6251 0.4506 0.6352 0.4647 0.6453 
##    231    232    233    234    235    236    237    238    239    240 
## 0.4789 0.6552 0.4932 0.6650 0.5074 0.6748 0.5216 0.6844 0.5358 0.6939 
##    241    242    243    244    245    246    247    248    249    250 
## 0.4060 0.6020 0.4199 0.6123 0.4339 0.6226 0.4480 0.6327 0.4621 0.6428 
##    251    252    253    254    255    256    257    258    259    260 
## 0.4763 0.6528 0.4906 0.6627 0.5048 0.6724 0.5190 0.6821 0.5332 0.6916 
##    261    262    263    264    265    266    267    268    269    270 
## 0.4035 0.5995 0.4174 0.6098 0.4313 0.6201 0.4454 0.6303 0.4595 0.6404 
##    271    272    273    274    275    276    277    278    279    280 
## 0.4737 0.6504 0.4879 0.6603 0.5022 0.6700 0.5164 0.6797 0.5306 0.6893 
##    281    282    283    284    285    286    287    288    289    290 
## 0.4010 0.5969 0.4148 0.6073 0.4288 0.6176 0.4428 0.6278 0.4569 0.6379 
##    291    292    293    294    295    296    297    298    299    300 
## 0.4711 0.6479 0.4853 0.6579 0.4996 0.6677 0.5138 0.6774 0.5280 0.6869 
##    301    302    303    304    305    306    307    308    309    310 
## 0.3984 0.5944 0.4123 0.6048 0.4262 0.6151 0.4402 0.6253 0.4543 0.6354 
##    311    312    313    314    315    316    317    318    319    320 
## 0.4685 0.6455 0.4827 0.6554 0.4970 0.6653 0.5112 0.6750 0.5254 0.6846 
##    321    322    323    324    325    326    327    328    329    330 
## 0.3959 0.5919 0.4097 0.6023 0.4236 0.6126 0.4376 0.6228 0.4517 0.6330 
##    331    332    333    334    335    336    337    338    339    340 
## 0.4659 0.6431 0.4801 0.6530 0.4943 0.6629 0.5086 0.6726 0.5228 0.6823 
##    341    342    343    344    345    346    347    348    349    350 
## 0.3934 0.5893 0.4072 0.5997 0.4211 0.6101 0.4351 0.6203 0.4492 0.6305 
##    351    352    353    354    355    356    357    358    359    360 
## 0.4633 0.6406 0.4775 0.6506 0.4917 0.6605 0.5060 0.6703 0.5202 0.6799 
##    361    362    363    364    365    366    367    368    369    370 
## 0.3909 0.5868 0.4046 0.5972 0.4185 0.6075 0.4325 0.6178 0.4466 0.6280 
##    371    372    373    374    375    376    377    378    379    380 
## 0.4607 0.6382 0.4749 0.6482 0.4891 0.6581 0.5033 0.6679 0.5176 0.6776 
##    381    382    383    384    385    386    387    388    389    390 
## 0.3883 0.5842 0.4021 0.5946 0.4159 0.6050 0.4299 0.6153 0.4440 0.6256 
##    391    392    393    394    395    396    397    398    399    400 
## 0.4581 0.6357 0.4723 0.6457 0.4865 0.6557 0.5007 0.6655 0.5150 0.6752 
##    401    402    403    404    405    406    407    408    409    410 
## 0.3858 0.5816 0.3995 0.5921 0.4134 0.6025 0.4273 0.6128 0.4414 0.6231 
##    411    412    413    414    415    416    417    418    419    420 
## 0.4555 0.6332 0.4697 0.6433 0.4839 0.6533 0.4981 0.6631 0.5123 0.6729 
##    421    422    423    424    425    426    427    428    429    430 
## 0.3833 0.5791 0.3970 0.5896 0.4108 0.6000 0.4248 0.6103 0.4388 0.6206 
##    431    432    433    434    435    436    437    438    439    440 
## 0.4529 0.6308 0.4671 0.6408 0.4813 0.6508 0.4955 0.6607 0.5097 0.6705 
##    441    442    443    444    445    446    447    448    449    450 
## 0.3808 0.5765 0.3945 0.5870 0.4083 0.5974 0.4222 0.6078 0.4362 0.6181 
##    451    452    453    454    455    456    457    458    459    460 
## 0.4503 0.6283 0.4645 0.6384 0.4787 0.6484 0.4929 0.6583 0.5071 0.6681 
##    461    462    463    464    465    466    467    468    469    470 
## 0.3783 0.5739 0.3920 0.5845 0.4057 0.5949 0.4196 0.6053 0.4336 0.6156 
##    471    472    473    474    475    476    477    478    479    480 
## 0.4477 0.6258 0.4619 0.6359 0.4760 0.6460 0.4903 0.6559 0.5045 0.6657 
##    481    482    483    484    485    486    487    488    489    490 
## 0.3758 0.5714 0.3895 0.5819 0.4032 0.5924 0.4171 0.6027 0.4311 0.6131 
##    491    492    493    494    495    496    497    498    499    500 
## 0.4451 0.6233 0.4593 0.6335 0.4734 0.6435 0.4877 0.6535 0.5019 0.6634 
## 
## $se.fit
##       1       2       3       4       5       6       7       8       9 
## 0.10906 0.11971 0.09785 0.10469 0.08926 0.09169 0.08427 0.08147 0.08364 
##      10      11      12      13      14      15      16      17      18 
## 0.07488 0.08750 0.07263 0.09527 0.07477 0.10602 0.08065 0.11883 0.08923 
##      19      20      21      22      23      24      25      26      27 
## 0.13296 0.09953 0.10626 0.11718 0.09465 0.10190 0.08564 0.08862 0.08033 
##      28      29      30      31      32      33      34      35      36 
## 0.07816 0.07956 0.07148 0.08352 0.06934 0.09157 0.07183 0.10267 0.07818 
##      37      38      39      40      41      42      43      44      45 
## 0.11584 0.08725 0.13030 0.09800 0.10365 0.11478 0.09163 0.09923 0.08220 
##      46      47      48      49      50      51      52      53      54 
## 0.08568 0.07653 0.07498 0.07562 0.06818 0.07968 0.06617 0.08801 0.06903 
##      55      56      57      58      59      60      61      62      63 
## 0.09947 0.07586 0.11299 0.08542 0.12779 0.09661 0.10123 0.11251 0.08881 
##      64      65      66      67      68      69      70      71      72 
## 0.09671 0.07895 0.08288 0.07291 0.07193 0.07184 0.06502 0.07599 0.06315 
##      73      74      75      76      77      78      79      80      81 
## 0.08461 0.06638 0.09642 0.07372 0.11030 0.08376 0.12542 0.09538 0.09903 
##      82      83      84      85      86      87      88      89      90 
## 0.11040 0.08621 0.09435 0.07591 0.08025 0.06949 0.06905 0.06824 0.06203 
##      91      92      93      94      95      96      97      98      99 
## 0.07249 0.06030 0.08139 0.06393 0.09355 0.07176 0.10777 0.08229 0.12320 
##     100     101     102     103     104     105     106     107     108 
## 0.09432 0.09705 0.10845 0.08386 0.09217 0.07312 0.07780 0.06631 0.06636 
##     109     110     111     112     113     114     115     116     117 
## 0.06485 0.05922 0.06920 0.05766 0.07838 0.06170 0.09088 0.07003 0.10543 
##     118     119     120     121     122     123     124     125     126 
## 0.08102 0.12115 0.09344 0.09530 0.10667 0.08176 0.09017 0.07060 0.07556 
##     127     128     129     130     131     132     133     134     135 
## 0.06339 0.06388 0.06173 0.05665 0.06614 0.05525 0.07560 0.05971 0.08843 
##     136     137     138     139     140     141     142     143     144 
## 0.06853 0.10328 0.07997 0.11927 0.09276 0.09381 0.10508 0.07994 0.08839 
##     145     146     147     148     149     150     151     152     153 
## 0.06838 0.07355 0.06077 0.06166 0.05889 0.05435 0.06337 0.05313 0.07307 
##     154     155     156     157     158     159     160     161     162 
## 0.05801 0.08621 0.06730 0.10134 0.07914 0.11758 0.09227 0.09257 0.10369 
##     163     164     165     166     167     168     169     170     171 
## 0.07842 0.08683 0.06650 0.07179 0.05850 0.05973 0.05639 0.05236 0.06091 
##     172     173     174     175     176     177     178     179     180 
## 0.05134 0.07084 0.05662 0.08424 0.06635 0.09963 0.07856 0.11608 0.09198 
##     181     182     183     184     185     186     187     188     189 
## 0.09160 0.10251 0.07722 0.08551 0.06497 0.07032 0.05662 0.05811 0.05427 
##     190     191     192     193     194     195     196     197     198 
## 0.05072 0.05881 0.04992 0.06892 0.05558 0.08255 0.06570 0.09815 0.07823 
##     199     200     201     202     203     204     205     206     207 
## 0.11479 0.09191 0.09091 0.10154 0.07633 0.08445 0.06382 0.06915 0.05515 
##     208     209     210     211     212     213     214     215     216 
## 0.05686 0.05259 0.04949 0.05711 0.04891 0.06735 0.05491 0.08115 0.06536 
##     217     218     219     220     221     222     223     224     225 
## 0.09691 0.07817 0.11370 0.09206 0.09049 0.10081 0.07578 0.08366 0.06306 
##     226     227     228     229     230     231     232     233     234 
## 0.06831 0.05415 0.05599 0.05137 0.04869 0.05583 0.04833 0.06615 0.05464 
##     235     236     237     238     239     240     241     242     243 
## 0.08006 0.06536 0.09594 0.07837 0.11284 0.09243 0.09035 0.10031 0.07557 
##     244     245     246     247     248     249     250     251     252 
## 0.08315 0.06272 0.06780 0.05362 0.05553 0.05065 0.04836 0.05502 0.04823 
##     253     254     255     256     257     258     259     260     261 
## 0.06533 0.05477 0.07929 0.06568 0.09524 0.07885 0.11220 0.09303 0.09049 
##     262     263     264     265     266     267     268     269     270 
## 0.10005 0.07570 0.08293 0.06280 0.06765 0.05358 0.05549 0.05045 0.04852 
##     271     272     273     274     275     276     277     278     279 
## 0.05468 0.04860 0.06492 0.05532 0.07886 0.06634 0.09480 0.07959 0.11180 
##     280     281     282     283     284     285     286     287     288 
## 0.09384 0.09091 0.10004 0.07617 0.08301 0.06328 0.06785 0.05403 0.05589 
##     289     290     291     292     293     294     295     296     297 
## 0.05078 0.04916 0.05483 0.04944 0.06492 0.05627 0.07876 0.06733 0.09465 
##     298     299     300     301     302     303     304     305     306 
## 0.08061 0.11162 0.09488 0.09159 0.10028 0.07695 0.08338 0.06417 0.06842 
##     307     308     309     310     311     312     313     314     315 
## 0.05496 0.05671 0.05163 0.05027 0.05547 0.05074 0.06533 0.05761 0.07900 
##     316     317     318     319     320     321     322     323     324 
## 0.06864 0.09478 0.08188 0.11168 0.09614 0.09252 0.10077 0.07806 0.08406 
##     325     326     327     328     329     330     331     332     333 
## 0.06543 0.06934 0.05633 0.05796 0.05295 0.05183 0.05657 0.05247 0.06615 
##     334     335     336     337     338     339     340     341     342 
## 0.05932 0.07957 0.07026 0.09519 0.08342 0.11198 0.09762 0.09371 0.10151 
##     343     344     345     346     347     348     349     350     351 
## 0.07945 0.08502 0.06705 0.07061 0.05812 0.05959 0.05473 0.05380 0.05811 
##     352     353     354     355     356     357     358     359     360 
## 0.05459 0.06735 0.06138 0.08048 0.07218 0.09587 0.08519 0.11251 0.09930 
##     361     362     363     364     365     366     367     368     369 
## 0.09513 0.10250 0.08113 0.08628 0.06900 0.07221 0.06028 0.06160 0.05691 
##     370     371     372     373     374     375     376     377     378 
## 0.05616 0.06004 0.05707 0.06891 0.06375 0.08170 0.07436 0.09682 0.08721 
##     379     380     381     382     383     384     385     386     387 
## 0.11328 0.10118 0.09678 0.10372 0.08306 0.08781 0.07124 0.07412 0.06277 
##     388     389     390     391     392     393     394     395     396 
## 0.06394 0.05945 0.05885 0.06234 0.05987 0.07082 0.06642 0.08322 0.07681 
##     397     398     399     400     401     402     403     404     405 
## 0.09804 0.08945 0.11426 0.10326 0.09863 0.10518 0.08524 0.08960 0.07374 
##     406     407     408     409     410     411     412     413     414 
## 0.07633 0.06555 0.06659 0.06230 0.06184 0.06496 0.06294 0.07304 0.06934 
##     415     416     417     418     419     420     421     422     423 
## 0.08503 0.07949 0.09951 0.09189 0.11548 0.10552 0.10067 0.10687 0.08762 
##     424     425     426     427     428     429     430     431     432 
## 0.09164 0.07649 0.07880 0.06858 0.06951 0.06541 0.06508 0.06787 0.06626 
##     433     434     435     436     437     438     439     440     441 
## 0.07554 0.07249 0.08710 0.08238 0.10122 0.09454 0.11690 0.10797 0.10289 
##     442     443     444     445     446     447     448     449     450 
## 0.10877 0.09021 0.09393 0.07944 0.08152 0.07183 0.07267 0.06875 0.06856 
##     451     452     453     454     455     456     457     458     459 
## 0.07101 0.06979 0.07829 0.07585 0.08942 0.08548 0.10316 0.09737 0.11853 
##     460     461     462     463     464     465     466     467     468 
## 0.11058 0.10528 0.11087 0.09297 0.09643 0.08258 0.08447 0.07527 0.07605 
##     469     470     471     472     473     474     475     476     477 
## 0.07228 0.07223 0.07437 0.07351 0.08127 0.07940 0.09197 0.08875 0.10531 
##     478     479     480     481     482     483     484     485     486 
## 0.10038 0.12036 0.11335 0.10782 0.11318 0.09588 0.09914 0.08587 0.08763 
##     487     488     489     490     491     492     493     494     495 
## 0.07886 0.07963 0.07598 0.07608 0.07791 0.07739 0.08445 0.08311 0.09473 
##     496     497     498     499     500 
## 0.09219 0.10767 0.10354 0.12238 0.11627 
## 
## $residual.scale
## [1] 1

It is then relatively straight forward to plot the predicted probabilities for all of our data. We'll start with the simple models, then look at the models with interactions and the additional covariate x3.

Simple, no-interaction model

plot(NA, xlim = c(0, 1), ylim = c(0, 1), xlab = "x2", ylab = "Predicted Probability of y=1")
# `x1==0`
lines(newdata1$x2[newdata1$x1 == 0], p1$fit[newdata1$x1 == 0], col = "red")
lines(newdata1$x2[newdata1$x1 == 0], p1$fit[newdata1$x1 == 0] + 1.96 * p1$se.fit[newdata1$x1 == 
    0], col = "red", lty = 2)
lines(newdata1$x2[newdata1$x1 == 0], p1$fit[newdata1$x1 == 0] - 1.96 * p1$se.fit[newdata1$x1 == 
    0], col = "red", lty = 2)
# `x1==1`
lines(newdata1$x2[newdata1$x1 == 1], p1$fit[newdata1$x1 == 1], col = "blue")
lines(newdata1$x2[newdata1$x1 == 1], p1$fit[newdata1$x1 == 1] + 1.96 * p1$se.fit[newdata1$x1 == 
    1], col = "blue", lty = 2)
lines(newdata1$x2[newdata1$x1 == 1], p1$fit[newdata1$x1 == 1] - 1.96 * p1$se.fit[newdata1$x1 == 
    1], col = "blue", lty = 2)

plot of chunk unnamed-chunk-10

The above plot shows two predicted probability curves with heavily overlapping confidence bands. While the effect of x2 is clearly different from zero for both x1==0 and x1==1, the difference between the two curves is not significant. But this model is based on data with no underlying interaction. Let's look next at the outcome that is a function of an interaction between covariates.

Interaction model

Recall that the interaction model (with outcome y2s) was estimated in two different ways. The first estimated model did not account for the interaction, while the second estimated model did account for the interaction. Let's see the two models side-by-side to compare the inference we would draw about the interaction:

# Model estimated without interaction
layout(matrix(1:2, nrow = 1))
plot(NA, xlim = c(0, 1), ylim = c(0, 1), xlab = "x2", ylab = "Predicted Probability of y=1", 
    main = "Estimated without interaction")
# `x1==0`
lines(newdata1$x2[newdata1$x1 == 0], p2a$fit[newdata1$x1 == 0], col = "red")
lines(newdata1$x2[newdata1$x1 == 0], p2a$fit[newdata1$x1 == 0] + 1.96 * p2a$se.fit[newdata1$x1 == 
    0], col = "red", lty = 2)
lines(newdata1$x2[newdata1$x1 == 0], p2a$fit[newdata1$x1 == 0] - 1.96 * p2a$se.fit[newdata1$x1 == 
    0], col = "red", lty = 2)
# `x1==1`
lines(newdata1$x2[newdata1$x1 == 1], p2a$fit[newdata1$x1 == 1], col = "blue")
lines(newdata1$x2[newdata1$x1 == 1], p2a$fit[newdata1$x1 == 1] + 1.96 * p2a$se.fit[newdata1$x1 == 
    1], col = "blue", lty = 2)
lines(newdata1$x2[newdata1$x1 == 1], p2a$fit[newdata1$x1 == 1] - 1.96 * p2a$se.fit[newdata1$x1 == 
    1], col = "blue", lty = 2)
# Model estimated with interaction
plot(NA, xlim = c(0, 1), ylim = c(0, 1), xlab = "x2", ylab = "Predicted Probability of y=1", 
    main = "Estimated with interaction")
# `x1==0`
lines(newdata1$x2[newdata1$x1 == 0], p2b$fit[newdata1$x1 == 0], col = "red")
lines(newdata1$x2[newdata1$x1 == 0], p2b$fit[newdata1$x1 == 0] + 1.96 * p2b$se.fit[newdata1$x1 == 
    0], col = "red", lty = 2)
lines(newdata1$x2[newdata1$x1 == 0], p2b$fit[newdata1$x1 == 0] - 1.96 * p2b$se.fit[newdata1$x1 == 
    0], col = "red", lty = 2)
# `x1==1`
lines(newdata1$x2[newdata1$x1 == 1], p2b$fit[newdata1$x1 == 1], col = "blue")
lines(newdata1$x2[newdata1$x1 == 1], p2b$fit[newdata1$x1 == 1] + 1.96 * p2b$se.fit[newdata1$x1 == 
    1], col = "blue", lty = 2)
lines(newdata1$x2[newdata1$x1 == 1], p2b$fit[newdata1$x1 == 1] - 1.96 * p2b$se.fit[newdata1$x1 == 
    1], col = "blue", lty = 2)

plot of chunk unnamed-chunk-11

The lefthand model leads us to some incorrect inference. Both predicted probability curves are essentially identical, suggesting that the influence of x2 is constant at both levels of x1. This is because our model did not account for any interaction. The righthand model leads us to substantially different inference. When x1==0 (shown in red), there appears to be almost no effect of x2, but when x1==1, the effect of x2 is strongly positive.

Model with additional covariate

When we add an additional covariate to the model, things become much more complicated. Recall that the predicted probabilities have to be calculated on some value of each covariate. In other words, we have to define the predicted probability in terms of all of the covariates in the model. Thus, when we add an additional covariate (even if it does not interact with our focal covariates x1 and x2), we need to account for it when estimating our predicted probabilities. We'll see this at work when we plot the predicted probabilities for our (incorrect) model estimated without the x1*x2 interaction and in our (correct) model estimated with that interaction. No-Interaction model with an additional covariate

plot(NA, xlim = c(0, 1), ylim = c(0, 1), xlab = "x2", ylab = "Predicted Probability of y=1")
s <- sapply(unique(newdata2$x3), function(i) {
    # `x1==0`
    lines(newdata2$x2[newdata2$x1 == 0 & newdata2$x3 == i], p3a$fit[newdata2$x1 == 
        0 & newdata2$x3 == i], col = rgb(1, 0, 0, 0.5))
    lines(newdata2$x2[newdata2$x1 == 0 & newdata2$x3 == i], p3a$fit[newdata2$x1 == 
        0 & newdata2$x3 == i] + 1.96 * p3a$se.fit[newdata2$x1 == 0 & newdata2$x3 == 
        i], col = rgb(1, 0, 0, 0.5), lty = 2)
    lines(newdata2$x2[newdata2$x1 == 0 & newdata2$x3 == i], p3a$fit[newdata2$x1 == 
        0 & newdata2$x3 == i] - 1.96 * p3a$se.fit[newdata2$x1 == 0 & newdata2$x3 == 
        i], col = rgb(1, 0, 0, 0.5), lty = 2)
    # `x1==1`
    lines(newdata2$x2[newdata2$x1 == 1 & newdata2$x3 == i], p3a$fit[newdata2$x1 == 
        1 & newdata2$x3 == i], col = rgb(0, 0, 1, 0.5))
    lines(newdata2$x2[newdata2$x1 == 1 & newdata2$x3 == i], p3a$fit[newdata2$x1 == 
        1 & newdata2$x3 == i] + 1.96 * p3a$se.fit[newdata2$x1 == 1 & newdata2$x3 == 
        i], col = rgb(0, 0, 1, 0.5), lty = 2)
    lines(newdata2$x2[newdata2$x1 == 1 & newdata2$x3 == i], p3a$fit[newdata2$x1 == 
        1 & newdata2$x3 == i] - 1.96 * p3a$se.fit[newdata2$x1 == 1 & newdata2$x3 == 
        i], col = rgb(0, 0, 1, 0.5), lty = 2)
})

plot of chunk unnamed-chunk-12

Note how the above code is much more complicated than previously because we now need to draw a separate predicted probability curve (with associated confidence interval) at each level of x3 even though we're not particularly interested in x3. The result is a very confusing plot because the predicted probability curves at each level ofx3 are the essentially the same, but the confidence intervals vary widely because of different levels of certainty due to the sparsity of the original data.

One common response is to simply draw the curve conditional on all other covariates (in this case x3) being at their means, but this is an arbitrary choice. We could also select minimum or maximum, or any other value. Let's write a small function to redraw our curves at different values of x3 to see the impact of this choice:

ppcurve <- function(value_of_x3, title) {
    tmp <- expand.grid(x1 = 0:1, x2 = seq(0, 1, length.out = 10), x3 = value_of_x3)
    p3tmp <- predict(m3a, tmp, type = "response", se.fit = TRUE)
    plot(NA, xlim = c(0, 1), ylim = c(0, 1), xlab = "x2", ylab = "Predicted Probability of y=1", 
        main = title)
    # `x1==0`
    lines(tmp$x2[tmp$x1 == 0], p3tmp$fit[tmp$x1 == 0], col = "red")
    lines(tmp$x2[tmp$x1 == 0], p3tmp$fit[tmp$x1 == 0] + 1.96 * p3tmp$se.fit[tmp$x1 == 
        0], col = "red", lty = 2)
    lines(tmp$x2[tmp$x1 == 0], p3tmp$fit[tmp$x1 == 0] - 1.96 * p3tmp$se.fit[tmp$x1 == 
        0], col = "red", lty = 2)
    # `x1==1`
    lines(tmp$x2[tmp$x1 == 1], p3tmp$fit[tmp$x1 == 1], col = "blue")
    lines(tmp$x2[tmp$x1 == 1], p3tmp$fit[tmp$x1 == 1] + 1.96 * p3tmp$se.fit[tmp$x1 == 
        1], col = "blue", lty = 2)
    lines(tmp$x2[tmp$x1 == 1], p3tmp$fit[tmp$x1 == 1] - 1.96 * p3tmp$se.fit[tmp$x1 == 
        1], col = "blue", lty = 2)
}

We can then draw a plot that shows the curves for the mean of x3, the minimum of x3 and the maximum of x3.

layout(matrix(1:3, nrow = 1))
ppcurve(mean(x3), title = "x3 at mean")
ppcurve(min(x3), title = "x3 at min")
ppcurve(max(x3), title = "x3 at max")

plot of chunk unnamed-chunk-14

The above set of plots show that while the inference about the predicted probability curves is the same, the choice of what value of x3 to condition on is meaningful for the confidence intervals. The confidence intervals are much narrower when we condition on the mean value of x3 than the minimum or maximum.

Recall that this model did not properly account for the x1*x2 interaction. Thus while our inference is somewhat sensitive to the choice of conditioning value of the x3 covariate, it is unclear if this minimal sensitivity holds when we properly account for the interaction. Let's take a look at our m3b model that accounts for the interaction.

Interaction model with additional covariate

Let's start by drawing a plot showing the predicted values of the outcome for every combination of x1, x2, and x3:

plot(NA, xlim = c(0, 1), ylim = c(0, 1), xlab = "x2", ylab = "Predicted Probability of y=1")
s <- sapply(unique(newdata2$x3), function(i) {
    # `x1==0`
    lines(newdata2$x2[newdata2$x1 == 0 & newdata2$x3 == i], p3b$fit[newdata2$x1 == 
        0 & newdata2$x3 == i], col = rgb(1, 0, 0, 0.5))
    lines(newdata2$x2[newdata2$x1 == 0 & newdata2$x3 == i], p3b$fit[newdata2$x1 == 
        0 & newdata2$x3 == i] + 1.96 * p3b$se.fit[newdata2$x1 == 0 & newdata2$x3 == 
        i], col = rgb(1, 0, 0, 0.5), lty = 2)
    lines(newdata2$x2[newdata2$x1 == 0 & newdata2$x3 == i], p3b$fit[newdata2$x1 == 
        0 & newdata2$x3 == i] - 1.96 * p3b$se.fit[newdata2$x1 == 0 & newdata2$x3 == 
        i], col = rgb(1, 0, 0, 0.5), lty = 2)
    # `x1==1`
    lines(newdata2$x2[newdata2$x1 == 1 & newdata2$x3 == i], p3b$fit[newdata2$x1 == 
        1 & newdata2$x3 == i], col = rgb(0, 0, 1, 0.5))
    lines(newdata2$x2[newdata2$x1 == 1 & newdata2$x3 == i], p3b$fit[newdata2$x1 == 
        1 & newdata2$x3 == i] + 1.96 * p3b$se.fit[newdata2$x1 == 1 & newdata2$x3 == 
        i], col = rgb(0, 0, 1, 0.5), lty = 2)
    lines(newdata2$x2[newdata2$x1 == 1 & newdata2$x3 == i], p3b$fit[newdata2$x1 == 
        1 & newdata2$x3 == i] - 1.96 * p3b$se.fit[newdata2$x1 == 1 & newdata2$x3 == 
        i], col = rgb(0, 0, 1, 0.5), lty = 2)
})

plot of chunk unnamed-chunk-15

This plot is incredibly messy. Now, not only our the confidence bands sensitive to what value of x3 we condition on, so too are the predicted probability curves themselves. It is therefore a fairly important decision what level of additional covariates to condition on when estimating the predicted probabilities.

Marginal Effects Plots

A different approach when deal with interactions is to show marginal effects. Marginal effect, I think, are a bit abstract (i.e., a bit removed from the actual data because they attempt to summarize a lot of information in a single number). The marginal effect is the slope of the curve drawn by taking the difference between, e.g., the predicted probability that y==1 when x1==1 and the predicted probability that y== when x1==0, at each level of x2. Thus, the marginal effect is simply the slope of the difference between the two curves that we were drawing in the above graphs (i.e., the slope of the change in predicted probabilities). Of course, we just saw, if any additional covariate(s) are involved in the data-generating process, then the marginal effect - like the predicted probabilities - is going to differ across levels of that covariate.

Simple interaction model without additional covariates

Let's see how this works by first returning to our simple interaction model (without x3) and then look at the interaction model with the additional covariate.

To plot the change in predicted probabilities due to x1 across the values of x2, we simply need to take our predicted probabilities from above and difference the values predicted for x1==0 and x1==1. The predicted probabilities for our simple interaction model are stored in p2b, based on new data from newdata1. Let's separate out the values predicted from x1==0 and x1==1 and then take their difference. Let's create a new dataframe that binds newdata1 and the predicted probability and standard error values from p2b together. Then we'll use the split function to that dataframe based upon the value of x1.

tmpdf <- newdata1
tmpdf$fit <- p2b$fit
tmpdf$se.fit <- p2b$se.fit
tmpsplit <- split(tmpdf, tmpdf$x1)

The result is a list of two dataframes, each containing values of x1, x2, and the associated predicted probabilities:

tmpsplit
## $`0`
##    x1     x2    fit  se.fit
## 1   0 0.0000 0.5014 0.09235
## 3   0 0.1111 0.5011 0.07665
## 5   0 0.2222 0.5007 0.06320
## 7   0 0.3333 0.5003 0.05373
## 9   0 0.4444 0.5000 0.05053
## 11  0 0.5556 0.4996 0.05470
## 13  0 0.6667 0.4992 0.06484
## 15  0 0.7778 0.4989 0.07867
## 17  0 0.8889 0.4985 0.09459
## 19  0 1.0000 0.4982 0.11171
## 
## $`1`
##    x1     x2    fit  se.fit
## 2   1 0.0000 0.3494 0.09498
## 4   1 0.1111 0.3839 0.08187
## 6   1 0.2222 0.4194 0.06887
## 8   1 0.3333 0.4556 0.05769
## 10  1 0.4444 0.4921 0.05089
## 12  1 0.5556 0.5287 0.05093
## 14  1 0.6667 0.5650 0.05770
## 16  1 0.7778 0.6009 0.06862
## 18  1 0.8889 0.6358 0.08115
## 20  1 1.0000 0.6697 0.09362

To calculate the change in predicted probabilty of y==1 due to x1==1 at each value of x2, we'll simply difference the the fit variable from each dataframe:

me <- tmpsplit[[2]]$fit - tmpsplit[[1]]$fit
me
##  [1] -0.152032 -0.117131 -0.081283 -0.044766 -0.007877  0.029079  0.065793
##  [8]  0.101966  0.137309  0.171555

We also want the standard error of that difference:

me_se <- sqrt(0.5 * (tmpsplit[[2]]$se.fit + tmpsplit[[1]]$se.fit))

Now Let's plot the original predicted probability plot on the left and the change in predicted probability plot on the right:

layout(matrix(1:2, nrow = 1))
plot(NA, xlim = c(0, 1), ylim = c(0, 1), xlab = "x2", ylab = "Predicted Probability of y=1", 
    main = "Predicted Probabilities")
# `x1==0`
lines(newdata1$x2[newdata1$x1 == 0], p2b$fit[newdata1$x1 == 0], col = "red")
lines(newdata1$x2[newdata1$x1 == 0], p2b$fit[newdata1$x1 == 0] + 1.96 * p2b$se.fit[newdata1$x1 == 
    0], col = "red", lty = 2)
lines(newdata1$x2[newdata1$x1 == 0], p2b$fit[newdata1$x1 == 0] - 1.96 * p2b$se.fit[newdata1$x1 == 
    0], col = "red", lty = 2)
# `x1==1`
lines(newdata1$x2[newdata1$x1 == 1], p2b$fit[newdata1$x1 == 1], col = "blue")
lines(newdata1$x2[newdata1$x1 == 1], p2b$fit[newdata1$x1 == 1] + 1.96 * p2b$se.fit[newdata1$x1 == 
    1], col = "blue", lty = 2)
lines(newdata1$x2[newdata1$x1 == 1], p2b$fit[newdata1$x1 == 1] - 1.96 * p2b$se.fit[newdata1$x1 == 
    1], col = "blue", lty = 2)
# plot of change in predicted probabilities:
plot(NA, type = "l", xlim = c(0, 1), ylim = c(-1, 1), xlab = "x2", ylab = "Change in Predicted Probability of y=1", 
    main = "Change in Predicted Probability due to x1")
abline(h = 0, col = "gray")  # gray line at zero
lines(tmpsplit[[1]]$x2, me, lwd = 2)  # change in predicted probabilities
lines(tmpsplit[[1]]$x2, me - 1.96 * me_se, lty = 2)
lines(tmpsplit[[1]]$x2, me + 1.96 * me_se, lty = 2)

plot of chunk unnamed-chunk-20

As should be clear, the plot on the right is simply a further information reduction of the lefthand plot. Where the separate predicted probabilities show the predicted probability of the outcome at each combination of x1 and x2, the righthand plot simply shows the difference between these two curves.

The marginal effect of x2 is thus a further information reduction: it is the slope of the line showing the difference in predicted probabilities. Because our x2 variable is scaled [0,1], we can see the marginal effect simply by subtracting the value of change in predicted probabilities when x2==0 from the value of change in predicted probabilities when x2==1, which is simply:

me[length(me)] - me[1]
## [1] 0.3236

Thus the marginal effect of x1 on the outcome, is the slope of the line representing the change in predicted probabilities between x1==1 and x1==0 across the range of x2. I don't find that a particularly intuitive measure of effect and would instead prefer to draw some kind of plot rather than reduce that plot to a single number.

Simple interaction model without additional covariates

Things get more complicated, as we might expect, when we have to account for the additional covariate x3, which influenced our predicted probabilities above. Our predicted probabilies for these data are stored in p3b (based on input data in newdata2). We'll follow the same procedure just used to add those predicted probabilities into a dataframe with the variables from newdata2, then we'll split it based on x1:

tmpdf <- newdata2
tmpdf$fit <- p3b$fit
tmpdf$se.fit <- p3b$se.fit
tmpsplit <- split(tmpdf, tmpdf$x1)

The result is a list of two large dataframes:

str(tmpsplit)
## List of 2
##  $ 0:'data.frame':   250 obs. of  5 variables:
##   ..$ x1    : int [1:250] 0 0 0 0 0 0 0 0 0 0 ...
##   ..$ x2    : num [1:250] 0 0.111 0.222 0.333 0.444 ...
##   ..$ x3    : num [1:250] 0 0 0 0 0 0 0 0 0 0 ...
##   ..$ fit   : num [1:250] 0.437 0.451 0.465 0.479 0.493 ...
##   ..$ se.fit: num [1:250] 0.1091 0.0979 0.0893 0.0843 0.0836 ...
##   ..- attr(*, "out.attrs")=List of 2
##   .. ..$ dim     : Named int [1:3] 2 10 25
##   .. .. ..- attr(*, "names")= chr [1:3] "x1" "x2" "x3"
##   .. ..$ dimnames:List of 3
##   .. .. ..$ x1: chr [1:2] "x1=0" "x1=1"
##   .. .. ..$ x2: chr [1:10] "x2=0.0000" "x2=0.1111" "x2=0.2222" "x2=0.3333" ...
##   .. .. ..$ x3: chr [1:25] "x3=0.0000" "x3=0.2083" "x3=0.4167" "x3=0.6250" ...
##  $ 1:'data.frame':   250 obs. of  5 variables:
##   ..$ x1    : int [1:250] 1 1 1 1 1 1 1 1 1 1 ...
##   ..$ x2    : num [1:250] 0 0.111 0.222 0.333 0.444 ...
##   ..$ x3    : num [1:250] 0 0 0 0 0 0 0 0 0 0 ...
##   ..$ fit   : num [1:250] 0.632 0.642 0.652 0.662 0.672 ...
##   ..$ se.fit: num [1:250] 0.1197 0.1047 0.0917 0.0815 0.0749 ...
##   ..- attr(*, "out.attrs")=List of 2
##   .. ..$ dim     : Named int [1:3] 2 10 25
##   .. .. ..- attr(*, "names")= chr [1:3] "x1" "x2" "x3"
##   .. ..$ dimnames:List of 3
##   .. .. ..$ x1: chr [1:2] "x1=0" "x1=1"
##   .. .. ..$ x2: chr [1:10] "x2=0.0000" "x2=0.1111" "x2=0.2222" "x2=0.3333" ...
##   .. .. ..$ x3: chr [1:25] "x3=0.0000" "x3=0.2083" "x3=0.4167" "x3=0.6250" ...

Now, we need to calculate the change in predicted probability within each of those dataframes, at each value of x3. That is tedious. So let's instead split by both x1 and x3:

tmpsplit <- split(tmpdf, list(tmpdf$x3, tmpdf$x1))

The result is a list of 50 dataframes, the first 25 of which contain data for x1==0 and the latter 25 of which contain data for x1==1:

length(tmpsplit)
## [1] 50
names(tmpsplit)
##  [1] "0.0"                 "0.208333333333333.0" "0.416666666666667.0"
##  [4] "0.625.0"             "0.833333333333333.0" "1.04166666666667.0" 
##  [7] "1.25.0"              "1.45833333333333.0"  "1.66666666666667.0" 
## [10] "1.875.0"             "2.08333333333333.0"  "2.29166666666667.0" 
## [13] "2.5.0"               "2.70833333333333.0"  "2.91666666666667.0" 
## [16] "3.125.0"             "3.33333333333333.0"  "3.54166666666667.0" 
## [19] "3.75.0"              "3.95833333333333.0"  "4.16666666666667.0" 
## [22] "4.375.0"             "4.58333333333333.0"  "4.79166666666667.0" 
## [25] "5.0"                 "0.1"                 "0.208333333333333.1"
## [28] "0.416666666666667.1" "0.625.1"             "0.833333333333333.1"
## [31] "1.04166666666667.1"  "1.25.1"              "1.45833333333333.1" 
## [34] "1.66666666666667.1"  "1.875.1"             "2.08333333333333.1" 
## [37] "2.29166666666667.1"  "2.5.1"               "2.70833333333333.1" 
## [40] "2.91666666666667.1"  "3.125.1"             "3.33333333333333.1" 
## [43] "3.54166666666667.1"  "3.75.1"              "3.95833333333333.1" 
## [46] "4.16666666666667.1"  "4.375.1"             "4.58333333333333.1" 
## [49] "4.79166666666667.1"  "5.1"

We can then calculate our change in predicted probabilities at each level of x1 and x3. We'll use the mapply function to do this quickly:

change <- mapply(function(a, b) b$fit - a$fit, tmpsplit[1:25], tmpsplit[26:50])

The resulting object change is a matrix, each column of which is the change in predicted probability at each level of x3. We can then use this matrix to plot each change in predicted probability on a single plot. Let's again draw this side-by-side with the predicted probability plot:

layout(matrix(1:2, nrow = 1))
# predicted probabilities
plot(NA, xlim = c(0, 1), ylim = c(0, 1), xlab = "x2", ylab = "Predicted Probability of y=1", 
    main = "Predicted Probabilities")
s <- sapply(unique(newdata2$x3), function(i) {
    # `x1==0`
    lines(newdata2$x2[newdata2$x1 == 0 & newdata2$x3 == i], p3b$fit[newdata2$x1 == 
        0 & newdata2$x3 == i], col = rgb(1, 0, 0, 0.5))
    lines(newdata2$x2[newdata2$x1 == 0 & newdata2$x3 == i], p3b$fit[newdata2$x1 == 
        0 & newdata2$x3 == i] + 1.96 * p3b$se.fit[newdata2$x1 == 0 & newdata2$x3 == 
        i], col = rgb(1, 0, 0, 0.5), lty = 2)
    lines(newdata2$x2[newdata2$x1 == 0 & newdata2$x3 == i], p3b$fit[newdata2$x1 == 
        0 & newdata2$x3 == i] - 1.96 * p3b$se.fit[newdata2$x1 == 0 & newdata2$x3 == 
        i], col = rgb(1, 0, 0, 0.5), lty = 2)
    # `x1==1`
    lines(newdata2$x2[newdata2$x1 == 1 & newdata2$x3 == i], p3b$fit[newdata2$x1 == 
        1 & newdata2$x3 == i], col = rgb(0, 0, 1, 0.5))
    lines(newdata2$x2[newdata2$x1 == 1 & newdata2$x3 == i], p3b$fit[newdata2$x1 == 
        1 & newdata2$x3 == i] + 1.96 * p3b$se.fit[newdata2$x1 == 1 & newdata2$x3 == 
        i], col = rgb(0, 0, 1, 0.5), lty = 2)
    lines(newdata2$x2[newdata2$x1 == 1 & newdata2$x3 == i], p3b$fit[newdata2$x1 == 
        1 & newdata2$x3 == i] - 1.96 * p3b$se.fit[newdata2$x1 == 1 & newdata2$x3 == 
        i], col = rgb(0, 0, 1, 0.5), lty = 2)
})
# change in predicted probabilities
plot(NA, type = "l", xlim = c(0, 1), ylim = c(-1, 1), xlab = "x2", ylab = "Change in Predicted Probability of y=1", 
    main = "Change in Predicted Probability due to x1")
abline(h = 0, col = "gray")
apply(change, 2, function(a) lines(tmpsplit[[1]]$x2, a))

plot of chunk unnamed-chunk-27

## NULL

As we can see, despite the craziness of the left-hand plot, the marginal effect of x1 is actually not affected by x3 (which makes sense because it is not interacted with x3 in the data-generating process). Thus, while the choice of value of x3 on which to estimated the predicted probabilities matters, the marginal effect is constant. We can estimate it simply by following the same procedure above from any column of our change matrix:

change[nrow(change), 1] - change[1, 1]
##     0.0 
## -0.0409

The result here is a negligible marginal effect, which is what we would expect given the lack of an interaction between x1 and x3 in the underlying data. If such an interaction were in the actual data, then we should expect that this marginal effect would vary across values of x3 and we would need to further state the marginal effect as conditional on a particular value of x3.