我尝试使用Shiny App动态编辑data.frame。该表描述了项目的实验结构,因此将包括可变的实验因子,每个因子具有可变水平。对于多水平因子,我希望允许从下拉菜单中选择值。出于实际原因,我希望用户也能够在Excel/任何其他表格编辑器中编辑该表,所以将保存为csv太.所以应用程序应该:
- 重新加载本地csv表
- 在相应的单元格中使用允许值(因子水平)为因子创建下拉选项,
- 还允许手动编辑其他列中的值,
- 当点击“保存”按钮时,应用程序应保存更新的csv表,然后关闭。然而,当手动编辑按预期保存时,如下例所示,下拉选择不起作用。
require(shiny)
require(DT)
# Prepare mock data
wd <- getwd()
Factors <- c("Experiment", "Condition", "Replicate", "Genotype")
ExpMapNm <- "Experiment map"
ExpMapPath <- paste0(wd, "/", ExpMapNm, ".csv")
ExpMap <- data.frame(Experiment = "Exp1",
Sample = paste0("Sample", 1:20),
Condition = as.character(sapply(c("Treated", "Mock"), function(x) { rep(x, 10) })),
Genotype = as.character(sapply(c("KO", "WT"), function(x) { rep(x, 5) })),
Replicate = 1:5)
FactorsList <- setNames(lapply(Factors, function(x) { unique(ExpMap[[x]]) }), Factors)
#
# Users should be able to edit this table in two ways:
# - in Excel manually, hence why a local version is saved below and in server when closing the app
# - within the Shiny app
# In either case, the edited table will then be reloaded into r as a data.frame which will guide further data processing
if (!file.exists(ExpMapPath)) { write.csv(ExpMap, ExpMapPath, row.names = FALSE) }
ui <- shiny::fluidPage(shiny::titlePanel(ExpMapNm), # This is the name of the table
shiny::mainPanel(shiny::br(),
shiny::actionButton("saveBtn", "Save"),
DT::dataTableOutput("Data"),
verbatimTextOutput(Factors)
))
server <- function(input, output) {
Data <- read.csv(ExpMapPath)
for (Fact in Factors) {
if (length(FactorsList[[Fact]]) > 1) { # We only want to have a drop-down selection if a factor has more than one level.
# Thus, in this example we do not create a drop-down selection for factor Experiment.
lvls <- FactorsList[[Fact]] # Allowed values
for (i in 1:nrow(Data)) {
val <- Data[[Fact]][i]
dflt <- c(lvls[1], val)[(val %in% lvls)+1] # If the current value is not a valid level, revert to default (1st) level
# Otherwise keep current value, but still apply drop-down selection so choices can be changed
Data[[Fact]][i] <- as.character(selectInput(paste0(Fact, "_", i), NULL, lvls, dflt, width = "100px"))
}
} else { Data[[Fact]] <- FactorsList[[Fact]] }
}
output$Data <- DT::renderDataTable(
Data, escape = FALSE, selection = "none", server = FALSE,
editable = TRUE, # Non-drop-down fields can still be edited: this works
options = list(paging = TRUE, searching = TRUE, fixedColumns = TRUE, autoWidth = TRUE,
ordering = TRUE, dom = "Bfrtip"),
callback = JS("table.rows().every(function(i, tab, row) {
var $this = $(this.node());
$this.attr('id', this.data()[0]);
$this.addClass('shiny-input-container');
});
Shiny.unbindAll(table.table().node());
Shiny.bindAll(table.table().node());"),
class = "display")
shiny::observeEvent(input$Data_cell_edit, {
Data[input$Data_cell_edit$row, input$Data_cell_edit$col] <<- input$Data_cell_edit$value
})
shiny::observeEvent(input$saveBtn, {
# Hacky HTML cleanup
# I am not interested in getting cells with html formatting in my table, I just want the value!
# Moreover, this would clash with the code above when initiliazing the table and comparing values.
#print(Data$Condition[[1]])
for (Fact in Factors) {
if (length(FactorsList[[Fact]]) > 1) {
for (i in 1:nrow(Data)) {
val <- Data[[Fact]][i]
val <- gsub("^option value=\"[^\"]+\" selected>", "",
grep("^option value=\"[^\"]+\" selected>", unlist(strsplit(a, " *<|>[\n ]*<|> *$")), value = TRUE))
Data[[Fact]][i] <- val
}
}
}
# Check if I have managed to change from the default value:
print(Data$Condition[[1]])
# When testing, I change from "Treated" to "Mock" in the table, hit save, but it prints "Treated", and sure enough the table saved contains "Treated".
#
write.csv(Data, ExpMapPath, row.names = FALSE)
tstExpMap <<- Data # Another way to check table values
stopApp()
})
}
print(shiny::shinyApp(ui, server, options = list(launch.browser = TRUE)))
> sessionInfo()
R version 4.2.3 (2023-03-15 ucrt)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows Server x64 (build 20348)
Matrix products: default
locale:
[1] LC_COLLATE=English_United Kingdom.utf8 LC_CTYPE=English_United Kingdom.utf8 LC_MONETARY=English_United Kingdom.utf8
[4] LC_NUMERIC=C LC_TIME=English_United Kingdom.utf8
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] DT_0.26 shiny_1.7.4
loaded via a namespace (and not attached):
[1] Rcpp_1.0.9 rstudioapi_0.14 magrittr_2.0.3 xtable_1.8-4 R6_2.5.1 rlang_1.0.6 fastmap_1.1.0 tools_4.2.3
[9] aRmel_4.0.0.13 cli_3.5.0 jquerylib_0.1.4 htmltools_0.5.4 crosstalk_1.2.0 ellipsis_0.3.2 yaml_2.3.6 digest_0.6.31
[17] lifecycle_1.0.3 crayon_1.5.2 later_1.3.0 sass_0.4.4 htmlwidgets_1.6.0 promises_1.2.0.1 memoise_2.0.1 cachem_1.0.6
[25] mime_0.12 compiler_4.2.3 bslib_0.4.2 jsonlite_1.8.4 httpuv_1.6.7
我在StackOverflow上看过很多类似的帖子,比如https://github.com/ejbeaty/CellEdit/tree/master/js,DT:基于R shiny app和Edit datatable in shiny with dropdown selection (for DT v0.19)中另一列的selectinput动态更改列值,我实际上是基于这个应用程序的,但我一定是错过了什么,因为它对我不起作用。不幸的是,我对JavaScript一无所知,所以我很难弄清楚缺少了什么。
2条答案
按热度按时间juud5qan1#
下面是我的答案的修改(一般化)版本,它读入csv文件并允许将其保存回磁盘。
您可能需要调整一些东西以满足您的需要,但是,它们并不需要显示原理。
nr7wwzry2#
我想我现在解决了,非常感谢ismirsehregal!
澄清我的原始问题:
所提出的解决方案的问题是,从多水平因子列中进行的任何选择都会将之前的任何自由编辑还原为“其他”。为了解决此问题,我重写了代码,以便在定义了每列中允许的编辑类型后,由同一行代码处理这两个编辑。