robertvidigal commited on
Commit
d86b4f7
·
verified ·
1 Parent(s): 8ba63ef

Upload 11 files

Browse files
Files changed (12) hide show
  1. .RData +3 -0
  2. .Rhistory +512 -0
  3. .gitattributes +4 -0
  4. .gitignore +8 -1
  5. app.R +1566 -0
  6. cses-shiny.Rproj +17 -0
  7. cses_labs.rds +0 -0
  8. cses_labs_sec.rds +0 -0
  9. cses_shiny_data.rda +3 -0
  10. cses_shiny_data.rds +3 -0
  11. cses_variable_labels.csv +52 -0
  12. world.rda +3 -0
.RData ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1f6725ad5536f6ac30971212826b4956210c8ed27a6f18e7a3a37bcb293557ca
3
+ size 34939611
.Rhistory ADDED
@@ -0,0 +1,512 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # REMOVING SOME SPECIFIC CATEGORIES FOR SIMPLICITY OF ATA PRESENTATION
2
+ # # -----------------------------------------------------------------------
3
+ #cses_imd$IMD3010[cses_imd$IMD3010==6]<-3;
4
+ #cses_imd$IMD2004[cses_imd$IMD2004==5]<-NA
5
+ #cses_imd$IMD5007[cses_imd$IMD5007==5]<-0; # 4-point
6
+ #cses_imd$IMD5052_2<-round(cses_imd$IMD5052_2, 1)
7
+ # # -----------------------------------------------------------------------
8
+ # WEIGHTS
9
+ # # -----------------------------------------------------------------------
10
+ cses_imd$no_weight <- 1 # UNWEIGHTED (RAW DATA)
11
+ table(cses_imd$no_weight)
12
+ table(cses_imd$IMD1010_1) # SAMPLE (SELECTION BIAS)
13
+ cses_imd$weight_sample <- cses_imd$IMD1010_1
14
+ table(cses_imd$IMD1010_2) # DEMOGRAPHIC (NON-RESPONSE BIAS)
15
+ cses_imd$weight_demographic <- cses_imd$IMD1010_2 # PREFERRED WEIGHT (DEFAULT)
16
+ #table(cses_imd$IMD1010_3) # POLITICAL (VOTING)- NOT USING IT
17
+ #cses_imd$weight_political <- cses_imd$IMD1010_3
18
+ # # -----------------------------------------------------------------------
19
+ # SELECTING VARIABLES FOR DATA PLAYGROUND
20
+ # # -----------------------------------------------------------------------
21
+ vars <- c(
22
+ "gendermc",
23
+ "wealthf",
24
+ "edrerf",
25
+ "age",
26
+ "ur",
27
+ "labor_force",
28
+ "compulsory_vote",
29
+ "pais",
30
+ "wave",
31
+ "pais_num",
32
+ "pais_lab",
33
+ "no_weight",
34
+ "weight_sample",
35
+ "weight_demographic",
36
+ "IMD1008_MOD"
37
+ )
38
+ # --- Variable Labels
39
+ vars_labels <- read.csv("./Data preprocessing/cses_variable_labels_raw.csv",
40
+ encoding = "latin1")
41
+ # Variable Display for Dropdown Menu (category + question + name)
42
+ vars_labels$display_en <- paste0(vars_labels$category_short_en, ": ",
43
+ vars_labels$question_short_en,
44
+ " (", vars_labels$column_name, ")", sep = "")
45
+ vars2 <- vars_labels$column_name
46
+ vars3 <- c(vars2, vars)
47
+ # Check if all variables are present and subset dataset for shinyapp
48
+ vars3 %in% names(cses_imd)
49
+ cses_out <- cses_imd[vars3]
50
+ # CHECKING INDIVIDUAL VARIABLES
51
+ #table(cses_out$IMD2005_1) # remove 7 8 9; 6-point
52
+ #table(cses_out$IMD2005_2) # remove 7 8 9; 4-point
53
+ #table(cses_out$IMD2016) # remove 7:9 values; 5-point
54
+ #table(cses_out$IMD2019_1) # remove 7:9 values; 2-point
55
+ #table(cses_out$IMD3001_PR_1) # remove 9999993 9999995 9999996 9999997 9999998 9999999; 2-point
56
+ #table(cses_out$IMD3001_PR_2) # remove 9999993 9999995 9999996 9999997 9999998 9999999; 2-point
57
+ #table(cses_out$IMD3001_LH) # remove 9999993 9999995 9999996 9999997 9999998 9999999; 2-point
58
+ #table(cses_out$IMD3001_UH) # remove 9999993 9999995 9999996 9999997 9999998 9999999; 2-point
59
+ #table(cses_out$IMD3001_TS) # remove 9; 6-point
60
+ #table(cses_out$IMD3002_OUTGOV) # remove 9999996 9999997 9999998 9999999; 2-point
61
+ #table(cses_out$IMD3002_VS_1) # remove 9; 2-point
62
+ #table(cses_out$IMD3002_LR_CSES) # remove 9; 3-point
63
+ #table(cses_out$IMD3005_1) # remove 7 8 9; 2-point
64
+ #table(cses_out$IMD3010) # remove 7 8 9, 6 should be recoded to 3; 5-point
65
+ #table(cses_out$IMD3011) # remove 7 8 9; 5-point
66
+ #table(cses_out$IMD3012) # remove 7 8 9; 5-point
67
+ #table(cses_out$IMD3013_1) # remove 7 8 9; 3-point with 1,3,5
68
+ #table(cses_out$IMD3013_2) # remove 7 8 9; 2-point with 1,2
69
+ #table(cses_out$IMD3013_3) # remove 7 8 9; 2-point with 4 and 5
70
+ #table(cses_out$IMD3014) # remove 6 7 8 9; 4-point
71
+ #table(cses_out$IMD5006_1) # remove 999; TOO MANY
72
+ #table(cses_out$IMD5006_2) # remove 999; TOO MANY
73
+ #table(cses_out$IMD5013) # ok; 3-point
74
+ #table(cses_out$IMD5014) # remove 7; 5-point
75
+ #table(cses_out$IMD5032_4) # remove 9; 3-point
76
+ #table(cses_out$IMD5033) # remove 9; 3-point
77
+ #table(cses_out$IMD5034_2) # remove 6 9; 2-point
78
+ #table(cses_out$IMD5035) # remove 999; TOO MANY
79
+ #table(cses_out$IMD5048) # ok; 3-point
80
+ #table(cses_out$IMD5049) # remove 999; TOO MANY
81
+ #table(cses_out$IMD5050_1) # ok; 7-point
82
+ #table(cses_out$IMD5051_1) # remove 99 and -88; 10-point
83
+ #table(cses_out$IMD5052_2) # remove 99; TOO MANY
84
+ #table(cses_out$IMD5053_1) # remove 999999; TOO MANY
85
+ #table(cses_out$IMD5054_2) # remove 999; TOO MANY
86
+ #table(cses_out$IMD5055_1) # remove 999; TOO MANY
87
+ #table(cses_out$IMD5056_2) # remove 99999; TOO MANY
88
+ #table(cses_out$IMD5057_1) # remove 9999999999; TOO MANY
89
+ #table(cses_out$IMD5058_1) # remove 997 999; TOO MANY
90
+ #table(cses_imd$IMD3001_TS) # REMOVE 9
91
+ #table(cses_imd$IMD5054_2) # REMOVE 999
92
+ #table(cses_imd$IMD5057_1) # REMOVE 9999999999
93
+ #table(cses_imd$IMD5035) # REMOVE 999
94
+ #table(cses_imd$IMD5056_2) # REMOVE 99999
95
+ #table(cses_imd$IMD5055_1) # remove 999
96
+ #table(cses_imd$IMD5053_1) # remove 999999
97
+ #table(cses_imd$IMD5052_2) # remove 99
98
+ #table(cses_imd$IMD5006_2) # REMOVE 999
99
+ #table(cses_imd$IMD5006_1) # REMOVE 999
100
+ #table(cses_imd$IMD5058_1) # REMOVE 997 999
101
+ #table(cses_imd$IMD5049) # REMOVE 999
102
+ # # -----------------------------------------------------------------------
103
+ # REMOVING NAs/NRs/DKs
104
+ # # -----------------------------------------------------------------------
105
+ # 95. VOLUNTEERED: HAVEN'T HEARD OF LEFT-RIGHT
106
+ # 97. VOLUNTEERED: REFUSED
107
+ # 98. VOLUNTEERED: DON'T KNOW WHERE TO PLACE
108
+ # 99. MISSING
109
+ cses_out <- cses_out %>%
110
+ mutate(across(c(IMD2014, IMD3006,
111
+ IMD5051_1, IMD5052_2,
112
+ IMD2014, IMD3006), ~
113
+ replace(.x, .x %in% c(-88, -77, -66, 95:99), NA)))
114
+ # FIX NEGATIVE VALUES
115
+ #cses_out$IMD5051_1<-(cses_out$IMD5051_1+10)/2
116
+ cses_out <- cses_out %>%
117
+ mutate(across(c(IMD3001, IMD3001_PR_1, IMD3001_PR_2,
118
+ IMD3001_LH, IMD3001_UH, IMD2001_2,
119
+ IMD3002_OUTGOV, IMD3002_LR_CSES), ~
120
+ replace(.x, .x %in% c(9999993:9999999, 9997:9999), NA)))
121
+ cses_out <- cses_out %>%
122
+ mutate(across(c(IMD5006_1, IMD5006_2, IMD5035,
123
+ IMD5049, IMD5053_1, IMD5054_2,
124
+ IMD5045_1, IMD5055_1, IMD5056_2,
125
+ IMD5057_1, IMD5058_1,
126
+ IMD5054_2), ~
127
+ replace(.x, .x %in% c(9999999999, 999999, 99999, 997, 998, 999), NA)))
128
+ cses_out <- cses_out %>%
129
+ mutate(across(c(IMD3001_TS, IMD3002_VS_1, IMD5032_4,
130
+ IMD5033, IMD3002_LR_CSES), ~ replace(.x, .x %in% c(9), NA)))
131
+ cses_out <- cses_out %>%
132
+ mutate(across(c(IMD2005_1, IMD2005_2, IMD2016,
133
+ IMD2019_1, IMD3005_1, IMD3011,
134
+ IMD3012, IMD3013_1, IMD3013_2,
135
+ IMD3010, IMD3013_3, IMD2002, IMD5036_3,
136
+ IMD2004, IMD2007), ~ replace(.x, .x %in% c(7:9), NA)))
137
+ cses_out <- cses_out %>%
138
+ mutate(across(c(IMD3014, IMD5014, IMD5034_2, IMD2003, IMD2006), ~
139
+ replace(.x, .x %in% c(6:9), NA)))
140
+ # # -----------------------------------------------------------------------
141
+ # CONTINUOUS VARIABLES RECODE INTO QUINTILES (~20% per category)
142
+ # # -----------------------------------------------------------------------
143
+ # Quintiles (5 groups) — overwrite the column with a factor that shows pretty labels.
144
+ # The factor's internal codes are 1..5 in order,
145
+ # so as.integer(df[[col]]) returns 1..5.
146
+ quantile_cut_quintiles <- function(
147
+ data, value_col, digits = 0, big_mark = ",",
148
+ include_lowest = TRUE, type = 7) # linear interpolation of the empirical CDF
149
+ {
150
+ stopifnot(is.data.frame(data), value_col %in% names(data))
151
+ x <- data[[value_col]]
152
+ # Breaks at "0,20,40,60,80,100"
153
+ br <- as.numeric(quantile(x, probs = seq(0, 1, 0.2), na.rm = TRUE, type = type))
154
+ br <- br[is.finite(br)]
155
+ br <- unique(br)
156
+ # Edge cases: all-NA or constant
157
+ if (length(br) < 2) {
158
+ rng <- if (all(is.na(x))) c(NA, NA) else range(x, na.rm = TRUE)
159
+ lab <- if (all(is.na(x))) NA_character_ else paste0("[", rng[1], ", ", rng[2], "]")
160
+ f <- factor(ifelse(is.na(x), NA_character_, lab), levels = lab) # single level => code 1
161
+ data[[value_col]] <- f
162
+ attr(data[[value_col]], "breaks") <- br
163
+ return(data)
164
+ }
165
+ # Ensure strictly increasing breaks (avoid duplicate labels)
166
+ for (i in 2:length(br)) {
167
+ if (br[i] <= br[i - 1]) {
168
+ bump <- 1e-8 * max(1, abs(br[i - 1]))
169
+ br[i] <- br[i - 1] + bump
170
+ }
171
+ }
172
+ # Formatting
173
+ fmt <- function(v) formatC(v, format = "f", digits = digits, big.mark = big_mark)
174
+ # Labels: first/last open-ended text
175
+ labs <- c(
176
+ paste0("Less than ", fmt(br[2])),
177
+ paste0(fmt(br[2]), " to ", fmt(br[3])),
178
+ paste0(fmt(br[3]), " to ", fmt(br[4])),
179
+ paste0(fmt(br[4]), " to ", fmt(br[5])),
180
+ paste0("More than ", fmt(br[5]))
181
+ )
182
+ # Get quintile codes 1..5, then map to labels
183
+ codes <- as.integer(findInterval(x, br, rightmost.closed = TRUE, all.inside = TRUE))
184
+ codes[is.na(x)] <- NA_integer_
185
+ # Create factor with levels in the *desired* order so codes 1..5 align
186
+ f <- factor(ifelse(is.na(codes), NA_character_, labs[codes]), levels = labs)
187
+ data[[value_col]] <- f
188
+ attr(data[[value_col]], "breaks") <- br
189
+ data
190
+ }
191
+ # Check the new factor labels (built from raw cutpoints)
192
+ cses_out <- quantile_cut_quintiles(cses_out, "IMD5054_2")
193
+ table(cses_out$IMD5054_2); levels(cses_out$IMD5054_2)
194
+ cses_out <- quantile_cut_quintiles(cses_out, "IMD5057_1")
195
+ table(cses_out$IMD5057_1); levels(cses_out$IMD5057_1)
196
+ cses_out <- quantile_cut_quintiles(cses_out, "IMD5035")
197
+ table(cses_out$IMD5035); levels(cses_out$IMD5035)
198
+ cses_out <- quantile_cut_quintiles(cses_out, "IMD5056_2")
199
+ table(cses_out$IMD5056_2); levels(cses_out$IMD5056_2)
200
+ cses_out <- quantile_cut_quintiles(cses_out, "IMD5055_1", digits=2)
201
+ table(cses_out$IMD5055_1); levels(cses_out$IMD5055_1)
202
+ cses_out <- quantile_cut_quintiles(cses_out, "IMD5053_1")
203
+ table(cses_out$IMD5053_1); levels(cses_out$IMD5053_1)
204
+ cses_out <- quantile_cut_quintiles(cses_out, "IMD5052_2")
205
+ table(cses_out$IMD5052_2); levels(cses_out$IMD5052_2)
206
+ cses_out <- quantile_cut_quintiles(cses_out, "IMD5006_2")
207
+ table(cses_out$IMD5006_2); levels(cses_out$IMD5006_2)
208
+ cses_out <- quantile_cut_quintiles(cses_out, "IMD5006_1")
209
+ table(cses_out$IMD5006_1); levels(cses_out$IMD5006_1)
210
+ cses_out <- quantile_cut_quintiles(cses_out, "IMD5058_1")
211
+ table(cses_out$IMD5058_1); levels(cses_out$IMD5058_1)
212
+ cses_out <- quantile_cut_quintiles(cses_out, "IMD5049")
213
+ table(cses_out$IMD5049); levels(cses_out$IMD5049)
214
+ # Turn factor quintiles into labelled numerics 1..5 using the factor's own levels as value labels
215
+ factor_quintiles_to_labelled <- function(df, vars) {
216
+ stopifnot(is.data.frame(df))
217
+ for (v in vars) {
218
+ if (!v %in% names(df)) next
219
+ f <- df[[v]]
220
+ if (!is.factor(f)) { warning(sprintf("'%s' is not a factor; skipping.", v)); next }
221
+ lv <- levels(f)
222
+ if (length(lv) != 5) { warning(sprintf("'%s' has %d levels (expected 5); skipping.", v, length(lv))); next }
223
+ df[[v]] <- haven::labelled(as.integer(f), labels = stats::setNames(1:5, lv))
224
+ }
225
+ df
226
+ }
227
+ qvars <- c("IMD5054_2","IMD5057_1","IMD5035","IMD5056_2",
228
+ "IMD5055_1","IMD5053_1","IMD5052_2","IMD5006_2","IMD5006_1",
229
+ "IMD5058_1","IMD5049")
230
+ cses_out <- factor_quintiles_to_labelled(cses_out, qvars)
231
+ # REMOVING CONTINUOUS VARS FOR NOW...
232
+ #labs <- labs[!labs %in% c("IMD3001_TS", "IMD5054_2", "IMD5057_1", "IMD5035",
233
+ # "IMD5056_2", "IMD5055_1", "IMD5053_1", "IMD5052_2",
234
+ # "IMD5006_2", "IMD5006_1", "IMD5058_1", "IMD5049")]
235
+ # # -----------------------------------------------------------------------
236
+ # OUTCOME VARIABLES RECODE (NUMERIC TO LABELS)
237
+ # # -----------------------------------------------------------------------
238
+ # Function to convert labels to sentence case
239
+ to_sentence_case <- function(x) {
240
+ sapply(x, function(s) {
241
+ s <- tolower(s)
242
+ # Capitalize first letter after ) or .
243
+ s <- gsub("([).]\\s*)([a-z])", "\\1\\U\\2", s, perl = TRUE)
244
+ # Also capitalize the very first character if needed
245
+ sub("^(\\w)", "\\U\\1", s, perl = TRUE)
246
+ }, USE.NAMES = FALSE)
247
+ }
248
+ # Label output dataset to include the labels through haven package
249
+ # Safer: won't touch your quintile-binned columns
250
+ label_all_for_haven <- function(data, data_out, exclude_vars = NULL) {
251
+ label_table <- attr(data, "label.table", exact = TRUE)
252
+ data_labeled <- data_out
253
+ for (var in names(data_out)) {
254
+ # 1) explicit exclude list
255
+ if (!is.null(exclude_vars) && var %in% exclude_vars) next
256
+ # 2) skip if already a factor (e.g., your quintile pretty labels)
257
+ if (is.factor(data_out[[var]])) next
258
+ # 3) skip if marked as quintiled by attribute you set (optional)
259
+ if (!is.null(attr(data_out[[var]], "q5_breaks"))) next
260
+ if (!is.null(label_table[[var]])) {
261
+ values <- label_table[[var]]
262
+ # Clean labels: remove number prefixes, apply sentence case
263
+ clean_labels <- sub("^\\d+\\.\\s*", "", names(values))
264
+ clean_labels <- to_sentence_case(clean_labels)
265
+ # Build named vector: names = labels, values = numeric codes
266
+ labelled_vec <- setNames(as.numeric(values), clean_labels)
267
+ # Preserve original column's storage; attach labels if numeric/integer
268
+ var_data <- data_out[[var]]
269
+ if (is.numeric(var_data) || is.integer(var_data)) {
270
+ data_labeled[[var]] <- haven::labelled(var_data, labels = labelled_vec)
271
+ } else {
272
+ # Non-numeric targets get skipped to avoid clobbering (e.g., factors/quintiles)
273
+ # Alternatively, you could coerce if you really want:
274
+ # data_labeled[[var]] <- haven::labelled(as.numeric(var_data), labels = labelled_vec)
275
+ }
276
+ }
277
+ }
278
+ data_labeled
279
+ }
280
+ cses_out_labels <- label_all_for_haven(cses_imd, cses_out,
281
+ exclude_vars = c("IMD5054_2","IMD5057_1","IMD5035","IMD5056_2",
282
+ "IMD5055_1","IMD5053_1","IMD5052_2","IMD5006_2",
283
+ "IMD5006_1","IMD5058_1","IMD5049"))
284
+ # # -----------------------------------------------------------------------
285
+ # CUSTOM FIXES FOR NOW
286
+ # # -----------------------------------------------------------------------
287
+ cses_out_labels$IMD2004[cses_out_labels$IMD2004==5]<-NA
288
+ cses_out_labels$IMD3010 <- labelled(
289
+ x = replace(cses_out_labels$IMD3010, cses_out_labels$IMD3010 == 6, 3),
290
+ labels = c(
291
+ "Very satisfied" = 1,
292
+ "Satisfied" = 2,
293
+ "Neither" = 3,
294
+ "Not very satisfied" = 4,
295
+ "Not all satisfied" = 5
296
+ )
297
+ )
298
+ cses_out_labels$IMD5007 <- labelled(
299
+ x = replace(cses_out_labels$IMD5007, cses_out_labels$IMD5007 == 5, 0),
300
+ labels = c(
301
+ "No" = 0,
302
+ "Yes; no sanctions" = 3,
303
+ "Yes; weakly enforced" = 2,
304
+ "Yes; strictly enforced" = 1
305
+ )
306
+ )
307
+ cses_out_labels$IMD5052_2<-round(cses_out_labels$IMD5052_2, 1)
308
+ # # -----------------------------------------------------------------------
309
+ # Exporting DATA (.rds lighter file storage)
310
+ # # -----------------------------------------------------------------------
311
+ # MERGE PAIS_LAB TO CSES_OUT BEFORE EXPORT
312
+ pais_lab_merge<-subset(pais_lab, select=c("pais_lab", "pais_nam"))
313
+ cses_out_labels <- merge(cses_out_labels, pais_lab_merge, by = "pais_lab")
314
+ str(cses_out_labels)
315
+ # REMOVING CONTINUOUS VARS FOR NOW...
316
+ #cses_out_labels<-subset(cses_out_labels, select=-c(IMD3001_TS, IMD5054_2, IMD5057_1, IMD5035,
317
+ # IMD5056_2, IMD5055_1, IMD5053_1, IMD5052_2,
318
+ # IMD5006_2, IMD5006_1, IMD5058_1, IMD5049))
319
+ # EXPORT RDS
320
+ saveRDS(cses_out_labels, "./cses_shiny_data.rds")
321
+ # EXPORT RDA (MAX COMPRESSION)
322
+ cses_shiny_data<-cses_out_labels
323
+ save(cses_shiny_data, file="cses_shiny_data.rda")
324
+ tools::resaveRdaFiles("./cses_shiny_data.rda", compress = "xz")
325
+ tools::checkRdaFiles("./cses_shiny_data.rda")
326
+ # # -----------------------------------------------------------------------
327
+ # EXTRACTING RESPONSE OPTIONS FOR VARS_LABELS DATA
328
+ # # -----------------------------------------------------------------------
329
+ # Define the format_labels function to extract and format ROs
330
+ format_labels <- function(label_table) {
331
+ valid_labels <- label_table[label_table > -15 & label_table < 90] # Exclude special codes / DK / NA / NR / ETC
332
+ formatted <- sapply(seq_along(valid_labels), function(i) {
333
+ paste0("(", valid_labels[i], ") ", names(valid_labels)[i])
334
+ })
335
+ paste(formatted, collapse = " ")
336
+ }
337
+ # Extract Response Options from Dataset Attributes (label.table)
338
+ for (var in names(cses_imd)) {
339
+ if (var %in% vars_labels$column_name) {
340
+ label_table <- attr(cses_imd, "label.table", exact=T)[[var]]
341
+ if (!is.null(label_table)) {
342
+ vars_labels$responses_en[vars_labels$column_name == var] <- format_labels(label_table)
343
+ }
344
+ }
345
+ }
346
+ table(vars_labels$responses_en=="" | is.na(vars_labels$responses_en))
347
+ # # -----------------------------------------------------------------------
348
+ # FILLING "Question Wording" & "Response Options" for Variables.
349
+ # # -----------------------------------------------------------------------
350
+ qword_ro<-read.csv("./Data preprocessing/cses_qwording.csv", header=T)
351
+ vars_labels <- vars_labels %>%
352
+ left_join(qword_ro, by = "column_name", suffix = c(".old", "")) %>%
353
+ # Select columns from qword_ro and non-duplicated columns from vars_labels
354
+ select(-contains(".old"))
355
+ table(vars_labels$responses_en=="" | is.na(vars_labels$responses_en))
356
+ # REMOVING NAs/NRs/DKs from ROs and other CLEANING (mojibake/encoding errors)
357
+ #vars_labels$responses_en<-gsub("–", ":", vars_labels$responses_en)
358
+ #vars_labels$responses_en<-gsub("00.", "0.", vars_labels$responses_en)
359
+ #vars_labels$question_short_en<-gsub("–", ":", vars_labels$question_short_en)
360
+ #vars_labels$question_short_en<-gsub("–", "", vars_labels$question_short_en)
361
+ vars_labels$responses_en<-trimws(vars_labels$responses_en)
362
+ vars_labels$responses_en<-gsub(" \\(7\\) 7. VOLUNTEERED: REFUSED", "", vars_labels$responses_en)
363
+ vars_labels$responses_en<-gsub(" \\(8\\) 8. VOLUNTEERED: DON'T KNOW", "", vars_labels$responses_en)
364
+ vars_labels$responses_en<-gsub(" \\(9\\) 9. MISSING", "", vars_labels$responses_en)
365
+ vars_labels$responses_en<-gsub(" \\(6\\) 6. OTHER", "", vars_labels$responses_en)
366
+ vars_labels$responses_en<-gsub(" \\(6\\) 6. \\[SEE ELECTION STUDY NOTES\\]", "", vars_labels$responses_en)
367
+ vars_labels$responses_en<-gsub(" \\(7\\) 7. NOT APPLICABLE \\[NO ALLIANCES PERMITTED\\]", "", vars_labels$responses_en)
368
+ vars_labels$responses_en<-gsub(" \\(6\\) 6. NO INTERNATIONAL ELECTION OBSERVERS", "", vars_labels$responses_en)
369
+ vars_labels$responses_en<-gsub(" \\(7\\) 7. NOT APPLICABLE", "", vars_labels$responses_en)
370
+ vars_labels$responses_en<-gsub("\\(1\\) 1. VERY SATISFIED \\(2\\) 2. FAIRLY SATISFIED \\(4\\) 4. NOT VERY SATISFIED \\(5\\) 5. NOT AT ALL SATISFIED \\(6\\) 6. NEITHER SATISFIED NOR DISSATISFIED",
371
+ "\\(1\\) 1. VERY SATISFIED \\(2\\) 2. FAIRLY SATISFIED \\(3\\) 3. NEITHER SATISFIED NOR DISSATISFIED \\(4\\) 4. NOT VERY SATISFIED \\(5\\) 5. NOT AT ALL SATISFIED",
372
+ vars_labels$responses_en)
373
+ vars_labels$responses_en<-trimws(vars_labels$responses_en)
374
+ # Remove Extra and WhiteSpaces respectivelly
375
+ vars_labels$responses_en <- gsub("(\\s|^)-?\\d+\\.\\s", "\\1", vars_labels$responses_en)
376
+ vars_labels$responses_en <- gsub("([).])\\s+", "\\1 ", vars_labels$responses_en)
377
+ vars_labels$responses_en<-gsub("\\(5\\) NO", "\\(0\\) NO", vars_labels$responses_en) # COMPULSORY VOTE IMD5007
378
+ # Create responses_en_rec
379
+ vars_labels$responses_en_rec<-to_sentence_case(vars_labels$responses_en)
380
+ vars_labels$question_short_en<-to_sentence_case(vars_labels$question_short_en)
381
+ vars_labels$question_short_en <- gsub("([).-])\\s+", "\\1 ", vars_labels$question_short_en)
382
+ # # -----------------------------------------------------------------------
383
+ # EXPORT CSES LABELS
384
+ # # -----------------------------------------------------------------------
385
+ # REMOVING CONTINUOUS VARS FOR NOW...
386
+ #vars_labels <- vars_labels[!vars_labels$column_name %in%
387
+ # c("IMD3001_TS", "IMD5054_2", "IMD5057_1", "IMD5035",
388
+ # "IMD5056_2", "IMD5055_1", "IMD5053_1", "IMD5052_2",
389
+ # "IMD5006_2", "IMD5006_1", "IMD5058_1", "IMD5049"), ]
390
+ write.csv(vars_labels, "./cses_variable_labels.csv", row.names=F)
391
+ # # -----------------------------------------------------------------------
392
+ # LABS VECTOR
393
+ # # -----------------------------------------------------------------------
394
+ labs <- vars_labels$column_name
395
+ names(labs) <- vars_labels$display_en
396
+ labs[order(names(labs))]
397
+ names(vars_labels$column_name) <- vars_labels$display_en
398
+ vars_labels$labs2 <- labs
399
+ vars_labels$question_en_comp <- paste0(vars_labels$question_en,
400
+ vars_labels$responses_en_rec,
401
+ sep = " ")
402
+ # Dropping MACRO Variables from Outcomes but keeping at secondary vars
403
+ drop_macro <- grep("Macro Data:", names(labs), value = TRUE)
404
+ labs_sec <- labs
405
+ labs <- labs[!(names(labs) %in% drop_macro)]
406
+ # # -----------------------------------------------------------------------
407
+ # FINAL EXPORT OF LABELS
408
+ # # -----------------------------------------------------------------------
409
+ saveRDS(labs, "./cses_labs.rds")
410
+ saveRDS(labs_sec, "./cses_labs_sec.rds")
411
+ # END
412
+ # # -----------------------------------------------------------------------
413
+ message("Code ended succesfully")
414
+ # # -----------------------------------------------------------------------
415
+ ### CSES DATA PLAYGROUND
416
+ # Date: November 10th, 2025
417
+ # Author: Robert Vidigal, PhD
418
+ # Purpose: CSES Shiny Data Playground based on LAPOP Lab Data Playground
419
+ # Prev file: ./shiny_preprocessing.R
420
+ # Machine: Windows OS
421
+ # Status: On-going
422
+ # # -----------------------------------------------------------------------
423
+ ### Data In:
424
+ # 1. cses_shiny_data.rda
425
+ # 2. cses_variable_labels.csv
426
+ # 3. cses_labs.rds
427
+ # 4. and fonts from /wwww/
428
+ ### Data Out: N/A
429
+ # # -----------------------------------------------------------------------
430
+ options(shiny.useragg = TRUE) # speed it up
431
+ # # -----------------------------------------------------------------------
432
+ # Packages loading
433
+ # # -----------------------------------------------------------------------
434
+ library(lapop); library(bslib); library(htmltools); require(bsplus)
435
+ suppressPackageStartupMessages(library(dplyr))
436
+ library(tidyr); library(stringr); library(haven)
437
+ require(shiny); library(shinyWidgets); require(shinyjs)
438
+ suppressPackageStartupMessages(library(Hmisc, exclude = c("src", "summarize", "units", "format.pval")))
439
+ lapop_fonts() # LAPOP GRAPH STYLE
440
+ # IMD CSES Data (only preselected variables)
441
+ # # -----------------------------------------------------------------------
442
+ # RDA FILE BEST COMPRESSION FOR RSHINY
443
+ load(file="./cses_shiny_data.rda"); dstrata<-cses_shiny_data; rm(cses_shiny_data)
444
+ # Labels data (for DP display)
445
+ vars_labels <- read.csv("./cses_variable_labels.csv", encoding = "latin1")
446
+ # Labs vector (for DP display outcomes versus secondary vars that include macro vars)
447
+ labs <- readRDS("./cses_labs.rds")
448
+ labs_sec <- readRDS("./cses_labs_sec.rds")
449
+ # Dropping Demographics (OLD, ALLOW USERS TO USE BOTH RAW AND RECODE DEMOG VARS)
450
+ #drop_demoglabs <- c("IMD2001_2", "IMD2002", "IMD2003", "IMD2006", "IMD2007") # Demographics
451
+ #labs_sec <- labs[ !(unname(labs) %in% drop_demoglabs) ]
452
+ # Error handling function (so app does not break easily)
453
+ Error<-function(x){
454
+ tryCatch(x,error=function(e) return(FALSE))
455
+ }
456
+ # CSES election-year vector for TS
457
+ waves_total = c("1996", "1997", "1998", "1999", "2000", "2001", "2002",
458
+ "2003", "2004", "2005", "2006", "2007", "2008", "2009",
459
+ "2010", "2011", "2012", "2013", "2014", "2015", "2016",
460
+ "2017", "2018", "2019", "2020", "2021")
461
+ # Escape stuff that ggtext/markdown treats specially
462
+ sanitize_for_ggtext <- function(x) {
463
+ x <- as.character(x)
464
+ # HTML specials
465
+ x <- gsub("&", "&amp;", x, fixed = TRUE)
466
+ x <- gsub("<", "&lt;", x, fixed = TRUE)
467
+ x <- gsub(">", "&gt;", x, fixed = TRUE)
468
+ # Markdown link/emphasis/backtick
469
+ x <- gsub("\\[", "&#91;", x)
470
+ x <- gsub("\\]", "&#93;", x)
471
+ x <- gsub("\\(", "&#40;", x)
472
+ x <- gsub("\\)", "&#41;", x)
473
+ x <- gsub("\\*", "&#42;", x)
474
+ x <- gsub("_", "&#95;", x)
475
+ x <- gsub("`", "&#96;", x)
476
+ x
477
+ }
478
+ # # -----------------------------------------------------------------------
479
+ # Helper function for TS (handle missing values at end or middle of series)
480
+ # # -----------------------------------------------------------------------
481
+ omit_na_edges <- function(df) {
482
+ # Find which rows have NA values
483
+ na_rows <- apply(df, 1, function(row) any(is.na(row)))
484
+ # Find the first and last non-NA row
485
+ first_non_na <- which(!na_rows)[1]
486
+ last_non_na <- which(!na_rows)[length(which(!na_rows))]
487
+ # Subset df to only include rows between the first and last non-NA rows
488
+ df_clean <- df[first_non_na:last_non_na, ]
489
+ return(df_clean)
490
+ }
491
+ ### MOVE IT TO LAPOP_TS???
492
+ View(dstrata)
493
+ View(vars_labels)
494
+ View(pais_lab_merge)
495
+ View(qword_ro)
496
+ grep("Not at all", qword_ro$responses_en)
497
+ grep("Not at all", qword_ro$responses_en, value = T)
498
+ grep("all", qword_ro$responses_en, value = T)
499
+ grep("not", qword_ro$responses_en, value = T)
500
+ grep("none", qword_ro$responses_en, value = T)
501
+ grep("never", qword_ro$responses_en, value = T)
502
+ grep("never", qword_ro$responses_en, value = T)
503
+ grepl("never", qword_ro$responses_en, value = T)
504
+ grep("(1)", qword_ro$responses_en, value = T)
505
+ grep("no", qword_ro$responses_en, value = T)
506
+ grep("NOT AT ALL", qword_ro$responses_en, value = T)
507
+ grep("NOT AT ALL", qword_ro$responses_en)
508
+ devtools::install_github("lapop-central/lapop",
509
+ force = TRUE,
510
+ build_vignettes = TRUE)
511
+ shiny::runApp()
512
+ runApp()
.gitattributes CHANGED
@@ -45,3 +45,7 @@ cses-shiny/www/Roboto-Bold.ttf filter=lfs diff=lfs merge=lfs -text
45
  cses-shiny/www/Roboto-Light.ttf filter=lfs diff=lfs merge=lfs -text
46
  cses-shiny/www/Roboto-Medium.ttf filter=lfs diff=lfs merge=lfs -text
47
  cses-shiny/www/Roboto-Regular.ttf filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
45
  cses-shiny/www/Roboto-Light.ttf filter=lfs diff=lfs merge=lfs -text
46
  cses-shiny/www/Roboto-Medium.ttf filter=lfs diff=lfs merge=lfs -text
47
  cses-shiny/www/Roboto-Regular.ttf filter=lfs diff=lfs merge=lfs -text
48
+ .RData filter=lfs diff=lfs merge=lfs -text
49
+ cses_shiny_data.rda filter=lfs diff=lfs merge=lfs -text
50
+ cses_shiny_data.rds filter=lfs diff=lfs merge=lfs -text
51
+ world.rda filter=lfs diff=lfs merge=lfs -text
.gitignore CHANGED
@@ -1 +1,8 @@
1
- .DS_Store
 
 
 
 
 
 
 
 
1
+ .Rproj.user
2
+ .Rhistory
3
+ .RData
4
+ .Ruserdata
5
+ *.dcf
6
+ rsconnect/shinyapps.io/centerforglobaldemocracy/cses-shiny.dcf
7
+ app_old_warning.R
8
+ app_w_stack.R
app.R ADDED
@@ -0,0 +1,1566 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### CSES DATA PLAYGROUND
2
+ # Date: November 10th, 2025
3
+ # Author: Robert Vidigal, PhD
4
+ # Purpose: CSES Shiny Data Playground based on LAPOP Lab Data Playground
5
+ # Prev file: ./shiny_preprocessing.R
6
+ # Machine: Windows OS
7
+ # Status: On-going
8
+ # # -----------------------------------------------------------------------
9
+ ### Data In:
10
+ # 1. cses_shiny_data.rda
11
+ # 2. cses_variable_labels.csv
12
+ # 3. cses_labs.rds
13
+ # 4. and fonts from /wwww/
14
+ ### Data Out: N/A
15
+ # # -----------------------------------------------------------------------
16
+ options(shiny.useragg = TRUE) # speed it up
17
+
18
+ # # -----------------------------------------------------------------------
19
+ # Packages loading
20
+ # # -----------------------------------------------------------------------
21
+ library(lapop); library(bslib); library(htmltools); require(bsplus)
22
+ suppressPackageStartupMessages(library(dplyr))
23
+ library(tidyr); library(stringr); library(haven)
24
+ require(shiny); library(shinyWidgets); require(shinyjs); require(ggtext)
25
+ suppressPackageStartupMessages(library(Hmisc, exclude = c("src", "summarize", "units", "format.pval")))
26
+
27
+ lapop_fonts() # LAPOP GRAPH STYLE
28
+
29
+ # IMD CSES Data (only preselected variables)
30
+ # # -----------------------------------------------------------------------
31
+ # RDA FILE BEST COMPRESSION FOR RSHINY
32
+ load(file="./cses_shiny_data.rda");
33
+
34
+ # Labels data (for DP display)
35
+ vars_labels <- read.csv("./cses_variable_labels.csv", encoding = "latin1")
36
+
37
+ # Labs vector (for DP display outcomes versus secondary vars that include macro vars)
38
+ labs <- readRDS("./cses_labs.rds")
39
+ labs_sec <- readRDS("./cses_labs_sec.rds")
40
+ load(file="./world.rda")
41
+
42
+ # Dropping Demographics (OLD, ALLOW USERS TO USE BOTH RAW AND RECODE DEMOG VARS)
43
+ #drop_demoglabs <- c("IMD2001_2", "IMD2002", "IMD2003", "IMD2006", "IMD2007") # Demographics
44
+ #labs_sec <- labs[ !(unname(labs) %in% drop_demoglabs) ]
45
+
46
+ # # -----------------------------------------------------------------------
47
+ # Error handling function (so app does not break easily)
48
+ # # -----------------------------------------------------------------------
49
+
50
+ Error<-function(x){
51
+ tryCatch(x,error=function(e) return(FALSE))
52
+ }
53
+
54
+ # CSES election-year vector for TS
55
+ waves_total = c("1996", "1997", "1998", "1999", "2000", "2001", "2002",
56
+ "2003", "2004", "2005", "2006", "2007", "2008", "2009",
57
+ "2010", "2011", "2012", "2013", "2014", "2015", "2016",
58
+ "2017", "2018", "2019", "2020", "2021")
59
+
60
+ # Escape stuff that ggtext/markdown treats specially
61
+ sanitize_for_ggtext <- function(x) {
62
+ x <- as.character(x)
63
+ # HTML specials
64
+ x <- gsub("&", "&amp;", x, fixed = TRUE)
65
+ x <- gsub("<", "&lt;", x, fixed = TRUE)
66
+ x <- gsub(">", "&gt;", x, fixed = TRUE)
67
+ # Markdown link/emphasis/backtick
68
+ x <- gsub("\\[", "&#91;", x)
69
+ x <- gsub("\\]", "&#93;", x)
70
+ x <- gsub("\\(", "&#40;", x)
71
+ x <- gsub("\\)", "&#41;", x)
72
+ x <- gsub("\\*", "&#42;", x)
73
+ x <- gsub("_", "&#95;", x)
74
+ x <- gsub("`", "&#96;", x)
75
+ x
76
+ }
77
+
78
+ # # -----------------------------------------------------------------------
79
+ # Helper function for TS
80
+ # # -----------------------------------------------------------------------
81
+ # (handle missing values at end or middle of series)
82
+
83
+ omit_na_edges <- function(df) {
84
+ # Find which rows have NA values
85
+ na_rows <- apply(df, 1, function(row) any(is.na(row)))
86
+
87
+ # Find the first and last non-NA row
88
+ first_non_na <- which(!na_rows)[1]
89
+ last_non_na <- which(!na_rows)[length(which(!na_rows))]
90
+
91
+ # Subset df to only include rows between the first and last non-NA rows
92
+ df_clean <- df[first_non_na:last_non_na, ]
93
+
94
+ return(df_clean)
95
+ }
96
+
97
+ # # -----------------------------------------------------------------------
98
+ # Custom weighted averages & CIs, much faster than survey_mean() etc
99
+ # # -----------------------------------------------------------------------
100
+ weighted.ttest.ci <- function(x, weights) {
101
+ nx <- length(x)
102
+ vx <- Hmisc::wtd.var(x, weights, normwt = TRUE, na.rm = TRUE) # Weighted variance
103
+ mx <- weighted.mean(x, weights, na.rm = TRUE) # Weighted mean
104
+ stderr <- sqrt(vx/nx)
105
+ tstat <- mx/stderr ## not mx - mu
106
+ cint <- qt(1 - 0.05/2, nx - 1)
107
+ cint <- tstat + c(-cint, cint)
108
+ confint = cint * stderr
109
+ result = data.frame(prop = mx, lb = confint[1], ub = confint[2])
110
+ return(result)
111
+ }
112
+
113
+ # # -----------------------------------------------------------------------
114
+ # Helper for missing country-year by outcome_var
115
+ # # -----------------------------------------------------------------------
116
+ get_missing_combinations <- function(data, outcome_var, wave_var,
117
+ selected_waves, selected_countries) {
118
+ # Convert wave values to string using haven labels
119
+ data <- data %>%
120
+ mutate(wave_str = as.character(haven::as_factor(.data[[wave_var]])))
121
+
122
+ # Build the full country-wave grid
123
+ all_combos <- expand.grid(
124
+ pais_nam = selected_countries,
125
+ wave = selected_waves,
126
+ stringsAsFactors = FALSE
127
+ )
128
+
129
+ # Subset only relevant countries
130
+ data <- data %>%
131
+ filter(pais_nam %in% selected_countries)
132
+
133
+ # Summarize: how many valid (non-NA and not 0) values exist per combo
134
+ summary <- data %>%
135
+ group_by(pais_nam, wave = wave_str) %>%
136
+ summarise(
137
+ n_valid = sum(!is.na(.data[[outcome_var]]) & .data[[outcome_var]] != 0),
138
+ .groups = "drop"
139
+ )
140
+
141
+ # Merge and detect missing
142
+ missing <- all_combos %>%
143
+ left_join(summary, by = c("pais_nam", "wave")) %>%
144
+ filter(is.na(n_valid) | n_valid == 0) %>%
145
+ select(pais_nam, wave)
146
+
147
+ return(missing)
148
+ }
149
+
150
+ # # -----------------------------------------------------------------------
151
+ # Helper function for mover plot (weighting and handling NAs)
152
+ # # -----------------------------------------------------------------------
153
+ process_data <- function(data, outcome_var, recode_range,
154
+ group_var, var_label, weight_var) {
155
+
156
+ if (is.null(group_var)) {
157
+ return(NULL)
158
+ }
159
+
160
+ processed_data <- data %>%
161
+ drop_na(!!sym(outcome_var)) %>%
162
+ mutate(outcome_rec = case_when(
163
+ is.na(!!sym(outcome_var)) ~ NA_real_,
164
+ !!sym(outcome_var) >= recode_range[1] & !!sym(outcome_var) <= recode_range[2] ~ 100,
165
+ TRUE ~ 0
166
+ )) %>%
167
+ group_by(vallabel = haven::as_factor(haven::zap_missing(!!sym(group_var)))) %>%
168
+ summarise_at(vars("outcome_rec"), list(~weighted.ttest.ci(., !!sym(weight_var)))) %>%
169
+ unnest_wider(col = "outcome_rec") %>%
170
+ mutate(
171
+ varlabel = var_label,
172
+ proplabel = paste0(round(prop), "%")
173
+ ) %>%
174
+ drop_na(.)
175
+
176
+ return(processed_data)
177
+ }
178
+
179
+ # # -----------------------------------------------------------------------
180
+ # BOOTSTRAP THEME
181
+ # # -----------------------------------------------------------------------
182
+ cses_theme <- bs_theme(
183
+ version = 5,
184
+ bootswatch = "cosmo",
185
+ bg = "#ffffff",
186
+ fg = "#212529",
187
+ primary = "#C4722A",
188
+ secondary = "#C4722A",
189
+ success = "#28a745",
190
+ info = "#0066cc",
191
+ warning = "#dc3545",
192
+ danger = "#dc3545",
193
+ #base_font = font_google("Open Sans"),
194
+ #heading_font = font_google("Roboto Slab"),
195
+ #code_font = font_google("Fira Mono"),
196
+ #font_scale = 1
197
+ )
198
+
199
+ # # -----------------------------------------------------------------------
200
+ # HOVER POP-UP FOR LEFTSIDE MENU
201
+ # # -----------------------------------------------------------------------
202
+ info_badge <- function(text, title, content) {
203
+ bsplus::bs_embed_popover(
204
+ tags$span(text, tags$span(icon("info-circle"), class = "me-1", style = "color:#C4722A;")),
205
+ title = title,
206
+ content = content,
207
+ placement = "right",
208
+ trigger = "click",
209
+ container = "body"
210
+ )
211
+ }
212
+
213
+ # # -----------------------------------------------------------------------
214
+ # N-SIZE FUNCTION TO PULL COUNTRY-YEAR COMBOS (CHATGPT)
215
+ # # -----------------------------------------------------------------------
216
+ get_sample_counts <- function(
217
+ data, outcome_var,
218
+ wave_var = "wave", country_var = "pais_nam",
219
+ selected_waves = NULL, selected_countries = NULL,
220
+ complete_grid = FALSE
221
+ ) {
222
+ df <- data
223
+ if (!is.null(selected_waves)) df <- dplyr::filter(df, .data[[wave_var]] %in% selected_waves)
224
+ if (!is.null(selected_countries)) df <- dplyr::filter(df, .data[[country_var]] %in% selected_countries)
225
+ df <- dplyr::filter(df, !is.na(.data[[outcome_var]]))
226
+
227
+ per_wave <- df |>
228
+ dplyr::count(wave = .data[[wave_var]], name = "n") |>
229
+ dplyr::arrange(wave)
230
+
231
+ per_country <- df |>
232
+ dplyr::count(pais = .data[[country_var]], name = "n") |>
233
+ dplyr::arrange(pais)
234
+
235
+ per_country_wave <- df |>
236
+ dplyr::count(pais = .data[[country_var]], wave = .data[[wave_var]], name = "n") |>
237
+ dplyr::arrange(pais, wave)
238
+
239
+ if (complete_grid) {
240
+ all_waves <- if (!is.null(selected_waves)) selected_waves else sort(unique(data[[wave_var]]))
241
+ all_countries <- if (!is.null(selected_countries)) selected_countries else sort(unique(data[[country_var]]))
242
+
243
+ per_country_wave <- per_country_wave |>
244
+ tidyr::complete(pais = all_countries, wave = all_waves, fill = list(n = 0)) |>
245
+ dplyr::arrange(pais, wave)
246
+
247
+ per_country <- per_country_wave |>
248
+ dplyr::group_by(pais) |>
249
+ dplyr::summarise(n = sum(n), .groups = "drop") |>
250
+ dplyr::arrange(pais)
251
+
252
+ per_wave <- per_country_wave |>
253
+ dplyr::group_by(wave) |>
254
+ dplyr::summarise(n = sum(n), .groups = "drop") |>
255
+ dplyr::arrange(wave)
256
+ }
257
+
258
+ list(
259
+ overall = nrow(df),
260
+ per_wave = per_wave,
261
+ per_country = per_country,
262
+ per_country_wave = per_country_wave
263
+ )
264
+ }
265
+
266
+ # # -----------------------------------------------------------------------
267
+ # # -----------------------------------------------------------------------
268
+ # # -----------------------------------------------------------------------
269
+
270
+ # # -----------------------------------------------------------------------
271
+ # Creating User Interface UI!
272
+ # # -----------------------------------------------------------------------
273
+ ui <- fluidPage(
274
+
275
+ useShinyjs(),
276
+
277
+ theme = cses_theme,
278
+ tags$h2("CSES Data Playground",
279
+ style = "color: #C4722A; font-weight: bold; font-size: 36px;"),
280
+
281
+ sidebarLayout(
282
+ # ----- Sidebar panel for inputs
283
+ sidebarPanel(width = 3,
284
+ selectInput("variable", "Outcome",
285
+ labs[order(names(labs))],
286
+ selected = "IMD3010"),
287
+ # Default picks most recent module
288
+ pickerInput(inputId = "module",
289
+ label = tagList(info_badge("Module",
290
+ HTML("Please select which CSES Modules to be available in the analysis. Then, select which countries and years below."),
291
+ "Module")),
292
+ choices = sort(levels(as_factor(cses_shiny_data$IMD1008_MOD)[!is.na(cses_shiny_data$IMD1008_MOD)])),
293
+ selected = c("MODULE 5"),
294
+ options = list(`actions-box` = TRUE),
295
+ multiple = TRUE),
296
+
297
+ # ----- COUNTRY
298
+ pickerInput(inputId = "pais",
299
+ label = "Countries",
300
+ #tagList(info_badge("Countries",
301
+ #HTML("Please select which countries to be included in the analysis."),
302
+ #"Countries")),
303
+ choices = sort(levels(as_factor(cses_shiny_data$pais)[!is.na(cses_shiny_data$pais)])),
304
+ options = list(`actions-box` = TRUE),
305
+ multiple = TRUE),
306
+
307
+ # ----- WAVE
308
+ pickerInput(inputId = "wave",
309
+ label = "Years",
310
+ #tagList(info_badge("Years",
311
+ # HTML("Please select which years to be included in the analysis."),
312
+ # "Years")),
313
+ choices = c("1996" = "1996", "1997" = "1997", "1998" = "1998",
314
+ "1999" = "1999", "2000" = "2000", "2001" = "2001",
315
+ "2002" = "2002", "2003" = "2003", "2004" = "2004",
316
+ "2005" = "2005", "2006" = "2006", "2007" = "2007",
317
+ "2008" = "2008", "2009" = "2009", "2010" = "2010",
318
+ "2011" = "2011", "2012" = "2012", "2013" = "2013",
319
+ "2014" = "2014", "2015" = "2015", "2016" = "2016",
320
+ "2017" = "2017", "2018" = "2018", "2019" = "2019",
321
+ "2020" = "2020", "2021" = "2021"),
322
+ options = list(`actions-box` = TRUE),
323
+ multiple = TRUE),
324
+
325
+ # ----- WEIGHT selection radio buttons
326
+ bsplus::use_bs_popover(),
327
+ radioButtons(
328
+ inputId = "weight_type",
329
+ label = tagList(info_badge("Weights",
330
+ HTML("Further information on weights is available in <b>Part 6</b> of CSES Module 4."),
331
+ "Weights")),
332
+ # For a link, add:
333
+ # HTML('Further information on weights is available in <b>Part 6</b> of CSES Module 4. <br><a href=\"#\" target=\"_blank\">Open doc</a>')
334
+ choiceValues = c("no_weight", "weight_demographic", "weight_sample"),
335
+ choiceNames = list(
336
+ info_badge("Unweighted", "No weights applied. Raw proportions/percentages.",
337
+ "Unweighted"),
338
+ info_badge("Demographic weight", "Post-stratification targets.",
339
+ "Demographic weight"),
340
+ info_badge("Sample weight", "Design/selection probability weights.",
341
+ "Sample weight")
342
+ ),
343
+ selected = "no_weight"),
344
+
345
+ # This fixes a formatting issue with checkboxGroupInput() below
346
+ tags$head(
347
+ tags$style(
348
+ HTML("
349
+ .checkbox-inline {
350
+ margin-left: 0px;
351
+ margin-right: 10px;
352
+ }
353
+ .checkbox-inline+.checkbox-inline {
354
+ margin-left: 0px;
355
+ margin-right: 10px;
356
+ }
357
+ .shiny-notification {
358
+ width: 615px !important; /* max width */
359
+ max-height: 140px; /* max height */
360
+ word-wrap: break-word;
361
+ white-space: normal;
362
+ overflow-y: auto; /* scrollbar */
363
+ right: 330px !important; /* shift away from right edge */
364
+ box-sizing: border-box;
365
+ font-size: 14px;
366
+ }"))),
367
+
368
+ # This triggers the "Generate" button
369
+ tags$script(HTML("
370
+ Shiny.addCustomMessageHandler('clickGenerateButton', function(message) {
371
+ $('#go').click();
372
+ });
373
+ ")),
374
+ # This makes the slider input to allow only integers for CSES years
375
+ tags$style(type = "text/css", ".irs-grid-pol.small {height: 0px;}"),
376
+
377
+ # Make popovers white + wire TRUE hover with JavaScript
378
+ tags$style(HTML("
379
+ .popover {
380
+ --bs-popover-bg: #ffffff;
381
+ --bs-popover-border-color: #dddddd;
382
+ --bs-popover-header-bg: #ffffff;
383
+ --bs-popover-header-color: #212529;
384
+ --bs-popover-body-color: #212529;
385
+ border-color: #dddddd;
386
+ }
387
+ .popover .popover-header,
388
+ .popover .popover-body {
389
+ background-color: #ffffff;
390
+ color: #212529;
391
+ }
392
+ ")),
393
+ tags$script(HTML("
394
+ (function() {
395
+ function upgradeDataAttr(el){
396
+ // Force manual disable
397
+ if (el.getAttribute('data-toggle') === 'popover') {
398
+ el.setAttribute('data-bs-toggle','popover');
399
+ el.removeAttribute('data-toggle');
400
+ }
401
+ }
402
+
403
+ function getPopover(el){
404
+ // Force manual trigger so we fully control hover behavior
405
+ return bootstrap.Popover.getOrCreateInstance(el, {
406
+ container: 'body',
407
+ trigger: 'manual',
408
+ html: true, sanitize: false
409
+ });
410
+ }
411
+
412
+ function addHoverBehavior(el){
413
+ var timer = null;
414
+ var inst = getPopover(el);
415
+
416
+ function startHide(delay){
417
+ if (timer) clearTimeout(timer);
418
+ timer = setTimeout(function(){
419
+ inst.hide();
420
+ }, delay);
421
+ }
422
+ function cancelHide(){
423
+ if (timer) { clearTimeout(timer); timer = null; }
424
+ }
425
+
426
+ // Show on hover
427
+ el.addEventListener('mouseenter', function(){
428
+ cancelHide();
429
+ inst.show();
430
+
431
+ // Auto-dismiss after 10s (unless user is hovering the popover)
432
+ startHide(10000);
433
+ });
434
+
435
+ // Hide shortly after leaving the icon (unless pointer is on the popover)
436
+ el.addEventListener('mouseleave', function(){
437
+ // small delay to allow moving into the popover
438
+ setTimeout(function(){
439
+ var pop = document.getElementById(el.getAttribute('aria-describedby'));
440
+ if (!pop || !pop.matches(':hover')) inst.hide();
441
+ }, 150);
442
+ });
443
+
444
+ // Keep open while hovering the popover; hide when leaving it
445
+ el.addEventListener('shown.bs.popover', function(){
446
+ var pop = document.getElementById(el.getAttribute('aria-describedby'));
447
+ if (!pop) return;
448
+ pop.addEventListener('mouseenter', cancelHide);
449
+ pop.addEventListener('mouseleave', function(){
450
+ startHide(150); // quick close after leaving the box
451
+ });
452
+ });
453
+
454
+ // Prevent click toggling from fighting our hover logic
455
+ el.addEventListener('click', function(e){ e.preventDefault(); });
456
+ }
457
+
458
+ document.addEventListener('DOMContentLoaded', function(){
459
+ document.querySelectorAll('[data-bs-toggle=\"popover\"], [data-toggle=\"popover\"]').forEach(function(el){
460
+ upgradeDataAttr(el);
461
+ getPopover(el); // ensure BS5 instance exists
462
+ addHoverBehavior(el); // wire hover behavior + auto-dismiss
463
+ });
464
+ });
465
+ })();
466
+ ")),
467
+ # Show recode slider only for TS, CC, and mover plots (not for histogram)
468
+ conditionalPanel(
469
+ 'input.tabs == "Time Series" |
470
+ input.tabs == "Cross Country" |
471
+ input.tabs == "World Map" |
472
+ input.tabs == "Breakdown"',
473
+
474
+ uiOutput("sliderUI"),
475
+ # Mean Value toggle
476
+ # checkboxInput("use_mean", "Mean value", FALSE),
477
+ ),
478
+
479
+ # Add additional breakdown variable in mover plot
480
+ conditionalPanel(
481
+ 'input.tabs == "Breakdown"',
482
+ selectInput("variable_sec",
483
+ label = tagList(
484
+ info_badge("Subgroup for analysis",
485
+ HTML("Optionally split the Breakdown plot by another subgroup from the dataset.
486
+ Select <b>None</b> to disable."), "Secondary Variable")),
487
+ c("None" = "None",
488
+ labs_sec[order(names(labs_sec))])),
489
+ checkboxGroupInput("demog", "Demographic Variables",
490
+ c("Gender" = "gendermc",
491
+ "Age" = "age",
492
+ "Income" = "wealth",
493
+ "Education" = "edre",
494
+ "Urban/Rural" = "ur"),
495
+ selected = c("gendermc", "age", "edre"),
496
+ inline = TRUE)),
497
+ # Include button in UI (disabled)
498
+ #actionButton("go", "Generate")
499
+ tags$div(
500
+ style = "display: none;",
501
+ actionButton("go", "Generate"))),
502
+
503
+ # Main panel for displaying outputs ----
504
+ # # -----------------------------------------------------------------------
505
+ mainPanel(
506
+ # Output: Formatted text for caption ----
507
+ h3(textOutput("caption")),
508
+ h5(textOutput("wording")),
509
+ h5(textOutput("response")),
510
+ tabsetPanel(id = "tabs",
511
+ tabPanel("Histogram", plotOutput("hist")),
512
+ tabPanel("Time Series", plotOutput("ts")),
513
+ tabPanel("Cross Country", plotOutput("cc")),
514
+ tabPanel("Breakdown", plotOutput("mover")),
515
+ tabPanel("World Map", plotOutput("map"))),
516
+
517
+ br(),
518
+ fluidRow(column(12,
519
+ tags$div(style = "margin-top:-15px"),
520
+ downloadButton(outputId = "downloadPlot", label = "Download Figure"),
521
+ downloadButton(outputId = "downloadTable", label = "Download Table"),
522
+ tags$div(style = "height:10px"),
523
+ uiOutput("ns_card"),
524
+ #uiOutput("missing_warning_card"),
525
+ )
526
+ )
527
+ )
528
+ )
529
+ )
530
+
531
+ # # -----------------------------------------------------------------------
532
+ # # -----------------------------------------------------------------------
533
+ # # -----------------------------------------------------------------------
534
+
535
+ # # -----------------------------------------------------------------------
536
+ # Define SERVER logic
537
+ # # -----------------------------------------------------------------------
538
+ # The server function will be called when each client (browser) loads the app.
539
+ server <- function(input, output, session) {
540
+ observe({
541
+ req(input$variable)
542
+ if (!input$variable %in% names(cses_shiny_data)) {
543
+ showNotification("Selected variable not found in data!", type = "error")
544
+ }
545
+ if (!input$weight_type %in% names(cses_shiny_data)) {
546
+ showNotification("Selected weight column not found!", type = "error")
547
+ }
548
+ })
549
+
550
+ # Triggers "go" between server and ui to generate default plots
551
+ observe({
552
+ if (!is.null(input$module) && !is.null(input$pais) && !is.null(input$wave)) {
553
+ isolate({
554
+ session$sendCustomMessage("clickGenerateButton", list())
555
+ })
556
+ }
557
+ })
558
+
559
+ # Check the number of selected variables for breakdown
560
+ observeEvent(input$demog, {
561
+ if (length(input$demog) > 3) {
562
+ # Show a warning message
563
+ showNotification(HTML("You should only select a maximum of 3 demographic variables to plot."), type = "warning")
564
+ }
565
+ })
566
+
567
+ # MAKE IT REACTIVE
568
+ # # -----------------------------------------------------------------------
569
+ formulaText <- reactive({
570
+ paste(input$variable)
571
+ })
572
+
573
+ outcome <- reactive({
574
+ input$variable
575
+ })
576
+
577
+ wave <- reactive({
578
+ input$wave
579
+ })
580
+
581
+ outcome_code <- reactive({
582
+ vars_labels$column_name[which(vars_labels$column_name == paste(outcome()))]
583
+ })
584
+
585
+ outcome_lab <- reactive({
586
+ vars_labels$question_short_en[which(vars_labels$column_name == paste(outcome()))]
587
+ })
588
+
589
+ variable_sec <- reactive({
590
+ input$variable_sec
591
+ })
592
+
593
+ variable_sec_lab <- reactive({
594
+ vars_labels$question_short_en[which(vars_labels$column_name == paste(variable_sec()))]
595
+ })
596
+
597
+ sliderParams <- reactiveValues(valuex = c(1, 1))
598
+
599
+ # Reactive: Filter dataset based on selected module(s)
600
+ # # -----------------------------------------------------------------------
601
+ filtered_data <- reactive({
602
+ req(input$module)
603
+ cses_shiny_data %>%
604
+ dplyr::filter(IMD1008_MOD %in% input$module)
605
+ })
606
+
607
+ # OLD CODE THAT WOULD FORCE PRESELECTION, BUT IT BREAKS THE APP WITH FULL DATASET
608
+ # Observe changes in module input to update wave and pais
609
+ observeEvent(filtered_data(), {
610
+ data <- filtered_data()
611
+
612
+ wave_choices <- sort(unique(data$wave))
613
+ pais_choices <- sort(unique(data$pais))
614
+
615
+ updatePickerInput(
616
+ session = session,
617
+ inputId = "wave",
618
+ choices = wave_choices,
619
+ selected = wave_choices # you can leave this empty if no preselection
620
+ )
621
+ #
622
+ # updatePickerInput(
623
+ # session = session,
624
+ # inputId = "pais",
625
+ # choices = pais_choices,
626
+ # selected = pais_choices
627
+ # )
628
+ })
629
+
630
+ all_waves <- sort(unique(cses_shiny_data$wave))
631
+ all_paises <- sort(unique(cses_shiny_data$pais))
632
+
633
+ observeEvent(input$module, {
634
+ req(input$module)
635
+
636
+ # Filter for the selected module
637
+ valid <- dplyr::filter(cses_shiny_data, IMD1008_MOD %in% input$module)
638
+ valid_waves <- sort(unique(valid$wave))
639
+ valid_paises <- sort(unique(valid$pais))
640
+
641
+ wave_disabled <- !(all_waves %in% valid_waves)
642
+ pais_disabled <- !(all_paises %in% valid_paises)
643
+
644
+ # 🔹 Automatically select *all valid waves* when a module is chosen
645
+ shinyWidgets::updatePickerInput(
646
+ session, "wave",
647
+ choices = all_waves,
648
+ selected = valid_waves, # <-- changed line
649
+ choicesOpt = list(
650
+ disabled = wave_disabled,
651
+ style = ifelse(wave_disabled, "color:#999;", "")
652
+ )
653
+ )
654
+
655
+ # 🔹 Automatically select *all valid countries* too (optional)
656
+ shinyWidgets::updatePickerInput(
657
+ session, "pais",
658
+ choices = all_paises,
659
+ selected = valid_paises, # <-- changed line
660
+ choicesOpt = list(
661
+ disabled = pais_disabled,
662
+ style = ifelse(pais_disabled, "color:#999;", "")
663
+ )
664
+ )
665
+ })
666
+
667
+
668
+ # Set default recode slider values:
669
+ # # -----------------------------------------------------------------------
670
+ # 2-point: 1-1
671
+ # 3-point: 3-3
672
+ # 4-point: 1-2
673
+ # 5-point: 4-5
674
+ # 6-point: 3-3
675
+ # 7-point: 5-7
676
+ # 10-point: 8-10
677
+ # ALL OTHER: MEAN
678
+
679
+ # UPDATE SLIDER DEFAULTS AND MEAN BEHAVIOR
680
+ # -----------------------------------------------------------------------
681
+ observeEvent({
682
+ list(input$variable, input$use_mean)
683
+ }, {
684
+ # compute numeric vector safely
685
+ xvals <- suppressWarnings(as.numeric(cses_shiny_data[[formulaText()]]))
686
+ maxval <- max(xvals, na.rm = TRUE)
687
+
688
+ # --- DEFAULT RECODE RANGES ---
689
+ if (maxval == 1) {
690
+ sliderParams$valuex <- c(1, 1)
691
+ } else if (maxval == 2) {
692
+ sliderParams$valuex <- c(1, 1)
693
+ } else if (maxval == 3) {
694
+ sliderParams$valuex <- c(3, 3)
695
+ } else if (maxval == 4) {
696
+ sliderParams$valuex <- c(1, 2)
697
+ } else if (maxval == 5) {
698
+ sliderParams$valuex <- c(4, 5)
699
+ } else if (maxval == 6) {
700
+ sliderParams$valuex <- c(3, 3)
701
+ } else if (maxval == 7) {
702
+ sliderParams$valuex <- c(5, 7)
703
+ } else if (maxval == 10) {
704
+ sliderParams$valuex <- c(8, 10)
705
+ } else {
706
+ mean_val <- mean(xvals, na.rm = TRUE)
707
+ sliderParams$valuex <- c(mean_val, mean_val)
708
+ }
709
+
710
+ # --- IF USER SELECTED "USE MEAN VALUE" ---
711
+ if (isTRUE(input$use_mean)) {
712
+ mean_val <- mean(xvals, na.rm = TRUE)
713
+ sliderParams$valuex <- c(mean_val, mean_val)
714
+ }
715
+
716
+ # force slider update
717
+ updateSliderInput(
718
+ session,
719
+ inputId = "recode",
720
+ value = sliderParams$valuex
721
+ )
722
+ })
723
+
724
+ # RECODE SLIDER
725
+ # # -----------------------------------------------------------------------
726
+ output$sliderUI <- renderUI({
727
+ sliderInput(
728
+ inputId = "recode",
729
+ label = tagList(
730
+ info_badge(
731
+ "Which values do you want to graph?",
732
+ HTML("Please select which outcome values to be displayed."),
733
+ "Which values do you want to graph?"
734
+ )
735
+ ),
736
+ min = min(as.numeric(cses_shiny_data[[formulaText()]]), na.rm = TRUE),
737
+ max = max(as.numeric(cses_shiny_data[[formulaText()]]), na.rm = TRUE),
738
+ value = sliderParams$valuex,
739
+ step = 1
740
+ )
741
+ })
742
+
743
+
744
+ # Filtering data based on user's selection (dff)
745
+ dff <- eventReactive(input$go, ignoreNULL = FALSE, {
746
+ cses_shiny_data %>%
747
+ dplyr::filter(as_factor(wave) %in% input$wave) %>% # year
748
+ dplyr::filter(pais_nam %in% input$pais) # country
749
+ })
750
+
751
+ # Rendering var caption based on user's var selection
752
+ cap <- renderText({
753
+ vars_labels$question_short_en[which(vars_labels$column_name == formulaText())]
754
+ })
755
+ output$caption <- renderText({
756
+ cap()
757
+ })
758
+
759
+ # Rendering variable code + wording based on user's var selection
760
+ word <- renderText({
761
+ paste0(toupper(vars_labels$column_name[which(vars_labels$column_name == formulaText())]), ". ",
762
+ vars_labels$question_en[which(vars_labels$column_name == formulaText())])
763
+ })
764
+ output$wording <- renderText({
765
+ word()
766
+ })
767
+
768
+ # Rendering ROs based on user's var selection
769
+ resp <- renderText({
770
+ vars_labels$responses_en_rec[which(vars_labels$column_name == formulaText())]
771
+ })
772
+ output$response <- renderText({
773
+ resp()
774
+ })
775
+
776
+ # Rendering variable_sec ROs
777
+ resp_sec <- renderText({
778
+ vars_labels$responses_en_rec[which(vars_labels$column_name == input$variable_sec)]
779
+ })
780
+ output$response_sec <- renderText({
781
+ resp_sec()
782
+ })
783
+
784
+ # Rendering User selected recode value(s)
785
+ slider_values <- renderText({
786
+ if(input$recode[1] == input$recode[2]) {
787
+ paste0("(value: ", unique(input$recode), ")")
788
+ } else {
789
+ paste0("(range: ", paste(input$recode, collapse = " to "), ")")
790
+ }
791
+ })
792
+ output$selected_values <- renderText({
793
+ slider_values()
794
+ })
795
+
796
+
797
+ # Toggle recode slider
798
+ # # -----------------------------------------------------------------------
799
+ observe({
800
+ if (isTRUE(input$use_mean)) {
801
+ shinyjs::disable("recode")
802
+ } else {
803
+ shinyjs::enable("recode")
804
+ }
805
+ })
806
+
807
+ # # -----------------------------------------------------------------------
808
+ # N-SIZE CARD
809
+ # # -----------------------------------------------------------------------
810
+ output$ns_card <- renderUI({
811
+ req(dff(), outcome(), input$wave, input$pais)
812
+
813
+ selected_waves <- as.character(input$wave)
814
+ selected_countries <- as.character(input$pais)
815
+
816
+ ns <- get_sample_counts(
817
+ data = dff(),
818
+ outcome_var = outcome(),
819
+ wave_var = "wave",
820
+ country_var = "pais_nam", # adjust if your helper uses a different input col
821
+ selected_waves = selected_waves,
822
+ selected_countries = selected_countries
823
+ )
824
+
825
+ # If absolutely no non-missing data, show a gentle note
826
+ if (is.null(ns$overall) || ns$overall == 0) {
827
+ return(tags$div(
828
+ style = "border:2px solid #17a2b8; border-radius:8px; padding:14px; background:#e9f7ff; margin-bottom:20px;",
829
+ HTML(paste0("ℹ️ <b>Ns</b> for <b>", outcome(), "</b>: No non-missing observations in the current selection."))
830
+ ))
831
+ }
832
+
833
+ # Expect columns: ns$per_wave (wave, n) and ns$per_country_wave (pais, wave, n)
834
+ pCW <- ns$per_country_wave
835
+ # If your helper names the country column differently, change "pais" below
836
+
837
+ # Control whether to show zeros
838
+ show_zeros <- FALSE
839
+
840
+ # Order waves nicely
841
+ waves <- unique(pCW$wave)
842
+
843
+ # If waves are numeric-like but char, coerce to numeric for sorting (silently)
844
+ suppressWarnings({
845
+ wave_num <- suppressWarnings(as.numeric(as.character(waves)))
846
+ if (all(!is.na(wave_num))) waves <- waves[order(wave_num)] else waves <- sort(waves)
847
+ })
848
+
849
+ # Create a quick lookup for total N per wave
850
+ per_wave_tbl <- ns$per_wave |>
851
+ dplyr::mutate(wave_chr = as.character(wave)) |>
852
+ dplyr::select(wave_chr, n)
853
+
854
+ # Build one <details> block per wave
855
+ blocks <- lapply(seq_along(waves), function(i) {
856
+ w <- waves[i]
857
+ w_chr <- as.character(w)
858
+
859
+ wt <- per_wave_tbl$n[match(w_chr, per_wave_tbl$wave_chr)]
860
+ wt <- ifelse(is.na(wt), 0, wt)
861
+
862
+ rows <- pCW |>
863
+ dplyr::filter(as.character(wave) == w_chr)
864
+
865
+ if (!show_zeros) rows <- dplyr::filter(rows, n > 0)
866
+
867
+ rows <- dplyr::arrange(rows, dplyr::desc(n), .by_group = FALSE)
868
+
869
+ items <- lapply(seq_len(nrow(rows)), function(j) {
870
+ n_j <- format(rows$n[j], big.mark = ",")
871
+ is_zero <- isTRUE(rows$n[j] == 0)
872
+ li_style <- if (is_zero) "color:#6c757d;" else NULL
873
+ # country column is "pais" as returned by the helper
874
+ tags$li(
875
+ tags$span(HTML(paste0("<b>", rows$pais[j], "</b>: N=", n_j))),
876
+ style = li_style
877
+ )
878
+ })
879
+
880
+ tags$details(
881
+ open = (i == 1), # first year open by default
882
+ class = "ns-year",
883
+ tags$summary(
884
+ HTML(paste0("<b>", w_chr, "</b> — Total N=", format(wt, big.mark=",")))
885
+ ),
886
+ tags$ul(items)
887
+ )
888
+ })
889
+
890
+ tags$div(
891
+ style = "border:2px solid #17a2b8;
892
+ border-radius:8px;
893
+ padding:14px;
894
+ background:#e9f7ff;
895
+ margin-bottom:20px;
896
+ max-height:180px;
897
+ overflow-y:auto;",
898
+ # Title + grand total
899
+ tags$div(
900
+ HTML(paste0(
901
+ "📊 <b>Sample sizes</b> (non-missing <b>", outcome(), "</b>)<br>",
902
+ "<b>Total across selection:</b> ", format(ns$overall, big.mark = ",")
903
+ )),
904
+ style = "margin-bottom:6px;"
905
+ ),
906
+ tags$hr(style="margin:8px 0;"),
907
+ # Small CSS polish for the dropdowns
908
+ tags$style(HTML("
909
+ details.ns-year { margin-bottom: 8px; }
910
+ details > summary { cursor: pointer; list-style: none; }
911
+ details > summary::-webkit-details-marker { display: none; }
912
+ ")),
913
+ blocks
914
+ )
915
+ })
916
+
917
+ # -----------------------------------------------------------------------
918
+ # SOURCE INFO WITH ACTUAL DATA AVAILABILITY (not just user selections)
919
+ # -----------------------------------------------------------------------
920
+ source_info_both <- reactive({
921
+ req(dff(), outcome(), input$wave, input$pais, input$module)
922
+
923
+ # Reuse your helper to get Ns
924
+ ns <- get_sample_counts(
925
+ data = dff(),
926
+ outcome_var = outcome(),
927
+ wave_var = "wave",
928
+ country_var = "pais_nam",
929
+ selected_waves = input$wave,
930
+ selected_countries = input$pais
931
+ )
932
+
933
+ # Extract actual combinations with nonzero N
934
+ valid_combos <- ns$per_country_wave %>%
935
+ dplyr::filter(n > 0)
936
+
937
+ # Actual waves and countries that have data
938
+ valid_waves <- sort(unique(valid_combos$wave))
939
+ valid_countries <- sort(unique(valid_combos$pais))
940
+ selected_module = input$module
941
+
942
+ # Get abbreviations for these countries (match order)
943
+ pais_abbr <- cses_shiny_data %>%
944
+ dplyr::filter(pais_nam %in% valid_countries) %>%
945
+ distinct(pais_nam, pais_lab) %>%
946
+ arrange(match(pais_nam, valid_countries)) %>%
947
+ pull(pais_lab)
948
+
949
+ paste0(
950
+ "Source: CSES Data Playground\n\n",
951
+ str_wrap(paste0("CSES ", selected_module,
952
+ " - Years: ", paste(valid_waves, collapse = ", "),
953
+ ". Countries: ", paste(pais_abbr, collapse = ", ")
954
+ ), 130),
955
+ "\n\n",
956
+ str_wrap(paste0(word(), " ", resp()), 130)
957
+ )
958
+ })
959
+
960
+ # -----------------------------------------------------------------------
961
+ source_info_pais <- reactive({
962
+ req(dff(), outcome(), input$wave, input$pais)
963
+
964
+ ns <- get_sample_counts(
965
+ data = dff(),
966
+ outcome_var = outcome(),
967
+ wave_var = "wave",
968
+ country_var = "pais_nam",
969
+ selected_waves = input$wave,
970
+ selected_countries = input$pais
971
+ )
972
+
973
+ valid_combos <- ns$per_country_wave %>%
974
+ dplyr::filter(n > 0)
975
+
976
+ valid_countries <- sort(unique(valid_combos$pais))
977
+
978
+ pais_abbr <- cses_shiny_data %>%
979
+ dplyr::filter(pais_nam %in% valid_countries) %>%
980
+ distinct(pais_nam, pais_lab) %>%
981
+ arrange(match(pais_nam, valid_countries)) %>%
982
+ pull(pais_lab)
983
+
984
+ paste0(
985
+ "Source: CSES Data Playground\n",
986
+ "Countries: ", str_wrap(paste(pais_abbr, collapse = ", "), 130),
987
+ "\n\n",
988
+ str_wrap(paste0(word(), " ", resp()), 130)
989
+ )
990
+ })
991
+
992
+ # -----------------------------------------------------------------------
993
+ source_info_wave <- reactive({
994
+ req(dff(), outcome(), input$wave, input$pais, input$module)
995
+
996
+ ns <- get_sample_counts(
997
+ data = dff(),
998
+ outcome_var = outcome(),
999
+ wave_var = "wave",
1000
+ country_var = "pais_nam",
1001
+ selected_waves = input$wave,
1002
+ selected_countries = input$pais
1003
+ )
1004
+
1005
+ valid_combos <- ns$per_country_wave %>%
1006
+ dplyr::filter(n > 0)
1007
+ valid_waves <- sort(unique(valid_combos$wave))
1008
+ selected_module = input$module
1009
+
1010
+ paste0(
1011
+ "Source: CSES Data Playground\n", "CSES ", selected_module,
1012
+ " - Years: ", str_wrap(paste(valid_waves, collapse = ", "), 130),
1013
+ "\n\n",
1014
+ str_wrap(paste0(word(), " ", resp()), 130)
1015
+ )
1016
+ })
1017
+ # # -----------------------------------------------------------------------
1018
+ # PLOTS
1019
+ # # -----------------------------------------------------------------------
1020
+
1021
+ # Histogram
1022
+ # # -----------------------------------------------------------------------
1023
+ # must break into data event, graph event, and renderPlot to get download to work
1024
+ histd <- reactive({
1025
+ req(dff(), input$variable, input$weight_type)
1026
+
1027
+ if (!input$variable %in% names(dff()) ||
1028
+ !input$weight_type %in% names(dff())) {
1029
+ return(NULL)
1030
+ }
1031
+
1032
+ tryCatch({
1033
+ dff() %>%
1034
+ drop_na(!!sym(input$variable), !!sym(input$weight_type)) %>%
1035
+ group_by(cat = haven::as_factor(!!sym(input$variable))) %>%
1036
+ summarise(w = sum(!!sym(input$weight_type), na.rm = TRUE)) %>%
1037
+ mutate(
1038
+ prop = w / sum(w) * 100,
1039
+ proplabel = paste0(round(prop), "%"),
1040
+ cat = str_wrap(as.character(cat), width = 25)
1041
+ )
1042
+ }, error = function(e) {
1043
+ NULL
1044
+ })
1045
+ })
1046
+
1047
+ histg <- reactive({lapop_hist(histd(),
1048
+ ymax = ifelse(any(histd()$prop > 90), 110, 100),
1049
+ source_info = "Source: CSES Data Playground")})
1050
+
1051
+ output$hist <- renderPlot({
1052
+ req(dff(), nrow(dff()) > 0, input$variable, input$variable %in% names(dff()))
1053
+ return(histg())
1054
+ })
1055
+
1056
+ # Time-series
1057
+ # # -----------------------------------------------------------------------
1058
+ tsd <- reactive({
1059
+ dta_ts <- Error(
1060
+ dff() %>%
1061
+ drop_na(!!sym(outcome()), !!sym(input$weight_type)) %>%
1062
+ mutate(outcome_rec = case_when(
1063
+ !!sym(outcome()) >= input$recode[1] &
1064
+ !!sym(outcome()) <= input$recode[2] ~ 100,
1065
+ TRUE ~ 0
1066
+ )) %>%
1067
+ group_by(wave = as.character(as_factor(wave))) %>%
1068
+ summarise_at(
1069
+ vars("outcome_rec"),
1070
+ list(~weighted.ttest.ci(., !!sym(input$weight_type)))
1071
+ ) %>%
1072
+ unnest_wider(col = "outcome_rec") %>%
1073
+ mutate(proplabel = paste0(round(prop), "%")) %>%
1074
+ dplyr::filter(prop != 0)
1075
+ )
1076
+
1077
+ validate(
1078
+ need(dta_ts, "Error: no data available. Please verify that this question was asked in this country/year combination.")
1079
+ )
1080
+
1081
+ dta_ts <- merge(dta_ts,
1082
+ data.frame(wave = as.character(waves_total), empty = 1),
1083
+ by = "wave", all.y = TRUE) %>%
1084
+ dplyr::filter(!is.na(prop)) # TO EXCLUDE YEARS NOT IN THE SELECTION
1085
+ # THEN YEARS ARE NOT SEQUENTIAL
1086
+
1087
+ return(omit_na_edges(dta_ts))
1088
+ })
1089
+
1090
+ tsg <- reactive({lapop_ts(tsd(),
1091
+ ymax = ifelse(any(tsd()$prop > 85, na.rm = TRUE), 110, 100),
1092
+ #label_vjust = -1.5,
1093
+ label_vjust = ifelse(any(tsd()$prop > 80, na.rm = TRUE), -1.1, -1.5),
1094
+ source_info = "Source: CSES Data Playground",
1095
+ subtitle = "% in selected category",
1096
+ ci_type = "errorbar")
1097
+ })
1098
+
1099
+ output$ts <- renderPlot({
1100
+ return(tsg())
1101
+ })
1102
+
1103
+ # Cross Country
1104
+ # # -----------------------------------------------------------------------
1105
+ # define macro (aggregate-level) variables
1106
+ continuous_vars <- c("IMD3001_TS", "IMD5054_2", "IMD5057_1", "IMD5035",
1107
+ "IMD5056_2", "IMD5055_1", "IMD5053_1", "IMD5052_2")
1108
+
1109
+ ccd <- reactive({
1110
+ var_sel <- outcome()
1111
+ rec_min <- input$recode[1]
1112
+ rec_max <- input$recode[2]
1113
+
1114
+ # CASE 1: Continuous macro-level variable (mean = TRUE)
1115
+ if (var_sel %in% continuous_vars) {
1116
+ curr_outcome <- sym(var_sel)
1117
+
1118
+ dta_cc <- dff() %>%
1119
+ # Apply recode range first
1120
+ mutate(
1121
+ tmp_val = as.numeric(!!curr_outcome),
1122
+ tmp_val = ifelse(tmp_val >= rec_min & tmp_val <= rec_max, tmp_val, NA_real_)
1123
+ ) %>%
1124
+ group_by(vallabel = pais_lab) %>%
1125
+ summarise(
1126
+ prop = mean(tmp_val, na.rm = TRUE)
1127
+ ) %>%
1128
+ ungroup() %>%
1129
+ mutate(
1130
+ lb = NA_real_,
1131
+ ub = NA_real_,
1132
+ proplabel = ifelse(is.na(prop), "", sprintf("%.2f", prop))
1133
+ ) %>%
1134
+ filter(!is.na(prop))
1135
+
1136
+ # CASE 2: Regular individual-level variable (weighted proportion)
1137
+ } else {
1138
+
1139
+ dta_cc <- dff() %>%
1140
+ drop_na(!!sym(var_sel), !!sym(input$weight_type)) %>%
1141
+ mutate(outcome_rec = case_when(
1142
+ !!sym(var_sel) >= rec_min & !!sym(var_sel) <= rec_max ~ 100,
1143
+ TRUE ~ 0
1144
+ )) %>%
1145
+ group_by(vallabel = pais_lab) %>%
1146
+ summarise_at(
1147
+ vars("outcome_rec"),
1148
+ list(~weighted.ttest.ci(., !!sym(input$weight_type)))
1149
+ ) %>%
1150
+ unnest_wider(col = "outcome_rec") %>%
1151
+ filter(prop != 0) %>%
1152
+ mutate(proplabel = paste0(round(prop), "%"))
1153
+ }
1154
+
1155
+ validate(
1156
+ need(nrow(dta_cc) > 0,
1157
+ "Error: no data available. Please verify that this question was asked in this country/year combination.")
1158
+ )
1159
+
1160
+ return(dta_cc)
1161
+ })
1162
+
1163
+ ccg <- reactive({
1164
+ lapop_cc(
1165
+ ccd(),
1166
+ sort = "hi-lo",
1167
+ subtitle = ifelse(outcome() %in% continuous_vars,
1168
+ "Countries (within selected range)",
1169
+ "% in selected category"),
1170
+ ymax = ifelse(outcome() %in% continuous_vars, 6,
1171
+ ifelse(any(ccd()$prop > 90, na.rm = TRUE), 110, 100)),
1172
+ source_info = "Source: CSES Data Playground"
1173
+ )
1174
+ })
1175
+
1176
+ output$cc <- renderPlot({
1177
+ ccg()
1178
+ })
1179
+
1180
+ # World Map
1181
+ # # -----------------------------------------------------------------------
1182
+ mapd <- reactive({
1183
+
1184
+ var_sel <- outcome()
1185
+ rec_min <- input$recode[1]
1186
+ rec_max <- input$recode[2]
1187
+
1188
+ continuous_vars <- c("IMD3001_TS", "IMD5054_2", "IMD5057_1", "IMD5035",
1189
+ "IMD5056_2", "IMD5055_1", "IMD5053_1", "IMD5052_2")
1190
+
1191
+ req(input$module)
1192
+
1193
+ # --- NEW: allow only one module at a time ---
1194
+ validate(
1195
+ need(
1196
+ length(input$module) == 1,
1197
+ "Please select only ONE module to display a map."
1198
+ )
1199
+ )
1200
+
1201
+ # CASE 1: Continuous macro variable (mean values)
1202
+ if (var_sel %in% continuous_vars) {
1203
+
1204
+ dta_map <- dff() %>%
1205
+ mutate(
1206
+ tmp_val = as.numeric(.data[[var_sel]]),
1207
+ tmp_val = ifelse(tmp_val >= rec_min & tmp_val <= rec_max, tmp_val, NA_real_)
1208
+ ) %>%
1209
+ group_by(pais_lab = pais_lab) %>% # IMPORTANT: must exist in your dataset
1210
+ summarise(
1211
+ value = mean(tmp_val, na.rm = TRUE),
1212
+ .groups = "drop"
1213
+ ) %>%
1214
+ filter(!is.na(value))
1215
+
1216
+ } else {
1217
+
1218
+ # CASE 2: Categorical / proportion variables
1219
+ dta_map <- dff() %>%
1220
+ drop_na(.data[[var_sel]], .data[[input$weight_type]]) %>%
1221
+ mutate(outcome_rec = case_when(
1222
+ .data[[var_sel]] >= rec_min & .data[[var_sel]] <= rec_max ~ 100,
1223
+ TRUE ~ 0
1224
+ )) %>%
1225
+ group_by(pais_lab = pais_lab) %>%
1226
+ summarise_at(
1227
+ vars("outcome_rec"),
1228
+ list(~weighted.ttest.ci(., .data[[input$weight_type]]))
1229
+ ) %>%
1230
+ unnest_wider(col = "outcome_rec") %>%
1231
+ filter(prop > 0) %>%
1232
+ rename(value = prop)
1233
+ }
1234
+
1235
+ validate(
1236
+ need(nrow(dta_map) > 0,
1237
+ "Error: no map data available for this country/year/variable selection.")
1238
+ )
1239
+
1240
+ return(dta_map)
1241
+ })
1242
+
1243
+ mapg <- reactive({
1244
+ lapop_map(
1245
+ mapd(), survey = "CSES",
1246
+ source_info = "\nSource: CSES Data Playground"
1247
+ )
1248
+ })
1249
+
1250
+ output$map <- renderPlot({
1251
+ mapg()
1252
+ })
1253
+
1254
+ # Breakdown
1255
+ # # -----------------------------------------------------------------------
1256
+ # Use function for each demographic breakdown variable
1257
+
1258
+ secdf <- eventReactive(input$go, ignoreNULL = FALSE, {
1259
+ if (input$variable_sec == "None") {
1260
+ NULL
1261
+ } else if (variable_sec() == outcome()) {
1262
+ showNotification(HTML("You cannot break the outcome variable by itself."), type = "error")
1263
+ NULL
1264
+ } else {
1265
+
1266
+ process_data(
1267
+ data = dff(),
1268
+ outcome_var = outcome(),
1269
+ recode_range = input$recode,
1270
+ group_var = input$variable_sec,
1271
+ weight_var = input$weight_type,
1272
+ var_label = stringr::str_wrap(variable_sec_lab(), width = 25)
1273
+ )
1274
+ }
1275
+ })
1276
+
1277
+ genderdf <- eventReactive(input$go, ignoreNULL = FALSE, {
1278
+ if ("gendermc" %in% input$demog) {
1279
+ process_data(
1280
+ data = dff(),
1281
+ outcome_var = outcome(),
1282
+ recode_range = input$recode,
1283
+ weight_var = input$weight_type,
1284
+ group_var = "gendermc",
1285
+ var_label = "Gender"
1286
+ )
1287
+ } else {
1288
+ NULL
1289
+ }
1290
+ })
1291
+
1292
+ wealthdf <- eventReactive(input$go, ignoreNULL = FALSE, {
1293
+ if ("wealth" %in% input$demog) {
1294
+ process_data(
1295
+ data = dff(),
1296
+ outcome_var = outcome(),
1297
+ recode_range = input$recode,
1298
+ weight_var = input$weight_type,
1299
+ group_var = "wealthf",
1300
+ var_label = "Wealth"
1301
+ )
1302
+ } else {
1303
+ NULL
1304
+ }
1305
+ })
1306
+
1307
+ eddf <- eventReactive(input$go, ignoreNULL = FALSE, {
1308
+ if ("edre" %in% input$demog) {
1309
+ process_data(
1310
+ data = dff(),
1311
+ outcome_var = outcome(),
1312
+ recode_range = input$recode,
1313
+ weight_var = input$weight_type,
1314
+ group_var = "edrerf",
1315
+ var_label = "Education"
1316
+ )
1317
+ } else {
1318
+ NULL
1319
+ }
1320
+ })
1321
+
1322
+ agedf <- eventReactive(input$go, ignoreNULL = FALSE, {
1323
+ if ("age" %in% input$demog) {
1324
+ process_data(
1325
+ data = dff(),
1326
+ outcome_var = outcome(),
1327
+ recode_range = input$recode,
1328
+ weight_var = input$weight_type,
1329
+ group_var = "age",
1330
+ var_label = "Age"
1331
+ )
1332
+ } else {
1333
+ NULL
1334
+ }
1335
+ })
1336
+
1337
+ urdf <- eventReactive(input$go, ignoreNULL = FALSE, {
1338
+ if ("ur" %in% input$demog) {
1339
+ process_data(
1340
+ data = dff(),
1341
+ outcome_var = outcome(),
1342
+ recode_range = input$recode,
1343
+ weight_var = input$weight_type,
1344
+ group_var = "ur",
1345
+ var_label = "Place of\nResidence"
1346
+ )
1347
+ } else {
1348
+ NULL
1349
+ }
1350
+ })
1351
+
1352
+ # Combine demographic data frames into one df
1353
+ moverd <- reactive({
1354
+ req(dff(), input$recode, input$weight_type)
1355
+
1356
+ dta_mover <- Error(rbind(
1357
+ if (input$variable_sec != "None" && input$variable_sec != outcome()) {
1358
+ process_data(
1359
+ data = dff(),
1360
+ outcome_var = outcome(),
1361
+ recode_range = input$recode,
1362
+ group_var = input$variable_sec,
1363
+ weight_var = input$weight_type,
1364
+ var_label = str_wrap(variable_sec_lab(), width = 25)
1365
+ )
1366
+ },
1367
+ if ("gendermc" %in% input$demog) {
1368
+ process_data(dff(), outcome(), input$recode, "gendermc", "Gender",
1369
+ input$weight_type)
1370
+ },
1371
+ if ("age" %in% input$demog) {
1372
+ process_data(dff(), outcome(), input$recode, "age", "Age",
1373
+ input$weight_type)
1374
+ },
1375
+ if ("wealth" %in% input$demog) {
1376
+ process_data(dff(), outcome(), input$recode, "wealthf", "Wealth",
1377
+ input$weight_type)
1378
+ },
1379
+ if ("edre" %in% input$demog) {
1380
+ process_data(dff(), outcome(), input$recode, "edrerf", "Education",
1381
+ input$weight_type)
1382
+ },
1383
+ if ("ur" %in% input$demog) {
1384
+ process_data(dff(), outcome(), input$recode, "ur", "Place of\nResidence",
1385
+ input$weight_type)
1386
+ }
1387
+ ))
1388
+
1389
+ validate(
1390
+ need(dta_mover, "Error: no data available. Please verify that this question was asked in this country/year combination")
1391
+ )
1392
+
1393
+ dta_mover$vallabel <- as.character(dta_mover$vallabel)
1394
+ return(dta_mover)
1395
+ })
1396
+
1397
+
1398
+ moverg <- reactive({
1399
+ moverg <- lapop_mover(moverd(),
1400
+ subtitle = "% in selected category",
1401
+ ymax = ifelse(any(moverd()$prop > 90, na.rm = TRUE), 119,
1402
+ ifelse(any(moverd()$prop > 80, na.rm = TRUE), 109, 100)),
1403
+ source_info = "Source: CSES Data Playground")
1404
+ return(moverg)
1405
+ })
1406
+
1407
+ output$mover <- renderPlot({
1408
+ return(moverg())
1409
+ })
1410
+
1411
+ # # -----------------------------------------------------------------------
1412
+ # DOWNLOAD SECTION
1413
+ # # -----------------------------------------------------------------------
1414
+
1415
+ # Download Plot
1416
+ # # -----------------------------------------------------------------------
1417
+ output$downloadPlot <- downloadHandler(
1418
+ filename = function(file) {
1419
+
1420
+ weight_suffix <- switch(input$weight_type, # Add weight type to plot export
1421
+ "no_weight" = "unweighted",
1422
+ "weight_demographic" = "demogweighted",
1423
+ "weight_sample" = "sampleweighted")
1424
+
1425
+ ifelse(input$tabs == "Histogram", paste0("hist_", outcome(), "_", weight_suffix, ".svg"),
1426
+ ifelse(input$tabs == "Time Series", paste0("ts_", outcome(), "_", weight_suffix, ".svg"),
1427
+ ifelse(input$tabs == "Cross Country", paste0("cc_", outcome(), "_", weight_suffix, ".svg"),
1428
+ ifelse(input$tabs == "World Map", paste0("map_", outcome(), "_", weight_suffix, ".svg"),
1429
+ paste0("mover_", outcome(), "_", weight_suffix, ".svg"))))) # Add plot type to file export
1430
+ },
1431
+
1432
+ content = function(file) {
1433
+
1434
+ if(input$tabs == "Histogram") {
1435
+ title_text <- isolate(cap())
1436
+ word_text <- isolate(word())
1437
+
1438
+ hist_to_save <- lapop_hist(histd(),
1439
+ main_title = title_text,
1440
+ subtitle = "% in selected category ",
1441
+ ymax = ifelse(any(histd()$prop > 90), 110, 100),
1442
+ source_info = source_info_both()
1443
+ )
1444
+
1445
+ lapop_save(hist_to_save, file)
1446
+ showNotification(HTML("Histogram plot download complete ✓ "), type = "message")
1447
+
1448
+ } else if (input$tabs == "Time Series") {
1449
+ title_text <- isolate(cap())
1450
+ subtitle_text <- slider_values()
1451
+
1452
+ ts_to_save <- lapop_ts(tsd(),
1453
+ main_title = title_text,
1454
+ subtitle = paste0("% in selected category ", subtitle_text),
1455
+ ymax = ifelse(any(tsd()$prop > 88, na.rm = TRUE), 110, 100),
1456
+ label_vjust = ifelse(any(tsd()$prop > 80, na.rm = TRUE), -1.1, -1.5),
1457
+ source_info = source_info_pais()
1458
+ )
1459
+
1460
+ lapop_save(ts_to_save, file)
1461
+ showNotification(HTML("Time series plot download complete ✓ "), type = "message")
1462
+
1463
+ } else if (input$tabs == "Cross Country") {
1464
+ title_text <- isolate(cap())
1465
+ subtitle_text <- slider_values()
1466
+
1467
+ cc_to_save <- lapop_cc(ccd(), sort = "hi-lo",
1468
+ main_title = title_text,
1469
+ subtitle = paste0("% in selected category ", subtitle_text),
1470
+ ymax = ifelse(any(ccd()$prop > 90, na.rm = TRUE), 110, 100),
1471
+ label_angle = 90,
1472
+ source_info = source_info_wave()
1473
+ )
1474
+
1475
+ lapop_save(cc_to_save, file)
1476
+ showNotification(HTML("Cross country plot download complete ✓ "), type = "message")
1477
+
1478
+ } else if (input$tabs == "World Map") {
1479
+ title_text <- isolate(cap())
1480
+ subtitle_text <- slider_values()
1481
+
1482
+ map_to_save <- lapop_map(mapd(),
1483
+ main_title = title_text,
1484
+ subtitle = paste0("% in selected category ", subtitle_text),
1485
+ source_info = paste0("\n", source_info_both()),
1486
+ survey = "CSES"
1487
+ )
1488
+
1489
+ lapop_save(map_to_save, file)
1490
+ showNotification(HTML("Map plot download complete ✓ "), type = "message")
1491
+
1492
+ } else {
1493
+ title_text <- isolate(cap())
1494
+ subtitle_text <- slider_values()
1495
+ word_text <- isolate(word())
1496
+
1497
+ mover_to_save <- lapop_mover(
1498
+ moverd(),
1499
+ main_title = title_text,
1500
+ subtitle = paste0("% in selected category ", subtitle_text),
1501
+ ymax = ifelse(any(moverd()$prop > 90, na.rm = TRUE), 119,
1502
+ ifelse(any(moverd()$prop > 80, na.rm = TRUE), 109, 100)),
1503
+ source_info = source_info_both()
1504
+ )
1505
+
1506
+ lapop_save(mover_to_save, file)
1507
+ showNotification(HTML("Break down plot download complete ✓ "), type = "message")
1508
+
1509
+ }
1510
+ }
1511
+ )
1512
+
1513
+ # DOWNLOAD TABLE
1514
+ # -----------------------------------------------------------------------
1515
+ output$downloadTable <- downloadHandler(
1516
+ filename = function(file) {
1517
+
1518
+ weight_suffix <- switch(input$weight_type, # Add weight type to file export
1519
+ "no_weight" = "unweighted",
1520
+ "weight_demographic" = "demogweighted",
1521
+ "weight_sample" = "sampleweighted")
1522
+
1523
+ ifelse(input$tabs == "Histogram", paste0("hist_", outcome(), "_", weight_suffix, ".csv"),
1524
+ ifelse(input$tabs == "Time Series", paste0("ts_", outcome(), "_", weight_suffix,".csv"),
1525
+ ifelse(input$tabs == "Cross Country", paste0("cc_", outcome(), "_", weight_suffix, ".csv"),
1526
+ ifelse(input$tabs == "World Map", paste0("map_", outcome(), "_", weight_suffix, ".csv"),
1527
+ paste0("mover_", outcome(), "_", weight_suffix, ".csv")))))
1528
+ },
1529
+ content = function(file) {
1530
+ if(input$tabs == "Histogram") {
1531
+ write.csv(histd(), file, row.names=F)
1532
+ showNotification(HTML("Histogram file download complete ✓ "),
1533
+ type = "message")
1534
+
1535
+ } else if (input$tabs == "Time Series") {
1536
+ write.csv(tsd(), file, row.names=F)
1537
+ showNotification(HTML("Time series file download complete ✓ "),
1538
+ type = "message")
1539
+
1540
+ } else if (input$tabs == "Cross Country") {
1541
+ write.csv(ccd(), file, row.names=F)
1542
+ showNotification(HTML("Cross country file download complete ✓ "),
1543
+ type = "message")
1544
+
1545
+ } else if (input$tabs == "World Map") {
1546
+ write.csv(mapd(), file, row.names=F)
1547
+ showNotification(HTML("Map file download complete ✓ "),
1548
+ type = "message")
1549
+
1550
+ } else {
1551
+ write.csv(moverd(), file, row.names=F)
1552
+ showNotification(HTML("Break down file download complete ✓ "),
1553
+ type = "message")
1554
+
1555
+ }
1556
+ }
1557
+ )
1558
+ }
1559
+
1560
+ # RUN APP
1561
+ # # -----------------------------------------------------------------------
1562
+ shinyApp(ui, server)
1563
+
1564
+ # # -----------------------------------------------------------------------
1565
+ # END
1566
+ # # -----------------------------------------------------------------------
cses-shiny.Rproj ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Version: 1.0
2
+ ProjectId: 77d77540-4c5c-492a-b2c3-65b0f1499688
3
+
4
+ RestoreWorkspace: Default
5
+ SaveWorkspace: Default
6
+ AlwaysSaveHistory: Default
7
+
8
+ EnableCodeIndexing: Yes
9
+ UseSpacesForTab: Yes
10
+ NumSpacesForTab: 2
11
+ Encoding: UTF-8
12
+
13
+ RnwWeave: Sweave
14
+ LaTeX: pdfLaTeX
15
+
16
+ AutoAppendNewline: Yes
17
+ StripTrailingWhitespace: Yes
cses_labs.rds ADDED
Binary file (864 Bytes). View file
 
cses_labs_sec.rds ADDED
Binary file (1.44 kB). View file
 
cses_shiny_data.rda ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e3cad18ac3c75feeb3b170223f2ba6e11bfa690c08bb3bfa53e1ed9379381692
3
+ size 3058556
cses_shiny_data.rds ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:698677f42d76fca9de837613d8b303e192033d81f1db33fd1f30f011244ed906
3
+ size 4950894
cses_variable_labels.csv ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "column_name","question_short_en","category_short_en","display_en","question_en","responses_en","responses_en_rec"
2
+ "IMD2001_2","Age of respondent (in categories)","Demographic Data","Demographic Data: AGE OF RESPONDENT (IN CATEGORIES) (IMD2001_2)","Age of respondent (in categories listed below).","(1) YOUNGEST - 24 YEARS (2) 25-34 YEARS (3) 35-44 YEARS (4) 45-54 YEARS (5) 55-64 YEARS (6) 65 - OLDEST","(1) Youngest - 24 years (2) 25-34 years (3) 35-44 years (4) 45-54 years (5) 55-64 years (6) 65 - oldest"
3
+ "IMD2002","Gender","Demographic Data","Demographic Data: GENDER (IMD2002)","Gender of Respondent.","(1) MALE (2) FEMALE (3) OTHER","(1) Male (2) Female (3) Other"
4
+ "IMD2003","Education","Demographic Data","Demographic Data: EDUCATION (IMD2003)","Education of Respondent.","(0) NONE (NO EDUCATION) / ILLITERATE (1) PRIMARY EDUCATION/LOWER SECONDARY EDUCATION (2) HIGHER SECONDARY EDUCATION (3) POST-SECONDARY (NON-UNIVERSITY) EDUCATION (4) UNIVERSITY EDUCATION","(0) None (no education) / illiterate (1) Primary education/lower secondary education (2) Higher secondary education (3) Post-secondary (non-university) Education (4) University education"
5
+ "IMD2004","Marital status","Demographic Data","Demographic Data: MARITAL STATUS (IMD2004)","Respondent's marital or civil union status.","(1) MARRIED OR LIVING TOGETHER AS MARRIED (2) WIDOWED (3) DIVORCED OR SEPARATED (MARRIED BUT SEPARATED/NOT LIVING WITH LEGAL SPOUSE) (4) SINGLE, NEVER MARRIED (5) [SEE ELECTION STUDY NOTES]","(1) Married or living together as married (2) Widowed (3) Divorced or separated (married but separated/not living with legal spouse) (4) Single, never married (5) [see election study notes]"
6
+ "IMD2005_1","Religious services attendance","Demographic Data","Demographic Data: RELIGIOUS SERVICES ATTENDANCE (IMD2005_1)","Religious denomination.","(1) NEVER (2) ONCE A YEAR (3) TWO TO ELEVEN TIMES A YEAR (4) ONCE A MONTH (5) TWO OR MORE TIMES A MONTH (6) ONCE A WEEK/MORE THAN ONCE A WEEK","(1) Never (2) Once a year (3) Two to eleven times a year (4) Once a month (5) Two or more times a month (6) Once a week/more than once a week"
7
+ "IMD2005_2","Religiosity","Demographic Data","Demographic Data: RELIGIOSITY (IMD2005_2)","Religiosity.","(1) HAVE NO RELIGIOUS BELIEFS (2) NOT VERY RELIGIOUS (3) SOMEWHAT RELIGIOUS (4) VERY RELIGIOUS","(1) Have no religious beliefs (2) Not very religious (3) Somewhat religious (4) Very religious"
8
+ "IMD2006","Household income","Demographic Data","Demographic Data: HOUSEHOLD INCOME (IMD2006)","Household income quintile appropriate to the respondent.","(1) LOWEST HOUSEHOLD INCOME QUINTILE (2) SECOND HOUSEHOLD INCOME QUINTILE (3) THIRD HOUSEHOLD INCOME QUINTILE (4) FOURTH HOUSEHOLD INCOME QUINTILE (5) HIGHEST HOUSEHOLD INCOME QUINTILE","(1) Lowest household income quintile (2) Second household income quintile (3) Third household income quintile (4) Fourth household income quintile (5) Highest household income quintile"
9
+ "IMD2007","Rural or urban residence","Demographic Data","Demographic Data: RURAL OR URBAN RESIDENCE (IMD2007)","Rural/Urban Residence.","(1) RURAL AREA OR VILLAGE (2) SMALL OR MIDDLE-SIZED TOWN (3) SUBURBS OF LARGE TOWN OR CITY (4) LARGE TOWN OR CITY","(1) Rural area or village (2) Small or middle-sized town (3) Suburbs of large town or city (4) Large town or city"
10
+ "IMD2014","Current employment status","Demographic Data","Demographic Data: CURRENT EMPLOYMENT STATUS (IMD2014)","Current employment status of respondent.","(0) EMPLOYED : NO HOURS SPECIFIED (1) EMPLOYED : FULL-TIME (32 OR MORE HOURS WEEKLY) (2) EMPLOYED : PART-TIME (15-32 HOURS WEEKLY) (3) EMPLOYED : LESS THAN 15 HOURS (4) HELPING FAMILY MEMBER (5) UNEMPLOYED (6) STUDENT, IN SCHOOL, IN VOCATIONAL TRAINING (7) RETIRED (8) HOMEMAKER, HOME DUTIES (9) PERMANENTLY DISABLED (10) OTHERS, NOT IN LABOR FORCE (11) ON TEMPORARY JOB LEAVE (MATERNITY LEAVE, SICK LEAVE, ETC.) (12) CIVIL / MILITARY SERVICE","(0) Employed : no hours specified (1) Employed : full-time (32 or more hours weekly) (2) Employed : part-time (15-32 hours weekly) (3) Employed : less than 15 hours (4) Helping family member (5) Unemployed (6) Student, in school, in vocational training (7) Retired (8) Homemaker, home duties (9) Permanently disabled (10) Others, not in labor force (11) On temporary job leave (maternity leave, sick leave, etc.) (12) Civil / military service"
11
+ "IMD2016","Socio economic status","Demographic Data","Demographic Data: SOCIO ECONOMIC STATUS (IMD2016)","Respondent's socio-economic status.","(1) WHITE COLLAR (2) WORKER (3) FARMER (4) SELF-EMPLOYED (5) OTHER","(1) White collar (2) Worker (3) Farmer (4) Self-employed (5) Other"
12
+ "IMD2019_1","Union membership of respondent","Demographic Data","Demographic Data: UNION MEMBERSHIP OF RESPONDENT (IMD2019_1)","Union membership of respondent.","(0) R IS NOT A MEMBER OF A UNION (1) R IS MEMBER OF A UNION","(0) R is not a member of a union (1) R is member of a union"
13
+ "IMD3001","Turnout: main election","Survey Data","Survey Data: TURNOUT: MAIN ELECTION (IMD3001)","Whether or not the respondent cast a ballot in the main election.","(0) RESPONDENT DID NOT CAST A BALLOT (1) RESPONDENT CAST A BALLOT","(0) Respondent did not cast a ballot (1) Respondent cast a ballot"
14
+ "IMD3001_PR_1","Turnout: current presidential election - round 1","Survey Data","Survey Data: TURNOUT: CURRENT PRESIDENTIAL ELECTION - ROUND 1 (IMD3001_PR_1)","Whether or not the respondent cast a ballot in the first/second round of the Presidential elections.","(0) RESPONDENT DID NOT CAST A BALLOT (1) RESPONDENT CAST A BALLOT","(0) Respondent did not cast a ballot (1) Respondent cast a ballot"
15
+ "IMD3001_PR_2","Turnout: current presidential election - round 2","Survey Data","Survey Data: TURNOUT: CURRENT PRESIDENTIAL ELECTION - ROUND 2 (IMD3001_PR_2)","Whether or not the respondent cast a ballot in the first/second round of the Presidential elections.","(0) RESPONDENT DID NOT CAST A BALLOT (1) RESPONDENT CAST A BALLOT","(0) Respondent did not cast a ballot (1) Respondent cast a ballot"
16
+ "IMD3001_LH","Turnout: current lower house election","Survey Data","Survey Data: TURNOUT: CURRENT LOWER HOUSE ELECTION (IMD3001_LH)","Whether or not the respondent cast a ballot in the lower house election.","(0) RESPONDENT DID NOT CAST A BALLOT (1) RESPONDENT CAST A BALLOT","(0) Respondent did not cast a ballot (1) Respondent cast a ballot"
17
+ "IMD3001_UH","Turnout: current upper house election","Survey Data","Survey Data: TURNOUT: CURRENT UPPER HOUSE ELECTION (IMD3001_UH)","Whether or not the respondent cast a ballot in the upper house election.","(0) RESPONDENT DID NOT CAST A BALLOT (1) RESPONDENT CAST A BALLOT","(0) Respondent did not cast a ballot (1) Respondent cast a ballot"
18
+ "IMD3001_TS","Turnout: turnout switcher between current","Survey Data","Survey Data: TURNOUT: TURNOUT SWITCHER BETWEEN CURRENT (IMD3001_TS)","Whether or not the respondent reports voting in the current and previous election.","(0) RESPONDENT ABSTAINED IN BOTH ELECTIONS (1) RESPONDENT ABSTAINED IN CURRENT ELECTION BUT VOTED IN PREVIOUS ELECTION (2) RESPONDENT VOTED IN CURRENT ELECTION BUT ABSTAINED IN PREVIOUS ELECTION (3) RESPONDENT VOTED IN BOTH CURRENT AND PREVIOUS ELECTION (5) RESPONDENT ABSTAINED IN CURRENT ELECTION BUT INELIGIBLE TO VOTE IN PREVIOUS ELECTION (6) RESPONDENT VOTED IN CURRENT ELECTION BUT INELIGIBLE TO VOTE IN PREVIOUS ELECTION","(0) Respondent abstained in both elections (1) Respondent abstained in current election but voted in previous election (2) Respondent voted in current election but abstained in previous election (3) Respondent voted in both current and previous election (5) Respondent abstained in current election but ineligible to vote in previous election (6) Respondent voted in current election but ineligible to vote in previous election"
19
+ "IMD3002_OUTGOV","Vote choice: current main election - vote for outgoing government (incumbent)","Survey Data","Survey Data: VOTE CHOICE: CURRENT MAIN ELECTION - VOTE FOR OUTGOING GOVERNMENT (INCUMBENT) (IMD3002_OUTGOV)","Whether or not the respondent cast a ballot for the outgoing incumbent.","(0) DID NOT VOTE FOR THE OUTGOING GOVERNMENT (INCUMBENT) (1) VOTED FOR THE OUTGOING GOVERNMENT (INCUMBENT)","(0) Did not vote for the outgoing government (incumbent) (1) Voted for the outgoing government (incumbent)"
20
+ "IMD3002_VS_1","Vote choice: vote switcher between current election and previous election","Survey Data","Survey Data: VOTE CHOICE: VOTE SWITCHER BETWEEN CURRENT ELECTION AND PREVIOUS ELECTION (IMD3002_VS_1)"," Whether or not the respondent reports voting for the same party/coalition in the current and previous main election.","(0) DID NOT SWITCH (VOTED FOR SAME PARTY/COALITION IN CURRENT & PREVIOUS ELECTION) (1) SWITCHER (CHANGED VOTE IN CURRENT ELECTION FROM PREVIOUS ELECTION)","(0) Did not switch (voted for same party/coalition in current & previous election) (1) Switcher (changed vote in current election from previous election)"
21
+ "IMD3002_LR_CSES","Vote choice: current main election - vote for leftist/center/rightist - cses","Survey Data","Survey Data: VOTE CHOICE: CURRENT MAIN ELECTION - VOTE FOR LEFTIST/CENTER/RIGHTIST - CSES (IMD3002_LR_CSES)","Whether or not the respondent reports voting for a leftist/center/rightist party/candidate of the party, based on CSES Collaborators experts' judgment of parties' ideology.","(1) VOTED FOR LEFTIST PARTY/CANDIDATE (2) VOTED FOR CENTER PARTY/CANDIDATE (3) VOTED FOR RIGHTIST PARTY/CANDIDATE","(1) Voted for leftist party/candidate (2) Voted for center party/candidate (3) Voted for rightist party/candidate"
22
+ "IMD3005_1","Party identification: are you close to any political party","Survey Data","Survey Data: PARTY IDENTIFICATION: ARE YOU CLOSE TO ANY POLITICAL PARTY (IMD3005_1)","Do you usually think of yourself as close to any particular party?","(0) NO (1) YES","(0) No (1) Yes"
23
+ "IMD3006","Ideology: left-right - self","Survey Data","Survey Data: IDEOLOGY: LEFT-RIGHT - SELF (IMD3006)","Respondents' self-placement on a 0-10 left-right scale.","(0) LEFT (10) RIGHT","(0) Left (10) Right"
24
+ "IMD3010","Satisfaction with democracy","Survey Data","Survey Data: SATISFACTION WITH DEMOCRACY (IMD3010)","On the whole, are you very satisfied, fairly satisfied, not very satisfied, or not at all satisfied with the way democracy works in [COUNTRY]?","(1) VERY SATISFIED (2) FAIRLY SATISFIED (3) NEITHER SATISFIED NOR DISSATISFIED (4) NOT VERY SATISFIED (0) NOT AT ALL SATISFIED","(1) Very satisfied (2) Fairly satisfied (3) Neither satisfied nor dissatisfied (4) Not very satisfied (0) Not at all satisfied"
25
+ "IMD3011","Efficacy: who is in power can make a difference","Survey Data","Survey Data: EFFICACY: WHO IS IN POWER CAN MAKE A DIFFERENCE (IMD3011)","Some people say that it doesn't make any difference who is in power. Others say that it makes a big difference who is in power. Using the scale on this card, (where ONE means that it doesn't make any difference who is in power and FIVE means that it makes a big difference who is in power), where would you place yourself?","(1) IT DOESN'T MAKE ANY DIFFERENCE WHO IS IN POWER (5) IT MAKES A BIG DIFFERENCE WHO IS IN POWER","(1) It doesn't make any difference who is in power (5) It makes a big difference who is in power"
26
+ "IMD3012","Efficacy: who people vote for makes a difference","Survey Data","Survey Data: EFFICACY: WHO PEOPLE VOTE FOR MAKES A DIFFERENCE (IMD3012)","Some people say that no matter who people vote for, it won't make any difference to what happens. Others say that who people vote for can make a big difference to what happens. Using the scale on this card, (where ONE means that voting won't make any difference to what happens and FIVE means that voting can make a big difference), where would you place yourself?","(1) WHO PEOPLE VOTE FOR WON'T MAKE ANY DIFFERENCE (5) WHO PEOPLE VOTE FOR CAN MAKE A BIG DIFFERENCE","(1) Who people vote for won't make any difference (5) Who people vote for can make a big difference"
27
+ "IMD3013_1","State of economy (over past 12 months)","Survey Data","Survey Data: STATE OF ECONOMY (OVER PAST 12 MONTHS) (IMD3013_1)","Would you say that over the past twelve months, the state of the economy in [COUNTRY] has gotten better, stayed about the same, or gotten worse?","(1) GOTTEN BETTER (3) STAYED THE SAME (5) GOTTEN WORSE","(1) Gotten better (3) Stayed the same (5) Gotten worse"
28
+ "IMD3013_2","State of economy - better","Survey Data","Survey Data: STATE OF ECONOMY - BETTER (IMD3013_2)","Would you say much better or somewhat better?","(1) MUCH BETTER (2) SOMEWHAT BETTER","(1) Much better (2) Somewhat better"
29
+ "IMD3013_3","State of economy - worse","Survey Data","Survey Data: STATE OF ECONOMY - WORSE (IMD3013_3)","Would you say much worse or somewhat worse?","(4) SOMEWHAT WORSE (5) MUCH WORSE","(4) Somewhat worse (5) Much worse"
30
+ "IMD3014","Government performance: general","Survey Data","Survey Data: GOVERNMENT PERFORMANCE: GENERAL (IMD3014)","Now thinking about the performance of the [government in [CAPITAL]/President] in general, how good or bad a job do you think the [government/President in [CAPITAL]] has done over the past [NUMBER OF YEARS SINCE LAST GOVERNMENT TOOK OFFICE, BEFORE THE CURRENT ELECTION] years? Has [it/he/she] done a very good job? A good job? A bad job? A very bad job?","(1) VERY GOOD JOB (2) GOOD JOB (3) BAD JOB (4) VERY BAD JOB","(1) Very good job (2) Good job (3) Bad job (4) Very bad job"
31
+ "IMD5006_1","Electoral turnout - turnout as a percentage of registered voters (er)","Macro Data","Macro Data: ELECTORAL TURNOUT - TURNOUT AS A PERCENTAGE OF REGISTERED VOTERS (ER) (IMD5006_1)","Official voter turnout - Percentage of the registered voters (ER).","(0 to 100). PERCENT OF REGISTERED VOTERS (ER) WHO VOTED","(0 to 100). Percent of registered voters (er) Who voted"
32
+ "IMD5006_2","Electoral turnout - turnout as a percentage of the voting age population (vap)","Macro Data","Macro Data: ELECTORAL TURNOUT - TURNOUT AS A PERCENTAGE OF THE VOTING AGE POPULATION (VAP) (IMD5006_2)","Official voter turnout - Percentage of Voting Age Population (VAP).","(0 to 100). PERCENT OF VOTING AGE POPULATION (VAP) WHO VOTED","(0 to 100). Percent of voting age population (vap) Who voted"
33
+ "IMD5007","Compulsory voting","Macro Data","Macro Data: COMPULSORY VOTING (IMD5007)","Is voting compulsory?","(1) YES; STRICTLY ENFORCED SANCTIONS (2) YES; WEAKLY ENFORCED SANCTIONS (3) YES; WITHOUT SANCTION FOR VIOLATION (0) NO","(1) Yes; strictly enforced sanctions (2) Yes; weakly enforced sanctions (3) Yes; without sanction for violation (0) No"
34
+ "IMD5013","Electoral formula in all segments: lower house","Macro Data","Macro Data: ELECTORAL FORMULA IN ALL SEGMENTS: LOWER HOUSE (IMD5013)","Whether the country uses a majoritarian formula, a proportional formula, or a mixed formula in all of its electoral segments/tiers.","(1) MAJORITARIAN (2) PROPORTIONAL (3) MIXED","(1) Majoritarian (2) Proportional (3) Mixed"
35
+ "IMD5014","Electoral formula: presidential election","Macro Data","Macro Data: ELECTORAL FORMULA: PRESIDENTIAL ELECTION (IMD5014)","The electoral formula used to elect the President that is elected by popular vote.","(1) PLURALITY (2) ABSOLUTE MAJORITY RULE (3) QUALIFIED MAJORITY RULE (4) ELECTORAL COLLEGE (5) ALTERNATIVE VOTE","(1) Plurality (2) Absolute majority rule (3) Qualified majority rule (4) Electoral college (5) Alternative vote"
36
+ "IMD5032_4","Post-election protest","Macro Data","Macro Data: POST-ELECTION PROTEST (IMD5032_4)","To what extent was there protest following the election?","(1) NO PROTEST AT ALL (2) SPORADIC PROTEST (3) SIGNIFICANT PROTEST","(1) No protest at all (2) Sporadic protest (3) Significant protest"
37
+ "IMD5033","Fairness of the election","Macro Data","Macro Data: FAIRNESS OF THE ELECTION (IMD5033)","How impartial was the body that administered the election law?","(1) VERY IMPARTIAL (2) MOSTLY IMPARTIAL (3) NOT VERY IMPARTIAL (4) NOT IMPARTIAL AT ALL","(1) Very impartial (2) Mostly impartial (3) Not very impartial (4) Not impartial at all"
38
+ "IMD5034_2","Election irregularities reported","Macro Data","Macro Data: ELECTION IRREGULARITIES REPORTED (IMD5034_2)","Were there irregularities reported by international election observers?","(0) NO (1) YES","(0) No (1) Yes"
39
+ "IMD5035","Number of parties participating in election","Macro Data","Macro Data: NUMBER OF PARTIES PARTICIPATING IN ELECTION (IMD5035)","How many political parties received votes in the election?","(1 to 900). NUMBER OF PARTIES","(1 to 900). Number of parties"
40
+ "IMD5036_3","Did any electoral alliance form?","Macro Data","Macro Data: DID ANY ELECTORAL ALLIANCE FORM? (IMD5036_3)","(If yes to IMD5036_1 or IMD5036_2) Did any electoral alliances form?","(0) NO (1) YES","(0) No (1) Yes"
41
+ "IMD5045_1","Average district magnitude - lowest segment (tier) - lower house","Macro Data","Macro Data: AVERAGE DISTRICT MAGNITUDE - LOWEST SEGMENT (TIER) - LOWER HOUSE (IMD5045_1)","Average district magnitude in the first or lowest electoral segment (tier).","(1 to 900). NUMBER OF SEATS ELECTED PER DISTRICT","(1 to 900). Number of seats elected per district"
42
+ "IMD5048","Regime: type of executive","Macro Data","Macro Data: REGIME: TYPE OF EXECUTIVE (IMD5048)","Classification of political regimes. ","(1) PARLIAMENTARY REGIME (2) MIXED REGIME (3) PRESIDENTIAL REGIME","(1) Parliamentary regime (2) Mixed regime (3) Presidential regime"
43
+ "IMD5049","Age of current regime","Macro Data","Macro Data: AGE OF CURRENT REGIME (IMD5049)","The number of years since the most recent regime change.","(0 to 500). AGE OF THE REGIME (YEARS)","(0 to 500). Age of the regime (years)"
44
+ "IMD5050_1","Freedom house rating - time t","Macro Data","Macro Data: FREEDOM HOUSE RATING - TIME T (IMD5050_1)","Freedom House's rating at three time periods (average of the ""Political Rights"" and ""Civil Liberties"" scores).","(1 to 7). FREEDOM HOUSE RATING SCORE","(1 to 7). Freedom house rating score"
45
+ "IMD5051_1","Democracy-autocracy - polity iv rating - time t","Macro Data","Macro Data: DEMOCRACY-AUTOCRACY - POLITY IV RATING - TIME T (IMD5051_1)","The POLITY IV ratings of institutionalized democracy versus autocracy in a country at three time periods.","(-10) AUTOCRATIC (10) DEMOCRATIC","(-10) Autocratic (10) Democratic"
46
+ "IMD5052_2","Gdp growth annual % (world bank) - time t-1 year","Macro Data","Macro Data: GDP GROWTH ANNUAL % (WORLD BANK) - TIME T-1 YEAR (IMD5052_2)","World Bank estimate of the annual GDP growth at three time periods.","(-20 to +25). PERCENT ANNUAL GROWTH","(-20 to +25). Percent annual growth"
47
+ "IMD5053_1","Gdp per capita, ppp (world bank) - time t","Macro Data","Macro Data: GDP PER CAPITA, PPP (WORLD BANK) - TIME T (IMD5053_1)","World Bank estimate of the GDP per capita at three time periods.","(0 to 899,999). GDP PER CAPITA","(0 to 899,999). Gdp per capita"
48
+ "IMD5054_2","Unemployment, total (world bank) - time t-1 year","Macro Data","Macro Data: UNEMPLOYMENT, TOTAL (WORLD BANK) - TIME T-1 YEAR (IMD5054_2)","World Bank estimates of the unemployment rate (% of total labor force) at three time periods.","(0 to 100). UNEMPLOYMENT RATE (% OF TOTAL LABOR FORCE)","(0 to 100). Unemployment rate (% of total labor force)"
49
+ "IMD5055_1","Human development index (unpd) - time t","Macro Data","Macro Data: HUMAN DEVELOPMENT INDEX (UNPD) - TIME T (IMD5055_1)","UNDP Human Development Index (HDI) at three time periods.","(0 to 1). HUMAN DEVELOPMENT INDEX","(0 to 1). Human development index"
50
+ "IMD5056_2","Inflation, gdp deflator (annual %) (world bank) - time t-1 year","Macro Data","Macro Data: INFLATION, GDP DEFLATOR (ANNUAL %) (WORLD BANK) - TIME T-1 YEAR (IMD5056_2)","World Bank estimate of Inflation at three time periods.","(-100 to 10000). INFLATION (ANNUAL %)","(-100 to 10000). Inflation (annual %)"
51
+ "IMD5057_1","Population, total (world bank) - time t","Macro Data","Macro Data: POPULATION, TOTAL (WORLD BANK) - TIME T (IMD5057_1)","World Bank estimates of the total population size, at three time periods.","(1000 to 9,999,999,999). POPULATION SIZE","(1000 to 9,999,999,999). Population size"
52
+ "IMD5058_1","Effective number of electoral parties","Macro Data","Macro Data: EFFECTIVE NUMBER OF ELECTORAL PARTIES (IMD5058_1)","Effective Number of Electoral Parties (ENEP).","(0 to 1500). EFFECTIVE NUMBER OF ELECTORAL PARTIES","(0 to 1500). Effective number of electoral parties"
world.rda ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:16b3c603996b162bed809ca5a638e7e8baca0bdabe08b9dea4f52b21557d5742
3
+ size 504800