Skip to content

Instantly share code, notes, and snippets.

View andrewheiss's full-sized avatar
👨‍💻
#rstats-ing all the things

Andrew Heiss andrewheiss

👨‍💻
#rstats-ing all the things
View GitHub Profile

The approach I typically take is to draw them as a pseudo node that points at the middle of a path between two real nodes (see Figure 7 here https://royalsocietypublishing.org/doi/10.1098/rspb.2020.2815 where Q interacts with/modifies X (and how to draw it with R: https://gist.github.com/andrewheiss/6546a22672a13a4eacd8a9a726eac88a). It’s just like Figure 3A in the Attia et al. article too (https://academic.oup.com/ije/article/51/4/1047/6607680), and Laubach, Murray, et al. have a longer appendix about it all too.

It’s a tricky solution because the regular rules of do-calculus don’t apply, since the Q node isn’t really in the DAG in normal ways.

In practice, what I’ve generally seen is that people generally don’t care about interaction nodes because interactions are a statement of functional form and DAGs are supposed to be function-agnostic.

For instance, if you have a situation like y ~ x + a + a*x, where a is binary, and your main treatment is x, the causal effect of x depends on whether or

library(tidyverse)

# geom_bar() doesn't need a variable mapped to the y-axis; it calculates it on its own behind the scenes
ggplot(mpg, aes(x = drv, fill = factor(year))) +
  geom_bar()

library(tidyverse)
library(marginaleffects)
library(broom)
library(palmerpenguins)

penguins <- penguins %>% drop_na(sex)

# Marginal means in conjoint world are just the averages for each of the levels in a factor 
# variable included in a regression model (i.e. instead of using an omitted reference 
library(tidyverse)
library(broom)

# Model with a cateogorical predictor
example_model <- lm(hwy ~ displ + drv, data = mpg)

# Extract all the right-hand variables
rhs <- all.vars(stats::update(formula(example_model), 0 ~ .))
rhs
library(tidyverse)
library(sf)
library(rnaturalearth)
library(countrycode)
library(gapminder)

# rerturnclass = "sf" makes it so the resulting dataframe has the special
# sf-enabled geometry column
world_map <- ne_countries(scale = 50, returnclass = "sf") %>% 
library(tidyverse)
library(palmerpenguins)
library(patchwork)
library(rcartocolor)
library(scales)

# Clean up the data
penguins <- penguins |> 
  drop_na(sex) |>
library(tidyverse)
library(ggdist)
library(ggpattern)

# Just look at a few categories
diamonds_small <- diamonds %>% 
  filter(cut %in% c("Fair", "Good", "Very Good"))

# Manually calculate summary statistics since we can't use stat_pointinterval()
library(tidyverse)
library(brms)
library(tidybayes)


data_thing <- tribble(
  ~answer,               ~regime_type,  ~n,
  "Very familiar",       "Democracy",   117,
  "Very familiar",       "Autocracy",   88,
library(tidyverse)
library(brms)
library(tidybayes)
library(marginaleffects)

# Example data with proportions
example_data <- tribble(
  ~answer,               ~n, ~total,
  "Very familiar",       88, 205,