I am trying to create a shiny app with a plotly output.
The plot should have multiple y axes, and update based on the variables selected.
The question is how to combine the shiny reactivity and plotly while using add_lines, as at the moment if I select less variables than add_lines the code does not function
Sample code:
library(shiny)
library(dplyr)
library(plotly)
library(tidyr)
data <- cbind(
seq(from = 1, to = 30, by = 1),
sample(seq(from = 100, to = 300, by = 10), size = 30, replace = TRUE),
sample(seq(from = 1, to = 100, by = 9), size = 30, replace = TRUE),
sample(seq(from = 50, to = 60, by = 2), size = 30, replace = TRUE),
sample(seq(from = 100, to = 130, by = 1), size = 30, replace = TRUE)
) %>%
as.data.frame()
names(data) <- c("date", "a", "b", "x", "y")
data <- data %>% gather("key", "value", 2:5)
ui <- fluidPage(
column(
width = 3,
selectInput("select", "Select var:", choices = c("a", "b", "x", "y"), selected = c("a", "b", "x"), multiple = TRUE)
),
column(
width = 9
),
column(
width = 12,
plotlyOutput("plot")
)
)
server <- function(input, output){
output$plot <- renderPlotly({
data <- data %>% filter(key %in% c("date", input$select)) %>% spread(key, value)
plot_ly(x = ~data$date) %>%
add_lines(y = ~data[, 2], name = input$select[1], line = list(color = "red")) %>%
add_lines(y = ~data[, 3], name = input$select[2], line = list(color = "blue"), yaxis = "y2") %>%
add_lines(y = ~data[, 4], name = input$select[3], line = list(color = "green"), yaxis = "y3") %>%
layout(
yaxis = list(
side = "left"
),
yaxis2 = list(
side = "left",
overlaying = "y",
anchor = "free",
position = 0.02
),
yaxis3 = list(
side = "left",
overlaying = "y",
anchor = "free",
position = 0.04
)
)
})
}
shinyApp(ui, server)
Here is the solution for you:
library(shiny)
library(dplyr)
library(plotly)
library(tidyr)
data <- cbind(
seq(from = 1, to = 30, by = 1),
sample(seq(from = 100, to = 300, by = 10), size = 30, replace = TRUE),
sample(seq(from = 1, to = 100, by = 9), size = 30, replace = TRUE),
sample(seq(from = 50, to = 60, by = 2), size = 30, replace = TRUE),
sample(seq(from = 100, to = 130, by = 1), size = 30, replace = TRUE)
) %>%
as.data.frame()
names(data) <- c("date", "a", "b", "x", "y")
data <- data %>% gather("key", "value", 2:5)
ui <- fluidPage(
column(
width = 3,
selectInput("select", "Select var:", choices = c("a", "b", "x", "y"), selected = c("a", "b", "x"), multiple = TRUE)
),
column(
width = 9
),
column(
width = 12,
plotlyOutput("plot")
)
)
server <- function(input, output){
output$plot <- renderPlotly({
data <- data %>% filter(key %in% c("date", input$select))
plot_ly(data, x = ~date, y=~value, color=~key) %>%
layout(
yaxis = list(
side = "left"
),
yaxis2 = list(
side = "left",
overlaying = "y",
anchor = "free",
position = 0.02
),
yaxis3 = list(
side = "left",
overlaying = "y",
anchor = "free",
position = 0.04
)
)
})
}
shinyApp(ui, server)
It is very easy, you should not spread your dataset, instead with long format you can setup an argument color= in plotly, which will directly group your data according to set variable:
plot_ly(data, x = ~date, y=~value, color=~key)
At least of ~6 months ago, my understanding (from plotly support) was that plotly did not concisely handle this sort of conditional plotting. Instead, if you can enumerate all plotting scenarios, you can use something like the following (which does not actually work yet, need to fix other parts of your code), with an else if for each plotting scenario:
output$plot <- renderPlotly({
data <- data %>% filter(key %in% c("date", input$select)) %>% spread(key, value)
if (input$select == c("a", "b", "x")) {
plot_ly(x = ~data$date) %>%
add_lines(y = ~data[, 2], name = input$select[1], line = list(color = "red")) %>%
add_lines(y = ~data[, 3], name = input$select[2], line = list(color = "blue"), yaxis = "y2") %>%
add_lines(y = ~data[, 4], name = input$select[3], line = list(color = "green"), yaxis = "y3") %>%
layout(
yaxis = list(
side = "left"
),
yaxis2 = list(
side = "left",
overlaying = "y",
anchor = "free",
position = 0.02
),
yaxis3 = list(
side = "left",
overlaying = "y",
anchor = "free",
position = 0.04
)
)
} else if (input$select == c("a", "b")) {
plot_ly(x = ~data$date) %>%
add_lines(y = ~data[, 2], name = input$select[1], line = list(color = "red")) %>%
add_lines(y = ~data[, 3], name = input$select[2], line = list(color = "blue"), yaxis = "y2") %>%
layout(
yaxis = list(
side = "left"
),
yaxis2 = list(
side = "left",
overlaying = "y",
anchor = "free",
position = 0.02
),
yaxis3 = list(
side = "left",
overlaying = "y",
anchor = "free",
position = 0.04
)
)
}
})
It is not concise, but may work if there are not a huge number of scenarios.
It also appears you're overwriting your data with the filter call; if your data set is not huge you might skip that step and organize your data outside of the plot call. Otherwise you probably need a reactive function outside of the plot call, that gets called from within the plotting functioning, leaving the original data unmodified.
Related
I'm trying to reproduce in R Plotly a 2 categorical variables violin plot that works just fine in ggplot2. But when I set the widths of the individual violins to be the same, using scalemode = "width", as described in the reference (https://plotly.com/r/reference/violin/), it simply wont work. Instead, it shows the widths (violin maximum) proportional to the counts in each category.
Here is an example:
# Paths:
path_data = "data/"
path_lib = "renv/library/R-4.1/x86_64-pc-linux-gnu/"
# Packages:
require(dplyr, lib = path_lib)
require(readr, lib = path_lib)
require(RColorBrewer, lib = path_lib)
require(plotly, lib = path_lib)
# Dataset:
df = readr::read_csv(paste0(path_data, "nasa_exoplanets.csv")) %>%
as.data.frame()
attr(df, "spec") = NULL
df_varnames = readr::read_csv(paste0(path_data, "nasa_exoplanets_var_names.csv")) %>%
as.data.frame()
attr(df_varnames, "spec") = NULL
# Variables:
cat_var1 = "st_metratio"
cat_var2 = "disc_locale"
cat_var_name1 = (df_varnames %>%
dplyr::filter(var == cat_var1))$var_name
cat_var_name2 = (df_varnames %>%
dplyr::filter(var == cat_var2))$var_name
num_var = "sy_dist"
num_var_name = (df_varnames %>%
dplyr::filter(var == num_var))$var_name
# Adapt the data:
df_plot = df %>%
dplyr::select(cat_var1,
cat_var2,
num_var)
# Deal with NA:
df_plot[which(is.na(df_plot[, cat_var1])), cat_var1] = "NA"
df_plot[which(is.na(df_plot[, cat_var2])), cat_var2] = "NA"
df_plot = df_plot[which(!is.na(df_plot[, num_var])), ]
# Levels order:
sorted_levels1 = sort(unique(df_plot[, cat_var1]))
df_plot[, cat_var1] = factor(x = df_plot[, cat_var1],
levels = sorted_levels1)
sorted_levels2 = sort(unique(df_plot[, cat_var2]))
df_plot[, cat_var2] = factor(x = df_plot[, cat_var2],
levels = sorted_levels2)
# Plot:
my_palette = colorRampPalette(c("#111539", "#97A1D9"))
n_levels2 = length(unique(df_plot[, cat_var2]))
p = plot_ly(
data = df_plot,
type = "violin",
x = ~eval(parse(text = cat_var1)),
y = ~eval(parse(text = num_var)),
color = ~eval(parse(text = cat_var2)),
colors = my_palette(n_levels2),
spanmode = "hard",
alpha = 1,
box = list(visible = FALSE),
meanline = list(visible = FALSE),
points = FALSE,
scalemode = "width" ### this doesn't work ###
) %>%
layout(
xaxis = list(
title = paste0("<b>", cat_var_name1, "</b>"),
titlefont = list(size = 20),
tickfont = list(size = 18),
categoryorder = "array"
),
yaxis = list(
title = paste0("<b>", num_var_name, "</b>"),
titlefont = list(size = 20),
tickfont = list(size = 18),
type = "log"
),
margin = list(
l = 10,
r = 10,
t = 10,
b = 10
),
legend = list(
title = list(
text = paste0("<br><b>", cat_var_name2, "</b>"),
font = list(size = 18)
)
),
hoverlabel = list(font = list(size = 16)),
showlegend = TRUE,
violinmode = "group"
)
p
data file: https://github.com/rafael747cardoso/Data_Visualization_Gallery/blob/main/data/nasa_exoplanets.csv
How it should be, plotted in ggplot2:
How it is with R Plotly:
I am working with the R programming language. I am trying to replicate this tutorial over here for my own data: https://plotly.com/r/dropdowns/
I created some fake data and made 4 plots:
#load libraries
library(plotly)
library(MASS)
library(dplyr)
# create data
x <- sample( LETTERS[1:4], 731, replace=TRUE, prob=c(0.25, 0.25, 0.25, 0.25) )
y <- rnorm(731,10,10)
z <- rnorm(731,5,5)
date= seq(as.Date("2014/1/1"), as.Date("2016/1/1"),by="day")
df <- data.frame(x,y, z, date)
df$x = as.factor(df$x)
# plot 1 : time series
aggregate = df %>%
mutate(date = as.Date(date)) %>%
group_by(month = format(date, "%Y-%m")) %>%
summarise( mean = mean(y))
ts_1 <- ggplot(aggregate) + geom_line(aes(x = month, y = mean, group = 1)) + theme(axis.text.x = element_text(angle = 90)) + ggtitle("time series 1")
plot_1 = ggplotly(ts_1)
#plot 2 : box plot
plot_2 <- plot_ly(df, y = ~y, color = ~x, type = "box") %>% layout(title = "boxplot")
#plot 3, 4 : scatter plots
df_1 <- df[which(df$x == "A"),]
df_2 <- df[which(df$x == "B"),]
plot_3 <- plot_ly( data = df_1, type = "scatter", mode = "markers", x = ~ y, y = ~z) %>% layout(title = "graph 3")
plot_4 <- plot_ly( data = df_2, type = "scatter", mode = "markers", x = ~ y, y = ~z) %>% layout(title = "graph 4")
Once these 4 plots have been created, I know how to save them together:
sub = subplot(plot_1, plot_2, plot_3, plot_4, nrows = 2)
#view result
sub
Now what I am trying to do, is have the user "toggle" (switch) between these graphs (as seen here: https://plotly.com/r/dropdowns/)
In a previous post (R: Switching Between Graphs ), I learned how to "glue" similar graphs together (e.g. 4 scatter plots). Now, I am trying to do so with different graphs (2 scatter plots, 1 time series and 1 box plot). I tried to adapt the code from the previous post to suit my example:
fig <- df %>%
add_trace(name = "A", plot_1) %>%
add_trace (name = "B" , df, y = ~y, color = ~x, type = "box") %>% layout(title = "boxplot")
add_trace (name = "C" , data = df_1, type = "scatter", mode = "markers", x = ~ y, y = ~z) %>% layout(title = "graph 3") %>%
add_trace( name = "D", data = df_2, type = "scatter", mode = "markers", x = ~ y, y = ~z) %>% layout(title = "graph 4") %>%
layout(xaxis = list(domain = c(0.1, 1)),
yaxis = list(title = "y"),
updatemenus = list(
list(
y = 0.7,
buttons = list(
list(method = "restyle",
args = list("visible", list(TRUE, FALSE, FALSE, FALSE)),
label = "A"),
list(method = "restyle",
args = list("visible", list(FALSE, TRUE, FALSE, FALSE)),
label = "B"),
list(method = "restyle",
args = list("visible", list(FALSE, FALSE, TRUE, FALSE)),
label = "C"),
list(method = "restyle",
args = list("visible", list(FALSE, FALSE, FALSE, TRUE)),
label = "D")))))
But this produces the following errors:
Error: $ operator is invalid for atomic vectors
Error in add_data(p, data) : argument "p" is missing, with no default
Can someone please show me if it is possible to fix this problem? Instead of using the "add_trace" approach, is it somehow possible to individually call each plotly graph object by its name (e.g. subplot(plot_1, plot_2, plot_3, plot_4, nrows = 2)), "glue" all the graphs together, and then add a "toggle button" that lets the user switch between them?
(note: I need to be able to save the final result as a "html" file)
Thanks
First of all, you should take care about plots which add multiple traces (see nTracesA etc.)
Besides changing the trace visibility you'll need to seperate categorial and numerical data onto separate x and y-axes and manage their visibility, too (see xaxis2, xaxis3, xaxis4 - this also works with a single y-axis but in this case the grid isn't displayed properly)
As described in the docs:
The updatemenu method determines which plotly.js function will be used
to modify the chart. There are 4 possible methods:
"restyle": modify data or data attributes
"relayout": modify layout attributes
"update": modify data and layout attributes
"animate": start or pause an animation (only available offline)
Accordingly the following, is using the update method (a lot of repition here - needs some cleanup, but I think it's better to understand this way):
# load libraries
library(dplyr)
library(plotly)
# create data
x <- sample(LETTERS[1:4],
731,
replace = TRUE,
prob = c(0.25, 0.25, 0.25, 0.25))
y <- rnorm(731, 10, 10)
z <- rnorm(731, 5, 5)
date <- seq(as.Date("2014/1/1"), as.Date("2016/1/1"), by = "day")
df <- data.frame(x, y, z, date)
df$x = as.factor(df$x)
nTracesA <- nTracesC <- nTracesD <- 1
nTracesB <- length(unique(df$x))
plotA <- plot_ly(data = df %>%
mutate(date = as.Date(date)) %>%
group_by(month = format(date, "%Y-%m")) %>%
summarise(mean = mean(y)),
type = 'scatter', mode = 'lines', x= ~ month, y= ~ mean, name = "plotA", visible = TRUE, xaxis = "x", yaxis = "y")
plotAB <- add_trace(plotA, data = df, x = ~x, y = ~y, color = ~ x, name = ~ paste0("plotB_", x),
type = "box", xaxis = "x2", yaxis = "y2", visible = FALSE, inherit = FALSE)
plotABC <- add_trace(plotAB, data = df[which(df$x == "A"),],
type = "scatter", mode = "markers", x = ~ y, y = ~ z,
name = "plotC", xaxis = "x3", yaxis = "y3", visible = FALSE, inherit = FALSE)
plotABCD <- add_trace(plotABC, data = df[which(df$x == "B"),], x = ~ y, y = ~ z,
type = "scatter", mode = "markers", name = "plotD", xaxis = "x4", yaxis = "y4", visible = FALSE, inherit = FALSE)
fig <- layout(plotABCD, title = "Initial Title",
xaxis = list(domain = c(0.1, 1), visible = TRUE, type = "date"),
xaxis2 = list(overlaying = "x", visible = FALSE),
xaxis3 = list(overlaying = "x", visible = FALSE),
xaxis4 = list(overlaying = "x", visible = FALSE),
yaxis = list(title = "y"),
yaxis2 = list(overlaying = "y", visible = FALSE),
yaxis3 = list(overlaying = "y", visible = FALSE),
yaxis4 = list(overlaying = "y", visible = FALSE),
updatemenus = list(
list(
y = 0.7,
buttons = list(
list(label = "A",
method = "update",
args = list(list(name = paste0("new_trace_name_", 1:7), visible = unlist(Map(rep, x = c(TRUE, FALSE, FALSE, FALSE), each = c(nTracesA, nTracesB, nTracesC, nTracesD)))),
list(title = "title A",
xaxis = list(visible = TRUE),
xaxis2 = list(overlaying = "x", visible = FALSE),
xaxis3 = list(overlaying = "x", visible = FALSE),
xaxis4 = list(overlaying = "x", visible = FALSE),
yaxis = list(visible = TRUE),
yaxis2 = list(overlaying = "y", visible = FALSE),
yaxis3 = list(overlaying = "y", visible = FALSE),
yaxis4 = list(overlaying = "y", visible = FALSE)))
),
list(label = "B",
method = "update",
args = list(list(visible = unlist(Map(rep, x = c(FALSE, TRUE, FALSE, FALSE), each = c(nTracesA, nTracesB, nTracesC, nTracesD)))),
list(title = "title B",
xaxis = list(visible = FALSE),
xaxis2 = list(overlaying = "x", visible = TRUE),
xaxis3 = list(overlaying = "x", visible = FALSE),
xaxis4 = list(overlaying = "x", visible = FALSE),
yaxis = list(visible = FALSE),
yaxis2 = list(overlaying = "y", visible = TRUE),
yaxis3 = list(overlaying = "y", visible = FALSE),
yaxis4 = list(overlaying = "y", visible = FALSE)))),
list(label = "C",
method = "update",
args = list(list(visible = unlist(Map(rep, x = c(FALSE, FALSE, TRUE, FALSE), each = c(nTracesA, nTracesB, nTracesC, nTracesD)))),
list(title = "title C",
xaxis = list(visible = FALSE),
xaxis2 = list(overlaying = "x", visible = FALSE),
xaxis3 = list(overlaying = "x", visible = TRUE),
xaxis4 = list(overlaying = "x", visible = FALSE),
yaxis = list(visible = FALSE),
yaxis2 = list(overlaying = "y", visible = FALSE),
yaxis3 = list(overlaying = "y", visible = TRUE),
yaxis4 = list(overlaying = "y", visible = FALSE)))),
list(label = "D",
method = "update",
args = list(list(visible = unlist(Map(rep, x = c(FALSE, FALSE, FALSE, TRUE), each = c(nTracesA, nTracesB, nTracesC, nTracesD)))),
list(title = "title D",
xaxis = list(visible = FALSE),
xaxis2 = list(overlaying = "x", visible = FALSE),
xaxis3 = list(overlaying = "x", visible = FALSE),
xaxis4 = list(overlaying = "x", visible = TRUE),
yaxis = list(visible = FALSE),
yaxis2 = list(overlaying = "y", visible = FALSE),
yaxis3 = list(overlaying = "y", visible = FALSE),
yaxis4 = list(overlaying = "y", visible = TRUE))))
))))
print(fig)
# htmlwidgets::saveWidget(partial_bundle(fig), file = "fig.html", selfcontained = TRUE)
# utils::browseURL("fig.html")
Some related info:
https://plotly.com/r/custom-buttons/
https://plotly.com/r/multiple-axes/
This is just a guess from the documentation but there is no add_data() call so maybe try this for your first line:
fig <- plot_ly() %>% add_data(df) %>%
See docs example:
plot_ly() %>% add_data(economics) %>% add_trace(x = ~date, y = ~pce)
A user on the Rstudio community forum provided an answer : https://community.rstudio.com/t/gluing-graphs-together-switch-toggle-between-graphs-in-r-plotly/95891/3
I am still trying to figure out how to format the axis - maybe someone could take a look at this?
#load libraries
library(plotly)
library(MASS)
library(dplyr)
# create data
x <- sample( LETTERS[1:4], 731, replace=TRUE, prob=c(0.25, 0.25, 0.25, 0.25) )
y <- rnorm(731,10,10)
z <- rnorm(731,5,5)
date= seq(as.Date("2014/1/1"), as.Date("2016/1/1"),by="day")
df <- data.frame(x,y, z, date)
df$x = as.factor(df$x)
# plot 1 : time series
aggregate = df %>%
mutate(date = as.Date(date)) %>%
group_by(month = format(date, "%Y-%m")) %>%
summarise( mean = mean(y))
ts_1 <- ggplot(aggregate) + geom_line(aes(x = month, y = mean, group = 1)) + theme(axis.text.x = element_text(angle = 90)) + ggtitle("time series 1")
plot_1 = ggplotly(ts_1)
#plot 2 : box plot
plot_2 <- plot_ly(df, y = ~y, color = ~x, type = "box") %>% layout(title = "boxplot")
#plot 3, 4 : scatter plots
df_1 <- df[which(df$x == "A"),]
df_2 <- df[which(df$x == "B"),]
plot_3 <- plot_ly( data = df_1, type = "scatter", mode = "markers", x = ~ y, y = ~z) %>% layout(title = "graph 3")
plot_4 <- plot_ly( data = df_2, type = "scatter", mode = "markers", x = ~ y, y = ~z) %>% layout(title = "graph 4")
fig = plot_ly()
fig = fig %>% add_trace(data = df %>%
mutate(date = as.Date(date)) %>%
group_by(month = format(date, "%Y-%m")) %>%
summarise( mean = mean(y)), type = 'scatter', mode = 'lines', x= ~month, y= ~mean,
name = "timeseries")
fig = fig %>% add_trace(data = df[which(df$x == "A"),], y = ~y, color = ~x,
type = "box", name = "boxplot")
fig = fig %>% add_trace( data = df[which(df$x == "B"),],
type = "scatter", mode = "markers", x = ~ y, y = ~z,
name= "graph2")
fig = fig %>% add_trace(data = df[which(df$x == "A"),], y = ~y, color = ~x,
type = "box", name = "boxplot2")
fig %>% layout(xaxis = list(domain = c(0.1, 1)),
yaxis = list(title = "y"),
updatemenus = list(
list(
y = 0.7,
buttons = list(
list(method = "restyle",
args = list("visible", list(TRUE, FALSE, FALSE, FALSE)),
label = "A"),
list(method = "restyle",
args = list("visible", list(FALSE, TRUE, FALSE, FALSE)),
label = "B"),
list(method = "restyle",
args = list("visible", list(FALSE, FALSE, TRUE, FALSE)),
label = "C"),
list(method = "restyle",
args = list("visible", list(FALSE, FALSE, FALSE, TRUE)),
label = "D")))))
I am trying to add custom button to update the y axis, here is a small example that is similar.
When I click on “frq” or “count” the correct bar chart appears, but the values are wrong (its take only one value for each city and put it in all gender categories bars)
library(plotly)
library(dplyr)
library(tidyr)
Occupation = c("Tel Aviv", "Paris", "Amsterdam", "Kyoto")
Gender = c("F", "M", "[missing]")
df <- crossing(Occupation, Gender) %>%
mutate(n = row_number()) %>%
group_by(.data[["Occupation"]]) %>%
mutate(frq = round(100 * (n / sum(n)), 1))
chart_type <- list(
type = "buttons",
direction = "right",
xanchor = 'center',
yanchor = "top",
pad = list('r' = 0, 't' = 10, 'b' = 10),
x = 0.1,
y = 1.20,
buttons = list(
list(
method = "update",
args = list(list(y = list(df$n))),
label = "count"
),
list(
method = "update",
args = list(list(y = list(df$frq))),
label = "frq"
)
)
)
df %>%
plot_ly(
x = ~ Occupation,
y = ~ n,
color = ~ Gender,
text = ~ n,
textposition = 'auto',
type = "bar"
) %>%
layout(updatemenus = list(chart_type))
Another version:
chart_type <- list(
type = "buttons",
direction = "right",
xanchor = 'center',
yanchor = "top",
pad = list('r'= 0, 't'= 10, 'b' = 10),
x = 0.1,
y = 1.20,
buttons = list(
list(method = "update",
args = list(list(visible = c(TRUE, FALSE)
)),
label = "count"),
list(method = "update",
args = list(list(visible = c(FALSE, TRUE)
)),
label = "frq")
))
df %>%
plot_ly(mode = 'markers',type = "bar") %>%
add_trace(x = ~Occupation, y = ~n, color = ~Gender,
text=~n, textposition = 'auto', visible = FALSE,
colorbar = list()) %>%
add_trace(x = ~Occupation, y = ~frq, color = ~Gender,
text=~frq, textposition = 'auto', visible = TRUE,
colorbar = list()) %>%
layout(updatemenus = list(chart_type))
After the click:
In the following app the user can select points in the plot by dragging, which should swap their Selected state between 0 and 1
points will get a shape and color depending on their 0 / 1 state, as a visual support for a user to select/deselect model parameters for the next model run.
in the version of the plots I had in my real app, the plotted data is a reactive variable values$RFImp_FP1 but I found out that the plot does not re-render when the content of column Selected of that data.table (or data.frame) changes.
Therefore I am trying to change it to a reactive object, but I'm failing to figure out how to change the Selected column of reactive data.table `RFImp
my attempts (comments in the code) so far produce either an assign error, or an infinite loop.
P.S.: Since i'm coding the stuff with lapply as I am using the code block several times in my app (identical "modules" with different serial number and using different data as the app takes the user through sequential stages of processing data), the second approach with values (app 2) has my preference as this allows me to do things like this:
lapply(c('FP1', 'FP2'), function(FP){
values[[paste('RFAcc', FP, sep = '_')]] <- ".... code to select a dataframe from model result list object values[[paste('RFResults', FP, sep = '_']]$Accuracy...."
which as far as I know can't be done with objectname <- reactive({....}) as you can't paste on the left side of the <- here
REACTIVE OBJECT APPROACH:
library(shiny)
library(plotly)
library(dplyr)
library(data.table)
ui <- fluidPage(
plotlyOutput('RFAcc_FP1', width = 450)
)
server <- function(input, output, session) {
values <- reactiveValues()
observe({
if(!is.null(RFImp_FP1()$Selected)) {
parsToChange <- event_data("plotly_selected", source = 'RFAcc_FP1')$y
if(!is.null(event_data("plotly_selected", source = 'RFAcc_FP1'))){
data_df <- RFImp_FP1()
data_df <- data_df %>% .[, Selected := if_else(Variables %in% parsToChange, 1-Selected, Selected)]
# how to get the reactive Data frame to update the selected
# values$Selected <- data_df$Selected #creates infinite loop.....
# RFImp_FP1$Selected <- data_df$Selected # throws an error
}
}
})
RFImp_FP1 <- reactive({
# in real app the dataframe RFImp_FP1 is a part of a list with randomForest results,
RFImp_FP1 <- data.table( MeanDecreaseAccuracy = runif(10, min = 0, max = 1), Variables = letters[1:10])
RFImp_FP1$Selected <- 1
# RFImp_FP1$Selected <- if(!is.null(values$Selected)){
# values$Selected } else {1 }
RFImp_FP1
})
output$RFAcc_FP1 <- renderPlotly({
RFImp_FP1()[order(MeanDecreaseAccuracy)]
RFImp_score <- RFImp_FP1()
plotheight <- length(RFImp_score$Variables) * 80
p <- plot_ly(data = RFImp_score,
source = 'RFAcc_FP1',
height = plotheight,
width = 450) %>%
add_trace(x = RFImp_score$MeanDecreaseAccuracy,
y = RFImp_score$Variables,
type = 'scatter',
mode = 'markers',
color = factor(RFImp_score$Selected),
colors = c('#1b73c1', '#797979'),
symbol = factor(RFImp_score$Selected),
symbols = c('circle','x'),
marker = list(size = 6),
hoverinfo = "text",
text = ~paste ('<br>', 'Parameter: ', RFImp_score$Variables,
'<br>', 'Mean decrease accuracy: ', format(round(RFImp_score$MeanDecreaseAccuracy*100, digits = 2), nsmall = 2),'%',
sep = '')) %>%
layout(
margin = list(l = 160, r= 20, b = 70, t = 50),
hoverlabel = list(font=list( color = '#1b73c1'), bgcolor='#f7fbff'),
xaxis = list(title = 'Mean decrease accuracy index (%)',
tickformat = "%",
showgrid = F,
showline = T,
zeroline = F,
nticks = 5,
font = list(size = 8),
ticks = "outside",
ticklen = 5,
tickwidth = 2,
tickcolor = toRGB("black")
),
yaxis = list(categoryarray = RFImp_score$Variables,
autorange = T,
showgrid = F,
showline = T,
autotick = T,
font = list(size = 8),
ticks = "outside",
ticklen = 5,
tickwidth = 2,
tickcolor = toRGB("black")
),
dragmode = "select"
) %>% add_annotations(x = 0.5,
y = 1.05,
textangle = 0,
font = list(size = 14,
color = 'black'),
text = "Contribution to accuracy",
showarrow = F,
xref='paper',
yref='paper')
p <- p %>% config(displayModeBar = F)
p
})
}
shinyApp(ui, server)
PREVIOUS reactiveValues() approach:
as you can see, with this app, the plot does not update when selecting a region in the plot even though the code changes the content of column Selected
ui <- fluidPage(
actionButton(inputId = 'Go', label = 'Go'),
plotlyOutput('RFAcc_FP1', width = 450)
)
server <- function(input, output, session) {
values <- reactiveValues()
observe({
if(!is.null(values$RFImp_FP1)) {
parsToChange <- event_data("plotly_selected", source = 'RFAcc_FP1')$y
if(!is.null(event_data("plotly_selected", source = 'RFAcc_FP1'))){
data_df <- values$RFImp_FP1
data_df <- data_df %>% .[, Selected := if_else(Variables %in% parsToChange, 1-Selected, Selected)]
values$RFImp_FP1 <- data_df
}
}
})
observeEvent(input$Go, {
values$RFImp_FP1 <- data.table(MeanDecreaseAccuracy = runif(10, min = 0, max = 1), Variables = letters[1:10])
values$RFImp_FP1$Selected <- 1
})
output$RFAcc_FP1 <- renderPlotly({
if(!is.null(values$RFImp_FP1)) {
RFImp_score <- values$RFImp_FP1[order(MeanDecreaseAccuracy)]
plotheight <- length(RFImp_score$Variables) * input$testme
p <- plot_ly(data = RFImp_score,
source = 'RFAcc_FP1',
height = plotheight,
width = 450) %>%
add_trace(x = RFImp_score$MeanDecreaseAccuracy,
y = RFImp_score$Variables,
type = 'scatter',
mode = 'markers',
color = factor(RFImp_score$Selected),
colors = c('#1b73c1', '#797979'),
symbol = factor(RFImp_score$Selected),
symbols = c('circle','x'),
marker = list(size = 6),
hoverinfo = "text",
text = ~paste ('<br>', 'Parameter: ', RFImp_score$Variables,
'<br>', 'Mean decrease accuracy: ', format(round(RFImp_score$MeanDecreaseAccuracy*100, digits = 2), nsmall = 2),'%',
sep = '')) %>%
layout(
margin = list(l = 160, r= 20, b = 70, t = 50),
hoverlabel = list(font=list( color = '#1b73c1'), bgcolor='#f7fbff'),
xaxis = list(title = 'Mean decrease accuracy index (%)',
tickformat = "%",
showgrid = F,
showline = T,
zeroline = F,
nticks = 5,
font = list(size = 8),
ticks = "outside",
ticklen = 5,
tickwidth = 2,
tickcolor = toRGB("black")
),
yaxis = list(categoryarray = RFImp_score$Variables,
autorange = T,
showgrid = F,
showline = T,
autotick = T,
font = list(size = 8),
ticks = "outside",
ticklen = 5,
tickwidth = 2,
tickcolor = toRGB("black")
),
dragmode = "select"
) %>% add_annotations(x = 0.5,
y = 1.05,
textangle = 0,
font = list(size = 14,
color = 'black'),
text = "Contribution to accuracy",
showarrow = F,
xref='paper',
yref='paper')
p$elementId <- NULL ## to surpress warning of widgetid
p <- p %>% config(displayModeBar = F)
p
} else {
p <- plot_ly( type = 'scatter', mode = 'markers', height = '400px', width = 450) %>% layout(
margin = list(l = 160, r= 20, b = 70, t = 50),
xaxis = list(title = 'Mean decrease accuracy index', range= c(0,1), nticks = 2, showline = TRUE),
yaxis = list(title = 'Model input variables', range = c(0,1), nticks = 2, showline = TRUE)) %>%
add_annotations(x = 0.5, y = 1.1, textangle = 0, font = list(size = 14, color = 'black'),
text = 'Contribution to accuracy',
showarrow = F, xref='paper', yref='paper')
p$elementId <- NULL
p <- p %>% config(displayModeBar = F)
p}
})
}
shinyApp(ui, server)
Not sure if this is what you want (it´s a bit weird that the plot updates with random numbers after selecting points ;-) ), but I hope it helps.
Instead of using a normal observer I use observeEvent that fires when selecting something in the plot. I generally prefer observeEvent to catch an event. This triggers an update ob a reactiveValues value, which will initially be NULL
library(shiny)
library(plotly)
library(dplyr)
library(data.table)
testDF <- data.table( MeanDecreaseAccuracy = runif(10, min = 0, max = 1), Variables = letters[1:10])
testDF$Selected <- T
ui <- fluidPage(
plotlyOutput('RFAcc_FP1', width = 450)
)
server <- function(input, output, session) {
values <- reactiveValues(val = NULL)
observeEvent(event_data("plotly_selected", source = 'RFAcc_FP1')$y, {
values$val <- runif(1, min = 0, max = 1)
})
RFImp_FP1 <- reactive({
RFImp_FP1 <- testDF
if(!is.null(values$val)) {
parsToChange <- event_data("plotly_selected", source = 'RFAcc_FP1')$y
RFImp_FP1 <- RFImp_FP1 %>% .[, Selected := if_else(Variables %in% parsToChange, 1-Selected, Selected)]
} else { }
# in real app the dataframe RFImp_FP1 is a part of a list with randomForest results,
RFImp_FP1
# RFImp_FP1$Selected <- if(!is.null(values$Selected)){
# values$Selected } else {1 }
})
output$RFAcc_FP1 <- renderPlotly({
RFImp_score <- RFImp_FP1()[order(MeanDecreaseAccuracy)]
plotheight <- length(RFImp_score$Variables) * 80
p <- plot_ly(data = RFImp_score,
source = 'RFAcc_FP1',
height = plotheight,
width = 450) %>%
add_trace(x = RFImp_score$MeanDecreaseAccuracy,
y = RFImp_score$Variables,
type = 'scatter',
mode = 'markers',
color = factor(RFImp_score$Selected),
colors = c('#1b73c1', '#797979'),
symbol = factor(RFImp_score$Selected),
symbols = c('circle','x'),
marker = list(size = 6),
hoverinfo = "text",
text = ~paste ('<br>', 'Parameter: ', RFImp_score$Variables,
'<br>', 'Mean decrease accuracy: ', format(round(RFImp_score$MeanDecreaseAccuracy*100, digits = 2), nsmall = 2),'%',
sep = '')) %>%
layout(
margin = list(l = 160, r= 20, b = 70, t = 50),
hoverlabel = list(font=list( color = '#1b73c1'), bgcolor='#f7fbff'),
xaxis = list(title = 'Mean decrease accuracy index (%)',
tickformat = "%",
showgrid = F,
showline = T,
zeroline = F,
nticks = 5,
font = list(size = 8),
ticks = "outside",
ticklen = 5,
tickwidth = 2,
tickcolor = toRGB("black")
),
yaxis = list(categoryarray = RFImp_score$Variables,
autorange = T,
showgrid = F,
showline = T,
autotick = T,
font = list(size = 8),
ticks = "outside",
ticklen = 5,
tickwidth = 2,
tickcolor = toRGB("black")
),
dragmode = "select"
) %>% add_annotations(x = 0.5,
y = 1.05,
textangle = 0,
font = list(size = 14,
color = 'black'),
text = "Contribution to accuracy",
showarrow = F,
xref='paper',
yref='paper')
p <- p %>% config(displayModeBar = F)
p
})
}
shinyApp(ui, server)
In the demo app below, the user can change the Selected state of the data rows by either clicking input$Go1 or select a region in the plot.
Selection the region in the plot is my intended functionality.
However, for a reason I fail to understand, the button does cause a re-render of the plot while select does not, even though both approaches have the same effect, i.e. a change in the values in column Selected of data.table RFImp_FP1
Why is it not working when I select points in the plot?
ui <- fluidPage(
actionButton(inputId = 'Go', label = 'Go'),
actionButton(inputId = 'Go2', label = 'Go2'),
plotlyOutput('RFAcc_FP1', width = 450)
)
server <- function(input, output, session) {
values <- reactiveValues()
observeEvent(input$Go, {
values$RFImp_FP1 <- data.table(MeanDecreaseAccuracy = runif(10, min = 0, max = 1), Variables = letters[1:10])
values$RFImp_FP1$Selected <- 1
})
observeEvent(input$Go2,{
values$RFImp_FP1$Selected[1:4] <- 1-values$RFImp_FP1$Selected[1:4]
print(values$RFImp_FP1$Selected)
})
observe({
if(!is.null(values$RFImp_FP1)) {
parsToChange <- event_data("plotly_selected", source = 'RFAcc_FP1')$y
if(!is.null(event_data("plotly_selected", source = 'RFAcc_FP1'))){
data_df <- values$RFImp_FP1
data_df <- data_df %>% .[, Selected := if_else(Variables %in% parsToChange, 1-Selected, Selected)]
values$RFImp_FP1$Selected <- data_df$Selected
print(values$RFImp_FP1)
}
}
})
observeEvent(values$RFImp_FP1, {
print('seeing change')
})
output$RFAcc_FP1 <- renderPlotly({
values$RFImp_FP1
if(!is.null(values$RFImp_FP1)) {
RFImp_score <- values$RFImp_FP1[order(MeanDecreaseAccuracy)]
plotheight <- length(RFImp_score$Variables) * input$testme
p <- plot_ly(data = RFImp_score,
source = 'RFAcc_FP1',
height = plotheight,
width = 450) %>%
add_trace(x = RFImp_score$MeanDecreaseAccuracy,
y = RFImp_score$Variables,
type = 'scatter',
mode = 'markers',
color = factor(RFImp_score$Selected),
colors = c('#1b73c1', '#797979'),
symbol = factor(RFImp_score$Selected),
symbols = c('circle','x'),
marker = list(size = 6),
hoverinfo = "text",
text = ~paste ('<br>', 'Parameter: ', RFImp_score$Variables,
'<br>', 'Mean decrease accuracy: ', format(round(RFImp_score$MeanDecreaseAccuracy*100, digits = 2), nsmall = 2),'%',
sep = '')) %>%
layout(
margin = list(l = 160, r= 20, b = 70, t = 50),
hoverlabel = list(font=list( color = '#1b73c1'), bgcolor='#f7fbff'),
xaxis = list(title = 'Mean decrease accuracy index (%)',
tickformat = "%",
showgrid = F,
showline = T,
zeroline = F,
nticks = 5,
font = list(size = 8),
ticks = "outside",
ticklen = 5,
tickwidth = 2,
tickcolor = toRGB("black")
),
yaxis = list(categoryarray = RFImp_score$Variables,
autorange = T,
showgrid = F,
showline = T,
autotick = T,
font = list(size = 8),
ticks = "outside",
ticklen = 5,
tickwidth = 2,
tickcolor = toRGB("black")
),
dragmode = "select"
) %>% add_annotations(x = 0.5,
y = 1.05,
textangle = 0,
font = list(size = 14,
color = 'black'),
text = "Contribution to accuracy",
showarrow = F,
xref='paper',
yref='paper')
p$elementId <- NULL ## to surpress warning of widgetid
p <- p %>% config(displayModeBar = F)
p
} else {
p <- plot_ly( type = 'scatter', mode = 'markers', height = '400px', width = 450) %>% layout(
margin = list(l = 160, r= 20, b = 70, t = 50),
xaxis = list(title = 'Mean decrease accuracy index', range= c(0,1), nticks = 2, showline = TRUE),
yaxis = list(title = 'Model input variables', range = c(0,1), nticks = 2, showline = TRUE)) %>%
add_annotations(x = 0.5, y = 1.1, textangle = 0, font = list(size = 14, color = 'black'),
text = 'Contribution to accuracy',
showarrow = F, xref='paper', yref='paper')
p$elementId <- NULL
p <- p %>% config(displayModeBar = F)
p}
})
}
shinyApp(ui, server)
select vs button result:
Don't ask me why, but I managed to get it to work with observeEvent and assigning NULL to the values$RFImp_FP1 before reassigning the altered data.table to it
values$RFImp_FP1 <- NULL
values$RFImp_FP1<- resDF
Full version:
library(shiny)
library(plotly)
library(dplyr)
library(data.table)
testDF <- data.table( MeanDecreaseAccuracy = runif(10, min = 0, max = 1), Variables = letters[1:10])
testDF$Selected <- T
ui <- fluidPage(
plotlyOutput('RFAcc_FP1', width = 450)
)
server <- function(input, output, session) {
values <- reactiveValues(RFImp_FP1 = testDF)
observeEvent(event_data("plotly_selected", source = 'RFAcc_FP1')$y, {
parsToChange <- event_data("plotly_selected", source = 'RFAcc_FP1')$y
resDF <- values$RFImp_FP1 %>% .[, Selected := if_else(Variables %in% parsToChange, !Selected, Selected)]
values$RFImp_FP1 <- NULL ## without this line the plot does not react
values$RFImp_FP1<- resDF ## re-assign the altered data.table to the reactiveValue
})
output$RFAcc_FP1 <- renderPlotly({
RFImp_score <- values$RFImp_FP1[order(MeanDecreaseAccuracy)]
plotheight <- length(RFImp_score$Variables) * 80
p <- plot_ly(data = RFImp_score,
source = 'RFAcc_FP1',
height = plotheight,
width = 450) %>%
add_trace(x = RFImp_score$MeanDecreaseAccuracy,
y = RFImp_score$Variables,
type = 'scatter',
mode = 'markers',
color = factor(RFImp_score$Selected),
colors = c('#F0F0F0', '#1b73c1'),
symbol = factor(RFImp_score$Selected),
symbols = c('x', 'circle'),
marker = list(size = 6),
hoverinfo = "text",
text = ~paste ('<br>', 'Parameter: ', RFImp_score$Variables,
'<br>', 'Mean decrease accuracy: ', format(round(RFImp_score$MeanDecreaseAccuracy*100, digits = 2), nsmall = 2),'%',
sep = '')) %>%
layout(
margin = list(l = 160, r= 20, b = 70, t = 50),
hoverlabel = list(font=list( color = '#1b73c1'), bgcolor='#f7fbff'),
xaxis = list(title = 'Mean decrease accuracy index (%)',
tickformat = "%",
showgrid = F,
showline = T,
zeroline = F,
nticks = 5,
font = list(size = 8),
ticks = "outside",
ticklen = 5,
tickwidth = 2,
tickcolor = toRGB("black")
),
yaxis = list(categoryarray = RFImp_score$Variables,
autorange = T,
showgrid = F,
showline = T,
autotick = T,
font = list(size = 8),
ticks = "outside",
ticklen = 5,
tickwidth = 2,
tickcolor = toRGB("black")
),
dragmode = "select"
) %>% add_annotations(x = 0.5,
y = 1.05,
textangle = 0,
font = list(size = 14,
color = 'black'),
text = "Contribution to accuracy",
showarrow = F,
xref='paper',
yref='paper')
p <- p %>% config(displayModeBar = F)
p
})
}
shinyApp(ui, server)
and to avoid the plotly warnings about not being registered, we can change the observe structure to
observe({
if(!is.null( values$RFImp_FP1)) {
values$Selected <- event_data("plotly_selected", source = 'RFAcc_FP1')$y
}
})
observeEvent(values$Selected, {
parsToChange <- event_data("plotly_selected", source = 'RFAcc_FP1')$y
if(!is.null(event_data("plotly_selected", source = 'RFAcc_FP1'))){
data_df <- values$RFImp_FP1
data_df <- data_df %>% .[, Selected := if_else(Variables %in% parsToChange, !Selected, Selected)]
values$RFImp_FP1 <- NULL
values$RFImp_FP1 <- data_df
}
})
One problem remains: making the same selection twice in a row does not trigger the observers as the selection is identical....