cjerzak commited on
Commit
aee2f01
·
verified ·
1 Parent(s): 0835b33

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +222 -53
app.R CHANGED
@@ -1,58 +1,227 @@
1
- library(shiny)
2
- library(bslib)
3
- library(dplyr)
4
- library(ggplot2)
 
 
 
 
5
 
6
- df <- readr::read_csv("penguins.csv")
7
- # Find subset of columns that are suitable for scatter plot
8
- df_num <- df |> select(where(is.numeric), -Year)
9
-
10
- ui <- page_sidebar(
11
- theme = bs_theme(bootswatch = "minty"),
12
- title = "Penguins explorer",
13
- sidebar = sidebar(
14
- varSelectInput("xvar", "X variable", df_num, selected = "Bill Length (mm)"),
15
- varSelectInput("yvar", "Y variable", df_num, selected = "Bill Depth (mm)"),
16
- checkboxGroupInput("species", "Filter by species",
17
- choices = unique(df$Species), selected = unique(df$Species)
18
- ),
19
- hr(), # Add a horizontal rule
20
- checkboxInput("by_species", "Show species", TRUE),
21
- checkboxInput("show_margins", "Show marginal plots", TRUE),
22
- checkboxInput("smooth", "Add smoother"),
23
- ),
24
- plotOutput("scatter")
25
- )
26
-
27
- server <- function(input, output, session) {
28
- subsetted <- reactive({
29
- req(input$species)
30
- df |> filter(Species %in% input$species)
31
  })
32
-
33
- output$scatter <- renderPlot(
34
- {
35
- p <- ggplot(subsetted(), aes(!!input$xvar, !!input$yvar)) +
36
- theme_light() +
37
- list(
38
- theme(legend.position = "bottom"),
39
- if (input$by_species) aes(color = Species),
40
- geom_point(),
41
- if (input$smooth) geom_smooth()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  )
43
-
44
- if (input$show_margins) {
45
- margin_type <- if (input$by_species) "density" else "histogram"
46
- p <- p |> ggExtra::ggMarginal(
47
- type = margin_type, margins = "both",
48
- size = 8, groupColour = input$by_species, groupFill = input$by_species
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  )
 
 
 
 
 
 
 
 
 
 
 
50
  }
51
-
52
- p
53
- },
54
- res = 100
55
- )
56
- }
57
-
58
- shinyApp(ui, server)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ # ====================================================================
3
+ # app.R – interactive demo for the DescriptiveRepresentation calculator
4
+ # Upload this single file to a Shiny‑typed Hugging Face Space.
5
+ # ====================================================================
6
+
7
+ # install.packages( "~/Documents/DescriptiveRepresentationCalculator-software/DescriptiveRepresentationCalculator", repos = NULL, type = "source",force = F)
8
+ options(error = NULL)
9
 
10
+ # ====================================================================
11
+ # app.R interactive demo for the Descriptive Representation Viewer
12
+ # Upload this single file to a Shiny‑typed Hugging Face Space.
13
+ # ====================================================================
14
+
15
+ options(error = NULL)
16
+ suppressPackageStartupMessages({
17
+ library(shiny)
18
+ library(ggplot2)
19
+ library(tidyr)
20
+ library(scales)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  })
22
+
23
+ # --------------------------------------------------------------------
24
+ # 1. Calculator functions (verbatim from Gerring, Jerzak & Öncel 2024)
25
+ # --------------------------------------------------------------------
26
+ library( DescriptiveRepresentationCalculator )
27
+
28
+ # --------------------------------------------------------------------
29
+ # 2. Shiny user interface
30
+ # --------------------------------------------------------------------
31
+ ui <- fluidPage(
32
+ # ---- Custom title block ---------------------------------------------------
33
+ tags$div(
34
+ style = "margin-top: 20px;",
35
+ tags$h2("Descriptive Representation Viewer"),
36
+ tags$p(
37
+ tags$a(href = "https://globalleadershipproject.net/",
38
+ tags$span("🔗 GlobalLeadershipProject.net"))
39
+ )
40
+ ),
41
+
42
+ sidebarLayout(
43
+ sidebarPanel(
44
+ numericInput("bodyN", "Size of the political body (N):",
45
+ value = 50, min = 1, step = 1),
46
+
47
+ sliderInput("ngroups", "Number of population groups (K):",
48
+ min = 2, max = 5, value = 3, step = 1),
49
+
50
+ uiOutput("popShareInputs"), # K – 1 numeric inputs + note
51
+
52
+ checkboxInput("hasBody", "I have the body’s member counts", FALSE),
53
+
54
+ conditionalPanel(
55
+ "input.hasBody == true",
56
+ uiOutput("bodyCountInputs")
57
+ ),
58
+
59
+ actionButton("go", "Compute", class = "btn-primary")
60
+ ),
61
+
62
+ mainPanel(
63
+ fluidRow(
64
+ column(4, verbatimTextOutput("expBox")),
65
+ column(4, verbatimTextOutput("obsBox")),
66
+ column(4, verbatimTextOutput("relBox"))
67
+ ),
68
+ hr(),
69
+ plotOutput("sharePlot", height = "400px"),
70
+ hr(),
71
+ helpText(
72
+ "Indices are based on the Rose Index (a = –0.5, b = 1). ",
73
+ "Expected values follow Gerring, Jerzak & Öncel (2024) ",
74
+ tags$a("[PDF]",
75
+ href = "https://www.cambridge.org/core/services/aop-cambridge-core/"
76
+ |> paste0("content/view/7EAEA1CA4C553AB9D76054D1FA9C0840/",
77
+ "S0003055423000680a.pdf/the-composition-of-",
78
+ "descriptive-representation.pdf"),
79
+ target = "_blank")
80
  )
81
+ )
82
+ )
83
+ )
84
+
85
+ # --------------------------------------------------------------------
86
+ # 3. Server logic
87
+ # --------------------------------------------------------------------
88
+ server <- function(input, output, session) {
89
+
90
+ # ----- Dynamic numericInputs for the first K‑1 shares ----------------------
91
+ output$popShareInputs <- renderUI({
92
+ n <- input$ngroups
93
+ if (n < 2) return()
94
+ tagList(
95
+ lapply(seq_len(n - 1), function(i) {
96
+ numericInput(
97
+ inputId = paste0("share", i),
98
+ label = paste("Share of group", LETTERS[i]),
99
+ value = round(1 / n, 3),
100
+ min = 0, max = 1, step = 0.001
101
+ )
102
+ }),
103
+ tags$p(
104
+ sprintf("The share of group %s is automatically set to 1 − (sum of the others).",
105
+ LETTERS[n]),
106
+ style = "font-style: italic;"
107
+ )
108
+ )
109
+ })
110
+
111
+ # ----- Dynamic numericInputs for group‑wise body counts --------------------
112
+ output$bodyCountInputs <- renderUI({
113
+ n <- input$ngroups
114
+ lapply(seq_len(n), function(i){
115
+ numericInput(
116
+ inputId = paste0("count", i),
117
+ label = paste("Members of group", LETTERS[i], "in body"),
118
+ value = 0, min = 0, step = 1
119
  )
120
+ })
121
+ })
122
+
123
+ # ----- Helper producing the full K‑vector of population shares -------------
124
+ popShares <- eventReactive(input$go, {
125
+ n <- input$ngroups
126
+ shares_first <- sapply(seq_len(n - 1),
127
+ function(i) input[[paste0("share", i)]])
128
+ if (anyNA(shares_first) || any(shares_first < 0)) {
129
+ showNotification("All shares must be non‑negative numbers.", type = "error")
130
+ return(NULL)
131
  }
132
+ remainder <- 1 - sum(shares_first)
133
+ if (remainder < -1e-6) {
134
+ showNotification("The entered shares exceed 1. Reduce them so that K‑1 shares "
135
+ |> paste("sum to at most 1."), type = "error")
136
+ return(NULL)
137
+ }
138
+ shares <- c(shares_first, max(remainder, 0))
139
+ setNames(shares, LETTERS[seq_len(n)])
140
+ })
141
+
142
+ # ----- Helper reading body counts into a named vector ----------------------
143
+ bodyCounts <- reactive({
144
+ req(input$hasBody)
145
+ n <- input$ngroups
146
+ counts <- sapply(seq_len(n), function(i) input[[paste0("count", i)]])
147
+ if (anyNA(counts) || any(counts < 0)) {
148
+ showNotification("Body counts must be non‑negative integers.", type = "error")
149
+ return(NULL)
150
+ }
151
+ setNames(counts, LETTERS[seq_len(n)])
152
+ })
153
+
154
+ # ----- Main computation on “Compute” --------------------------------------
155
+ observeEvent(input$go, {
156
+ validate(need(!is.null(popShares()), "Fix population shares first."))
157
+
158
+ # Expected representation
159
+ expVal <- ExpectedRepresentation(popShares(), input$bodyN)
160
+ output$expBox <- renderText(sprintf("Expected representation:\n%.3f", expVal))
161
+
162
+ # Observed / relative representation (if counts supplied)
163
+ if (input$hasBody) {
164
+ validate(need(!is.null(bodyCounts()), "Enter valid body counts."))
165
+ counts <- bodyCounts()
166
+ bodyTotal <- sum(counts)
167
+ if (bodyTotal == 0) {
168
+ showNotification("Total body member count cannot be zero.", type = "error")
169
+ output$obsBox <- renderText("Observed representation:\n—")
170
+ output$relBox <- renderText("Relative representation:\n—")
171
+ return()
172
+ }
173
+ if (bodyTotal != input$bodyN) {
174
+ showNotification(
175
+ sprintf("Sum of counts (%d) differs from N (%d). We use the counts.",
176
+ bodyTotal, input$bodyN),
177
+ type = "warning", duration = 7
178
+ )
179
+ }
180
+ bodyShares <- counts / bodyTotal
181
+ obsVal <- ObservedRepresentation(NULL, popShares(),
182
+ BodyShares = bodyShares)
183
+ relVal <- RelativeRepresentation(obsVal, expVal)
184
+ output$obsBox <- renderText(sprintf("Observed representation:\n%.3f", obsVal))
185
+ output$relBox <- renderText(sprintf("Relative representation:\n%.3f", relVal))
186
+ } else {
187
+ output$obsBox <- renderText("Observed representation:\n—")
188
+ output$relBox <- renderText("Relative representation:\n—")
189
+ }
190
+ }, ignoreNULL = TRUE)
191
+
192
+ # ----- Bar plot ------------------------------------------------------------
193
+ output$sharePlot <- renderPlot({
194
+ req(popShares())
195
+
196
+ # Body shares (only if counts provided)
197
+ if (input$hasBody && !is.null(bodyCounts())) {
198
+ counts <- bodyCounts()
199
+ bodyShares <- counts / sum(counts)
200
+ } else {
201
+ bodyShares <- rep(NA_real_, length(popShares()))
202
+ }
203
+
204
+ df <- data.frame(
205
+ Group = factor(names(popShares()), levels = names(popShares())),
206
+ Population = as.numeric(popShares()),
207
+ Body = as.numeric(bodyShares)
208
+ )
209
+
210
+ df_long <- pivot_longer(df, -Group, names_to = "Type", values_to = "Share")
211
+
212
+ ggplot(df_long, aes(Group, Share, fill = Type)) +
213
+ geom_col(position = position_dodge(width = 0.6),
214
+ width = 0.55, na.rm = TRUE) +
215
+ scale_y_continuous(labels = percent_format(accuracy = 1)) +
216
+ scale_fill_manual(values = c(Population = "grey60", Body = "steelblue")) +
217
+ labs(x = NULL, y = "Share", fill = NULL) +
218
+ theme_minimal(base_size = 14) +
219
+ theme(legend.position = "bottom")
220
+ })
221
+ }
222
+
223
+ # --------------------------------------------------------------------
224
+ # 4. Launch the app
225
+ # --------------------------------------------------------------------
226
+ shinyApp(ui, server)
227
+ }