Spaces:
Running
Running
Update app.R
Browse files
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
|
| 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(
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
),
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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),
|
| 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
|
| 746 |
-
|
|
|
|
| 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 |
-
|
| 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
|
| 893 |
} else {
|
| 894 |
-
valueBox(round(rt$tau_obs, 4), "Observed Effect
|
| 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 |
-
|
| 922 |
-
|
| 923 |
-
|
| 924 |
-
|
| 925 |
-
|
| 926 |
-
|
| 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 (
|
| 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 |
+
|