2025-02-25 09:51:42 +01:00
|
|
|
## Works, but not implemented
|
|
|
|
|
##
|
|
|
|
|
## These edits mainly allows for
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#' @title Module to Reorder the Levels of a Factor Variable
|
|
|
|
|
#'
|
|
|
|
|
#' @description
|
|
|
|
|
#' This module contain an interface to reorder the levels of a factor variable.
|
|
|
|
|
#'
|
|
|
|
|
#'
|
|
|
|
|
#' @param id Module ID.
|
|
|
|
|
#'
|
|
|
|
|
#' @return A [shiny::reactive()] function returning the data.
|
|
|
|
|
#' @export
|
|
|
|
|
#'
|
|
|
|
|
#' @importFrom shiny NS fluidRow tagList column actionButton
|
|
|
|
|
#' @importFrom shinyWidgets virtualSelectInput prettyCheckbox
|
|
|
|
|
#' @importFrom toastui datagridOutput
|
|
|
|
|
#' @importFrom htmltools tags
|
|
|
|
|
#'
|
|
|
|
|
#' @name update-factor
|
|
|
|
|
#'
|
|
|
|
|
update_factor_ui <- function(id) {
|
|
|
|
|
ns <- NS(id)
|
|
|
|
|
tagList(
|
|
|
|
|
tags$style(
|
|
|
|
|
".tui-grid-row-header-draggable span {width: 3px !important; height: 3px !important;}"
|
|
|
|
|
),
|
|
|
|
|
fluidRow(
|
|
|
|
|
column(
|
2026-03-27 21:54:19 +01:00
|
|
|
width = 3,
|
2025-05-10 13:02:04 +02:00
|
|
|
shinyWidgets::virtualSelectInput(
|
2025-02-25 09:51:42 +01:00
|
|
|
inputId = ns("variable"),
|
2026-03-27 21:54:19 +01:00
|
|
|
label = i18n$t("Choose variable:"),
|
2025-02-25 09:51:42 +01:00
|
|
|
choices = NULL,
|
|
|
|
|
width = "100%",
|
|
|
|
|
zIndex = 50
|
|
|
|
|
)
|
|
|
|
|
),
|
2026-03-27 21:54:19 +01:00
|
|
|
column(
|
|
|
|
|
width = 3,
|
|
|
|
|
class = "d-flex align-items-end",
|
|
|
|
|
actionButton(
|
|
|
|
|
disabled = TRUE,
|
|
|
|
|
inputId = ns("drop_levels"),
|
2026-04-01 23:41:23 +02:00
|
|
|
label = tagList(phosphoricons::ph("trash"), i18n$t("Drop empty")),
|
2026-03-27 21:54:19 +01:00
|
|
|
class = "btn-outline-primary mb-3",
|
|
|
|
|
width = "100%"
|
|
|
|
|
)
|
|
|
|
|
),
|
2025-02-25 09:51:42 +01:00
|
|
|
column(
|
|
|
|
|
width = 3,
|
|
|
|
|
class = "d-flex align-items-end",
|
|
|
|
|
actionButton(
|
|
|
|
|
inputId = ns("sort_levels"),
|
|
|
|
|
label = tagList(
|
2025-05-16 16:08:52 +02:00
|
|
|
phosphoricons::ph("sort-ascending"),
|
2025-09-10 12:00:03 +02:00
|
|
|
i18n$t("Sort by levels")
|
2025-02-25 09:51:42 +01:00
|
|
|
),
|
|
|
|
|
class = "btn-outline-primary mb-3",
|
|
|
|
|
width = "100%"
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
column(
|
|
|
|
|
width = 3,
|
|
|
|
|
class = "d-flex align-items-end",
|
|
|
|
|
actionButton(
|
|
|
|
|
inputId = ns("sort_occurrences"),
|
|
|
|
|
label = tagList(
|
2025-05-16 16:08:52 +02:00
|
|
|
phosphoricons::ph("sort-ascending"),
|
2025-09-10 12:00:03 +02:00
|
|
|
i18n$t("Sort by count")
|
2025-02-25 09:51:42 +01:00
|
|
|
),
|
|
|
|
|
class = "btn-outline-primary mb-3",
|
|
|
|
|
width = "100%"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
),
|
2025-05-10 13:02:04 +02:00
|
|
|
toastui::datagridOutput(ns("grid")),
|
2025-02-25 09:51:42 +01:00
|
|
|
tags$div(
|
|
|
|
|
class = "float-end",
|
2025-05-10 13:02:04 +02:00
|
|
|
shinyWidgets::prettyCheckbox(
|
2025-02-25 09:51:42 +01:00
|
|
|
inputId = ns("new_var"),
|
2026-03-27 21:54:19 +01:00
|
|
|
label = i18n$t(
|
|
|
|
|
"Create a new variable; otherwise replaces (Updating labels always creates new variable)"
|
|
|
|
|
),
|
2025-02-25 09:51:42 +01:00
|
|
|
value = FALSE,
|
|
|
|
|
status = "primary",
|
|
|
|
|
outline = TRUE,
|
|
|
|
|
inline = TRUE
|
|
|
|
|
),
|
|
|
|
|
actionButton(
|
|
|
|
|
inputId = ns("create"),
|
2026-02-23 13:22:52 +01:00
|
|
|
label = tagList(
|
|
|
|
|
phosphoricons::ph("arrow-clockwise"),
|
|
|
|
|
i18n$t("Update factor variable")
|
|
|
|
|
),
|
2025-02-25 09:51:42 +01:00
|
|
|
class = "btn-outline-primary"
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
tags$div(class = "clearfix")
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#' @param data_r A [shiny::reactive()] function returning a `data.frame`.
|
|
|
|
|
#'
|
|
|
|
|
#' @export
|
|
|
|
|
#'
|
|
|
|
|
#' @importFrom shiny moduleServer observeEvent reactive reactiveValues req bindEvent isTruthy updateActionButton
|
|
|
|
|
#' @importFrom shinyWidgets updateVirtualSelect
|
|
|
|
|
#' @importFrom toastui renderDatagrid datagrid grid_columns grid_colorbar
|
|
|
|
|
#'
|
|
|
|
|
#' @rdname update-factor
|
|
|
|
|
update_factor_server <- function(id, data_r = reactive(NULL)) {
|
2026-02-23 13:22:52 +01:00
|
|
|
moduleServer(id, function(input, output, session) {
|
|
|
|
|
rv <- reactiveValues(data = NULL, data_grid = NULL)
|
2025-02-25 09:51:42 +01:00
|
|
|
|
2026-02-23 13:22:52 +01:00
|
|
|
bindEvent(observe({
|
|
|
|
|
data <- data_r()
|
|
|
|
|
rv$data <- data
|
|
|
|
|
vars_factor <- vapply(data, is.factor, logical(1))
|
|
|
|
|
vars_factor <- names(vars_factor)[vars_factor]
|
|
|
|
|
updateVirtualSelect(
|
|
|
|
|
inputId = "variable",
|
|
|
|
|
choices = vars_factor,
|
|
|
|
|
selected = if (isTruthy(input$variable))
|
|
|
|
|
input$variable
|
|
|
|
|
else
|
|
|
|
|
vars_factor[1]
|
|
|
|
|
)
|
|
|
|
|
}), data_r(), input$hidden)
|
2025-02-25 09:51:42 +01:00
|
|
|
|
2026-02-23 13:22:52 +01:00
|
|
|
observeEvent(input$variable, {
|
|
|
|
|
data <- req(data_r())
|
|
|
|
|
variable <- req(input$variable)
|
|
|
|
|
grid <- as.data.frame(table(data[[variable]]))
|
|
|
|
|
rv$data_grid <- grid
|
|
|
|
|
})
|
2025-02-25 09:51:42 +01:00
|
|
|
|
2026-03-27 21:54:19 +01:00
|
|
|
observeEvent(rv$data_grid, {
|
|
|
|
|
variable <- req(input$variable)
|
|
|
|
|
if (isTRUE(has_empty_levels(rv$data[[variable]]))) {
|
|
|
|
|
# browser()
|
|
|
|
|
updateActionButton(inputId = "drop_levels", disabled = FALSE)
|
|
|
|
|
} else {
|
|
|
|
|
updateActionButton(inputId = "drop_levels", disabled = TRUE)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
observeEvent(input$drop_levels, {
|
|
|
|
|
rv$data_grid <- rv$data_grid[!rv$data_grid$Freq==0,]
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-23 13:22:52 +01:00
|
|
|
observeEvent(input$sort_levels, {
|
|
|
|
|
if (input$sort_levels %% 2 == 1) {
|
|
|
|
|
decreasing <- FALSE
|
|
|
|
|
label <- tagList(phosphoricons::ph("sort-descending"),
|
|
|
|
|
i18n$t("Sort by Levels"))
|
|
|
|
|
} else {
|
|
|
|
|
decreasing <- TRUE
|
|
|
|
|
label <- tagList(phosphoricons::ph("sort-ascending"),
|
|
|
|
|
i18n$t("Sort by Levels"))
|
|
|
|
|
}
|
|
|
|
|
updateActionButton(inputId = "sort_levels", label = label)
|
|
|
|
|
rv$data_grid <- rv$data_grid[order(rv$data_grid[[1]], decreasing = decreasing), ]
|
|
|
|
|
})
|
2025-02-25 09:51:42 +01:00
|
|
|
|
2026-02-23 13:22:52 +01:00
|
|
|
observeEvent(input$sort_occurrences, {
|
|
|
|
|
if (input$sort_occurrences %% 2 == 1) {
|
|
|
|
|
decreasing <- FALSE
|
|
|
|
|
label <- tagList(phosphoricons::ph("sort-descending"),
|
|
|
|
|
i18n$t("Sort by count"))
|
|
|
|
|
} else {
|
|
|
|
|
decreasing <- TRUE
|
|
|
|
|
label <- tagList(phosphoricons::ph("sort-ascending"),
|
|
|
|
|
i18n$t("Sort by count"))
|
|
|
|
|
}
|
|
|
|
|
updateActionButton(inputId = "sort_occurrences", label = label)
|
|
|
|
|
rv$data_grid <- rv$data_grid[order(rv$data_grid[[2]], decreasing = decreasing), ]
|
|
|
|
|
})
|
2025-02-25 09:51:42 +01:00
|
|
|
|
|
|
|
|
|
2026-02-23 13:22:52 +01:00
|
|
|
output$grid <- renderDatagrid({
|
|
|
|
|
req(rv$data_grid)
|
|
|
|
|
gridTheme <- getOption("datagrid.theme")
|
|
|
|
|
if (length(gridTheme) < 1) {
|
|
|
|
|
datamods:::apply_grid_theme()
|
|
|
|
|
}
|
|
|
|
|
on.exit(toastui::reset_grid_theme())
|
|
|
|
|
data <- rv$data_grid
|
|
|
|
|
data <- add_var_toset(data, "Var1", "New label")
|
2025-02-25 09:51:42 +01:00
|
|
|
|
2026-02-23 13:22:52 +01:00
|
|
|
grid <- datagrid(
|
|
|
|
|
data = data,
|
|
|
|
|
draggable = TRUE,
|
|
|
|
|
sortable = FALSE,
|
|
|
|
|
data_as_input = TRUE
|
|
|
|
|
)
|
|
|
|
|
grid <- grid_columns(
|
|
|
|
|
grid,
|
|
|
|
|
columns = c("Var1", "Var1_toset", "Freq"),
|
|
|
|
|
header = c(i18n$t("Levels"), "New label", i18n$t("Count"))
|
|
|
|
|
)
|
|
|
|
|
grid <- grid_colorbar(
|
|
|
|
|
grid,
|
|
|
|
|
column = "Freq",
|
|
|
|
|
label_outside = TRUE,
|
|
|
|
|
label_width = "30px",
|
|
|
|
|
background = "#D8DEE9",
|
|
|
|
|
bar_bg = datamods:::get_primary_color(),
|
|
|
|
|
from = c(0, max(rv$data_grid$Freq) + 1)
|
|
|
|
|
)
|
|
|
|
|
grid <- toastui::grid_style_column(grid = grid,
|
|
|
|
|
column = "Var1_toset",
|
|
|
|
|
fontStyle = "italic")
|
|
|
|
|
grid <- toastui::grid_editor(grid = grid,
|
|
|
|
|
column = "Var1_toset",
|
|
|
|
|
type = "text")
|
|
|
|
|
grid
|
|
|
|
|
})
|
2025-02-25 09:51:42 +01:00
|
|
|
|
2026-02-23 13:22:52 +01:00
|
|
|
data_updated_r <- reactive({
|
|
|
|
|
data <- req(data_r())
|
|
|
|
|
variable <- req(input$variable)
|
|
|
|
|
grid <- req(input$grid_data)
|
2025-12-02 13:58:37 +01:00
|
|
|
|
2026-02-23 13:22:52 +01:00
|
|
|
parameters <- list(
|
|
|
|
|
variable = variable,
|
|
|
|
|
new_variable = isTRUE(input$new_var) |
|
2026-03-11 10:17:42 +01:00
|
|
|
any(grid[["Var1_toset"]] != "New label"),
|
2026-02-23 13:22:52 +01:00
|
|
|
new_levels = as.character(grid[["Var1"]]),
|
|
|
|
|
new_labels = as.character(grid[["Var1_toset"]]),
|
|
|
|
|
ignore = "New label"
|
|
|
|
|
)
|
2025-12-02 13:58:37 +01:00
|
|
|
|
2026-02-23 13:22:52 +01:00
|
|
|
data <- tryCatch({
|
2026-03-27 21:54:19 +01:00
|
|
|
with_labels(data, {
|
2026-03-11 10:17:42 +01:00
|
|
|
rlang::exec(factor_new_levels_labels,
|
|
|
|
|
!!!modifyList(parameters, val = list(data = data)))
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-23 13:22:52 +01:00
|
|
|
}, error = function(err) {
|
|
|
|
|
showNotification(paste(
|
|
|
|
|
"We encountered the following error creating the new factor:",
|
|
|
|
|
err
|
|
|
|
|
),
|
2026-03-30 20:20:05 +02:00
|
|
|
type = "error")
|
2026-02-23 13:22:52 +01:00
|
|
|
})
|
2025-12-02 13:58:37 +01:00
|
|
|
|
2026-02-23 13:22:52 +01:00
|
|
|
# browser()
|
|
|
|
|
code <- rlang::call2("factor_new_levels_labels", !!!parameters, .ns = "FreesearchR")
|
|
|
|
|
attr(data, "code") <- code
|
2025-12-02 13:58:37 +01:00
|
|
|
|
2026-02-23 13:22:52 +01:00
|
|
|
data
|
|
|
|
|
})
|
2025-02-25 09:51:42 +01:00
|
|
|
|
2026-02-23 13:22:52 +01:00
|
|
|
data_returned_r <- observeEvent(input$create, {
|
|
|
|
|
rv$data <- data_updated_r()
|
|
|
|
|
})
|
|
|
|
|
return(reactive(rv$data))
|
|
|
|
|
})
|
2025-02-25 09:51:42 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-02 13:58:37 +01:00
|
|
|
#' Simple function to apply new levels and/or labels to factor
|
|
|
|
|
#'
|
|
|
|
|
#' @param variable factor variable
|
|
|
|
|
#' @param new_level new levels, same length as original
|
|
|
|
|
#' @param new_label new labels, same length as original
|
|
|
|
|
#' @param ignore character string to ignore in new labels
|
|
|
|
|
#'
|
|
|
|
|
#' @returns factor
|
|
|
|
|
#' @export
|
|
|
|
|
#'
|
|
|
|
|
#' @examples
|
|
|
|
|
#' data_n <- mtcars
|
|
|
|
|
#' data_n$cyl <- factor(data_n$cyl)
|
|
|
|
|
#' factor_new_levels_labels(data_n, "cyl", new_labels = c("four", "New label", "New label"))
|
2026-02-23 13:22:52 +01:00
|
|
|
factor_new_levels_labels <- function(data,
|
|
|
|
|
variable,
|
|
|
|
|
new_variable = TRUE,
|
|
|
|
|
new_levels = NULL,
|
|
|
|
|
new_labels = NULL,
|
|
|
|
|
ignore = "New label") {
|
2025-12-02 13:58:37 +01:00
|
|
|
if (!is.factor(data[[variable]])) {
|
|
|
|
|
return(data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is.null(new_levels)) {
|
|
|
|
|
new_levels <- levels(data[[variable]])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is.null(new_labels)) {
|
|
|
|
|
new_labels <- labels(data[[variable]])
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 13:22:52 +01:00
|
|
|
with_level <- factor(as.character(data[[variable]]), levels = new_levels)
|
|
|
|
|
with_label <- factor(with_level,
|
|
|
|
|
labels = ifelse(new_labels == "New label", new_levels, new_labels))
|
2025-12-02 13:58:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isTRUE(new_variable)) {
|
|
|
|
|
append_column(
|
|
|
|
|
data = data,
|
|
|
|
|
column = with_label,
|
2026-02-23 13:22:52 +01:00
|
|
|
name = unique_names(
|
|
|
|
|
new = paste0(variable, "_updated"),
|
|
|
|
|
existing = names(data)
|
|
|
|
|
)
|
2025-12-02 13:58:37 +01:00
|
|
|
)
|
|
|
|
|
} else {
|
2026-01-12 20:43:55 +01:00
|
|
|
data[[variable]] <- with_label
|
2025-12-02 13:58:37 +01:00
|
|
|
data
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-25 09:51:42 +01:00
|
|
|
|
|
|
|
|
#' @inheritParams shiny::modalDialog
|
|
|
|
|
#' @export
|
|
|
|
|
#'
|
|
|
|
|
#' @importFrom shiny showModal modalDialog textInput
|
|
|
|
|
#' @importFrom htmltools tagList
|
|
|
|
|
#'
|
|
|
|
|
#' @rdname update-factor
|
|
|
|
|
modal_update_factor <- function(id,
|
2025-09-10 12:00:03 +02:00
|
|
|
title = i18n$t("Update levels of a factor"),
|
2025-02-25 09:51:42 +01:00
|
|
|
easyClose = TRUE,
|
|
|
|
|
size = "l",
|
|
|
|
|
footer = NULL) {
|
|
|
|
|
ns <- NS(id)
|
2026-02-23 13:22:52 +01:00
|
|
|
showModal(
|
|
|
|
|
modalDialog(
|
|
|
|
|
title = tagList(title, datamods:::button_close_modal()),
|
|
|
|
|
update_factor_ui(id),
|
|
|
|
|
tags$div(
|
|
|
|
|
style = "display: none;",
|
|
|
|
|
textInput(
|
|
|
|
|
inputId = ns("hidden"),
|
|
|
|
|
label = NULL,
|
|
|
|
|
value = datamods:::genId()
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
easyClose = easyClose,
|
|
|
|
|
size = size,
|
|
|
|
|
footer = footer
|
|
|
|
|
)
|
|
|
|
|
)
|
2025-02-25 09:51:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#' @inheritParams shinyWidgets::WinBox
|
|
|
|
|
#' @export
|
|
|
|
|
#'
|
|
|
|
|
#' @importFrom shinyWidgets WinBox wbOptions wbControls
|
|
|
|
|
#' @importFrom htmltools tagList
|
2025-03-19 09:14:36 +01:00
|
|
|
#' @rdname update-factor
|
2025-02-25 09:51:42 +01:00
|
|
|
winbox_update_factor <- function(id,
|
2025-09-10 12:00:03 +02:00
|
|
|
title = i18n$t("Update levels of a factor"),
|
2025-02-25 09:51:42 +01:00
|
|
|
options = shinyWidgets::wbOptions(),
|
|
|
|
|
controls = shinyWidgets::wbControls()) {
|
|
|
|
|
ns <- NS(id)
|
|
|
|
|
WinBox(
|
|
|
|
|
title = title,
|
|
|
|
|
ui = tagList(
|
|
|
|
|
update_factor_ui(id),
|
2026-02-23 13:22:52 +01:00
|
|
|
tags$div(style = "display: none;", textInput(
|
|
|
|
|
inputId = ns("hidden"),
|
|
|
|
|
label = NULL,
|
|
|
|
|
value = genId()
|
|
|
|
|
))
|
2025-02-25 09:51:42 +01:00
|
|
|
),
|
|
|
|
|
options = modifyList(
|
|
|
|
|
shinyWidgets::wbOptions(height = "615px", modal = TRUE),
|
|
|
|
|
options
|
|
|
|
|
),
|
|
|
|
|
controls = controls,
|
|
|
|
|
auto_height = FALSE
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 13:58:37 +01:00
|
|
|
|
|
|
|
|
#' Make unique variable names
|
|
|
|
|
#'
|
|
|
|
|
#' Helper function to create new variable names that are unique
|
|
|
|
|
#' given a set of existing names (in a data set, for example).
|
|
|
|
|
#' If a variable name already exists, a number will be appended.
|
|
|
|
|
#'
|
|
|
|
|
#' @param new a vector of proposed new variable names
|
|
|
|
|
#' @param existing a vector of existing variable names
|
|
|
|
|
#' @return a vector of unique new variable names
|
|
|
|
|
#' @examples
|
|
|
|
|
#' unique_names(c("var_x", "var_y", "var_x"), c("var_x", "var_z"))
|
|
|
|
|
#'
|
|
|
|
|
#' @export
|
|
|
|
|
unique_names <- function(new, existing = character()) {
|
|
|
|
|
new_names <- make.unique(c(existing, new), sep = "_")
|
|
|
|
|
|
|
|
|
|
new_names[-seq_along(existing)]
|
|
|
|
|
}
|
2026-03-27 21:54:19 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
has_empty_levels <- function(x) {
|
|
|
|
|
if (is.factor(x)) {
|
|
|
|
|
any(!levels(x) %in% x)
|
|
|
|
|
} else {
|
|
|
|
|
return(FALSE)
|
|
|
|
|
}
|
|
|
|
|
}
|