Use sprintf in loop with changing number of variables

Viewed 33

I want to use sprintf to add an annotation to a ggplot in a loop.

att_fig <- ggdid(att, legend=F, x_lab='', ax_text_size = 8, ylab='', title='ATT by year', title_size = 10) + 
    labs(caption = sprintf('2005 ATT(SE): %.3f (%.3f)\n2010 ATT(SE): %.3f (%.3f)\n2014 ATT(SE): %.3f (%.3f)', 
              att$att.egt[[1]], att$se.egt[[1]], att$att.egt[[2]], att$se.egt[[2]], att$att.egt[[3]], att$se.egt[[3]]))

Problem is that in some cases, there are only 1 or 2 elements in the att.egt and se.egt vectors.

I tried with a try():

    try(
      labs(caption = sprintf('2005 ATT(SE): %.3f (%.3f)\n2010 ATT(SE): %.3f (%.3f)\n2014 ATT(SE): %.3f (%.3f)', 
              att$att.egt[[1]], att$se.egt[[1]], att$att.egt[[2]], att$se.egt[[2]], att$att.egt[[3]], att$se.egt[[3]]))
    ) + 
    try(
      labs(caption = sprintf('2010 ATT(SE): %.3f (%.3f)\n2014 ATT(SE): %.3f (%.3f)', 
              att$att.egt[[1]], att$se.egt[[1]], att$att.egt[[2]], att$se.egt[[2]]))
    )

But ggplot is unhappy and returns:

Error in att$att.egt[[3]] : subscript out of bounds
Error in `ggplot_add()`:
! Can't add `try(labs(caption = sprintf("2005 ATT(SE): %.3f (%.3f)\n2010 ATT(SE): %.3f (%.3f)\n2014 ATT(SE): %.3f (%.3f)", ` to a ggplot object.
• Can't add `    att$att.egt[[1]], att$se.egt[[1]], att$att.egt[[2]], att$se.egt[[2]], ` to a ggplot object.
• Can't add `    att$att.egt[[3]], att$se.egt[[3]])))` to a ggplot object.
Traceback:

1. `+.gg`(ggdid(att, legend = F, x_lab = "", ax_text_size = 8, ylab = "", 
 .     title = "ATT by year", title_size = 10), try(labs(caption = sprintf("2005 ATT(SE): %.3f (%.3f)\n2010 ATT(SE): %.3f (%.3f)\n2014 ATT(SE): %.3f (%.3f)", 
 .     att$att.egt[[1]], att$se.egt[[1]], att$att.egt[[2]], att$se.egt[[2]], 
 .     att$att.egt[[3]], att$se.egt[[3]]))))
2. add_ggplot(e1, e2, e2name)
3. ggplot_add(object, p, objectname)
4. ggplot_add.default(object, p, objectname)
5. abort(glue("Can't add `{object_name}` to a ggplot object."))
6. signal_abort(cnd, .file)

Any idea how I could proceed?

Edit: Here is the full code but the problem should be pretty obvious without it:

parties <- c('Union', 'SPD', 'FDP', 'Linke', 'Grüne', 'Andere')
treatments <- hash('treatment_0'='Direct Line', 'treatment_15'='Within 15km', 'treatment_30'='Within 30km', 'treatment_50'='Within 50km')
keys <- keys(treatments)
results  <- list()
for(party in parties){
  figures  <- list()
  for(treatment in keys){
    result <- att_gt(yname = party,
                  gname = treatment,
                  idname = 'AGS',
                  panel = TRUE,
                  tname = 'year',
                  xformla = ~ 1,
                  data = ltw,
                  est_method = 'reg',
                  anticipation = 0,
                  control_group = 'nevertreated',
                  clustervars = c('AGS'),
                  bstrap = TRUE,
                  cband = TRUE,
                  allow_unbalanced_panel = TRUE,
                  
    )
  results[[length(results)+1]] <- result
  # calculate ATT
  att <- aggte(result, type = "group", bstrap = TRUE, clustervars = c('AGS'))
  # plot results
  result_fig <- ggdid(result, 
            ylim = c(floor(min(result$att - result$se * 2.345 - 1)), ceiling(max(result$att + result$se * 2.345 + 1))),
            ncol = 3, ax_text_size = 8, grtitle='', title_size = 10, legend=F) +
            labs(caption = sprintf('Overall ATT (SE): %.3f (%.3f) \n Wald-P: %.3f \n # Obs.: %s' ,
            att$overall.att, att$overall.se, result$Wpval, result$n)) +
            theme(plot.caption = element_text(hjust=0.5, size=8),
            panel.background = element_rect(fill='transparent'),
            plot.background = element_rect(fill='transparent', color=NA),
            legend.background = element_rect(fill='transparent'),
            legend.box.background = element_rect(fill='transparent'),
            plot.margin=unit(c(0,0,0,0),"cm"))
  # Plot ATT
  att_fig <- ggdid(att, legend=F, x_lab='', ax_text_size = 8, ylab='', title='ATT by year', title_size = 10) + 
  labs(caption = sprintf('2005 ATT(SE): %.3f (%.3f)\n2010 ATT(SE): %.3f (%.3f)\n2014 ATT(SE): %.3f (%.3f)', 
            att$att.egt[[1]], att$se.egt[[1]], att$att.egt[[2]], att$se.egt[[2]], att$att.egt[[3]], att$se.egt[[3]])) + 
            theme(plot.caption = element_text(hjust=0.5, size=8), 
            axis.title.x = element_blank(),
            axis.text.y = element_text(angle=90, size=6, hjust=0.5),
            panel.background = element_rect(fill='transparent'),
            plot.background = element_rect(fill='transparent', color=NA),
            legend.background = element_rect(fill='transparent'),
            legend.box.background = element_rect(fill='transparent'),
            plot.margin=unit(c(0,0,0,0),"cm"))
  # Combine and annotate Group and ATT plot
  combined_fig <- ggarrange(result_fig, att_fig, widths = c(3, 1)) + 
                  theme(plot.margin=unit(c(0,0,0,0),"cm"))
  combined_fig_anno <- annotate_figure(combined_fig,
                top = text_grob(sprintf('%s', treatments[[treatment]])))
  figures[[length(figures)+1]] <- combined_fig_anno
  }
  # Combine Figures for all treatments
  arranged_fig <- ggarrange(plotlist=figures, nrow = 4, ncol = 1, common.legend = TRUE)
  # get legend
  result_fig <- ggdid(result) +
              theme(
              legend.background = element_rect(fill='transparent', color='transparent'),
              legend.box.background = element_rect(fill='transparent', color='transparent'),
              plot.margin=unit(c(0,0,0,0),"cm"))
  leg <- get_legend(result_fig)
  # add legend
  arranged_fig_leg <- ggarrange(arranged_fig, leg, nrow = 2, ncol = 1, heights = c(10, 1))
  final_fig <- annotate_figure(arranged_fig_leg,
                  top = text_grob(sprintf("LTW: Effect on %s's vote share", party), face = "bold", size = 14),
                  bottom = text_grob('Control Group: Never Treated. Estimation Method: Regression \n SE clustered on the municipality level.', size = 10),)
  ggsave(sprintf('%s_all.png', party), plot = final_fig, path = sprintf('%s/figures/R/LTW/pooled', path), units = 'cm', width = 21, height = 29.7, dpi="print")
  print(party)
}

EDIT 2:

Maybe theres a way to streamline this. ggdid(att) returns among others: att.egt se.egt glist

which look like this:

> dput(att$DIDparams$glist)
c(2005, 2010, 2014)
> dput(att$att.egt)
c(2.62788545990323, 0.335795773304143, 0.644448602052905)
> dput(att$se.egt)
c(0.151836561441285, 0.187353304600586, 0.200190942502885)

Now is there any way to put these together in one string (and add a Group before year) similar to list comprehension in python?

EDIT 3: Solved it

cap = ''
for(i in 1:length(att$DIDparams$glist)) {
  cap = sprintf('%s Group %s: ATT(SE) %.3f (%.3f) \n', cap, att$DIDparams$glist[[i]], att$att.egt[[i]], att$se.egt[[i]])
}
print(cap)
>>Group 2005: ATT(SE) 2.628 (0.152) \n Group 2010: ATT(SE) 0.336 (0.187) \n Group 2014: ATT(SE) 0.644 (0.200) \n"
1 Answers

As the code you provided is far away from being minimal - I made another example.

We can use do.call() along with sprintf():

strings <- list("Test %s" , "Test %s, Test %s", "Test %s, Test %s, Test %s")
vars <- list("A", c("A","B"), c("A","B","C"))

for (i in 1:3){
  print(do.call(sprintf, args = c(fmt = strings[i], vars[[i]])))
}

Result:

[1] "Test A"
[1] "Test A, Test B"
[1] "Test A, Test B, Test C"

Edit:

Using @jan.sfr's below example (object att was not provided):

strings <- list('Group 2014 ATT(SE): %.3f (%.3f)', 'Group 2010 ATT(SE): %.3f (%.3f) \n Group 2014 ATT(SE): %.3f (%.3f)', 'Group 2005 ATT(SE): %.3f (%.3f) \n Group 2010 ATT(SE): %.3f (%.3f) \n Group 2014 ATT(SE): %.3f (%.3f)')
vars <- list(c(1,2), c(1,2,3,4), c(1,2,3,4,5,6))
cap <- list()
for (i in 1:3){
  cap[[i]] <- do.call(sprintf, args = c(fmt = strings[i], vars[[i]])) 
}
cap

Result:

> cap
[[1]]
[1] "Group 2014 ATT(SE): 1.000 (2.000)"

[[2]]
[1] "Group 2010 ATT(SE): 1.000 (2.000) \n Group 2014 ATT(SE): 3.000 (4.000)"

[[3]]
[1] "Group 2005 ATT(SE): 1.000 (2.000) \n Group 2010 ATT(SE): 3.000 (4.000) \n Group 2014 ATT(SE): 5.000 (6.000)"

After 2. edit:

I'm not really sure where this is going now - are you looking for paste?

Please check this:

library(data.table)
att <- list(DIDparams = list(glist = c(2005, 2010, 2014)),att.egt = c(2.62788545990323, 0.335795773304143, 0.644448602052905), se.egt = c(0.155984116660681, 0.175881981452247, 0.185621749289287))
attDT <- setDT(as.data.frame(att))
attDT[, group := .GRP, by = glist]
setcolorder(attDT, "group")
attDT[, mytring := paste(group, glist, att.egt, se.egt, sep = " | ")]
cat(paste(attDT$mytring, collapse = "\n"))

Result:

1 | 2005 | 2.62788545990323 | 0.155984116660681
2 | 2010 | 0.335795773304143 | 0.175881981452247
3 | 2014 | 0.644448602052905 | 0.185621749289287
Related