Skip to content

Commit c73dcd8

Browse files
committed
add files
1 parent 07b44a2 commit c73dcd8

2 files changed

Lines changed: 227 additions & 0 deletions

File tree

-3 Bytes
Binary file not shown.

misc/HF-space.R

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
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+
}

0 commit comments

Comments
 (0)