How to reactively format data table columns? - r

In the simplified functioning code at the bottom, in the colDefs = list() section of datatable() under renderDT(), in server section, I manually center-align the two right-most column outputs of the table using the instructions targets = 1:2, class = "dt-center".
I've been trying to make the number of columns that are formatted in this manner reactive, based on the actual number of columns detected in the output table -- because in the full code this is extracted from, the number of output table columns varies based on the actual composition of the data. In the below code commented out with # you can see my latest attempt to reactively format the columns, and of course it doesn't work.
Please, how do I reactively format the data table columns where all columns to the right of the header rows are center-aligned?
Simplified functioning code:
library(dplyr)
library(DT)
library(shiny)
library(shinyWidgets)
library(tidyverse)
ui <-
fluidPage(
fluidRow(
column(width = 8,
h3("Data table:"),
tableOutput("data"),
h3("Sum the data table columns:"),
radioButtons(inputId = "grouping",
label = NULL,
choiceNames = c("By period 1", "By period 2"),
choiceValues = c("Period_1", "Period_2"),
selected = "Period_1",
inline = TRUE),
DT::dataTableOutput("sums")
)
)
)
server <- function(input, output, session) {
data <- reactive({
data.frame(
Period_1 = c("2020-01", "2020-02", "2020-03", "2020-01", "2020-02", "2020-03"),
Period_2 = c(1, 2, 3, 3, 1, 2),
ColA = c(1000.01, 20, 30, 40, 50, 60),
ColB = c(15.06, 25, 35, 45, 55, 65)
)
})
summed_data <- reactive({
data() %>%
group_by(!!sym(input$grouping)) %>%
select("ColA","ColB") %>% summarise(across(everything(), sum))
})
output$data <- renderTable(data())
output$sums <- renderDT({
datatable(
data = summed_data(),
rownames = FALSE,
options =
list(
columnDefs = list(
list(targets = 0, class = "dt-left"),
list(targets = 1:2, class = "dt-center")
# list(targets = 1:ncol(summed_data()), class = "dt-center")
)
),
)
})
}
shinyApp(ui, server)

The following seems to work. However, I'm not sure why your initial approach doesn't work - it looks good to me.
library(dplyr)
library(DT)
library(shiny)
library(shinyWidgets)
library(tidyverse)
ui <-
fluidPage(fluidRow(
column(
width = 8,
h3("Data table:"),
tableOutput("data"),
h3("Sum the data table columns:"),
radioButtons(
inputId = "grouping",
label = NULL,
choiceNames = c("By period 1", "By period 2"),
choiceValues = c("Period_1", "Period_2"),
selected = "Period_1",
inline = TRUE
),
DT::dataTableOutput("sums")
)
))
server <- function(input, output, session) {
data <- reactive({
data.frame(
Period_1 = c("2020-01", "2020-02", "2020-03", "2020-01", "2020-02", "2020-03"),
Period_2 = c(1, 2, 3, 3, 1, 2),
ColA = c(1000.01, 20, 30, 40, 50, 60),
ColB = c(15.06, 25, 35, 45, 55, 65)
)
})
summed_data <- reactive({
data() %>%
group_by(!!sym(input$grouping)) %>%
select("ColA", "ColB") %>% summarise(across(everything(), sum))
})
output$data <- renderTable(data())
output$sums <- renderDT({
print(names(summed_data())[1])
datatable(
data = summed_data(),
rownames = FALSE,
options = list(columnDefs = list(
list(className = 'dt-left', targets = 0),
list(className = 'dt-center', targets = seq_len(ncol(summed_data())) - 1)
))
)
})
}
shinyApp(ui, server)

Related

Why does group_by() in dplyr not work when switching from sum to counting unique occurrences?

The below MWE code works fine for summing data frame values, whereby the user selects which type of period to group by in the radio buttons in the "Sum the data table columns:" section rendered at the bottom. This grouping is performed in the summed_data() object below in the server section.
However I'm also trying to count the number of occurrences where Period_2 == 1. When I comment out the currently uncommented summed_data() section below, and uncomment the currently commented-out summed_data() for performing unique row counts, and try running the code, it fails. But if I run this unique row count function in the R console, as shown immediately below, it works fine and gives the desired results (manually changing the "Period..." in the group_by(...) section)!
data <- data.frame(
ID = c(115,115,111,88,120,16),
Period_1 = c("2020-01", "2020-02", "2020-03", "2020-01", "2020-02", "2020-03"),
Period_2 = c(1, 2, 3, 1, 1, 4),
ColA = c(1000.01, 20, 30, 40, 50, 60),
ColB = c(15.06, 25, 35, 45, 55, 65)
)
filter(data, Period_2 == "1") %>%
group_by(Period_1) %>%
summarise(count = length(unique(ID)))
Period_1 count
<chr> <int>
1 2020-01 2
2 2020-02 1
So, to me the problems appears to lie in the dplyr code below group_by(!!sym(input$grouping)). Does anyone have a suggestion for solving this?
MWE code:
library(dplyr)
library(DT)
library(shiny)
library(shinyWidgets)
library(tidyverse)
ui <-
fluidPage(
fluidRow(
column(width = 8,
h3("Data table:"),
tableOutput("data"),
h3("Sum the data table columns:"),
radioButtons(
inputId = "grouping",
label = NULL,
choiceNames = c("By period 1", "By period 2"),
choiceValues = c("Period_1", "Period_2"),
selected = "Period_1",
inline = TRUE
),
DT::dataTableOutput("sums")
)
)
)
server <- function(input, output, session) {
data <- reactive({
data.frame(
ID = c(115,115,111,88,120,16),
Period_1 = c("2020-01", "2020-02", "2020-03", "2020-01", "2020-02", "2020-03"),
Period_2 = c(1, 2, 3, 1, 1, 4),
ColA = c(1000.01, 20, 30, 40, 50, 60),
ColB = c(15.06, 25, 35, 45, 55, 65)
)
})
colNames <- reactive({c(input$grouping, "Col A", "Col B") })
# summed_data <- reactive({
# filter(data(), Period_2 == "1") %>%
# group_by(!!sym(input$grouping)) %>%
# summarise(count = length(unique(ID)))
# })
summed_data <- reactive({
data() %>%
group_by(!!sym(input$grouping)) %>%
select("ColA","ColB") %>%
summarise(across(everything(), sum))
})
output$data <- renderTable(data())
output$sums <- renderDT({
summed_data() %>%
datatable(
rownames = FALSE,
colnames=colNames() # < add colNames()
)
})
}
shinyApp(ui, server)
The problem were the colNames() that you defined and added to your call to datatable. I commented those lines out and it works. The problem didn't arise with your sum data.frame because here the colnames were actually present in the data.frame, which is not the case in the length(unique)) data.frame.
library(dplyr)
library(DT)
library(shiny)
library(shinyWidgets)
library(tidyverse)
ui <-
fluidPage(
fluidRow(
column(width = 8,
h3("Data table:"),
tableOutput("data"),
h3("Sum the data table columns:"),
radioButtons(
inputId = "grouping",
label = NULL,
choiceNames = c("By period 1", "By period 2"),
choiceValues = c("Period_1", "Period_2"),
selected = "Period_1",
inline = TRUE
),
DT::dataTableOutput("sums")
)
)
)
server <- function(input, output, session) {
mydat <- reactive({
data.frame(
ID = c(115,115,111,88,120,16),
Period_1 = c("2020-01", "2020-02", "2020-03", "2020-01", "2020-02", "2020-03"),
Period_2 = c(1, 2, 3, 1, 1, 4),
ColA = c(1000.01, 20, 30, 40, 50, 60),
ColB = c(15.06, 25, 35, 45, 55, 65)
)
})
# colNames <- reactive({c(input$grouping, "Col A", "Col B") })
summed_data <- reactive({
print(input$grouping)
mydat() %>%
dplyr::filter(Period_2 == 1) %>%
dplyr::group_by(!!sym(input$grouping)) %>%
dplyr::summarise(count = length(unique(ID)))
})
# summed_data <- reactive({
# print(input$grouping)
# data() %>%
# group_by(across(all_of(input$grouping))) %>%
# select("ColA","ColB") %>%
# summarise(across(everything(), sum))
# })
output$data <- renderTable(mydat())
output$sums <- renderDT({
summed_data() %>%
datatable(
rownames = FALSE,
# colnames=colNames() # < add colNames()
)
})
}
shinyApp(ui, server)

How to format data table inputs using the DT package in R shiny?

I'm having a hard time understanding the posts on this issue that delve into CSS/java script, of which I know very little about.
In running the below code, I'm trying to get the download buttons, number of table rows to view, and the filter, to present neatly. Does someone know how to do this?
I purposely crowd them in the below using fluidRow(column(width...)) to better illustrate the problem, because in the fuller App this derives from the table is rendered in a main panel which is narrow. Please see the image at the bottom to see some ideas for cleaning this up: by shrinking the size of the download buttons, reducing the text in the show number of rows input, etc. I'm open to any other formatting suggestions! Though I don't want to cut back on the items (download buttons, length, filter).
library(dplyr)
library(DT)
library(shiny)
library(shinyWidgets)
library(tidyverse)
ui <-
fluidPage(
fluidRow(
column(width = 8,
h3("Data table:"),
tableOutput("data"),
h3("Sum the data table columns:"),
radioButtons(
inputId = "grouping",
label = NULL,
choiceNames = c("By period 1", "By period 2"),
choiceValues = c("Period_1", "Period_2"),
selected = "Period_1",
inline = TRUE
),
DT::dataTableOutput("sums")
)
)
)
server <- function(input, output, session) {
data <- reactive({
data.frame(
Period_1 = c("2020-01", "2020-02", "2020-03", "2020-01", "2020-02", "2020-03"),
Period_2 = c(1, 2, 3, 3, 1, 2),
ColA = c(1000.01, 20, 30, 40, 50, 60),
ColB = c(15.06, 25, 35, 45, 55, 65)
)
})
summed_data <- reactive({
data() %>%
group_by(!!sym(input$grouping)) %>%
select("ColA","ColB") %>%
summarise(across(everything(), sum))
})
output$data <- renderTable(data())
output$sums <- renderDT({ # this section changed
summed_data() %>%
datatable(rownames = FALSE) %>%
formatCurrency(c("ColA", "ColB"), currency = '\U20AC', digits = 2)
})
output$sums <- renderDT({
summed_data() %>%
datatable(rownames = FALSE,
extensions = 'Buttons',
options = list(
buttons = list(
list(extend = 'copy', filename = "flowsBalances"),
list(extend = 'csv', filename = "flowsBalances"),
list(extend = 'excel', filename = "flowsBalances")
),
dom = 'Blfrtip'
),
class = "display"
) %>%
formatCurrency(c("ColA", "ColB"), currency = '', digits = 2)
})
}
shinyApp(ui, server)
Please don't ask multiple questions in a single question. Here is an answer, except for the alignment:
library(shiny)
library(DT)
dat <- iris[1:20, 1:3]
# change the width of the search box, and make the buttons smaller:
css <- '
.dataTables_filter input[type=search] {
width: 50px;
}
button.dt-button {
padding: 1px !important
}
'
ui <- fluidPage(
tags$head(
tags$style(
HTML(css)
)
),
br(),
DTOutput("dtable")
)
server <- function(input, output){
output[["dtable"]] <- renderDT({
datatable(
dat,
extensions = "Buttons",
options =
list(
dom = "Blfrtip",
language =
list(
lengthMenu = "Show _MENU_" # remove "Entries"
),
buttons = list("csv", "excel")
)
)
})
}
shinyApp(ui, server)

How to make the variable to aggregate data frame columns by reactive?

In the below MWE code and as shown in the image below, the aggregate() function is used to sum columns in a data frame. I'd like the user to be able to choose which variable to aggregate by, either Period_1 or Period_2 via clicking the radio button. Currently the below is coded only for Period_1.
How would I modify the $Period... in each aggregate() function, to reflect the user radio button input? So the user can also aggregate by Period 2 in this example.
MWE code:
library(shiny)
data <- data.frame(Period_1=c("2020-01","2020-02","2020-03","2020-01","2020-02","2020-03"),
Period_2=c(1,2,3,3,1,2),
ColA=c(10,20,30,40,50,60),
ColB=c(15,25,35,45,55,65)
)
ui <-
fluidPage(
h3("Data table:"),
tableOutput("data"),
h3("Sum the data table columns:"),
radioButtons(
inputId = 'vetaDataView2',
label = NULL,
choices = c('By period 1','By period 2'),
selected = 'By period 1',
inline = TRUE
),
tableOutput("totals")
)
server <- function(input, output, session) {
sumColA <- aggregate(data$ColA~Period_1,data,sum)
sumColB <- aggregate(data$ColB~Period_1,data,sum)
totals <- as.data.frame(c(sumColA, sumColB[2]))
colnames(totals) <- c("Period_1","Sum Col A","Sum Col B")
output$data <- renderTable(data)
output$totals <- renderTable(totals)
}
shinyApp(ui, server)
One option to achieve your desired result would be to use paste and as.formula to create the formula to aggregate your data base on the user input:
Note: To make my life a bit easier I switched to choiceNames and choiceValues.
library(shiny)
data <- data.frame(
Period_1 = c("2020-01", "2020-02", "2020-03", "2020-01", "2020-02", "2020-03"),
Period_2 = c(1, 2, 3, 3, 1, 2),
ColA = c(10, 20, 30, 40, 50, 60),
ColB = c(15, 25, 35, 45, 55, 65)
)
ui <-
fluidPage(
h3("Data table:"),
tableOutput("data"),
h3("Sum the data table columns:"),
radioButtons(
inputId = "vetaDataView2",
label = NULL,
choiceNames = c("By period 1", "By period 2"),
choiceValues = c("Period_1", "Period_2"),
selected = "Period_2",
inline = TRUE
),
tableOutput("totals")
)
server <- function(input, output, session) {
sumColA <- reactive({
fmlaA <- as.formula(paste("ColA", input$vetaDataView2, sep = " ~ "))
aggregate(fmlaA, data, sum)
})
sumColB <- reactive({
fmlaB <- as.formula(paste("ColB", input$vetaDataView2, sep = " ~ "))
aggregate(fmlaB, data, sum)
})
output$data <- renderTable(data)
output$totals <- renderTable({
totals <- as.data.frame(c(sumColA(), sumColB()[2]))
colnames(totals) <- c(input$vetaDataView2, "Sum Col A", "Sum Col B")
totals
})
}
shinyApp(ui, server)
#>
#> Listening on http://127.0.0.1:6231

How to create a function with a reactive object?

The below MWE code works fine. It allows the user to click on a radio button to choose the method for aggregating data: by either period 1 or period 2 in this case.
In the larger App this is to be deployed in, there are many columns to aggregate. Not just 2 like in this MWE. So I'm trying to create a general function that serves the purpose of sumColA() and sumColB() shown below. In the commented-out code below you can see one of my attempts. The lines are commented-out because they don't work.
How can I create a reactive function similar in concept to sumCol() where the it would be invoked with something like sumCol("ColA"), sumCol("ColB"), or something similar? In the full App there are too many columns to aggregate to create multiple versions of sumColA(), sumColB(), etc.
MWE code:
library(shiny)
data <- data.frame(
Period_1 = c("2020-01", "2020-02", "2020-03", "2020-01", "2020-02", "2020-03"),
Period_2 = c(1, 2, 3, 3, 1, 2),
ColA = c(10, 20, 30, 40, 50, 60),
ColB = c(15, 25, 35, 45, 55, 65)
)
ui <-
fluidPage(
h3("Data table:"),
tableOutput("data"),
h3("Sum the data table columns:"),
radioButtons(
inputId = "dataView",
label = NULL,
choiceNames = c("By period 1", "By period 2"),
choiceValues = c("Period_1", "Period_2"),
selected = "Period_1",
inline = TRUE
),
tableOutput("totals")
)
server <- function(input, output, session) {
sumColA <- reactive({
fmlaA <- as.formula(paste("ColA", input$dataView, sep = " ~ "))
aggregate(fmlaA, data, sum)
})
sumColB <- reactive({
fmlaB <- as.formula(paste("ColB", input$dataView, sep = " ~ "))
aggregate(fmlaB, data, sum)
})
### Create sumCol function ###
# sumCol <- function (x)
# {reactive({
# fmla <- as.formula(paste("x", input$dataView, sep = " ~ "))
# aggregate(fmla, data, sum)
# })
# }
### End sumCol ###
output$data <- renderTable(data)
output$totals <- renderTable({
totals <- as.data.frame(c(sumColA(), sumColB()[2]))
# totals <- as.data.frame(c(sumCol(ColA), sumCol(ColB)[2]))
colnames(totals) <- c(input$dataView, "Sum Col A", "Sum Col B")
totals
})
}
shinyApp(ui, server)
Just create one reactive object data and another reactive table summed_data containing the sums of all columns:
library(shiny)
library(tidyverse)
ui <-
fluidPage(
h3("Data table:"),
tableOutput("data"),
h3("Sum the data table columns:"),
radioButtons(
inputId = "grouping",
label = NULL,
choiceNames = c("By period 1", "By period 2"),
choiceValues = c("Period_1", "Period_2"),
selected = "Period_1",
inline = TRUE
),
tableOutput("sums")
)
server <- function(input, output, session) {
data <- reactive({
# example data. Might change dynamically
data.frame(
Period_1 = c("2020-01", "2020-02", "2020-03", "2020-01", "2020-02", "2020-03"),
Period_2 = c(1, 2, 3, 3, 1, 2),
ColA = c(10, 20, 30, 40, 50, 60),
ColB = c(15, 25, 35, 45, 55, 65)
)
})
summed_data <- reactive({
data() %>%
group_by(!!sym(input$grouping)) %>%
select(matches("^Col")) %>%
summarise(across(everything(), sum))
})
output$data <- renderTable(data())
output$sums <- renderTable(summed_data())
}
shinyApp(ui, server)
Here is a solution with dplyr and magrittr package.
Details of the change are in code comments.
library(shiny)
library(dplyr) # for data manipulation
library(magrittr) # for pipe operator
data <- data.frame(
Period_1 = c("2020-01", "2020-02", "2020-03", "2020-01", "2020-02", "2020-03"),
Period_2 = c(1, 2, 3, 3, 1, 2),
ColA = c(10, 20, 30, 40, 50, 60),
ColB = c(15, 25, 35, 45, 55, 65)
)
dataView_choices <- c("Period_1", "Period_2") # define choices for select input
ui <-
fluidPage(
h3("Data table:"),
tableOutput("data"),
h3("Sum the data table columns:"),
radioButtons(
inputId = "dataView",
label = NULL,
choiceNames = c("By period 1", "By period 2"),
choiceValues = dataView_choices, # choices for select input
selected = "Period_1",
inline = TRUE
),
tableOutput("totals")
)
server <- function(input, output, session) {
output$data <- renderTable(data)
output$totals <- renderTable({
totals <- data %>%
select(-setdiff(dataView_choices, input$dataView)) %>% # remove other periods in the select input
group_by_(input$dataView) %>% # group by the selected period
summarise(across(everything(), sum, .names = "Sum_{.col}")) # sum of all columns with a "Sum_" prefix
totals
})
}
shinyApp(ui, server)

How to perform calculations on a data table rendered with a reactive data frame, in R Shiny?

The below MWE code works as intended for summing columns of a reactive data frame (data() and summed_data() in the code). If you run the code or look at the image at the bottom, you'll see where the data frame columns are summed, based on either of two user input grouping criteria, under the header "Sum the data table columns:". The App works fine, through "Sum the data table columns:".
However, I am now trying to generate a new data table that takes the grouped values from summed_data() and performs the calculations described in the image under the heading "Calculations performed on summed data table columns:" (basically, [colA] divided by [average of colB in moving from one row to the next]). The user would be able to choose how to group the data in the calculations too, by Period_1 or by Period_2. The "Sum the data table columns" and "Calculations performed..." user input for grouping selection would be independent of each other.
Is there an efficient way to accomplish this? I'm trying to stick to base R and packages tidyverse and dplyr. I'd like avoid "package bloat".
Note that in the fuller App this is deployed, there are many more columns to calculate than in this simple MWE.
MWE code:
library(shiny)
library(tidyverse)
ui <-
fluidPage(
h3("Data table:"),
tableOutput("data"),
h3("Sum the data table columns:"),
radioButtons(
inputId = "grouping",
label = NULL,
choiceNames = c("By period 1", "By period 2"),
choiceValues = c("Period_1", "Period_2"),
selected = "Period_1",
inline = TRUE
),
tableOutput("sums"),
h3("Calculations performed on summed data table columns:"),
radioButtons(
inputId = "grouping2",
label = NULL,
choiceNames = c("By period 1", "By period 2"),
choiceValues = c("Period_1", "Period_2"),
selected = "Period_1",
inline = TRUE
)
)
server <- function(input, output, session) {
data <- reactive({
# example data. Might change dynamically
data.frame(
Period_1 = c("2020-01", "2020-02", "2020-03", "2020-01", "2020-02", "2020-03"),
Period_2 = c(1, 2, 3, 3, 1, 2),
ColA = c(10, 20, 30, 40, 50, 64),
ColB = c(15, 25, 35, 45, 55, 33)
)
})
summed_data <- reactive({
data() %>%
group_by(!!sym(input$grouping)) %>%
select(matches("^Col")) %>%
summarise(across(everything(), sum))
})
output$data <- renderTable(data())
output$sums <- renderTable(summed_data())
}
shinyApp(ui, server)
I figured out one solution, creating a new reactive object calculated_data() and using dplyr group_by() and mutate() functions to perform the calculations, as shown below in revised code:
ui <-
fluidPage(
h3("Data table:"),
tableOutput("data"),
h3("Sum the data table columns:"),
radioButtons(
inputId = "grouping1", # changed
label = NULL,
choiceNames = c("By period 1", "By period 2"),
choiceValues = c("Period_1", "Period_2"),
selected = "Period_1",
inline = TRUE
),
tableOutput("sums"),
h3("Calculations performed on summed data table columns:"),
radioButtons(
inputId = "grouping2", # changed
label = NULL,
choiceNames = c("By period 1", "By period 2"),
choiceValues = c("Period_1", "Period_2"),
selected = "Period_1",
inline = TRUE
),
tableOutput("calc") # added
)
server <- function(input, output, session) {
data <- reactive({
data.frame(
Period_1 = c("2020-01", "2020-02", "2020-03", "2020-01", "2020-02", "2020-03"),
Period_2 = c(1, 2, 3, 3, 1, 2),
ColA = c(10, 20, 30, 40, 50, 64),
ColB = c(15, 25, 35, 45, 55, 33)
)
})
summed_data <- reactive({
data() %>%
group_by(!!sym(input$grouping1)) %>%
select("ColA","ColB") %>%
summarise(across(everything(), sum))
})
calculated_data <- reactive({ # added this section
data() %>%
group_by(!!sym(input$grouping2)) %>%
select("ColA","ColB") %>%
summarise(across(everything(), sum)) %>%
mutate(avgColB=case_when(is.na(lag(ColB)) ~ ColB, TRUE ~ (lag(ColB) + ColB)/2)) %>%
mutate(ColAB = ColA / avgColB) %>%
select(-ColA,-ColB,-avgColB)
})
output$data <- renderTable(data())
output$sums <- renderTable(summed_data())
output$calc <- renderTable(calculated_data()) # added
}
shinyApp(ui, server)

Resources