robertvidigal commited on
Commit
bd3b397
Β·
verified Β·
1 Parent(s): 9ead566

Upload 4 files

Browse files
Files changed (3) hide show
  1. app.R +250 -59
  2. gm_shiny_data_en.rds +2 -2
  3. variable_labels_shiny.csv +0 -0
app.R CHANGED
@@ -2,10 +2,13 @@ library(lapop)
2
  library(haven)
3
  library(dplyr)
4
  library(tidyr)
 
5
  library(stringr)
6
  library(shinyWidgets)
 
7
  library(Hmisc)
8
 
 
9
  lapop_fonts()
10
 
11
  dstrata <- readRDS("gm_shiny_data_en.rds")
@@ -16,10 +19,13 @@ Error<-function(x){
16
  tryCatch(x,error=function(e) return(FALSE))
17
  }
18
 
19
- waves_total = c("2004", "2006", "2008", "2010", "2012", "2014", "2016/17", "2018/19", "2021", "2023")
 
20
 
21
 
22
  #helper function for cleaning ts -- handle missing values at end or middle of series
 
 
23
  omit_na_edges <- function(df) {
24
  # Find which rows have NA values
25
  na_rows <- apply(df, 1, function(row) any(is.na(row)))
@@ -34,7 +40,9 @@ omit_na_edges <- function(df) {
34
  return(df_clean)
35
  }
36
 
37
- #custom weighted averages and CIs, to speed up computational speed vs. survey_mean
 
 
38
  weighted.ttest.ci <- function(x, weights) {
39
  nx <- length(x)
40
  vx <- Hmisc::wtd.var(x, weights, normwt = TRUE, na.rm = TRUE) ## From Hmisc
@@ -49,10 +57,13 @@ weighted.ttest.ci <- function(x, weights) {
49
  }
50
 
51
  # helper function for mover
 
 
52
  process_data <- function(data, outcome_var, recode_range, group_var, var_label, weight_var = "weight1500") {
53
  if (is.null(group_var)) {
54
  return(NULL)
55
- }
 
56
  processed_data <- data %>%
57
  drop_na(!!sym(outcome_var)) %>%
58
  mutate(outcome_rec = case_when(
@@ -72,6 +83,47 @@ process_data <- function(data, outcome_var, recode_range, group_var, var_label,
72
  return(processed_data)
73
  }
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  ui <- fluidPage(
76
 
77
  titlePanel(""), # Leave it Empty
@@ -82,7 +134,8 @@ ui <- fluidPage(
82
  sidebarPanel(
83
  width = 3, # Reduce width (default is 4)
84
 
85
- selectInput("variable", "Variable",
 
86
  labs[order(names(labs))],
87
  selected = "ing4"),
88
 
@@ -98,7 +151,7 @@ ui <- fluidPage(
98
  multiple = TRUE),
99
 
100
 
101
- #this fixes a formatting issue with checkboxGroupInput below
102
  tags$head(
103
  tags$style(
104
  HTML(
@@ -115,7 +168,14 @@ ui <- fluidPage(
115
  )
116
  ),
117
 
118
- #this makes slider input only integers
 
 
 
 
 
 
 
119
  tags$style(type = "text/css", ".irs-grid-pol.small {height: 0px;}"),
120
 
121
 
@@ -135,10 +195,9 @@ ui <- fluidPage(
135
  selected = c("2006", "2008", "2010", "2012", "2014",
136
  "2016/17", "2018/19", "2021", "2023"),
137
  options = list(`actions-box` = TRUE),
138
- # options = list
139
  multiple = TRUE),
140
 
141
- # show recode slider only for time series, cc, and breakdown (not hist)
142
  conditionalPanel(
143
  'input.tabs == "Time Series" | input.tabs == "Cross Country" | input.tabs == "Breakdown"',
144
  uiOutput("sliderUI"),
@@ -160,7 +219,12 @@ ui <- fluidPage(
160
  inline = TRUE)
161
  ),
162
 
163
- actionButton("go", "Generate")
 
 
 
 
 
164
 
165
  ),
166
 
@@ -183,15 +247,38 @@ ui <- fluidPage(
183
  ),
184
  br(),
185
  fluidRow(column(12, "",
 
186
  downloadButton(outputId = "downloadPlot", label = "Download Figure"),
187
  downloadButton(outputId = "downloadTable", label = "Download Table")))
188
  )
189
  )
190
  )
191
 
192
- # Define server logic to plot various variables ----
 
 
 
193
  server <- function(input, output, session) {
194
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  formulaText <- reactive({
196
  paste(input$variable)
197
  })
@@ -200,6 +287,10 @@ server <- function(input, output, session) {
200
  input$variable
201
  })
202
 
 
 
 
 
203
  variable_sec <- reactive({
204
  input$variable_sec
205
  })
@@ -229,45 +320,58 @@ server <- function(input, output, session) {
229
 
230
  output$sliderUI <- renderUI({
231
  sliderInput(inputId = "recode",
232
- label = "Response values included in percentage",
233
  min = min(as.numeric(dstrata[[formulaText()]]), na.rm=TRUE),
234
  max = max(as.numeric(dstrata[[formulaText()]]), na.rm=TRUE),
235
  value = sliderParams$valuex,
236
  step = 1)
237
  })
238
 
239
-
240
-
241
  dff <- eventReactive(input$go, ignoreNULL = FALSE, {
242
  dstrata %>%
243
  filter(as_factor(wave) %in% input$wave) %>%
244
  filter(pais_nam %in% input$pais)
245
  })
246
 
 
247
  cap <- renderText({
248
  vars_labels$question_short_en[which(vars_labels$column_name == formulaText())]
249
  })
250
 
251
- output$caption <- eventReactive(input$go, ignoreNULL = FALSE, {
252
  cap()
253
  })
254
 
 
255
  word <- renderText({
256
- vars_labels$question_en[which(vars_labels$column_name == formulaText())]
 
257
  })
258
 
259
- output$wording <- eventReactive(input$go, ignoreNULL = FALSE, {
260
  word()
261
  })
262
 
 
263
  resp <- renderText({
264
  vars_labels$responses_en_rec[which(vars_labels$column_name == formulaText())]
265
  })
266
 
267
- output$response <- eventReactive(input$go, ignoreNULL = FALSE, {
268
  resp()
269
  })
270
 
 
 
 
 
 
 
 
 
 
 
271
  slider_values <- renderText({
272
  if(input$recode[1] == input$recode[2]) {
273
  paste0("(value: ", unique(input$recode), ")")
@@ -276,11 +380,66 @@ server <- function(input, output, session) {
276
  }
277
  })
278
 
279
- output$selected_values <- eventReactive(input$go, ignoreNULL = FALSE, {
280
  slider_values()
281
  })
282
 
283
- # SOURCE INFO WITH PAIS and WAVE
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  source_info_both <- reactive({
285
  # Get country abbreviations that match selected country names
286
  pais_abbr <- dstrata %>%
@@ -292,8 +451,14 @@ server <- function(input, output, session) {
292
  pais_display <- paste(pais_abbr, collapse = ", ")
293
  wave_display <- paste(input$wave, collapse = ", ")
294
 
295
- paste0(", AmericasBarometer Data Playground\nCountries included: ", pais_display, "\nSurvey rounds included: ", wave_display)
296
-
 
 
 
 
 
 
297
  })
298
 
299
  source_info_pais <- reactive({
@@ -307,7 +472,7 @@ server <- function(input, output, session) {
307
  pais_display <- paste(pais_abbr, collapse = ", ")
308
  wave_display <- paste(input$wave, collapse = ", ")
309
 
310
- paste0(", AmericasBarometer Data Playground\nCountries included: ", pais_display)
311
  })
312
 
313
  source_info_wave <- reactive({
@@ -321,12 +486,14 @@ server <- function(input, output, session) {
321
  pais_display <- paste(pais_abbr, collapse = ", ")
322
  wave_display <- paste(input$wave, collapse = ", ")
323
 
324
- paste0(", AmericasBarometer Data Playground\nSurvey rounds included: ", wave_display)
325
  })
326
 
327
- #hist
 
 
328
  # must break into data event, graph event, and renderPlot to get download buttons to work
329
- histd <- eventReactive(input$go, ignoreNULL = FALSE, {
330
  hist_df = Error(
331
  dff() %>%
332
  group_by(across(outcome())) %>%
@@ -344,7 +511,7 @@ server <- function(input, output, session) {
344
  })
345
 
346
 
347
- histg <- eventReactive(input$go, ignoreNULL = FALSE, {
348
  histg <- lapop_hist(histd(),
349
  ymax = ifelse(any(histd()$prop > 90), 110, 100),
350
  source_info = source_info_both())
@@ -356,8 +523,9 @@ server <- function(input, output, session) {
356
  })
357
 
358
 
359
- #ts
360
- tsd <- eventReactive(input$go, ignoreNULL = FALSE, {
 
361
  dta_ts = Error(
362
  dff() %>%
363
  drop_na(outcome()) %>%
@@ -381,7 +549,7 @@ server <- function(input, output, session) {
381
  return(omit_na_edges(dta_ts))
382
  })
383
 
384
- tsg <- eventReactive(input$go, ignoreNULL = FALSE, {
385
  tsg = lapop_ts(tsd(),
386
  ymax = ifelse(any(tsd()$prop > 88, na.rm = TRUE), 110, 100),
387
  label_vjust = ifelse(any(tsd()$prop > 80, na.rm = TRUE), -1.1, -1.5),
@@ -395,8 +563,9 @@ server <- function(input, output, session) {
395
  return(tsg())
396
  })
397
 
398
- #cc
399
- ccd <- eventReactive(input$go, ignoreNULL = FALSE, {
 
400
  dta_cc = Error(
401
  dff() %>%
402
  drop_na(outcome()) %>%
@@ -418,7 +587,7 @@ server <- function(input, output, session) {
418
  return(dta_cc)
419
  })
420
 
421
- ccg <- eventReactive(input$go, ignoreNULL = FALSE, {
422
  ccg = lapop_cc(ccd(), sort = "hi-lo",
423
  subtitle = "% in selected category",
424
  ymax = ifelse(any(ccd()$prop > 90, na.rm = TRUE), 110, 100),
@@ -430,12 +599,16 @@ server <- function(input, output, session) {
430
  return(ccg())
431
  })
432
 
 
 
433
  # Use function for each demographic breakdown variable
434
- secdf <- eventReactive(input$go, ignoreNULL = FALSE, {
 
435
  if (input$variable_sec == "None") {
436
  NULL
437
  } else if (variable_sec() == outcome()) {
438
- showNotification("You cannot break the outcome variable by itself.", type = "error")
 
439
  NULL
440
  } else {
441
  process_data(
@@ -448,7 +621,7 @@ server <- function(input, output, session) {
448
  }
449
  })
450
 
451
- genderdf <- eventReactive(input$go, ignoreNULL = FALSE, {
452
  if ("gendermc" %in% input$demog) {
453
  process_data(
454
  data = dff(),
@@ -462,7 +635,7 @@ server <- function(input, output, session) {
462
  }
463
  })
464
 
465
- wealthdf <- eventReactive(input$go, ignoreNULL = FALSE, {
466
  if ("wealth" %in% input$demog) {
467
  process_data(
468
  data = dff(),
@@ -476,7 +649,7 @@ server <- function(input, output, session) {
476
  }
477
  })
478
 
479
- eddf <- eventReactive(input$go, ignoreNULL = FALSE, {
480
  if ("edre" %in% input$demog) {
481
  process_data(
482
  data = dff(),
@@ -490,7 +663,7 @@ server <- function(input, output, session) {
490
  }
491
  })
492
 
493
- edaddf <- eventReactive(input$go, ignoreNULL = FALSE, {
494
  if ("edad" %in% input$demog) {
495
  process_data(
496
  data = dff(),
@@ -504,7 +677,7 @@ server <- function(input, output, session) {
504
  }
505
  })
506
 
507
- urdf <- eventReactive(input$go, ignoreNULL = FALSE, {
508
  if ("ur" %in% input$demog) {
509
  process_data(
510
  data = dff(),
@@ -518,8 +691,8 @@ server <- function(input, output, session) {
518
  }
519
  })
520
 
521
- # Combine =demographic data frames into one df
522
- moverd <- eventReactive(input$go, ignoreNULL = FALSE, {
523
  dta_mover <- Error(rbind(secdf(), genderdf(), edaddf(), wealthdf(), eddf(), urdf()))
524
  validate(
525
  need(dta_mover, "Error: no data available. Please verify that this question was asked in this country/year combination")
@@ -528,12 +701,12 @@ server <- function(input, output, session) {
528
  return(dta_mover)
529
  })
530
 
531
- moverg <- eventReactive(input$go, ignoreNULL = FALSE, {
532
  moverg <- lapop_mover(moverd(),
533
  subtitle = "% in selected category",
534
  ymax = ifelse(any(moverd()$prop > 90, na.rm = TRUE), 119,
535
  ifelse(any(moverd()$prop > 80, na.rm = TRUE), 109, 100)),
536
- source_info = source_info_both())
537
  return(moverg)
538
  })
539
 
@@ -541,23 +714,29 @@ server <- function(input, output, session) {
541
  return(moverg())
542
  })
543
 
 
544
  # DOWNLOAD SECTION
 
545
  output$downloadPlot <- downloadHandler(
546
  filename = function(file) {
547
  ifelse(input$tabs == "Histogram", paste0("hist_", outcome(),".svg"),
548
  ifelse(input$tabs == "Time Series", paste0("ts_", outcome(),".svg"),
549
- ifelse(input$tabs == "Cross Country", paste0("cc_", outcome(),".svg"), paste0("mover_", outcome(),".svg"))))
 
550
  },
551
 
552
  content = function(file) {
553
  if(input$tabs == "Histogram") {
554
  title_text <- isolate(cap())
 
555
 
556
  hist_to_save <- lapop_hist(histd(),
557
  main_title = title_text,
558
  subtitle = "% in selected category ",
559
  ymax = ifelse(any(histd()$prop > 90), 110, 100),
560
- source_info = source_info_both())
 
 
561
 
562
  lapop_save(hist_to_save, file)
563
  showNotification(HTML("Plot download complete βœ“ "), type = "message")
@@ -565,13 +744,15 @@ server <- function(input, output, session) {
565
  } else if (input$tabs == "Time Series") {
566
  title_text <- isolate(cap())
567
  subtitle_text <- slider_values()
568
-
569
  ts_to_save <- lapop_ts(tsd(),
570
  main_title = title_text,
571
  subtitle = paste0("% in selected category ", subtitle_text),
572
  ymax = ifelse(any(tsd()$prop > 88, na.rm = TRUE), 110, 100),
573
  label_vjust = ifelse(any(tsd()$prop > 80, na.rm = TRUE), -1.1, -1.5),
574
- source_info = source_info_pais())
 
 
575
 
576
  lapop_save(ts_to_save, file)
577
  showNotification(HTML("Plot download complete βœ“ "), type = "message")
@@ -584,7 +765,9 @@ server <- function(input, output, session) {
584
  main_title = title_text,
585
  subtitle = paste0("% in selected category ", subtitle_text),
586
  ymax = ifelse(any(ccd()$prop > 90, na.rm = TRUE), 110, 100),
587
- source_info = source_info_wave())
 
 
588
 
589
  lapop_save(cc_to_save, file)
590
  showNotification(HTML("Plot download complete βœ“ "), type = "message")
@@ -599,7 +782,8 @@ server <- function(input, output, session) {
599
  subtitle = paste0("% in selected category ", subtitle_text),
600
  ymax = ifelse(any(moverd()$prop > 90, na.rm = TRUE), 119,
601
  ifelse(any(moverd()$prop > 80, na.rm = TRUE), 109, 100)),
602
- source_info = source_info_both()
 
603
  )
604
 
605
  lapop_save(mover_to_save, file)
@@ -609,34 +793,41 @@ server <- function(input, output, session) {
609
  }
610
  )
611
 
612
-
 
 
613
  output$downloadTable <- downloadHandler(
614
  filename = function(file) {
615
- ifelse(input$tabs == "Histogram", paste0("hist_", outcome(),".svg"),
616
- ifelse(input$tabs == "Time Series", paste0("ts_", outcome(),".svg"),
617
- ifelse(input$tabs == "Cross Country", paste0("cc_", outcome(),".svg"), paste0("mover_", outcome(),".svg"))))
 
618
  },
619
  content = function(file) {
620
  if(input$tabs == "Histogram") {
621
- write.csv(histd(), file)
622
  showNotification(HTML("File download complete βœ“ "), type = "message")
623
 
624
  } else if (input$tabs == "Time Series") {
625
- write.csv(tsd(), file)
626
  showNotification(HTML("File download complete βœ“ "), type = "message")
627
 
628
  } else if (input$tabs == "Cross Country") {
629
- write.csv(ccd(), file)
630
  showNotification(HTML("File download complete βœ“ "), type = "message")
631
 
632
  } else {
633
- write.csv(moverd(), file)
634
  showNotification(HTML("File download complete βœ“ "), type = "message")
635
-
636
  }
637
  }
638
  )
639
  }
640
 
641
- # Launch App
 
642
  shinyApp(ui, server)
 
 
 
 
 
2
  library(haven)
3
  library(dplyr)
4
  library(tidyr)
5
+ library(shiny)
6
  library(stringr)
7
  library(shinyWidgets)
8
+ library(bslib)
9
  library(Hmisc)
10
 
11
+ # # -----------------------------------------------------------------------
12
  lapop_fonts()
13
 
14
  dstrata <- readRDS("gm_shiny_data_en.rds")
 
19
  tryCatch(x,error=function(e) return(FALSE))
20
  }
21
 
22
+ waves_total = c("2004", "2006", "2008", "2010", "2012", "2014",
23
+ "2016/17", "2018/19", "2021", "2023")
24
 
25
 
26
  #helper function for cleaning ts -- handle missing values at end or middle of series
27
+ # # -----------------------------------------------------------------------
28
+
29
  omit_na_edges <- function(df) {
30
  # Find which rows have NA values
31
  na_rows <- apply(df, 1, function(row) any(is.na(row)))
 
40
  return(df_clean)
41
  }
42
 
43
+ # custom weighted averages and CIs, to speed up computational speed vs. survey_mean
44
+ # # -----------------------------------------------------------------------
45
+
46
  weighted.ttest.ci <- function(x, weights) {
47
  nx <- length(x)
48
  vx <- Hmisc::wtd.var(x, weights, normwt = TRUE, na.rm = TRUE) ## From Hmisc
 
57
  }
58
 
59
  # helper function for mover
60
+ # # -----------------------------------------------------------------------
61
+
62
  process_data <- function(data, outcome_var, recode_range, group_var, var_label, weight_var = "weight1500") {
63
  if (is.null(group_var)) {
64
  return(NULL)
65
+ }
66
+ # Proceed with processing
67
  processed_data <- data %>%
68
  drop_na(!!sym(outcome_var)) %>%
69
  mutate(outcome_rec = case_when(
 
83
  return(processed_data)
84
  }
85
 
86
+ # helper for missing country-year by outcome_var
87
+ # # -----------------------------------------------------------------------
88
+ get_missing_combinations <- function(data, outcome_var, wave_var,
89
+ selected_waves, selected_countries) {
90
+ # Convert wave values to string using haven labels
91
+ data <- data %>%
92
+ mutate(wave_str = as.character(haven::as_factor(.data[[wave_var]])))
93
+
94
+ # Build the full country-wave grid
95
+ all_combos <- expand.grid(
96
+ pais_nam = selected_countries,
97
+ wave = selected_waves,
98
+ stringsAsFactors = FALSE
99
+ )
100
+
101
+ # Subset only relevant countries
102
+ data <- data %>%
103
+ filter(pais_nam %in% selected_countries)
104
+
105
+ # Summarize: how many valid (non-NA and not 0) values exist per combo
106
+ summary <- data %>%
107
+ group_by(pais_nam, wave = wave_str) %>%
108
+ summarise(
109
+ n_valid = sum(!is.na(.data[[outcome_var]]) & .data[[outcome_var]] != 0),
110
+ .groups = "drop"
111
+ )
112
+
113
+ # Merge and detect missing
114
+ missing <- all_combos %>%
115
+ left_join(summary, by = c("pais_nam", "wave")) %>%
116
+ filter(is.na(n_valid) | n_valid == 0) %>%
117
+ select(pais_nam, wave)
118
+
119
+ return(missing)
120
+ }
121
+
122
+
123
+ # # -----------------------------------------------------------------------
124
+ # UI
125
+ # # -----------------------------------------------------------------------
126
+
127
  ui <- fluidPage(
128
 
129
  titlePanel(""), # Leave it Empty
 
134
  sidebarPanel(
135
  width = 3, # Reduce width (default is 4)
136
 
137
+ selectInput(inputId = "variable",
138
+ label = "Variable",
139
  labs[order(names(labs))],
140
  selected = "ing4"),
141
 
 
151
  multiple = TRUE),
152
 
153
 
154
+ # This fixes a formatting issue with checkboxGroupInput below
155
  tags$head(
156
  tags$style(
157
  HTML(
 
168
  )
169
  ),
170
 
171
+ # This triggers the "Generate" button
172
+ tags$script(HTML("
173
+ Shiny.addCustomMessageHandler('clickGenerateButton', function(message) {
174
+ $('#go').click();
175
+ });
176
+ ")),
177
+
178
+ # This makes slider input only integers
179
  tags$style(type = "text/css", ".irs-grid-pol.small {height: 0px;}"),
180
 
181
 
 
195
  selected = c("2006", "2008", "2010", "2012", "2014",
196
  "2016/17", "2018/19", "2021", "2023"),
197
  options = list(`actions-box` = TRUE),
 
198
  multiple = TRUE),
199
 
200
+ # Show recode slider only for time series, cc, and breakdown (not hist)
201
  conditionalPanel(
202
  'input.tabs == "Time Series" | input.tabs == "Cross Country" | input.tabs == "Breakdown"',
203
  uiOutput("sliderUI"),
 
219
  inline = TRUE)
220
  ),
221
 
222
+ #actionButton("go", "Generate") # Include button in UI
223
+
224
+ tags$div(
225
+ style = "display: none;",
226
+ actionButton("go", "Generate")
227
+ )
228
 
229
  ),
230
 
 
247
  ),
248
  br(),
249
  fluidRow(column(12, "",
250
+ uiOutput("missing_warning_card"),
251
  downloadButton(outputId = "downloadPlot", label = "Download Figure"),
252
  downloadButton(outputId = "downloadTable", label = "Download Table")))
253
  )
254
  )
255
  )
256
 
257
+ # # -----------------------------------------------------------------------
258
+ # SERVER
259
+ # # -----------------------------------------------------------------------
260
+
261
  server <- function(input, output, session) {
262
 
263
+ # Triggers "go" between server and ui to generate default plots
264
+ observe({
265
+ if (!is.null(input$pais) && !is.null(input$wave)) {
266
+ isolate({
267
+ session$sendCustomMessage("clickGenerateButton", list())
268
+ })
269
+ }
270
+ })
271
+
272
+ # Check the number of selected variables for breakdown
273
+ observeEvent(input$demog, {
274
+ if (length(input$demog) > 3) {
275
+ # Show a warning message
276
+ showNotification(HTML("You should only select a maximum of 3 demographic variables to plot."), type = "warning")
277
+ }
278
+ })
279
+
280
+ # # -----------------------------------------------------------------------
281
+
282
  formulaText <- reactive({
283
  paste(input$variable)
284
  })
 
287
  input$variable
288
  })
289
 
290
+ outcome_code <- reactive({
291
+ vars_labels$column_name[which(vars_labels$column_name == paste(outcome()))]
292
+ })
293
+
294
  variable_sec <- reactive({
295
  input$variable_sec
296
  })
 
320
 
321
  output$sliderUI <- renderUI({
322
  sliderInput(inputId = "recode",
323
+ label = "Outcome variable response values shown as percentage",
324
  min = min(as.numeric(dstrata[[formulaText()]]), na.rm=TRUE),
325
  max = max(as.numeric(dstrata[[formulaText()]]), na.rm=TRUE),
326
  value = sliderParams$valuex,
327
  step = 1)
328
  })
329
 
330
+ # Filtering data based on user's selection (dff)
 
331
  dff <- eventReactive(input$go, ignoreNULL = FALSE, {
332
  dstrata %>%
333
  filter(as_factor(wave) %in% input$wave) %>%
334
  filter(pais_nam %in% input$pais)
335
  })
336
 
337
+ # Rendering var caption based on user's var selection
338
  cap <- renderText({
339
  vars_labels$question_short_en[which(vars_labels$column_name == formulaText())]
340
  })
341
 
342
+ output$caption <- renderText({
343
  cap()
344
  })
345
 
346
+ # Rendering variable code + wording based on user's var selection
347
  word <- renderText({
348
+ paste0(toupper(vars_labels$column_name[which(vars_labels$column_name == formulaText())]), ". ",
349
+ vars_labels$question_en[which(vars_labels$column_name == formulaText())])
350
  })
351
 
352
+ output$wording <- renderText({
353
  word()
354
  })
355
 
356
+ # Rendering ROs based on user's var selection
357
  resp <- renderText({
358
  vars_labels$responses_en_rec[which(vars_labels$column_name == formulaText())]
359
  })
360
 
361
+ output$response <- renderText({
362
  resp()
363
  })
364
 
365
+ # Rendering variable_sec ROs
366
+ resp_sec <- renderText({
367
+ vars_labels$responses_en_rec[which(vars_labels$column_name == input$variable_sec)]
368
+ })
369
+
370
+ output$response_sec <- renderText({
371
+ resp_sec()
372
+ })
373
+
374
+ # Rendering User selected recode value(s)
375
  slider_values <- renderText({
376
  if(input$recode[1] == input$recode[2]) {
377
  paste0("(value: ", unique(input$recode), ")")
 
380
  }
381
  })
382
 
383
+ output$selected_values <- renderText({
384
  slider_values()
385
  })
386
 
387
+ # WARNING FOR MISSING COMBOS
388
+ # # -----------------------------------------------------------------------
389
+ output$missing_warning_card <- renderUI({
390
+ req(input$go > 0, input$wave, input$pais)
391
+
392
+ # Normalize wave and country inputs
393
+ selected_waves <- as.character(input$wave)
394
+ selected_countries <- as.character(input$pais)
395
+
396
+ # Step 1: Compute missing combinations
397
+ missing <- get_missing_combinations(
398
+ data = dff(),
399
+ outcome_var = outcome(),
400
+ wave_var = "wave",
401
+ selected_waves = selected_waves,
402
+ selected_countries = selected_countries
403
+ )
404
+
405
+ # Step 2: Skip if none missing
406
+ if (nrow(missing) == 0) return(NULL)
407
+
408
+ # Add country abbreviations
409
+ missing <- missing %>%
410
+ left_join(dstrata %>% distinct(pais_nam, pais_lab), by = "pais_nam")
411
+
412
+ # Format message YEAR: COUNTRIES
413
+ warning_text <- missing %>%
414
+ group_by(wave) %>%
415
+ summarise(
416
+ country_list = paste(sort(unique(pais_lab)), collapse = ", "),
417
+ .groups = "drop"
418
+ ) %>%
419
+ mutate(combo_label = paste0("<b>", wave, "</b>: ", country_list)) %>%
420
+ pull(combo_label) %>%
421
+ paste(collapse = "<br>")
422
+
423
+ # Display warning card
424
+ tags$div(
425
+ style = "
426
+ border: 2px solid #ffc107;
427
+ border-radius: 8px;
428
+ padding: 15px;
429
+ background-color: #fff8e1;
430
+ margin-bottom: 20px;
431
+ max-height: 120px;
432
+ overflow-y: auto;
433
+ ",
434
+ HTML(paste0(
435
+ "<span style='font-size:16px; color: #856404;'>⚠️ <b>Warning:</b> The following country-years have no data for <b>",
436
+ outcome(), "</b>:<br>", warning_text
437
+ ))
438
+ )
439
+ })
440
+
441
+ # SOURCE INFO WITH PAIS and WAVE
442
+ # # -----------------------------------------------------------------------
443
  source_info_both <- reactive({
444
  # Get country abbreviations that match selected country names
445
  pais_abbr <- dstrata %>%
 
451
  pais_display <- paste(pais_abbr, collapse = ", ")
452
  wave_display <- paste(input$wave, collapse = ", ")
453
 
454
+ if (nchar(pais_display) > 15) {
455
+ paste0("Source: LAPOP Lab, AmericasBarometer Data Playground\n\nCountries selected: ", pais_display,
456
+ "\nSurvey rounds selected: ", wave_display)
457
+
458
+ } else {
459
+ paste0("Source: LAPOP Lab, AmericasBarometer Data Playground\n\nCountries selected: ", pais_display,
460
+ ". Survey rounds selected: ", wave_display)
461
+ }
462
  })
463
 
464
  source_info_pais <- reactive({
 
472
  pais_display <- paste(pais_abbr, collapse = ", ")
473
  wave_display <- paste(input$wave, collapse = ", ")
474
 
475
+ paste0("Source: LAPOP Lab, AmericasBarometer Data Playground\n\nCountries selected: ", pais_display)
476
  })
477
 
478
  source_info_wave <- reactive({
 
486
  pais_display <- paste(pais_abbr, collapse = ", ")
487
  wave_display <- paste(input$wave, collapse = ", ")
488
 
489
+ paste0("Source: LAPOP Lab, AmericasBarometer Data Playground\n\nSurvey rounds selected: ", wave_display)
490
  })
491
 
492
+ # Histogram
493
+ # # -----------------------------------------------------------------------
494
+
495
  # must break into data event, graph event, and renderPlot to get download buttons to work
496
+ histd <- reactive({
497
  hist_df = Error(
498
  dff() %>%
499
  group_by(across(outcome())) %>%
 
511
  })
512
 
513
 
514
+ histg <- reactive({
515
  histg <- lapop_hist(histd(),
516
  ymax = ifelse(any(histd()$prop > 90), 110, 100),
517
  source_info = source_info_both())
 
523
  })
524
 
525
 
526
+ # Time-series
527
+ # # -----------------------------------------------------------------------
528
+ tsd <- reactive({
529
  dta_ts = Error(
530
  dff() %>%
531
  drop_na(outcome()) %>%
 
549
  return(omit_na_edges(dta_ts))
550
  })
551
 
552
+ tsg <- reactive({
553
  tsg = lapop_ts(tsd(),
554
  ymax = ifelse(any(tsd()$prop > 88, na.rm = TRUE), 110, 100),
555
  label_vjust = ifelse(any(tsd()$prop > 80, na.rm = TRUE), -1.1, -1.5),
 
563
  return(tsg())
564
  })
565
 
566
+ # Cross Country
567
+ # # -----------------------------------------------------------------------
568
+ ccd <- reactive({
569
  dta_cc = Error(
570
  dff() %>%
571
  drop_na(outcome()) %>%
 
587
  return(dta_cc)
588
  })
589
 
590
+ ccg <- reactive({
591
  ccg = lapop_cc(ccd(), sort = "hi-lo",
592
  subtitle = "% in selected category",
593
  ymax = ifelse(any(ccd()$prop > 90, na.rm = TRUE), 110, 100),
 
599
  return(ccg())
600
  })
601
 
602
+ # Breakdown
603
+ # # -----------------------------------------------------------------------
604
  # Use function for each demographic breakdown variable
605
+
606
+ secdf <- reactive({
607
  if (input$variable_sec == "None") {
608
  NULL
609
  } else if (variable_sec() == outcome()) {
610
+ showNotification("❌ Error: You cannot break down the outcome variable by itself.",
611
+ type = "error")
612
  NULL
613
  } else {
614
  process_data(
 
621
  }
622
  })
623
 
624
+ genderdf <- reactive({
625
  if ("gendermc" %in% input$demog) {
626
  process_data(
627
  data = dff(),
 
635
  }
636
  })
637
 
638
+ wealthdf <- reactive({
639
  if ("wealth" %in% input$demog) {
640
  process_data(
641
  data = dff(),
 
649
  }
650
  })
651
 
652
+ eddf <- reactive({
653
  if ("edre" %in% input$demog) {
654
  process_data(
655
  data = dff(),
 
663
  }
664
  })
665
 
666
+ edaddf <- reactive({
667
  if ("edad" %in% input$demog) {
668
  process_data(
669
  data = dff(),
 
677
  }
678
  })
679
 
680
+ urdf <- reactive({
681
  if ("ur" %in% input$demog) {
682
  process_data(
683
  data = dff(),
 
691
  }
692
  })
693
 
694
+ # Combine demographic data frames into one df
695
+ moverd <- reactive({
696
  dta_mover <- Error(rbind(secdf(), genderdf(), edaddf(), wealthdf(), eddf(), urdf()))
697
  validate(
698
  need(dta_mover, "Error: no data available. Please verify that this question was asked in this country/year combination")
 
701
  return(dta_mover)
702
  })
703
 
704
+ moverg <- reactive({
705
  moverg <- lapop_mover(moverd(),
706
  subtitle = "% in selected category",
707
  ymax = ifelse(any(moverd()$prop > 90, na.rm = TRUE), 119,
708
  ifelse(any(moverd()$prop > 80, na.rm = TRUE), 109, 100)),
709
+ lang = "en", source_info = source_info_both())
710
  return(moverg)
711
  })
712
 
 
714
  return(moverg())
715
  })
716
 
717
+ # # -----------------------------------------------------------------------
718
  # DOWNLOAD SECTION
719
+ # # -----------------------------------------------------------------------
720
  output$downloadPlot <- downloadHandler(
721
  filename = function(file) {
722
  ifelse(input$tabs == "Histogram", paste0("hist_", outcome(),".svg"),
723
  ifelse(input$tabs == "Time Series", paste0("ts_", outcome(),".svg"),
724
+ ifelse(input$tabs == "Cross Country", paste0("cc_", outcome(),".svg"),
725
+ paste0("mover_", outcome(),".svg"))))
726
  },
727
 
728
  content = function(file) {
729
  if(input$tabs == "Histogram") {
730
  title_text <- isolate(cap())
731
+ subtitle_text <- slider_values()
732
 
733
  hist_to_save <- lapop_hist(histd(),
734
  main_title = title_text,
735
  subtitle = "% in selected category ",
736
  ymax = ifelse(any(histd()$prop > 90), 110, 100),
737
+ source_info = paste0(source_info_both(), "\n\n",
738
+ str_wrap(paste0(word(), " ", resp()), 125))
739
+ )
740
 
741
  lapop_save(hist_to_save, file)
742
  showNotification(HTML("Plot download complete βœ“ "), type = "message")
 
744
  } else if (input$tabs == "Time Series") {
745
  title_text <- isolate(cap())
746
  subtitle_text <- slider_values()
747
+
748
  ts_to_save <- lapop_ts(tsd(),
749
  main_title = title_text,
750
  subtitle = paste0("% in selected category ", subtitle_text),
751
  ymax = ifelse(any(tsd()$prop > 88, na.rm = TRUE), 110, 100),
752
  label_vjust = ifelse(any(tsd()$prop > 80, na.rm = TRUE), -1.1, -1.5),
753
+ source_info = paste0(source_info_pais(), "\n\n",
754
+ str_wrap(paste0(word(), " ", resp()), 125))
755
+ )
756
 
757
  lapop_save(ts_to_save, file)
758
  showNotification(HTML("Plot download complete βœ“ "), type = "message")
 
765
  main_title = title_text,
766
  subtitle = paste0("% in selected category ", subtitle_text),
767
  ymax = ifelse(any(ccd()$prop > 90, na.rm = TRUE), 110, 100),
768
+ source_info = paste0(source_info_wave(), "\n\n",
769
+ str_wrap(paste0(word(), " ", resp()), 125))
770
+ )
771
 
772
  lapop_save(cc_to_save, file)
773
  showNotification(HTML("Plot download complete βœ“ "), type = "message")
 
782
  subtitle = paste0("% in selected category ", subtitle_text),
783
  ymax = ifelse(any(moverd()$prop > 90, na.rm = TRUE), 119,
784
  ifelse(any(moverd()$prop > 80, na.rm = TRUE), 109, 100)),
785
+ source_info = paste0(source_info_both(), "\n\n",
786
+ str_wrap(paste0(word(), " ", resp()), 125))
787
  )
788
 
789
  lapop_save(mover_to_save, file)
 
793
  }
794
  )
795
 
796
+ # # -----------------------------------------------------------------------
797
+ # DOWNLOAD TABLE
798
+ # # -----------------------------------------------------------------------
799
  output$downloadTable <- downloadHandler(
800
  filename = function(file) {
801
+ ifelse(input$tabs == "Histogram", paste0("hist_", outcome(),".csv"),
802
+ ifelse(input$tabs == "Time Series", paste0("ts_", outcome(),".csv"),
803
+ ifelse(input$tabs == "Cross Country", paste0("cc_", outcome(),".csv"),
804
+ paste0("mover_", outcome(),".csv"))))
805
  },
806
  content = function(file) {
807
  if(input$tabs == "Histogram") {
808
+ write.csv(histd(), file, row.names=F)
809
  showNotification(HTML("File download complete βœ“ "), type = "message")
810
 
811
  } else if (input$tabs == "Time Series") {
812
+ write.csv(tsd(), file, row.names=F)
813
  showNotification(HTML("File download complete βœ“ "), type = "message")
814
 
815
  } else if (input$tabs == "Cross Country") {
816
+ write.csv(ccd(), file, row.names=F)
817
  showNotification(HTML("File download complete βœ“ "), type = "message")
818
 
819
  } else {
820
+ write.csv(moverd(), file, row.names=F)
821
  showNotification(HTML("File download complete βœ“ "), type = "message")
 
822
  }
823
  }
824
  )
825
  }
826
 
827
+ # RUN APP
828
+ # # -----------------------------------------------------------------------
829
  shinyApp(ui, server)
830
+
831
+ # # -----------------------------------------------------------------------
832
+ # END
833
+ # # -----------------------------------------------------------------------
gm_shiny_data_en.rds CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:8cfdfcac76039d0b5e32bcd5c7ce18b97d9bcae5de70ce0cd6f3722a30b177d7
3
- size 12414411
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4bd3953ee9276836512d148c5a2f9b4dbe9948aecaf6144160ede9ec508f9661
3
+ size 12414413
variable_labels_shiny.csv CHANGED
The diff for this file is too large to render. See raw diff