I'm trying to use a slider to control year in a longitudinal spatial data set, essentially a set of scatter plots. I can't figure out how to assign the slider to this variable - can you do this in ggvis?
A simplified data set:
data <- data.frame(year=rep(2000:2002, each=23),
x=rnorm(23*3,10), y=rnorm(23*3,10),
count=c(rnorm(23,2), rnorm(23,4), rnorm(23,6)))
What I've tried:
### This is what is looks like in ggplot2, I'm aiming to be able to toggle
### between these panels
ggplot(data, aes(x, y, size=count)) + geom_point() + facet_grid(~year)
### Here is where I'm at with ggvis
data %>%
ggvis(~x, ~y, size=~count) %>%
layer_points()
# I'm not sure how to assign a variable (year) to a slider, I've been trying
# within the layer_points() function
### I also tried using the props() function, but I don't fully understand
### how to use it.
data %>%
ggvis(~x, ~y, size=~count) %>%
layer_points() %>%
props(prop("fill", input_slider(min(data$year), max(data$year)))) #error message
Any help is appreciated!
I'm not sure if you want to use the slider to filter the data points (i.e. only show those points from the year selected on the slider), or to show the years in different colors according to the slider's value.
Case 1 (only display the points from a specific year)
data %>%
ggvis(~x, ~y, size=~count) %>%
layer_points(opacity=input_slider(min(data$year), max(data$year), step=1,
map=function(x) ifelse(data$year == x, 1, 0)))
Case 2 (highlight the selected years)
data %>%
ggvis(~x, ~y, size=~count) %>%
layer_points(fill=input_slider(min(data$year), max(data$year), step=1,
map=function(x) factor(x == data$year)))
EDIT2: How to simply wrap a left_right() function.
In the first edit I presented a solution that is not properly considered as wrapping.
I was interested in creating a wrapper of the reactive object returned by left_right(), avoiding modifying create_keyboard_event all together.
After reading the source code of ggvis more thoroughly and more on S4 objects in R,
I realized that yes, you can simply wrap a reactive object, as long as you preserve the broker class and its broker attribute appropriately.
This allows us to write more elegant code, like:
year_lr <- left_right(1997, 2002, value=2000, step=1)
year_wrapper <- reactive({
as.numeric(year_lr() == data$year)
})
class(year_wrapper) <- c("broker", class(year_wrapper))
attr(year_wrapper, "broker") <- attr(year_lr, "broker")
data %>%
ggvis(~x, ~y, size=~count) %>%
layer_points(opacity:=year_wrapper)
EDIT: How to create your own (modified) left_right() function
user3389288 asked me a good question, that since you don't have a map argument for left_right() function, how can you actually bind keyboard event to generate custom parameters. For example, in the context of this question, how can we tailor left_right() as a year filter?
If you dig into the source code of ggvis, you can see that left_right() is simply a thin wrapper function calling create_keyboard_event.
Hence we can create our own version of left_right(), or even h_j_k_l() say if you are fanatic about Vi.
But, here is a big but, if you dig one layer further to look at the implementation of create_keyboard_event, you will find that it is not quite suitable for our task.
This is because in order to show some of the dots, while hide others, we have to let left_right return a vector (that equals to the number of rows in data).
However, both left_right and create_keyboard_event are created with the assumption that the returned value (which is also the current state of the value modified by Left/Right key presses) is a scalar.
In order to separate the return value (vector) from the cached current state (scalar, i.e. the year), we have to create a slightly modified version of left_right() and create_keyboard_event.
Below is the source code that would work.
data <- data.frame(year=rep(1997:2002, each=12),
x=rnorm(24*3,10), y=rnorm(24*3,10),
count=c(rnorm(24,2), rnorm(24,4), rnorm(24,6)))
create_keyboard_event2 <- function(map, default.x = NULL, default.res = NULL) {
# A different version of ggvis::create_keyboard_event function:
# the major different is that the map function returns a list,
# list$x is the current value and list$res the result (returned to a ggvis prop).
# this seperation allows us to return a vector of different
# values instead of a single scalar variable.
if (!is.function(map)) stop("map must be a function")
vals <- shiny::reactiveValues()
vals$x <- default.x
vals$res <- default.res
# A reactive to wrap the reactive value
res <- reactive({
vals$res
})
# This function is run at render time.
connect <- function(session, plot_id) {
key_press_id <- paste0(plot_id, "_key_press")
shiny::observe({
key_press <- session$input[[key_press_id]]
if (!is.null(key_press)) {
# Get the current value of the reactive, without taking a dependency
current_value <- shiny::isolate(vals$x)
updated <- map(key_press, current_value)
vals$x <- updated$x
vals$res <- updated$res
}
})
}
ggvis:::connector_label(connect) <- "key_press"
spec <- list(type = "keyboard")
ggvis:::create_broker(res, connect = connect, spec = spec)
}
# a modified version of left_right. this closure encapsulates the
# data "year", allowing us to perform comparison of the current state of
# left_right (numeric year number) to the year vector.
left_right_year <- function(min, max, value = (min + max) / 2,
step = (max - min) / 40, year) {
# Given the key_press object and current value, return the next value
map <- function(key_press, current_value) {
key <- key_press$value
print(current_value)
if (key == "left") {
new_value <- pmax(min, current_value - step)
} else if (key == "right") {
new_value <- pmin(max, current_value + step)
} else {
new_value = current_value
}
list(x=new_value, res=as.numeric(year == new_value))
}
create_keyboard_event2(map, value, as.numeric(value==year))
}
# now with an additional argument, the data$year
alpha_by_year <- left_right_year(1997, 2002, value=2000, step=1, data$year)
data %>%
ggvis(~x, ~y, size=~count) %>%
layer_points(opacity:=alpha_by_year) # if you let left_right_year return
# a factor vector, you can use fill:=... as well
You can compare left_right_year and create_keyboard_event2 with their vanilla version counterparts.
For example, the original create_keyboard_event is:
create_keyboard_event <- function(map, default = NULL) {
if (!is.function(map)) stop("map must be a function")
vals <- shiny::reactiveValues()
vals$x <- default
# A reactive to wrap the reactive value
res <- reactive({
vals$x
})
# This function is run at render time.
connect <- function(session, plot_id) {
key_press_id <- paste0(plot_id, "_key_press")
shiny::observe({
key_press <- session$input[[key_press_id]]
if (!is.null(key_press)) {
# Get the current value of the reactive, without taking a dependency
current_value <- shiny::isolate(vals$x)
vals$x <- map(key_press, current_value)
}
})
}
connector_label(connect) <- "key_press"
spec <- list(type = "keyboard")
create_broker(res, connect = connect, spec = spec)
}
You can see that our modified version will not only cache the current state vals$x, but also the return vector vals$res.
The variable vals is a reactive value. The concept is borrowed from Shiny. You can check out this document about a high-level overview of reactive values and reactivity in general.
A question yet to be answered
Since vals$x is itself a reactive value. Intuitively, if
x <- left_right(1, 100, value=20, step=10)
then
y <- reactive(x() * 2)
should allow us to implement a quick map function.
However it doesn't work as expected. I am yet to figure out why exactly. If you know the answer, please kindly let me know!
UPDATED: c.f. EDIT2
The answers above are great. Definitively worth study. This is what I came up with for the original question for a quick fix.
Global.R:
library(shiny)
library(ggvis)
data<-data.frame(year=rep(2000:2002, each=23), x=rnorm(23*3,10), y=rnorm(23*3,10),
count=c(rnorm(23,2),rnorm(23,4),rnorm(23,6)))
ui.R:
shinyUI(bootstrapPage(
h3("Ploting Diferent Years Using a Slider",align="center"),
br(),
fluidRow(column(4,ggvisOutput("yearPlot"),offset=3)),
fluidRow(column(3,sliderInput("YearSelect", "Year: ",min=2000,max=2002,step=1,value=2000),offset=5))
))
Server.R:
shinyServer(function(input, output,session) {
plotdata <- reactive({
chosendat <- data[data$year==input$YearSelect, ]
names(chosendat) <- c("year","xvar","yvar","count")
return(chosendat)
})
vis1% ggvis(~xvar, ~yvar, size=~count) %>% layer_points()
})
vis1 %>% bind_shiny("yearPlot")
})
Related
I'm struggling to update a reactive variable, that is created with eventReactive(), in an observeEvent() with new data.
The background is following: I have a data.frame df with some variables (x and y) and number of observations depending on the selected city (created randomly for this example).
x and y are initialized with zeros.
Because I need to further process df, I pass df to city_df in an eventReactive().
So far, so good. Next, I want to add new data to city_df. The computation of this new data is dependent on the "compute" actionButton (input$compute), wherefore I update city_df in an observeEvent(). I manage to read the data stored in city_df, but I am struggling to overwrite its content.
Actually, I am a bit unsure if this is possible at all, but I hope that some of you could give me a hint on how to update the reactive variable city_df with the new data in this observeEvent() and have its output evaluated in the app(?).
library(shiny)
# global variables
cities <- c("Nairobi", "Kansas", "Uppsala", "Sangon", "Auckland", "Temuco")
# ui
ui <- fluidPage(
fluidPage(
fluidRow(
column(2,
selectInput("city", "Select city",
choices = cities,
selected = sample(cities,
size = 1)
),
actionButton("compute",
"Compute")),
column(8,
verbatimTextOutput("the_city"))
))
)
# server
server <- function(input, output, session) {
# create variable
city_df <- eventReactive(input$city, {
len <- round(runif(1, 20, 50), 0)
df <- data.frame(city = rep(input$city, len))
# initialize x and y with zeros
df <- cbind(df,
data.frame(x = rep.int(0, len),
y = rep.int(0, len)))
})
output$the_city <- renderText({
paste(city_df())
})
observeEvent(input$compute, {
# grab data
test <- city_df()
# compute new data
test$x <- runif(dim(test)[1], 11, 12)
test$y <- runif(dim(test)[1], 100, 1000)
# and how to send this values back to city_df?
})
}
# run app
shinyApp(ui, server)
The actual app is far more complex--so forgive me if this MWE app seems a bit overly complicated to achieve this usually simple task (I hope I managed to represent the more complex case in the MWE).
Instead of a data.frame, I am parsing layers of a GeoPackage and append some variables initialized with zeros. The selected layer is displayed in a Leaflet map. On pressing the "compute" button, a function computes new data that I wish to add to the layer to then have it displayed on the map.
The alternative solution I have on mind is to write the new data to the GeoPackage and then, reread the layer. However, I would appreciate if I could avoid this detour as loading the layer takes some time...
Many thanks :)
Rather than using an eventReactive, if you use a proper reactiveVal, then you can change the value whenever you like. Here's what that would look like
server <- function(input, output, session) {
# create variable
city_df <- reactiveVal(NULL)
observeEvent(input$city, {
len <- round(runif(1, 20, 50), 0)
df <- data.frame(city = rep(input$city, len))
# initialize x and y with zeros
df <- cbind(df,
data.frame(x = rep.int(0, len),
y = rep.int(0, len)))
city_df(df)
})
output$the_city <- renderText({
paste(city_df())
})
observeEvent(input$compute, {
# grab data
test <- city_df()
test$x <- runif(dim(test)[1], 11, 12)
test$y <- runif(dim(test)[1], 100, 1000)
city_df(test)
})
}
So calling city_df() get the current value and calling city_df(newval) updates the variable with a new value. We just swap out the eventReactive with observeEvent and do the updating ourselves.
I am having trouble sending a tibble loaded from a user file to a plotting function. It seems that the table is indeed read and modified properly, but when I ask another function to use it for plotting the entries are missing.
on the Shiny server I have the following:
myData <- reactive({
if (is.null(inFile())) {
return(NULL)
} else {
tmp_table = read_csv(inFile()$datapath[1])
# tmp_table = read_csv('Fazael_grain_size.csv')
big_table=tmp_table
# modify the factor columns into factors
big_table$sample_name <- factor(big_table$sample_name)
big_table$site <- factor(big_table$site)
big_table$fraction <- factor(big_table$fraction)
# remove NA rows
big_table=big_table[!is.na(big_table$sample_name),]
# make sure the table was loaded correctly
output$table <- renderTable((big_table))
return(big_table)
}
})
a screenshot showing the proper loading of the table
then I want to use this table for plotting, so I have the following chunk:
myPlot <- function(){
# make sure the data exists
req(myData())
big_table=myData()
# checking the tibble was properly sent to the plotting function
output$table <- renderTable(big_table)
# filter the data based on user selection
big_table = filter(big_table,sample_name %in% input$selected_sample_name)
big_table = filter(big_table,site %in% input$selected_site)
big_table = filter(big_table,fraction %in% input$fraction)
p = big_table %>%
ggplot(aes_string(x=colnames(big_table)[4],y=colnames(big_table)[4])) +
geom_point()
if (!(input$color_variable %in% c("none"))) {
p = p +
geom_point(aes_string(color=input$color_variable))
}
p
}
for some reason the plot remains empty, and after some debugging I found out that the tibble is forwarded empty to it. any answers?
a screenshot demonstrating that the entries in the table are not forwarded into the plotting function
and indeed, the plot doesn't show up:
output$plot1 <- renderPlot({
myPlot()
})
no plot appears on the plotting area
solved. there was a small bug in the filter function:
should have been:
big_table = filter(big_table,fraction %in% input$selected_fraction)
I am having problems with reactively passing date-time collapsing functions into index_by function from package tsibble.
index_by takes as an argument a time function (for example week() or month() from lubridate) and collapses the data accordingly.
I want the collapsing to be reactive to user input (specifically selected data-range).
Example: if selected date-range > 60, collapse using week(), if selected date-range > 120, collapse using month().
EDIT:
I am specifically refering to using tsibble in the context of server side of shiny module for plotting. Example:
mod_plot = function(input, output, session, df, date_Range){
output$plot = renderPlotly({
df() %>%
index_by(collapse_time = week(.)/month(.)) %>%
summarise(trace1 = sum(trace1))%>%
plot_ly(type = 'bar', x = ~collapse_time, y = ~trace1)
})
}
In order to avoid code duplicity, it would be great to somehow pass date collapsing functions into index_by. Example using tibbletime:
mod_plot = function(input, output, session, df, date_Range){
output$plot = renderPlotly({
#create reactive variable collapse_time based on selected time range
collapse_time = renderText(if(as.numeric(date_Range$selectdateRange[2] - date_Range$selectdateRange[1]) <= 60){"daily"}
else if(as.numeric(date_Range$selectdateRange[2] - date_Range$selectdateRange[1]) < 120){"weekly"}
else{"monthly"})
df() %>%
collapse_by(collapse_time ) %>%
group_by(date) %>%
summarise(trace1 = sum(trace1))%>%
plot_ly(type = 'bar', x = ~date, y = ~trace1)
})
}
This allows for concise and readable pipeline.
This custom function returns desired collapsing function based on selected date-range.
get_fct =
function(x){
if(date_Range$selectdateRange[2] - date_Range$selectdateRange[1] <= 60){
ymd
}
else if(date_Range$selectdateRange[2] - date_Range$selectdateRange[1] < 120){
yearweek
}
else if(date_Range$selectdateRange[2] - date_Range$selectdateRange[1] >= 120){
yearmonth}
}
collapsing_function = get_fct(x = as.numeric(date_Range$selectdateRange[2] - date_Range$selectdateRange[1]))
PROBLEM SYNOPSIS: I have a shiny app I am building where I am trying to support database calls that drive dynamic lists of choices for selectInputs and where that dynamic input drives what a ggvis plot shows. The dynamic selectInput is not correctly selected and filtering the ggvis plot
QUESTION: How can I support dynamic drop down lists while still ensuring my ggvis plot filters based on the chosen item?
PROBLEM DETAIL:
Within my Server.R file I have a reactive that grabs a slice of data from the database. The get_chunk function is a call to NEO4J that I know works:
shinyServer( function(input, output, session) {
CURRENT_CHUNK <- reactive({
#call NEO4J
chunk <- get_chunk(some_list = input$chunk)
return(chunk)
})
I also have a reactive that simply filters down the data based on the ui choices on the front end. The input$A value is the chosen value from a dynamically built dropdown list. The filter_reactive is below. NOTE: I've separated these two so I don't have to call the database all the time; only when I choose a different CHUNK. The filter reactive looks like the following:
NO_DB_REACTIVE <- reactive({
#react to current_chunk and pull back a chunk.
filter_down <- CURRENT_CHUNK()
#check for nulls
if (!is.null(chunk)) {
if (input$A != "All") {filter_down <- filter_down %>% filter(A == input$A)}
return(filter_down)
}
return(filter_down)
})
The input$A value is generated dynamically as follows within the server.R file:
# reactively /dynamically generated the choices for the channel owners
output$owner_choices <- renderUI({ selectInput("A", "FOR Owner"
, as.list(c("All",unique(CURRENT_CHUNK()[,'owner'] )))
, "All") })
I also have a reactive that generates my GGVIS plot that looks like the following:
#All of the visualizations
MY_VIS <- reactive({
# Lables for axes
yvar_name <- names(display_choices)[display_choices == input$yvar]
xvar_name <- names(cat_choices)[cat_choices == input$xvar]
#retrieving
yvar <- prop("y", as.symbol(input$yvar))
xvar <- prop("x", as.symbol(input$xvar))
CURRENT_CHUNK %>%
ggvis(x = xvar, y = yvar) %>%
layer_bars() %>%
add_axis("x", title = xvar_name, properties = axis_props(labels = list(angle = 45, align = "left", fontSize = 10))) %>%
add_axis("y", title = yvar_name) %>%
set_options(width = 900, height = 300)
})
I tried to slim this down as much as possible. There is some dynamic choosing of axes there but you get the point. Notice I currently call CURRENT CHUNK within the MY_VIS reactive. And the end of the file has these:
output$table <- renderDataTable({ NO_DB_REACTIVE() })
MY_VIS %>% bind_shiny("my_vis")
When I filter items this way I get no errors. My table filters on input$A changes and the vis does not; I want the vis to change based on what input$A is as well.
I originally tried having MY_VIS depend on NO_DB_REACTIVE. This fails as input$A is never generated. I guess because CURRENT_CHUNK never runs as it doesn't have to. Notice the output$owner_choices is generated by reacting to CURRENT_CHUNK() not NO_DB_REACTIVE().
KEY QUESTION: How can I set this up so my input$A value is available when I want to filter my ggvis plot?
I'm trying to learn how to use ggvis and shiny together, by adapting the code from the movie explorer exmaple.
I have come across a problem where when a user's interaction adds new data to the graph, the hover effects don't work for the new data if there is a key. I'd like to be able to keep the key, because I'll also want to have a tooltip.
A quick example is below. To reproduce bug:
1) Run shiny app.
2) Use the select box to add the "b" points.
3) Now the "a" points will increase in size upon hover, as expected, but the "b" points will not.
To get proper behavior, I could just comment out the line in server.r, but I don't want to do that (because of the tooltip I want to add). Is there something I can do with reactive to get the hover behavior on all points?
Thank you!
server.r
library(ggvis)
# Set up data on app start
all_data <- data.frame(x=1:4, y=1:4, z=c("a", "b", "b", "a"), ID=1:4)
shinyServer(function(input, output, session) {
# Filter the data
this_data <- reactive({
all_data[all_data$z %in% input$z, ]
})
# A reactive expression with the ggvis plot
vis <- reactive({
this_data %>%
ggvis(~x, ~y) %>%
layer_points(fill = ~z,
key := ~ID, # Comment out this line for proper behavior
size := 50, size.hover := 500,
fillOpacity := 0.9, fillOpacity.hover := 0.5)
})
vis %>% bind_shiny("plot1")
})
ui.r
library(ggvis)
shinyUI(fluidPage(
titlePanel("Example"),
fluidRow(
column(3,
wellPanel(
selectInput("z", "Select Z", c("a", "b"),
selected = "a", multiple=TRUE)
)
),
column(9,
ggvisOutput("plot1")
)
)
))
This had me pretty confused. I could replicate it in a bunch of strange ways - e.g. adding groups / adding points. The order in which you added groups and removed them also did strange things.
In one of my own shiny/ggvis plots that I made after the movie example, I had a similar issue with key/ID and I found that adding the ID after all data filtering worked better. I tried that here by adding the ID variable after the filtering but it still didn't work. However, adding it in a new reactive did.
here is my ggvis/shiny where I had to do something similar - https://jalapic.shinyapps.io/cricket
and the raw code - https://github.com/jalapic/shinyapps/blob/master/cricket/server.R
I think this works - at least for me. Effectively, note I remove the ID from the data setup. I then add it in a new reactive after our filtering is done in the first reactive.
# Set up data on app start
all_data <- data.frame(x=c(1,3:5), y=1:4, z=c("a", "b", "b", "a"))
shinyServer(function(input, output, session) {
# Filter the data
this_data <- reactive({
all_data[all_data$z %in% input$z, ]
})
this_data1 <- reactive({
as.data.frame(this_data() ) %>% mutate(ID = row_number(x))
})
# A reactive expression with the ggvis plot
vis <- reactive({
this_data1 %>%
ggvis(~x, ~y) %>%
layer_points(fill = ~z,
key := ~ID, # Comment out this line for proper behavior
size := 50, size.hover := 500,
fillOpacity := 0.9, fillOpacity.hover := 0.5)
})
vis %>% bind_shiny("plot1")
})
I should add that I have added the ID variable using the %>% mutate(row_number()) command from dplyr but I'm sure you could do it in another way if this doesn't suit you.