cjerzak commited on
Commit
8ea2821
·
verified ·
1 Parent(s): da997e8

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +79 -289
app.R CHANGED
@@ -1,4 +1,4 @@
1
- #
2
  # ============================================================
3
  # app.R | Shiny App for Rerandomization with fastrerandomize
4
  # ============================================================
@@ -25,248 +25,6 @@ library(parallel) # For detecting CPU cores
25
  # install.packages("devtools")
26
  # devtools::install_github("cjerzak/fastrerandomize-software/fastrerandomize")
27
 
28
- # ---------------------------------------------------------
29
- # HELPER FUNCTIONS (BASE R)
30
- # ---------------------------------------------------------
31
-
32
- # 1) Compute Hotelling's T^2 in base R
33
- baseR_hotellingT2 <- function(X, W) {
34
- # For a single assignment W:
35
- # T^2 = (n0 * n1 / (n0 + n1)) * (xbar1 - xbar0)^T * S_inv * (xbar1 - xbar0)
36
- n <- length(W)
37
- n1 <- sum(W)
38
- n0 <- n - n1
39
- if (n1 == 0 || n0 == 0) return(NA_real_) # invalid scenario
40
- xbar_treat <- colMeans(X[W == 1, , drop = FALSE])
41
- xbar_control <- colMeans(X[W == 0, , drop = FALSE])
42
- diff_vec <- (xbar_treat - xbar_control)
43
-
44
- # covariance (pooled) – we just use cov(X)
45
- S <- cov(X)
46
- Sinv <- tryCatch(solve(S), error = function(e) NULL)
47
- if (is.null(Sinv)) {
48
- # fallback: diagonal approximation if solve fails
49
- Sinv <- diag(1 / diag(S), ncol(S))
50
- }
51
-
52
- out <- (n0 * n1 / (n0 + n1)) * c(t(diff_vec) %*% Sinv %*% diff_vec)
53
- out
54
- }
55
-
56
- # 2) Generate randomizations in base R, filtering by acceptance probability
57
- # using T^2 and keep the best (lowest) fraction.
58
- baseR_generate_randomizations <- function(n_units, n_treated, X, accept_prob, random_type,
59
- max_draws, batch_size) {
60
-
61
- # For safety, check if exact enumerations will explode:
62
- if (random_type == "exact") {
63
- n_comb_total <- choose(n_units, n_treated)
64
- if (n_comb_total > 1e6) {
65
- warning(
66
- sprintf("Exact randomization is requested, but that is %s combinations.
67
- This may be infeasible in terms of memory/time.
68
- Consider Monte Carlo instead.", format(n_comb_total, big.mark=",")),
69
- immediate. = TRUE
70
- )
71
- }
72
- }
73
-
74
- if (random_type == "exact") {
75
- # -------------- EXACT RANDOMIZATIONS --------------
76
- cidx <- combn(n_units, n_treated)
77
- # Build assignment matrix
78
- n_comb <- ncol(cidx)
79
- assignment_mat <- matrix(0, nrow = n_comb, ncol = n_units)
80
- for (i in seq_len(n_comb)) {
81
- assignment_mat[i, cidx[, i]] <- 1
82
- }
83
- # Compute T^2 for each row
84
- T2vals <- apply(assignment_mat, 1, function(w) baseR_hotellingT2(X, w))
85
- # Drop any NA (in pathological cases)
86
- keep_idx <- which(!is.na(T2vals))
87
- assignment_mat <- assignment_mat[keep_idx, , drop = FALSE]
88
- T2vals <- T2vals[keep_idx]
89
-
90
- # acceptance threshold
91
- cutoff <- quantile(T2vals, probs = accept_prob)
92
- keep_final <- (T2vals < cutoff)
93
- assignment_mat_accepted <- assignment_mat[keep_final, , drop = FALSE]
94
- T2vals_accepted <- T2vals[keep_final]
95
-
96
- } else {
97
- # -------------- MONTE CARLO RANDOMIZATIONS --------------
98
- # We'll sample max_draws permutations
99
- base_assign <- c(rep(1, n_treated), rep(0, n_units - n_treated))
100
-
101
- # We'll store T^2's in chunks to reduce memory overhead
102
- batch_count <- ceiling(max_draws / batch_size)
103
- all_assign <- list()
104
- all_T2 <- numeric(0)
105
-
106
- cur_draw <- 0
107
- for (b in seq_len(batch_count)) {
108
- ndraws_here <- min(batch_size, max_draws - cur_draw)
109
- cur_draw <- cur_draw + ndraws_here
110
-
111
- # sample permutations
112
- perms <- matrix(nrow = ndraws_here, ncol = n_units)
113
- for (j in seq_len(ndraws_here)) {
114
- perms[j, ] <- sample(base_assign)
115
- }
116
- # T^2 for each
117
- T2vals_batch <- apply(perms, 1, function(w) baseR_hotellingT2(X, w))
118
-
119
- # collect
120
- all_assign[[b]] <- perms
121
- all_T2 <- c(all_T2, T2vals_batch)
122
- }
123
- assignment_mat <- do.call(rbind, all_assign)
124
-
125
- # remove any NA
126
- keep_idx <- which(!is.na(all_T2))
127
- assignment_mat <- assignment_mat[keep_idx, , drop = FALSE]
128
- all_T2 <- all_T2[keep_idx]
129
-
130
- # acceptance threshold
131
- cutoff <- quantile(all_T2, probs = accept_prob)
132
- keep_final <- (all_T2 < cutoff)
133
- assignment_mat_accepted <- assignment_mat[keep_final, , drop = FALSE]
134
- T2vals_accepted <- all_T2[keep_final]
135
- }
136
-
137
- list(randomizations = assignment_mat_accepted, balance = T2vals_accepted)
138
- }
139
-
140
- # Helper: compute difference in means quickly
141
- diff_in_means <- function(Y, W) {
142
- mean(Y[W == 1]) - mean(Y[W == 0])
143
- }
144
-
145
- # Helper: for a given tau, relabel outcomes and compute the difference in means for a single permutation
146
- compute_diff_at_tau_for_oneW <- function(Wprime, obsY, obsW, tau) {
147
- # Y0_under_null = obsY - obsW * tau
148
- Y0 <- obsY - obsW * tau
149
- # Y1_under_null = Y0 + tau
150
- # But in practice, for assignment Wprime, the observed outcome is:
151
- # Y'(i) = Y0(i) if Wprime(i) = 0, or Y0(i) + tau if Wprime(i)=1
152
- Yprime <- Y0
153
- Yprime[Wprime == 1] <- Y0[Wprime == 1] + tau
154
- diff_in_means(Yprime, Wprime)
155
- }
156
-
157
- # 3a) For base R randomization test: difference in means + optional p-value
158
- # *without* fiducial interval
159
- # (We will incorporate the FI logic below.)
160
- baseR_randomization_test <- function(obsW, obsY, allW, findFI = FALSE, alpha = 0.05) {
161
- # Observed diff in means
162
- tau_obs <- diff_in_means(obsY, obsW)
163
-
164
- # for each candidate assignment, compute diff in means on obsY
165
- diffs <- apply(allW, 1, function(w) diff_in_means(obsY, w))
166
-
167
- # p-value = fraction whose absolute diff >= observed
168
- pval <- mean(abs(diffs) >= abs(tau_obs))
169
-
170
- # optionally compute a fiducial interval
171
- FI <- NULL
172
- if (findFI) {
173
- FI <- baseR_find_fiducial_interval(obsW, obsY, allW, tau_obs, alpha = alpha)
174
- }
175
-
176
- list(p_value = pval, tau_obs = tau_obs, FI = FI)
177
- }
178
-
179
- # 3b) The fiducial interval logic for base R, mirroring the approach in fastrerandomize:
180
- # 1) Attempt to find a wide lower and upper bracket via random updates
181
- # 2) Then a grid search in [lowerBound-1, upperBound*2] for which tau are accepted.
182
- baseR_find_fiducial_interval <- function(obsW, obsY, allW, tau_obs, alpha = 0.05, c_initial = 2,
183
- n_search_attempts = 500) {
184
-
185
- # random bracket approach
186
- lowerBound_est <- tau_obs - 3*tau_obs
187
- upperBound_est <- tau_obs + 3*tau_obs
188
-
189
- z_alpha <- qnorm(1 - alpha)
190
- k <- 2 / (z_alpha * (2 * pi)^(-1/2) * exp(-z_alpha^2 / 2))
191
-
192
- # For each iteration, pick one random assignment from allW
193
- # then see how the implied difference changes, and update the bracket
194
- n_allW <- nrow(allW)
195
- for (step_t in seq_len(n_search_attempts)) {
196
- # pick random assignment
197
- idx <- sample.int(n_allW, 1)
198
- Wprime <- allW[idx, ]
199
-
200
- # ~~~~~ update lowerBound ~~~~~
201
- # Y0 = obsY - obsW * lowerBound_est
202
- # Y'(Wprime) = ...
203
- lowerY0 <- obsY - obsW * lowerBound_est
204
- Yprime_lower <- lowerY0
205
- Yprime_lower[Wprime == 1] <- lowerY0[Wprime == 1] + lowerBound_est
206
-
207
- tau_at_step_lower <- diff_in_means(Yprime_lower, Wprime)
208
-
209
- c_step <- c_initial
210
- # difference from obs
211
- delta <- tau_obs - tau_at_step_lower
212
-
213
- if (tau_at_step_lower < tau_obs) {
214
- # move lowerBound up
215
- lowerBound_est <- lowerBound_est + k * delta * (alpha/2) / step_t
216
- } else {
217
- # move it down
218
- lowerBound_est <- lowerBound_est - k * (-delta) * (1 - alpha/2) / step_t
219
- }
220
-
221
- # ~~~~~ update upperBound ~~~~~
222
- upperY0 <- obsY - obsW * upperBound_est
223
- Yprime_upper <- upperY0
224
- Yprime_upper[Wprime == 1] <- upperY0[Wprime == 1] + upperBound_est
225
-
226
- tau_at_step_upper <- diff_in_means(Yprime_upper, Wprime)
227
- delta2 <- tau_at_step_upper - tau_obs
228
-
229
- if (tau_at_step_upper > tau_obs) {
230
- # move upperBound down
231
- upperBound_est <- upperBound_est - k * delta2 * (alpha/2) / step_t
232
- } else {
233
- # move it up
234
- upperBound_est <- upperBound_est + k * (-delta2) * (1 - alpha/2) / step_t
235
- }
236
- }
237
-
238
- # Now we do a grid search from (lowerBound_est - 1) to (upperBound_est * 2)
239
- # in e.g. 100 steps, seeing which tau is "accepted".
240
- # We'll define "accepted" if the min of:
241
- # fraction(tau_obs >= distribution_of(tau_pseudo))
242
- # fraction(tau_obs <= distribution_of(tau_pseudo))
243
- # is > alpha, i.e. do not reject
244
- grid_lower <- lowerBound_est - 1
245
- grid_upper <- upperBound_est * 2
246
- tau_seq <- seq(grid_lower, grid_upper, length.out = 100)
247
-
248
- accepted <- logical(length(tau_seq))
249
- for (i in seq_along(tau_seq)) {
250
- tau_pseudo <- tau_seq[i]
251
- # for each row in allW, compute the diff in means if the true effect = tau_pseudo
252
- # distribution_of(tau_pseudo)
253
- diffs_pseudo <- apply(allW, 1, function(wp) compute_diff_at_tau_for_oneW(wp, obsY, obsW, tau_pseudo))
254
- # Then see how often diffs_pseudo >= tau_obs (or <= tau_obs)
255
- frac_ge <- mean(diffs_pseudo >= tau_obs)
256
- frac_le <- mean(diffs_pseudo <= tau_obs)
257
- # min(...) is the typical "two-sided" approach
258
- accepted[i] <- (min(frac_ge, frac_le) > alpha / 2) # or 0.05 if we want 5% test
259
- }
260
-
261
- if (!any(accepted)) {
262
- # no values accepted => degenerate?
263
- # We'll return the bracket we found, or NA.
264
- return(c(NA, NA))
265
- }
266
-
267
- c(min(tau_seq[accepted]), max(tau_seq[accepted]))
268
- }
269
-
270
  # ---------------------------------------------------------
271
  # UI Section
272
  # ---------------------------------------------------------
@@ -481,7 +239,7 @@ ui <- dashboardPage(
481
  numericInput("max_draws", "Max Draws (MC)", value = 1e5, min = 1e3),
482
  numericInput("batch_size", "Batch Size (MC)", value = 1e3, min = 1e2)
483
  ),
484
- actionButton("generate_btn", "Generate Randomizations")
485
  ),
486
 
487
  box(width = 8, title = "Summary of Accepted Randomizations",
@@ -516,32 +274,49 @@ ui <- dashboardPage(
516
  tabName = "randtest",
517
 
518
  fluidRow(
519
- box(width = 4, title = "Randomization Test Setup",
520
- status = "primary", solidHeader = TRUE,
521
-
522
- radioButtons("outcome_source", "Outcome Data (Y):",
523
- choices = c("Simulate Y" = "simulate",
524
- "Upload CSV" = "uploadY"),
525
- selected = "simulate"),
526
-
527
- conditionalPanel(
528
- condition = "input.outcome_source == 'simulate'",
529
- numericInput("true_tau", "True Effect (simulate)", 1, step = 0.5),
530
- numericInput("noise_sd", "Noise SD for Y", 0.5, step = 0.1),
531
- actionButton("simulateY_btn", "Simulate Y")
532
- ),
533
- conditionalPanel(
534
- condition = "input.outcome_source == 'uploadY'",
535
- fileInput("file_outcomes", "Choose CSV File with outcome vector Y",
536
- accept = c(".csv")),
537
- helpText("Single column with length = #units.")
538
- ),
539
-
540
- br(),
541
- actionButton("run_randtest_btn", "Run Randomization Test"),
542
- checkboxInput("findFI", "Compute Fiducial Interval?", value = TRUE)
 
 
 
543
  ),
544
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
545
  box(width = 8, title = "Test Results", status = "info", solidHeader = TRUE,
546
 
547
  # First row: p-value and observed effect (fastrerandomize)
@@ -671,7 +446,7 @@ server <- function(input, output, session) {
671
  # =========== 2) base R generation timing ===========
672
  t0_base <- Sys.time()
673
  out_base <- tryCatch({
674
- baseR_generate_randomizations(
675
  n_units = nrow(X_data()),
676
  n_treated = input$n_treated,
677
  X = X_data(),
@@ -709,7 +484,7 @@ server <- function(input, output, session) {
709
  if (is.null(rr) || is.null(rr$balance)) {
710
  valueBox("---", "Min Balance Measure", icon = icon("question"), color = "orange")
711
  } else {
712
- minBal <- round(min(rr$balance), 4)
713
  valueBox(minBal, "Min Balance Measure", icon = icon("thumbs-up"), color = "blue")
714
  }
715
  })
@@ -742,8 +517,9 @@ server <- function(input, output, session) {
742
  df <- data.frame(balance = rr$balance)
743
  ggplot(df, aes(x = balance)) +
744
  geom_histogram(binwidth = diff(range(df$balance))/30, fill = "darkblue", alpha = 0.7) +
745
- labs(title = "Distribution of Balance Measure",
746
- x = "Balance (e.g. T^2)",
 
747
  y = "Frequency") +
748
  theme_minimal(base_size = 14)
749
  })
@@ -804,6 +580,24 @@ server <- function(input, output, session) {
804
  }
805
  })
806
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
807
  # The randomization test result:
808
  RandTestResult <- reactiveVal(NULL)
809
  RandTestResult_base <- reactiveVal(NULL)
@@ -857,7 +651,7 @@ server <- function(input, output, session) {
857
 
858
  t0_testbase <- Sys.time()
859
  outTestBase <- tryCatch({
860
- baseR_randomization_test(
861
  obsW = obsW,
862
  obsY = obsY,
863
  allW = rr_base$randomizations,
@@ -889,9 +683,9 @@ server <- function(input, output, session) {
889
  output$tauobs_box <- renderValueBox({
890
  rt <- RandTestResult()
891
  if (is.null(rt)) {
892
- valueBox("---", "Observed Effect (fastrerandomize)", icon = icon("question"), color = "maroon")
893
  } else {
894
- valueBox(round(rt$tau_obs, 4), "Observed Effect (fastrerandomize)", icon = icon("bullseye"), color = "maroon")
895
  }
896
  })
897
 
@@ -917,19 +711,14 @@ server <- function(input, output, session) {
917
  })
918
 
919
  # If we have a fiducial interval from fastrerandomize, display it
920
- output$fi_text <- renderUI({
921
- rt <- RandTestResult()
922
- if (is.null(rt) || is.null(rt$FI)) {
923
- return(NULL)
924
- }
925
- fi_lower <- round(rt$FI[1], 4)
926
- fi_upper <- round(rt$FI[2], 4)
927
-
928
- tagList(
929
- strong("Fiducial Interval (fastrerandomize, 95%):"),
930
- p(sprintf("[%.4f, %.4f]", fi_lower, fi_upper))
931
- )
932
- })
933
 
934
  # If we have a fiducial interval from base R, display it
935
  output$fi_text_baseR <- renderUI({
@@ -941,7 +730,7 @@ server <- function(input, output, session) {
941
  fi_upper <- round(rt$FI[2], 4)
942
 
943
  tagList(
944
- strong("Fiducial Interval (base R, 95%):"),
945
  p(sprintf("[%.4f, %.4f]", fi_lower, fi_upper))
946
  )
947
  })
@@ -973,3 +762,4 @@ server <- function(input, output, session) {
973
  # Run the Application
974
  # ---------------------------------------------------------
975
  shinyApp(ui = ui, server = server)
 
 
1
+ # install.packages("~/Documents/fastrerandomize-software/fastrerandomize",repos = NULL, type = "source",force = F)
2
  # ============================================================
3
  # app.R | Shiny App for Rerandomization with fastrerandomize
4
  # ============================================================
 
25
  # install.packages("devtools")
26
  # devtools::install_github("cjerzak/fastrerandomize-software/fastrerandomize")
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  # ---------------------------------------------------------
29
  # UI Section
30
  # ---------------------------------------------------------
 
239
  numericInput("max_draws", "Max Draws (MC)", value = 1e5, min = 1e3),
240
  numericInput("batch_size", "Batch Size (MC)", value = 1e3, min = 1e2)
241
  ),
242
+ actionButton("generate_btn", "Generate")
243
  ),
244
 
245
  box(width = 8, title = "Summary of Accepted Randomizations",
 
274
  tabName = "randtest",
275
 
276
  fluidRow(
277
+ box(
278
+ width = 4, title = "Randomization Test Setup",
279
+ status = "primary", solidHeader = TRUE,
280
+
281
+ # (Existing UI elements for Y already in your code)
282
+ radioButtons("outcome_source", "Outcome Data (Y):",
283
+ choices = c("Simulate Y" = "simulate",
284
+ "Upload CSV" = "uploadY"),
285
+ selected = "simulate"),
286
+
287
+ conditionalPanel(
288
+ condition = "input.outcome_source == 'simulate'",
289
+ numericInput("true_tau", "True Effect (simulate)", 1, step = 0.5),
290
+ numericInput("noise_sd", "Noise SD for Y", 0.5, step = 0.1),
291
+ actionButton("simulateY_btn", "Simulate Y")
292
+ ),
293
+
294
+ conditionalPanel(
295
+ condition = "input.outcome_source == 'uploadY'",
296
+ fileInput("file_outcomes", "Choose CSV File with outcome vector Y",
297
+ accept = c(".csv")),
298
+ helpText("Single column with length = #units.")
299
+ ),
300
+
301
+ br(),
302
+ actionButton("run_randtest_btn", "Run Test"),
303
+ checkboxInput("findFI", "Compute Fiducial Interval?", value = TRUE)
304
  ),
305
 
306
+ box(
307
+ width = 8, title = "Preview of Outcomes (Y)",
308
+ status = "info", solidHeader = TRUE,
309
+ DTOutput("outcomes_table")
310
+ )
311
+ ),
312
+
313
+ fluidRow(
314
+ box(
315
+ width = 4, title = NULL, status = NULL,
316
+ background = NULL, solidHeader = FALSE, collapsible = FALSE,
317
+ tags$p("Note: Relative speedups greatest for large number of accepted randomizations.",
318
+ style = "color:#555; font-size:90%; margin:0;")
319
+ ),
320
  box(width = 8, title = "Test Results", status = "info", solidHeader = TRUE,
321
 
322
  # First row: p-value and observed effect (fastrerandomize)
 
446
  # =========== 2) base R generation timing ===========
447
  t0_base <- Sys.time()
448
  out_base <- tryCatch({
449
+ generate_randomizations_R(
450
  n_units = nrow(X_data()),
451
  n_treated = input$n_treated,
452
  X = X_data(),
 
484
  if (is.null(rr) || is.null(rr$balance)) {
485
  valueBox("---", "Min Balance Measure", icon = icon("question"), color = "orange")
486
  } else {
487
+ minBal <- round(min(rr$balance), 3)
488
  valueBox(minBal, "Min Balance Measure", icon = icon("thumbs-up"), color = "blue")
489
  }
490
  })
 
517
  df <- data.frame(balance = rr$balance)
518
  ggplot(df, aes(x = balance)) +
519
  geom_histogram(binwidth = diff(range(df$balance))/30, fill = "darkblue", alpha = 0.7) +
520
+ labs(title = "Distribution of Balance Statistic",
521
+ subtitle = "Among Accepted Randomizations",
522
+ x = "Balance (i.e., T^2)",
523
  y = "Frequency") +
524
  theme_minimal(base_size = 14)
525
  })
 
580
  }
581
  })
582
 
583
+ # Render a preview of Y
584
+ output$outcomes_table <- renderDT({
585
+ req(Y_data()) # Make sure Y_data is not NULL
586
+
587
+ # Convert to data frame for DT
588
+ dfy <- data.frame(Y = Y_data())
589
+
590
+ # Optionally round numeric data
591
+ dfy[] <- lapply(dfy, function(col) {
592
+ if (is.numeric(col)) signif(col, 3) else col
593
+ })
594
+
595
+ datatable(
596
+ dfy,
597
+ options = list(scrollX = TRUE, pageLength = 5)
598
+ )
599
+ })
600
+
601
  # The randomization test result:
602
  RandTestResult <- reactiveVal(NULL)
603
  RandTestResult_base <- reactiveVal(NULL)
 
651
 
652
  t0_testbase <- Sys.time()
653
  outTestBase <- tryCatch({
654
+ randomization_test_R(
655
  obsW = obsW,
656
  obsY = obsY,
657
  allW = rr_base$randomizations,
 
683
  output$tauobs_box <- renderValueBox({
684
  rt <- RandTestResult()
685
  if (is.null(rt)) {
686
+ valueBox("---", "Observed Effect", icon = icon("question"), color = "maroon")
687
  } else {
688
+ valueBox(round(rt$tau_obs, 4), "Observed Effect", icon = icon("bullseye"), color = "maroon")
689
  }
690
  })
691
 
 
711
  })
712
 
713
  # If we have a fiducial interval from fastrerandomize, display it
714
+ #output$fi_text <- renderUI({
715
+ # rt <- RandTestResult()
716
+ # if (is.null(rt) || is.null(rt$FI)) {
717
+ # return(NULL)
718
+ # }
719
+ # fi_lower <- round(rt$FI[1], 4)
720
+ # fi_upper <- round(rt$FI[2], 4)
721
+ #})
 
 
 
 
 
722
 
723
  # If we have a fiducial interval from base R, display it
724
  output$fi_text_baseR <- renderUI({
 
730
  fi_upper <- round(rt$FI[2], 4)
731
 
732
  tagList(
733
+ strong("Fiducial Interval (95%):"),
734
  p(sprintf("[%.4f, %.4f]", fi_lower, fi_upper))
735
  )
736
  })
 
762
  # Run the Application
763
  # ---------------------------------------------------------
764
  shinyApp(ui = ui, server = server)
765
+