Calculating stratum specific odds ratios in R has been really difficult for a long time. It pains me to say it but Stata has historically done a better job with the lincom command. However, I’ve finally found the excellent marginaleffects package. This has simple methods for calculating stratum-specific odds ratios.
Data and methods
To ensure that marginaleffects is producing the outputs that I expect, I’m going to replicate the analyses on pages 323 to 327 of Kirkwood and Sterne’s Essential Medical Statistics. I’m using the oncho_ems data set that can be downloaded here.
These data give 1302 observations on the presence or absence of microfilaria in individuals as a binary variable (mf), area of residence (0, 1 or 2) and age group (0, 1, 2 or 3). mf is the outcome of interest and area of residence (rainforest or savannah) and age group are factors that could affect the odds of the outcome.
Replication of the model with an interaction between the two exposures
The above analysis assumes that the effect of age is the same for each of the different areas. This may not be true, so we need to specify a different model to allow the effect of age to vary according to area
m2 <-glm(mf ~ area *factor(agegrp), data = dat, family = binomial) tidy(m2, exponentiate =TRUE, conf.int =TRUE)
In this output, there are now three extra estimates, we can use these to calculate stratum-specific odds ratios for the effect of area on mf infection. At the bottom of page 3 it works this for the effect of area in age group 1:
OR for area in age group 1 = Area x Area.Agegrp(1)
= 1.8275 x 1.3878 = 2.5362
Allowing for some differences in rounding, this matches our output above, the OR for area in our tidied output is 1.83 and the OR for the combination of area and age group 1 (area:factor(agegrp)1) is 1.39.
So, we can use these values to calculate stratum-specific odds ratios one-by-one. But, I don’t want to treat R like a hand-held calculator, I want to easily make a table of the stratum-specific odds ratios that I can easily plug into a manuscript or report for publication. This is the step that has been tricky until I learned to do it with marginaleffects.
Warning: The `agegrp` variable is treated as a categorical (factor) variable,
but the original data is of class integer. It is safer and faster to convert
such variables to factor before fitting the model and calling a
`marginaleffects` function. This warning appears once per session.
This is slightly different from the first output from comparisons. We now have an extra column Contrast. This is because, when looking at the age-specific odds ratios for the effect of area there were only two levels of area, so only one odds ratio for the effect of area to calculate. Now that we are looking at it the other way around, looking at the area specific odds ratios of age, we have three levels of age to calculate for each area. The output still matches the value given at the top of p327 in Kirkwood and Sterne, but we need to hunt to find it a bit. The top of 327 gives the area-specific OR for age group 1, compared to the baseline age group (0) for the rainforest area (1). So, in the comparisons output, we find the Contrast ln(odds(1) / odds(0)) and area 1, for which the estimate is 2.94, matching Kirkwood and Sterne’s estimate of 2.9386.
Further reading
There’s loads more to the marginaleffects package, including using it for producing outputs for splines and non-linear relationships between variables. Further reading should include the marginaleffects website and Andrew Heiss’ blog post marginalia.