Shiny列联表实现任意单元格(含合计)输入自动补全其他值的方案
实现方案
1 UI改造
首先将原本用verbatimTextOutput渲染的所有合计栏,全部替换为numericInput组件,支持用户直接输入合计数值,修改后UI示例代码如下:
fluidRow(strong("Contingency Table")), fluidRow(helptext("Input the ")), fluidRow(column(width = 3), column(h5("Outcome Positive"), width = 3), column(h5("Outcome Negative"), width = 3), column(h5("Total"), width = 3)), fluidRow(column(h5("Intervention"), width = 3), column(numericInput(inputId = "int_pos", label = NULL, value = 117, min = 0), width = 3), column(numericInput(inputId = "int_neg", label = NULL, value = 138, min = 0), width = 3), column(numericInput(inputId = "int_tot", label = NULL, value = 255, min = 0), width = 3)), fluidRow(column(h5("Control"), width = 3), column(numericInput(inputId = "con_pos", label = NULL, value = 109, min = 0), width = 3), column(numericInput(inputId = "con_neg", label = NULL, value = 145, min = 0), width = 3), column(numericInput(inputId = "con_tot", label = NULL, value = 254, min = 0), width = 3)), fluidRow(column(h5("Total"), width = 3), column(numericInput(inputId = "tot_pos", label = NULL, value = 226, min = 0), width = 3), column(numericInput(inputId = "tot_neg", label = NULL, value = 283, min = 0), width = 3), column(numericInput(inputId = "tot_tot", label = NULL, value = 509, min = 0), width = 3)) )
2 服务端逻辑改造
核心思路是通过状态锁避免循环触发更新,监听所有输入框的变化,根据已输入的合法数值自动推导补全剩余单元格,所有计算结果自动校验为非负值,符合列联表的数值规则,示例代码如下:
server <- function(input, output, session) { # 状态锁,避免更新输入时循环触发监听 updating <- reactiveVal(FALSE) # 监听所有输入框变化 observe({ req(!updating()) updating(TRUE) on.exit(updating(FALSE)) # 收集所有当前输入值 int_pos <- input$int_pos int_neg <- input$int_neg int_tot <- input$int_tot con_pos <- input$con_pos con_neg <- input$con_neg con_tot <- input$con_tot tot_pos <- input$tot_pos tot_neg <- input$tot_neg tot_tot <- input$tot_tot # 干预组行计算逻辑:两个值已知时补全第三个 if (!is.na(int_pos) && !is.na(int_tot) && is.na(int_neg)) { updateNumericInput(session, "int_neg", value = max(0, int_tot - int_pos)) } if (!is.na(int_neg) && !is.na(int_tot) && is.na(int_pos)) { updateNumericInput(session, "int_pos", value = max(0, int_tot - int_neg)) } if (!is.na(int_pos) && !is.na(int_neg) && is.na(int_tot)) { updateNumericInput(session, "int_tot", value = int_pos + int_neg) } # 对照组行计算逻辑 if (!is.na(con_pos) && !is.na(con_tot) && is.na(con_neg)) { updateNumericInput(session, "con_neg", value = max(0, con_tot - con_pos)) } if (!is.na(con_neg) && !is.na(con_tot) && is.na(con_pos)) { updateNumericInput(session, "con_pos", value = max(0, con_tot - con_neg)) } if (!is.na(con_pos) && !is.na(con_neg) && is.na(con_tot)) { updateNumericInput(session, "con_tot", value = con_pos + con_neg) } # 阳性列计算逻辑 if (!is.na(int_pos) && !is.na(con_pos) && is.na(tot_pos)) { updateNumericInput(session, "tot_pos", value = int_pos + con_pos) } if (!is.na(tot_pos) && !is.na(con_pos) && is.na(int_pos)) { updateNumericInput(session, "int_pos", value = max(0, tot_pos - con_pos)) } if (!is.na(tot_pos) && !is.na(int_pos) && is.na(con_pos)) { updateNumericInput(session, "con_pos", value = max(0, tot_pos - int_pos)) } # 阴性列计算逻辑 if (!is.na(int_neg) && !is.na(con_neg) && is.na(tot_neg)) { updateNumericInput(session, "tot_neg", value = int_neg + con_neg) } if (!is.na(tot_neg) && !is.na(con_neg) && is.na(int_neg)) { updateNumericInput(session, "int_neg", value = max(0, tot_neg - con_neg)) } if (!is.na(tot_neg) && !is.na(int_neg) && is.na(con_neg)) { updateNumericInput(session, "con_neg", value = max(0, tot_neg - int_neg)) } # 总合计计算逻辑 if (!is.na(int_tot) && !is.na(con_tot) && is.na(tot_tot)) { updateNumericInput(session, "tot_tot", value = int_tot + con_tot) } if (!is.na(tot_pos) && !is.na(tot_neg) && is.na(tot_tot)) { updateNumericInput(session, "tot_tot", value = tot_pos + tot_neg) } }) }
3 扩展说明
如果需要处理多值冲突场景(如同一行三个值都被输入且不满足和的关系),可新增一个reactiveVal记录用户最后修改的输入ID,冲突时优先保留最后输入的数值,推导其余两个值即可。
内容的提问来源于stack exchange,提问作者cjdbarlow
相关产品推荐
相关产品推荐

