Last updated: 2019-08-14

Checks: 6 0

Knit directory: cost_of_SR_Dpseudo/

This reproducible R Markdown analysis was created with workflowr (version 1.3.0). The Checks tab describes the reproducibility checks that were applied when the results were created. The Past versions tab lists the development history.


Great! Since the R Markdown file has been committed to the Git repository, you know the exact version of the code that produced these results.

Great job! The global environment was empty. Objects defined in the global environment can affect the analysis in your R Markdown file in unknown ways. For reproduciblity it’s best to always run the code in an empty environment.

The command set.seed(20190319) was run prior to running the code in the R Markdown file. Setting a seed ensures that any results that rely on randomness, e.g. subsampling or permutations, are reproducible.

Great job! Recording the operating system, R version, and package versions is critical for reproducibility.

Nice! There were no cached chunks for this analysis, so you can be confident that you successfully produced the results during this run.

Great! You are using Git for version control. Tracking code development and connecting the code version to the results is critical for reproducibility. The version displayed above was the version of the Git repository at the time these results were generated.

Note that you need to be careful to ensure that all relevant files for the analysis have been committed to Git prior to generating the results (you can use wflow_publish or wflow_git_commit). workflowr only checks the R Markdown file, but you know if there are other scripts or data files that it depends on. Below is the status of the Git repository when the results were generated:


Ignored files:
    Ignored:    .DS_Store
    Ignored:    .Rhistory
    Ignored:    .Rproj.user/
    Ignored:    output/.DS_Store

Unstaged changes:
    Modified:   figures/figure_2.pdf
    Modified:   figures/figure_3.pdf

Note that any generated files, e.g. HTML, png, CSS, etc., are not included in this status report because it is ok for generated content to have uncommitted changes.


These are the previous versions of the R Markdown and HTML files. If you’ve configured a remote Git repository (see ?wflow_git_remote), click on the hyperlinks in the table below to view them.

File Version Author Date Message
Rmd e5f7926 lukeholman 2019-08-14 working nicely
html e5f7926 lukeholman 2019-08-14 working nicely
html 24d4c9c lukeholman 2019-07-02 Build site.
Rmd 0479dbb lukeholman 2019-07-02 re-run model
html 13a5603 lukeholman 2019-07-02 Build site.
Rmd 2ef4d13 lukeholman 2019-07-02 re-run model
html 52ea758 lukeholman 2019-06-28 Build site.
Rmd 44d4565 lukeholman 2019-06-28 First complete commit
html e2cfeac lukeholman 2019-06-28 Build site.
Rmd fc7812e lukeholman 2019-06-28 First complete commit
html 93aec6e lukeholman 2019-06-28 Build site.
Rmd 01d7dbb lukeholman 2019-06-28 First complete commit
html 8c6e0c3 lukeholman 2019-06-28 Build site.
Rmd ffdc5d4 lukeholman 2019-06-28 First complete commit
html ffdc5d4 lukeholman 2019-06-28 First complete commit

Load R libraries

library(tidyverse)
library(brms)
library(bayestestR)
library(kableExtra)
library(ggbeeswarm)
library(RColorBrewer)
library(showtext)
font_add_google(name = "Lato", family = "Lato", regular.wt = 400, bold.wt = 700)
showtext_auto()
options(stringsAsFactors = FALSE)

SE <- function(x) sd(x) / sqrt(length(x))

Load the data

fitness_data <- read_csv("data/SR_fitness_data.csv") %>% 
  filter(!is.na(genotype)) %>%
  rename(body_size = `Body size`,
         female_age = `F age`) %>%
  mutate(genotype = factor(genotype, levels = c("STST", "SRST", "SRSR")))

Make a table of summary statistics and sample sizes

Here, we calculate the mean offspring produced by females from each of the three genotypes (STST, SRST, and SRSR), either within each isoline or across all the isolines. We also calculate the % females that failed to produce any offspring, and provide sample size information.

means_by_isoline <- fitness_data %>%
  group_by(genotype, Isoline) %>%
  summarise(
    Number_of_females_measured = n(),
    Mean_offspring_per_female = mean(offspring),
    SE = SE(offspring),
    n_females_producing_offspring = sum(offspring != 0),
    Percent_females_producing_offspring = 100 * n_females_producing_offspring / n()) 

means <- fitness_data %>%
  mutate(Isoline = "Across all isolines") %>%
  group_by(genotype, Isoline) %>%
  summarise(
    Number_of_females_measured = n(),
    Mean_offspring_per_female = mean(offspring),
    SE = SE(offspring),
    n_females_producing_offspring = sum(offspring != 0),
    Percent_females_producing_offspring = 100 * n_females_producing_offspring / n()) 


bind_rows(means_by_isoline, means) %>%
  rename_all(function(x) gsub("_", " ", x)) %>%
  rename_all(function(x) gsub("Percent", "%", x)) %>%
  rename(Genotype = genotype) %>%
  kable(digits = 2) %>% kable_styling()
Genotype Isoline Number of females measured Mean offspring per female SE n females producing offspring % females producing offspring
STST Lew 13 37 57.81 6.46 35 94.59
STST Lew 17 40 56.85 5.04 39 97.50
STST Slo B3 40 76.67 5.59 39 97.50
STST Slo B7 35 71.14 4.71 34 97.14
SRST Lew 13 39 72.82 8.70 32 82.05
SRST Lew 17 37 56.24 8.11 32 86.49
SRST Slo B3 31 49.10 5.20 26 83.87
SRST Slo B7 39 55.26 7.07 36 92.31
SRSR Lew 13 36 28.58 5.92 25 69.44
SRSR Lew 17 37 32.19 3.91 34 91.89
SRSR Slo B3 31 17.19 4.56 22 70.97
SRSR Slo B7 38 25.50 4.76 28 73.68
STST Across all isolines 152 65.59 2.81 147 96.71
SRST Across all isolines 146 58.89 3.83 126 86.30
SRSR Across all isolines 142 26.21 2.45 109 76.76

Fit a model to the data

Run the Bayesian hurdle model

The model assumes that the response variable, offspring number, is the result of a ‘hurdle’ process. Essentially this means that the model consists of two sub-models: one controlling the probability that offspring number is non-zero, and one controlling the number of offspring produced provided that more than zero are produced (we assume that offspring number follows a negative binomial distribution, because this improved model fit relative to the simpler hurdle-Poisson model).

We assume that the parameters controlling both the hurdle and the distribution of non-zero values are affected by four fixed effects (the female’s genotype: STST, SRST, or SRSR), her isoline, the female’s age, and the interaction between genotype and isoline. We also fit two random effects: isoline, and experimental block. All fixed effects were assumed to have a prior distribution following a normal distribution with mean 0 and SD = 5.

if(!file.exists("output/brms_model.rds")){
 
  # The hurdle and the mean have the same set of predictors
  model_formula <- bf(
    offspring ~ genotype * Isoline + female_age + (1 | Block), 
    hu        ~ genotype * Isoline + female_age + (1 | Block)  
  )
  
  model_formula2 <- bf(
    offspring ~ genotype + Isoline + female_age + (1 | Block), 
    hu        ~ genotype + Isoline + female_age + (1 | Block)  
  )
    
  model_formula3 <- bf(
    offspring ~ genotype + female_age + (1 | Block), 
    hu        ~ genotype + female_age + (1 | Block)  
  )
  
  
  # Find R^2 for a brms model, and its 95% CIs, and present neatly
  neat_R2 <- function(model){
    R2 <- bayes_R2(model) %>% round(2)
    paste(R2[1,1], " (95% CIs = ", R2[1,3], "-", R2[1,4], ")", sep = "")
  }
  
  # We set conservative, "regularising" priors - see McElreath's "Statistical Rethinking" textbook
  model_prior <- c(set_prior("normal(0, 3)", class = "b"),
                   set_prior("normal(0, 3)", class = "b", dpar = "hu"))
  
  full_model <- brm(model_formula,
                    family = "hurdle_negbinomial",
                    chains = 4, cores = 1, iter = 40000, inits = 0, seed = 12345,
                    control = list(adapt_delta = 0.9999, max_treedepth = 15),
                    save_all_pars = TRUE, 
                    prior = model_prior, 
                    data = fitness_data)
  
  no_interaction <- brm(model_formula2,
                        family = "hurdle_negbinomial",
                        chains = 4, cores = 1, iter = 40000, inits = 0, seed = 12345,
                        control = list(adapt_delta = 0.9999, max_treedepth = 15),
                        save_all_pars = TRUE, 
                        prior = model_prior, 
                        data = fitness_data)
  
  genotype_only_model <- brm(model_formula3,
                             family = "hurdle_negbinomial",
                             chains = 4, cores = 1, iter = 40000, inits = 0, seed = 12345,
                             control = list(adapt_delta = 0.9999, max_treedepth = 15),
                             save_all_pars = TRUE, 
                             prior = model_prior, 
                             data = fitness_data)

  saveRDS(post_prob(full_model, no_interaction, genotype_only_model), 
          file = "output/model_comparison.rds")
  saveRDS(full_model, file = "output/full_model.rds")
  saveRDS(genotype_only_model, file = "output/genotype_only_model.rds")
  saveRDS(neat_R2(full_model), file = "output/R2_of_full_model.rds")
  saveRDS(neat_R2(genotype_only_model), file = "output/R2_of_genotype_only_model.rds")
} else{
  full_model <- readRDS("output/full_model.rds")
  genotype_only_model <- readRDS("output/genotype_only_model.rds")
  model_probabilities <- readRDS("output/model_comparison.rds")
}

Graphically verify the fit of the model using a posterior predictive check

The idea behind posterior predictive checking is that if our model is a good fit, then we should be able to use it to generate a dataset which looks a lot like the dataset we actually observed. Here, we see 11 draws from the ‘posterior predictive distribution’ (pale blue), which indeed look quite similar to the distribution of the real data (dark blue), suggesting that our model is a good enough approximation of the true data-generating process for reliable inference.

pp_check(genotype_only_model, type = "hist", nsamples = 11, binwidth = 5)

Version Author Date
e5f7926 lukeholman 2019-08-14
13a5603 lukeholman 2019-07-02
ffdc5d4 lukeholman 2019-06-28

Inspect the parameter estimates

Genotype-only model

bayesian_p_values <- as.data.frame(p_direction(genotype_only_model)) %>% 
      mutate(pd = (100 - pd) / 100,
             Parameter = gsub("[.]", ":", gsub("b_", "", Parameter)))

random <- as.data.frame(summary(genotype_only_model)$random[[1]]) %>%
  rownames_to_column("Parameter") %>%
  mutate(p = NA,
         Parameter = c("sd(Block - Intercept)", "sd(Block - Hurdle intercept)"))

summary(genotype_only_model)$fixed %>% 
  as.data.frame() %>% 
  rownames_to_column("Parameter") %>%
  left_join(bayesian_p_values, by = "Parameter") %>%
  rename(p = pd) %>% arrange(grepl("hu_", Parameter)) %>%
  rbind(random) %>%
  mutate(Parameter = gsub("hu_", "Hurdle - ", Parameter),
         Estimate =  format(round(Estimate, 3), nsmall = 3),
         Est.Error =  format(round(Est.Error, 3), nsmall = 3),
         ` ` = ifelse(p < 0.05, "*", ""),
         ` ` = replace(` `, is.na(` `), ""),
         p = format(round(p, 4), nsmall = 4),
         Rhat = format(round(Rhat, 3), nsmall = 3),
         `l-95% CI` = format(round(`l-95% CI`, 3), nsmall = 3),
         `u-95% CI` = format(round(`u-95% CI`, 3), nsmall = 3),
         Eff.Sample = round(Eff.Sample, 0)
         ) %>% 
  kable() %>% kable_styling()
Parameter Estimate Est.Error l-95% CI u-95% CI Eff.Sample Rhat p
Intercept 4.244 0.236 3.782 4.705 74753 1.000 0.0000
genotypeSRST 0.030 0.089 -0.143 0.206 74642 1.000 0.3693
genotypeSRSR -0.669 0.094 -0.851 -0.482 77615 1.000 0.0000
female_age -0.010 0.053 -0.113 0.095 105447 1.000 0.4280
Hurdle - Intercept -2.304 0.940 -4.180 -0.486 79058 1.000 0.0067
Hurdle - genotypeSRST 1.488 0.505 0.542 2.533 55950 1.000 0.0007
Hurdle - genotypeSRSR 2.093 0.489 1.191 3.113 54153 1.000 0.0000
Hurdle - female_age -0.240 0.210 -0.651 0.170 101832 1.000 0.1257
sd(Block - Intercept) 0.137 0.125 0.006 0.453 18287 1.000 NA
sd(Block - Hurdle intercept) 0.328 0.344 0.011 1.176 22756 1.000 NA

Genotype-by-isoline model

bayesian_p_values <- as.data.frame(p_direction(full_model)) %>% 
      mutate(pd = (100 - pd) / 100,
             Parameter = gsub("[.]", ":", gsub("b_", "", Parameter)))

random <- as.data.frame(summary(full_model)$random[[1]]) %>%
  rownames_to_column("Parameter") %>%
  mutate(p = NA,
         Parameter = c("sd(Block - Intercept)", "sd(Block - Hurdle intercept)"))

summary(full_model)$fixed %>% 
  as.data.frame() %>% 
  rownames_to_column("Parameter") %>%
  left_join(bayesian_p_values, by = "Parameter") %>%
  rename(p = pd) %>% arrange(grepl("hu_", Parameter)) %>%
  rbind(random) %>%
  mutate(Parameter = gsub("hu_", "Hurdle - ", Parameter),
         Estimate =  format(round(Estimate, 3), nsmall = 3),
         Est.Error =  format(round(Est.Error, 3), nsmall = 3),
         ` ` = ifelse(p < 0.05, "*", ""),
         ` ` = replace(` `, is.na(` `), ""),
         p = format(round(p, 4), nsmall = 4),
         Rhat = format(round(Rhat, 3), nsmall = 3),
         `l-95% CI` = format(round(`l-95% CI`, 3), nsmall = 3),
         `u-95% CI` = format(round(`u-95% CI`, 3), nsmall = 3),
         Eff.Sample = round(Eff.Sample, 0)
         ) %>% 
  kable() %>% kable_styling()
Parameter Estimate Est.Error l-95% CI u-95% CI Eff.Sample Rhat p
Intercept 4.048 0.267 3.528 4.574 71105 1.000 0.0000
genotypeSRST 0.364 0.168 0.037 0.696 38430 1.000 0.0145
genotypeSRSR -0.426 0.183 -0.783 -0.064 39651 1.000 0.0106
IsolineLew17 -0.064 0.162 -0.383 0.253 43006 1.000 0.3465
IsolineSloB3 0.283 0.165 -0.042 0.607 43349 1.000 0.0434
IsolineSloB7 0.158 0.168 -0.171 0.487 43284 1.000 0.1735
female_age 0.023 0.057 -0.089 0.135 116178 1.000 0.3422
genotypeSRST:IsolineLew17 -0.220 0.243 -0.698 0.255 43832 1.000 0.1831
genotypeSRSR:IsolineLew17 -0.015 0.253 -0.512 0.480 43000 1.000 0.4758
genotypeSRST:IsolineSloB3 -0.682 0.251 -1.175 -0.190 47069 1.000 0.0033
genotypeSRSR:IsolineSloB3 -0.845 0.267 -1.368 -0.318 47414 1.000 0.0009
genotypeSRST:IsolineSloB7 -0.540 0.239 -1.011 -0.073 45588 1.000 0.0117
genotypeSRSR:IsolineSloB7 -0.331 0.260 -0.843 0.175 45084 1.000 0.1011
Hurdle - Intercept -1.472 1.135 -3.708 0.765 67332 1.000 0.0947
Hurdle - genotypeSRST 1.412 0.712 0.056 2.865 48615 1.000 0.0204
Hurdle - genotypeSRSR 2.104 0.700 0.788 3.540 48347 1.000 0.0005
Hurdle - IsolineLew17 -0.978 1.021 -3.104 0.904 47112 1.000 0.1673
Hurdle - IsolineSloB3 -0.738 0.982 -2.768 1.080 46047 1.000 0.2286
Hurdle - IsolineSloB7 -0.644 1.030 -2.792 1.255 45123 1.000 0.2709
Hurdle - female_age -0.324 0.234 -0.787 0.131 122650 1.000 0.0825
Hurdle - genotypeSRST:IsolineLew17 0.496 1.163 -1.702 2.858 50557 1.000 0.3420
Hurdle - genotypeSRSR:IsolineLew17 -0.836 1.203 -3.152 1.590 51634 1.000 0.2373
Hurdle - genotypeSRST:IsolineSloB3 0.661 1.147 -1.534 2.971 49740 1.000 0.2843
Hurdle - genotypeSRSR:IsolineSloB3 0.900 1.094 -1.160 3.121 47974 1.000 0.2061
Hurdle - genotypeSRST:IsolineSloB7 -0.591 1.231 -2.981 1.886 49543 1.000 0.3100
Hurdle - genotypeSRSR:IsolineSloB7 0.188 1.133 -1.958 2.496 46082 1.000 0.4416
sd(Block - Intercept) 0.180 0.152 0.010 0.556 22763 1.000 NA
sd(Block - Hurdle intercept) 0.559 0.516 0.023 1.882 24476 1.000 NA

Use the model to generate posterior estimates of group means

Generate posterior predictions of the group means

Here, we estimate the mean for three measures of female fitness using the model, for each genotype (across all isolines) and for each genotype-isoline combination. The model adjusts for variation due to experimental block and female age.

make_figure_data <- function(by_isoline = FALSE){
  if(by_isoline){
    new <- fitness_data %>% 
      select(genotype, Isoline, body_size, female_age) %>%
      mutate(body_size  = mean(body_size, na.rm = TRUE),
             female_age = mean(female_age)) %>% 
      distinct()
    model <- full_model
    col_names <- paste(new$genotype, new$Isoline, sep = "~")
  } else {
    new <- fitness_data %>% 
      select(genotype, body_size, female_age) %>%
      mutate(body_size  = mean(body_size, na.rm = TRUE),
             female_age = mean(female_age)) %>% 
      distinct()
    model <- genotype_only_model
    col_names <- new$genotype
  }
  
  # Summarise the posterior (dots and CIs in Figure 1 or S1)
  predicted_mean <- data.frame(new, fitted(model, newdata = new, re_formula = NA)) %>% 
    mutate(facet = "A. Mean offspring production")
  predicted_mean_when_fertile <- data.frame(new, fitted(model, newdata = new, dpar = "mu", re_formula = NA)) %>%
    mutate(facet = "B. Mean offspring production\n(excluding infertile females)")
  predicted_prop_fertile <- data.frame(new, fitted(model, newdata = new, dpar = "hu", re_formula = NA)) %>% 
    mutate(facet = "C. % fertile females",
           Estimate = 100 * (1 - Estimate), # Convert to percentage of fertile females, instead of *proportion* that are *in*fertile
           Q2.5 = (1 - Q2.5) * 100, 
           Q97.5 = (1 - Q97.5) * 100)
  
  summary_df <- bind_rows(predicted_mean,
                          predicted_mean_when_fertile,
                          predicted_prop_fertile) %>%
    mutate(genotype = factor(genotype, levels = c("STST", "SRST", "SRSR")))
  if(!by_isoline) summary_df <- summary_df %>% mutate(Isoline = "All isolines")
  
  # Posterior for facet A (overal progeny)
  posterior_means <- fitted(model, newdata = new, re_formula = NA, summary = FALSE) %>% as.data.frame()
  names(posterior_means) <- col_names
  posterior_facetA <- gather(posterior_means) %>% 
    mutate(facet = "A. Mean offspring production")
  
  # Posterior for facet B (excluding infertile females)
  posterior_means <- fitted(model, newdata = new, dpar = "mu", re_formula = NA, summary = FALSE) %>% as.data.frame()
  names(posterior_means) <- col_names
  posterior_facetB <- gather(posterior_means) %>% 
    mutate(facet = "B. Mean offspring production\n(excluding infertile females)")
  
  # Posterior for facet C (% infertile females)
  posterior_means <- fitted(model, newdata = new, dpar = "hu", re_formula = NA, summary = FALSE) %>% as.data.frame()
  names(posterior_means) <- col_names
  posterior_facetC <- gather(posterior_means) %>% 
    mutate(facet = "C. % fertile females")
  
  posterior_df <- bind_rows(
    posterior_facetA, posterior_facetB, posterior_facetC
  )
  
  if(by_isoline){
    posterior_df <- posterior_df %>%
      mutate(split = strsplit(key, split = "~"), 
             genotype = map_chr(split, ~ .x[1]),
             Isoline = map_chr(split, ~ .x[2])) %>% select(-key)
  } else {
    posterior_df <- posterior_df %>%
      rename(genotype = key)
  }
  
  posterior_df <- posterior_df %>% mutate(genotype = factor(genotype, levels = c("STST", "SRST", "SRSR")))
  
  list(summary_df, posterior_df)
}

figure1_data <- make_figure_data()
figureS1_data <- make_figure_data(by_isoline = TRUE)

Plot the posterior predictions of the group means

beeswarm_points <- bind_rows(
  fitness_data %>% mutate(facet = "A. Mean offspring production"),
  fitness_data %>% filter(offspring != 0) %>% mutate(facet = "B. Mean offspring production\n(excluding infertile females)")) %>% 
  mutate(Fertility = ifelse(offspring == 0, "Sterile", "Fertile"),
         genotype  = factor(genotype, levels = c("STST", "SRST", "SRSR"))) %>%
    rename(Estimate = offspring) 
  
pal <- c("#6ca0dc", "#e34132")

figure_1 <- figure1_data[[1]] %>%
  ggplot(aes(genotype, Estimate)) + 
  geom_quasirandom(data = beeswarm_points, aes(colour = Fertility),
                size = .7, alpha = 0.6) + 
  geom_errorbar(aes(ymin = Q2.5, ymax = Q97.5), colour = "grey20", size = .8, width = 0) + 
  geom_point(size = 3.1, pch = 21, colour = "black", fill = "grey20") + 
  scale_colour_manual(values = pal) + 
  facet_wrap(~facet, scale = "free_y") + 
  labs(y = "Posterior estimate \u00B1 95% CIs", x = "Genotype") + 
  theme_bw() + 
  theme(strip.background = element_blank(),
        text = element_text(family = "Lato", size = 12),
        panel.grid.major.x = element_blank(), 
        strip.text = element_text(hjust = 0))

figure_S1 <- figureS1_data[[1]] %>%
  ggplot(aes(genotype, Estimate, fill = Isoline)) + 
  geom_errorbar(aes(ymin = Q2.5, ymax = Q97.5), size = .7, width = 0, colour = "grey40", position = position_dodge(0.7)) + 
  geom_point(size = 3.1, pch = 21, colour = "black", position = position_dodge(0.7)) + 
  facet_wrap(~facet, scale = "free_y") + 
  scale_fill_brewer(palette = "Pastel1") +
  labs(y = "Posterior estimate \u00B1 95% CIs", x = "Genotype") + 
  theme_bw() + 
  theme(strip.background = element_blank(),
        text = element_text(family = "Lato", size = 12),
        panel.grid.major.x = element_blank(), 
        strip.text = element_text(hjust = 0))

figure_1 %>% ggsave(filename = "figures/figure_1.pdf", width = 9, height = 4)
figure_S1 %>% ggsave(filename = "figures/figure_S1.pdf", width = 9, height = 4)
figure_1

Version Author Date
e5f7926 lukeholman 2019-08-14
13a5603 lukeholman 2019-07-02



Figure 1: The black points and error bars show the posterior estimates of the genotype means for A) offspring production, B) offspring production among the set of females that produced at least one offspring, and C) the percentage of females that produced offspring. The estimates are all derived from a single hurdle model which adjusts for variation due to female age and experimental block, and each estimate is the average across the four isolines (see Figure S1 for estimates split by isoline). The points show the raw values of offspring production for individual females, and are coloured purple for females that produced no offspring. The error bars show the 95% credible intervals on each estimate.

figure_S1

Version Author Date
e5f7926 lukeholman 2019-08-14



Figure S1: The same information as in Figure 1, except split by isoline.

Calculate pairwise differences between genotypes

Table 1: Pairwise comparisons of genotypes for the three measures of female fitness shown in Figure 1: mean offspring production, mean offspring production among females that produced at least one offspring, and the % females that produced at least one offspring. The ‘Difference in means’ column shows the posterior estimate of the difference between the genotype means, in the original units (i.e. offspring number, or percentage points). A negative difference means that genotype with more copies of SR has lower female fitness, the parentheses show the 95% quantiles of the posterior difference in means, and the Error column gives the average absolute deviation. The ‘Relative difference’ column expresses each difference in relative terms; e.g. the first row shows that the mean number of offspring produced by SR/ST females was 87.7% as much as the number produced by ST/ST females. Finally, the \(p\) column shows the posterior probability that the true difference in means is zero or of the opposite sign to the estimate shown here (similar to a conventional \(p\)-value).

compare_means <- function(mean1, mean2, posterior){
  
  posterior <- posterior %>%
    filter(genotype %in% c(mean1, mean2)) %>% 
    select(genotype, value) %>% mutate(draw = rep(1:(n() / 2), 2)) %>%
    spread(genotype, value)
  
  difference <- posterior[, mean2] - posterior[, mean1]
  relative <- median(posterior[, mean2] / posterior[, mean1])
  p_value <- as.numeric(100 - p_direction(difference)) / 100
  as_tibble(posterior_summary(as.mcmc(difference))) %>%
    mutate(Comparison = paste(mean1, mean2, sep = " \u2192 "),
           `Relative difference` = paste(format(round(100 * relative, 1), nsmall = 1), "%", sep = ""),
           `95% CIs` = paste(" (", format(round(Q2.5, 1), nsmall = 1), " to ", format(round(Q97.5, 1), nsmall = 1), "%)", sep = ""),
           `Difference in means` = paste(format(round(Estimate, 2), nsmall = 2), `95% CIs`, sep = ""),
           `Fitness trait` = NA,
           p = p_value) %>%
    select( -Q2.5, -Q97.5) %>%
    select(Comparison, `Fitness trait`, `Difference in means`, Est.Error, `Relative difference`, p) %>%
    rename(Error = Est.Error)
}

table_of_contrasts <- bind_rows(
  compare_means("STST", "SRST", figure1_data[[2]] %>% filter(facet == "A. Mean offspring production")),
  compare_means("STST", "SRSR", figure1_data[[2]] %>% filter(facet == "A. Mean offspring production")),
  compare_means("SRST", "SRSR", figure1_data[[2]] %>% filter(facet == "A. Mean offspring production")),
  compare_means("STST", "SRST", figure1_data[[2]] %>% filter(facet == "B. Mean offspring production\n(excluding infertile females)")),
  compare_means("STST", "SRSR", figure1_data[[2]] %>% filter(facet == "B. Mean offspring production\n(excluding infertile females)")),
  compare_means("SRST", "SRSR", figure1_data[[2]] %>% filter(facet == "B. Mean offspring production\n(excluding infertile females)")),
  compare_means("STST", "SRST", figure1_data[[2]] %>% filter(facet == "C. % fertile females")),
  compare_means("STST", "SRSR", figure1_data[[2]] %>% filter(facet == "C. % fertile females")),
  compare_means("SRST", "SRSR", figure1_data[[2]] %>% filter(facet == "C. % fertile females"))
) %>% mutate(`Fitness trait` = rep(c("Mean offspring production",
                                     "Mean offspring production (excluding infertile females)",
                                     "% fertile females"), each = 3)) %>%
  mutate(Error = format(round(Error, 2), nsmall = 2),
         ` ` = ifelse(p < 0.05, "*", " "),
         p = format(round(p, 4), nsmall = 4))

table_of_contrasts %>%
  kable() %>% kable_styling()
Comparison Fitness trait Difference in means Error Relative difference p
STST → SRST Mean offspring production -5.53 (-18.0 to 6.5%) 6.23 91.5% 0.1842
STST → SRSR Mean offspring production -38.37 (-50.5 to -27.6%) 5.91 40.8% 0.0000
SRST → SRSR Mean offspring production -32.84 (-44.6 to -22.6%) 5.67 44.5% 0.0000
STST → SRST Mean offspring production (excluding infertile females) 2.04 (-9.9 to 14.2%) 6.12 103.0% 0.3693
STST → SRSR Mean offspring production (excluding infertile females) -32.88 (-44.5 to -22.3%) 5.70 51.2% 0.0000
SRST → SRSR Mean offspring production (excluding infertile females) -34.93 (-47.0 to -24.6%) 5.81 49.7% 0.0000
STST → SRST % fertile females 0.11 (0.0 to 0.2%) 0.04 384.8% 0.0007
STST → SRSR % fertile females 0.20 (0.1 to 0.3%) 0.05 627.0% 0.0000
SRST → SRSR % fertile females 0.09 (0.0 to 0.2%) 0.05 162.6% 0.0278

sessionInfo()
R version 3.5.1 (2018-07-02)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS High Sierra 10.13.6

Matrix products: default
BLAS: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRlapack.dylib

locale:
[1] en_AU.UTF-8/en_AU.UTF-8/en_AU.UTF-8/C/en_AU.UTF-8/en_AU.UTF-8

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

other attached packages:
 [1] showtext_0.5-1     showtextdb_2.0     sysfonts_0.7.2    
 [4] RColorBrewer_1.1-2 ggbeeswarm_0.6.0   kableExtra_0.9.0  
 [7] bayestestR_0.2.2   brms_2.9.0         Rcpp_1.0.2        
[10] forcats_0.4.0      stringr_1.4.0      dplyr_0.8.0.1     
[13] purrr_0.3.2        readr_1.1.1        tidyr_0.8.2       
[16] tibble_2.0.99.9000 ggplot2_3.1.0      tidyverse_1.2.1   

loaded via a namespace (and not attached):
  [1] colorspace_1.3-2     ggridges_0.5.0       rsconnect_0.8.8     
  [4] rprojroot_1.3-2      markdown_1.0         base64enc_0.1-3     
  [7] fs_1.3.1             rstudioapi_0.10      rstan_2.18.2        
 [10] DT_0.4               mvtnorm_1.0-11       lubridate_1.7.4     
 [13] xml2_1.2.0           bridgesampling_0.4-0 knitr_1.23          
 [16] shinythemes_1.1.1    bayesplot_1.6.0      jsonlite_1.6        
 [19] workflowr_1.3.0      broom_0.5.0          shiny_1.3.2         
 [22] compiler_3.5.1       httr_1.4.0           backports_1.1.2     
 [25] assertthat_0.2.1     Matrix_1.2-14        lazyeval_0.2.2      
 [28] cli_1.1.0            later_0.8.0          htmltools_0.3.6     
 [31] prettyunits_1.0.2    tools_3.5.1          igraph_1.2.1        
 [34] coda_0.19-2          gtable_0.2.0         glue_1.3.1.9000     
 [37] reshape2_1.4.3       cellranger_1.1.0     nlme_3.1-137        
 [40] crosstalk_1.0.0      insight_0.3.0        xfun_0.8            
 [43] ps_1.3.0             rvest_0.3.2          mime_0.7            
 [46] miniUI_0.1.1.1       gtools_3.8.1         zoo_1.8-3           
 [49] scales_1.0.0         colourpicker_1.0     hms_0.4.2           
 [52] promises_1.0.1       Brobdingnag_1.2-5    parallel_3.5.1      
 [55] inline_0.3.15        shinystan_2.5.0      curl_3.3            
 [58] yaml_2.2.0           gridExtra_2.3        loo_2.1.0           
 [61] StanHeaders_2.18.0   stringi_1.4.3        highr_0.8           
 [64] dygraphs_1.1.1.6     pkgbuild_1.0.2       rlang_0.4.0         
 [67] pkgconfig_2.0.2      matrixStats_0.54.0   evaluate_0.14       
 [70] lattice_0.20-35      labeling_0.3         rstantools_1.5.0    
 [73] htmlwidgets_1.2      tidyselect_0.2.5     processx_3.2.1      
 [76] plyr_1.8.4           magrittr_1.5         R6_2.4.0            
 [79] pillar_1.3.1.9000    haven_1.1.2          whisker_0.3-2       
 [82] withr_2.1.2          xts_0.11-0           abind_1.4-5         
 [85] modelr_0.1.2         crayon_1.3.4         rmarkdown_1.13      
 [88] grid_3.5.1           readxl_1.1.0         callr_2.0.4         
 [91] git2r_0.23.0         threejs_0.3.1        digest_0.6.20       
 [94] xtable_1.8-4         httpuv_1.5.1         stats4_3.5.1        
 [97] munsell_0.5.0        beeswarm_0.2.3       viridisLite_0.3.0   
[100] vipor_0.4.5          shinyjs_1.0