I am trying to add a linear trend line to a plotly plot in a shiny app. When I change the selection parameters, I see that the coefficients of the linear model change (using observe(print(summary(l)). However, the actual line on the plot seems to stay in the same place.
Here is one plot, where the trend line at least seems close to intersecting the two points:
In another plot, the trend line is nowhere near the first point:
Here is a minimal working example:
library(dplyr)
library(shiny)
library(plotly)
df <- as.data.frame(list("UserID"=c(1,1,1,1,2,2,2,2),
"QuestionID"=c(4,4,5,5,4,4,6,6),
"KeystrokeRate"=c(8,4,6,15,8,6,7,8),
"cumul.ans.keystroke"=c(1,7,1,5,1,14,1,9),
"Relative.Time.Progress"=c(0.1,1.0,0.4,1.0,0.8,1.0,0.8,1.0)
))
ui <- (fluidPage(
sidebarLayout(
sidebarPanel(
selectInput("userInput","Select User", sort(unique(df$UserID)),
selected = sort(unique(df$UserID))[1]),
uiOutput("answerOutput")#,
),
mainPanel(
plotlyOutput("mainPlot")#,
)
)
))
server <- function(input, output, session) {
# filter only based on selected user
filteredForUser <- reactive({
try(
df %>%
filter(
UserID == input$userInput
), silent=T)
})
# filter for both user and answer
filteredFull <- reactive({
try (
df %>%
filter(
UserID == input$userInput,
QuestionID == input$answerInput
), silent=T)
})
# filter answer choices based on user
output$answerOutput <- renderUI({
df.u <- filteredForUser()
if(!is.null(df)) {
selectInput("answerInput", "Select A Typing Session",
sort(unique(df.u$QuestionID)))
}
})
output$mainPlot <- renderPlotly({
if (class(filteredForUser()) == "try-error" ||
class(filteredFull()) == "try-error") {
return(geom_blank())
} else {
# plot scatter points and add trend lines
p <- plot_ly(filteredFull(), x=Relative.Time.Progress, y=cumul.ans.keystroke,
mode='markers', color=KeystrokeRate, size=KeystrokeRate,
marker=list(sizeref=100), type='scatter')
l <- lm(cumul.ans.keystroke ~ Relative.Time.Progress,
data=filteredFull())
observe(print(summary(l)))
p <- add_trace(p, y= fitted(l))
p
}
})
}
shinyApp(ui, server)
The problem is with the add_trace functionality. You need to provide it the x-axis to be able to correctly plot the lm result.
p <- add_trace(p, y = fitted(l), x = Relative.Time.Progress)
To see the problem more clearly, visualize the results with the entire dataset.
p <- plot_ly(df, x=Relative.Time.Progress, y=cumul.ans.keystroke,
mode='markers', color=KeystrokeRate, size=KeystrokeRate,
marker=list(sizeref=100), type='scatter')
p
l <- lm(cumul.ans.keystroke ~ Relative.Time.Progress,
data=df)
p <- add_trace(p, y = fitted(l))
p
p <- plot_ly(df, x=Relative.Time.Progress, y=cumul.ans.keystroke,
mode='markers', color=KeystrokeRate, size=KeystrokeRate,
marker=list(sizeref=100), type='scatter')
l <- lm(cumul.ans.keystroke ~ Relative.Time.Progress,
data=df)
p <- add_trace(p, y = fitted(l), x = Relative.Time.Progress)
p
So as you would see, add_trace was plotting the fitted(y) correctly but using x-axis to be c(0:7). I am guessing it is a default passed to add_trace, but I haven't looked deeply of the 'why' of this. The dataset df has eight points. You instead needed to give the actual Relative.Time.Progress values on the x-axis to correctly plot the fitted(y) w.r.t. actual x values. Hope this clarifies.
Related
I'm trying to include a plotly plot in a shiny app where the y variable is selected by the user. I initially used ggplot2 and plotly together, and the code I have works just fine for that. But because the number of data points is quite large, the plot takes several minutes to load, so I tried switching to plotly only because I read somewhere that that makes it faster. Unfortunately I cannot get the y variable selection to work.
I have tried the suggestions given here: Change plotly chart y variable based on selectInput and here: Error: invalid first argument with R Shiny plot and none of them work. At this point I have tried so many things I don't remember in detail, but basically I either get the error "invalid first argument" when using some variation of yvar <- get(input$yvariable1) and then including ~yvar in the plot function, or I get "Error: cannot set attribute on a symbol" when it's y = ~input$yvariable1. When I use y = newdata[ ,input$yvariable1] something gets plotted but it's completely wrong (the scale of the axis is up to 50k or something instead of 10 and the distribution is not right either - basically it looks nothing like when I plot it by simply entering the same y variable non-reactively).
My code looks as follows - in UI:
uiOutput("ySelection1")
in server:
function(input, output) {
output$ySelection1 <- renderUI({
varSelectInput("yvariable1", "Y Variable:", df[, c('PO_count_citing', 'cpc_3digits_count_citing', 'cpc_4digits_count_citing')], selected='PO_count_citing')
})
yvar1 <- eventReactive(input$yvariable1, {input$yvariable1})
output$plot1 <- renderPlotly({
newdata <- subset(df, Technology == input$type & appln_auth%in%input$PO)
validate(no_data(nrow(newdata)))
#yvar <- get(yvar1()) (failed attempt at making this work)
#yvar <- get(input$yvariable1) (another failed attempt)
scatterPlot <- plot_ly(newdata, x = ~appln_filing_year, y = ~input$yvariable1, type="scatter", mode="markers",
# Hover text:
text = ~paste(some text),
color = ~appln_auth)
})
}
But I can't get it to work. In the original ggplot2 version it was entered as aes(x = appln_filing_year, y = !!yvar1(), bla bla)
But the !! or even one ! or removing the brackets after yvar1 all throw up errors in plotly.
Does anyone have any suggestions?
Here is a simple example using get:
library(shiny)
library(plotly)
DF <- setNames(data.frame(rep(1:20, 5), mapply(runif, min = 1:5, max = 2:6, MoreArgs = list(n = 20))), c("x", paste0("y", 1:5)))
library(shiny)
ui <- fluidPage(
plotlyOutput("myPlot"),
selectInput("yvariable", "Select the Y variable", paste0("y", 1:5))
)
server <- function(input, output, session) {
output$myPlot <- renderPlotly({
req(input$yvariable)
plot_ly(data = DF, x = ~x, y = ~get(input$yvariable), type = "scatter", mode = "markers")
})
}
shinyApp(ui, server)
I have developed a shiny app, in which I am uploading a number of CSV files in each tab. After performing some mathematical operations on the uploaded data, I am getting N number of data tables as an output. I am rendering those data tables using DT::renderDataTable.
Now, let say I have 3 different datatables rendered using DT::renderDataTable I want to use the output rendered in those datatables to plot a combined graph. (3 geom_line() on top of each other)
This is how I am rendering the data in the datatable:
output$Data_FSC <- DT::renderDataTable({
x1 <- data2()[, c(input$xcol2, input$ycol2)]
M <- x1
#calculate rotation angle
alpha <- -atan((M[1,2]-tail(M,1)[,2])/(M[1,1]-tail(M,1)[,1]))
#rotation matrix
rotm <- matrix(c(cos(alpha),sin(alpha),-sin(alpha),cos(alpha)),ncol=2)
#shift, rotate, shift back
M2 <- t(rotm %*% (t(M)-c(M[1,1],M[1,2]))+c(M[1,1],M[1,2]))
M2[nrow(M2),2] <- M2[1,2]
M2
d_f3 <- data.frame(x = M2[,1], y = (M2[,2]-min(M2[1,2])))
v_f1 <- subset(d_f3, y > ((input$below2)/1000) & y < ((input$above2)/1000), select = c(x,y))
fla_upper2 <- lm(formula = y+((input$Upper_Poly_Limit2)/1000000) ~ poly(x,input$degree2, raw = TRUE), v_f1)
fla_lower2 <- lm(formula = y-((input$Lower_Poly_Limit2)/1000000) ~ poly(x,input$degree2, raw = TRUE), v_f1)
v_f1$upper2 <- predict(fla_upper2, newdata=v_f1)
v_f1$lower2 <- predict(fla_lower2, newdata=v_f1)
v_f1$region2 <- ifelse(v_f1[,2] <= v_f1$upper2 & v_f1[,2] >= v_f1$lower2, 'inside', 'outside')
kl <- subset(v_f1, region2 =='inside')
g <- ggplot() + theme_bw() +
geom_smooth(data = kl, aes_string(kl[,1], kl[,2]), formula = y ~ poly(x,input$degree_2, raw = TRUE), method = "lm", color = "green3", level = 1, size = 0.5)
r <- ggplot_build(g)$data[[1]]
q <- data.frame(x = r[,1], y = r[,2])
#calculate rotation angle
beta <- -atan((q[1,2]-tail(q,1)[,2])/(q[1,1]-tail(q,1)[,1]))
#rotation matrix
rot_m <- matrix(c(cos(beta),sin(beta),-sin(beta),cos(beta)),ncol=2)
#shift, rotate, shift back
M_2 <- t(rot_m %*% (t(q)-c(q[1,1],q[1,2]))+c(q[1,1],q[1,2]))
M_2[nrow(M_2),2] <- M_2[1,2]
M_2
M_3 <- data.frame(x= (M_2[,1]-median(M_2[,1])), y= (M_2[,2]-min(M_2[,2])))
the_data <- reactive(M_3)
the_data()
})
I tried feeding the output of the DT::renderDataTable as input for ggplot but my shiny app is showing me an error saying that
Reading from shinyoutput object is not allowed.
I already knew that 'Reading from shinyoutput object is not allowed'.
I just want to know whether there is any way I can use the output rendered in datatable for further plotting in a shiny app.
Here's a MWE demonstrating what I think you want to do.
Notice the separation of data from presentation: t1, t2 and t3 are reactives representing your CSV files. Each is rendered in a different data table. allData is a reactive containing union of the CSV data. This is used as the source data for the plot.
library(shiny)
library(DT)
library(tidyverse)
ui <- fluidPage(
numericInput("n", "Number of points:", min=2, max=20, value=10),
plotOutput("plot"),
dataTableOutput("table1"),
dataTableOutput("table2"),
dataTableOutput("table3")
)
server <- function(input, output) {
t1 <- reactive({ tibble(x=1:input$n, y=rnorm(input$n), key="Table 1") })
t2 <- reactive({ tibble(x=1:input$n, y=rnorm(input$n), key="Table 2") })
t3 <- reactive({ tibble(x=1:input$n, y=rnorm(input$n), key="Table 3") })
allData <- reactive({ bind_rows(t1(), t2(), t3()) })
output$table1 <- renderDT({ t1() })
output$table2 <- renderDT({ t2() })
output$table3 <- renderDT({ t3() })
output$plot <-renderPlot({ allData() %>% ggplot() + geom_line(aes(x=x, y=y, colour=key)) })
}
shinyApp(ui = ui, server = server)
It might be worth looking at using modules to manage and present the CSV files.
I am trying to make a shiny app that displays different variables of the "Cut" variable (Fair, good, very good...) in a bar graph. I am not totally sure how to do it using the checkboxGroupInput function. I pretty much want it so that if the user selects fair, good, and very good, the bar graph will display only those values. This is my first week with R, any help would be much appreciated.
library(shiny)
library(datasets)
library(tidyverse)
library(ggplot2)
jewl <- diamonds
# User interface ----
ui <- fluidPage(
titlePanel("Diamonds Information"),
sidebarLayout(
sidebarPanel(
helpText("Choose a Cut to Examine"),
checkboxGroupInput("vars", "What cuts would you like to display?", choices = c("Fair",
"Good",
"Very Good",
"Premium",
"Ideal", "All"),
selected = "All"
)
),
mainPanel(plotOutput("plot")
)
)
)
server <- function(input, output) {
output$plot <- renderPlot({
if (input$vars == "All"){
newdata <- group_by(diamonds, cut)
newdata2 <- summarize(newdata, avg = mean(price))
ggplot(data = newdata2) +
geom_col(mapping = aes(x = cut, y = avg))}
#WANT TO ADD SOMETHING HERE
})
}
# Run the app
shinyApp(ui, server)
You can subset your newdata2 to get only those variables selected like this:
# 'which' function does a comparison and returns the indexes
# which meets the conditions (check the subset_ids to see that is a vector of integers (the indexes)
subset_ids <- which(newdata2$cut %in% c('Fair', 'Premium'))
# now, you use your ids to subset the data.frame
# a data.frame can be subsetted like this
# data.frame[rows, columns] (you can leave blank for no subsetting)
newdata3 <- newdata2[subset_ids, ]
# OBS: Columns can be selected by numbers or its name
So in your code you can do like this
output$plot <- renderPlot({
if (input$vars == "All"){
newdata <- group_by(diamonds, cut)
newdata2 <- summarize(newdata, avg = mean(price))
ggplot(data = newdata2) +
geom_col(mapping = aes(x = cut, y = avg))
}
else {
newdata <- group_by(diamonds, cut)
newdata2 <- summarize(newdata, avg = mean(price))
ggplot(data = newdata2[which(newdata2$cut %in% input$vars),]) +
geom_col(mapping = aes(x = cut, y = avg))
}
})
}
Which will cause some warnings, but solves your problem.
Goal: I am trying to create a shiny app that displays (1) the stressplot of a non-metric multidimensional scaling solution, (2) a ggplot of the point configuration, and (3) the results of clustering the point configuration by plotting the point configuration and superimposing chulls of the clustering.
Problem: The first two plots work without difficulty. Instead of a third plot, I get the error: 'data' must be of a vector type, was 'NULL'
I would appreciate any advice on how to resolve the specific problem, i.e. "error in array: 'data' must be of a vector type, was 'NULL'"
I would also appreciate any general advice on how to debug shiny. My only strategy is to treat the code like it isn't reactive code, and I suspect that this strategy isn't terribly effective.
My attempt to solve: I've searched the error on rseek and stack overflow and reviewed the posts. In some of the cases with similar errors the problem was that necessary data wasn't being calculated. I went through the code, treated it as normal (non-reactive) code, and used fake data. When I did this I didn't have any problem, so I assume it is something about the reactivity? Question 2 about how to debug is a reaction to the fact that trying to debug like the code wasn't dynamic didn't identify the problem.
Reproducible Example: I put together a shiny app that has randomly generated data. Before doing the testing I updated R and all the packages I use.
# Packages and options
library(shiny)
library(vegan)
library(cluster)
library(tidyverse)
options(digits = 3)
# Create dissimilarity matrix
d <- rnorm(1000)
mat <- matrix(d, ncol = 10)
diss_m <- daisy(mat) %>% as.matrix()
# Function
find_chulls <- function(df, x, y) {
ch <- chull(df[[x]], df[[y]])
df[ch,] %>% as.data.frame()
}
ui <- fluidPage(
titlePanel("Research"),
sidebarLayout(
sidebarPanel(
numericInput('dim', 'Dimensions', 2, min = 2, max = 15)
),
mainPanel(
h3('Stressplot'),
plotOutput('plot0'),
h3('Non-Metric Multidimensional Scaling'),
plotOutput('plot1'),
h3('2d Density Plot'),
plotOutput('plot2'),
h3('Cluster Analysis'),
plotOutput('plot3')
)
)
)
server <- function(input, output, session) {
nmds <- reactive({
metaMDS(diss_m,
distance = "euclidean",
k = input$dim,
trymax = 200,
autotransform = FALSE,
noshare = FALSE,
wascores = FALSE)
})
output$plot0 <- renderPlot({
stressplot(nmds())
})
pts <- reactive({
nmds()$points %>% as.data.frame()
})
output$plot1 <- renderPlot({
ggplot(pts(), aes(x = MDS1, y = MDS2)) +
geom_point()
})
output$plot2 <- renderPlot({
ggplot(pts(), aes(x = MDS1, y = MDS2)) +
geom_point() +
geom_density2d()
})
df_cl <- reactive({
km <- kmeans(x = pts(), centers = input$clust)
cl <- km$cluster
data.frame(pts(), clust = cl)
})
df_ch <- reactive({
df_ch_temp <- df_cl() %>% group_by(clust) %>% do(find_chulls(., 1, 2))
df_ch_temp %>% as.data.frame()
})
The plot below is the one that doesn't work
output$plot3 <- renderPlot({
ggplot(df_ch(), aes(x = MDS1, y = MDS2, fill = as.factor(clust))) + geom_polygon(alpha = 0.10)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Your input$clust is undefined in:
df_cl <- reactive({
km <- kmeans(x = pts(), centers = input$clust)
cl <- km$cluster
data.frame(pts(), clust = cl)
})
You need to add an input binding for clust, e.g.:
numericInput('clust', 'Clusters', 2, min = 2, max = 15)
As for debugging: I added browser() at the top in df_cl, then execution stops and you can inspect variables and run code in the terminal (e.g. in Rstudio). When I ran km <- kmeans(x = pts(), centers = input$clust) I got the error you described and could then see that input contains no clust element.
I want to assign the position in a plot if a condition is TRUE in R.
I am using shiny R package. in the Server.R the codes are as following:
output$plotmahal<-renderPlot({
#identify the current position of project
x0<-subset(x1,Type==1)
xc<-x0[,c(input$KPI1,input$KPI2)]
#change list to integer
xc1<-as.numeric(unlist(xc))
#current point
d0<-xc1[1]
d1<-xc1[2]
#Centroid point
centroid<-colMeans(x[,c(input$KPI1,input$KPI2)])
c0<-centroid[1]
c1<-centroid[2]
#Quantile of .5 to show if the current is inside 50% of benchmark space or not
xq<-subset(x1,Type!=1)
qKPI1high<-quantile(xq[,input$KPI1],1)
qKPI2high<-quantile(xq[,input$KPI2],1)
qKPI1low<-quantile(xq[,input$KPI1],0)
qKPI2low<-quantile(xq[,input$KPI1],0)
if((d0>qKPI1low && d0<qKPI1high) && (d1>qKPI2low && d1<qKPI2high))
{currentstatus<-"Within Benchmark"}
else{
currentstatus<-"out of benchmark"}
output$c0<-renderText({
paste(currentstatus,input$currentstatus)
})
segments(d0,d1,c0,c1,col='brown',cex=10)
})
output$dss<-renderPlot({
if(is.element("out of benchmark",input$currentstatus)){
x<-c(1)
y<-c(1)
}
if(is.element("within benchmark",input$currentstatus)){
x<-c(1)
y<-c(2)
}
plot(x,y,xaxt='n',yaxt='n',cex=1,pch=19,col=ifelse(x==1,"red","green"),ylab="status",xlab="period")
axis(1,at=1:2,labels=c("t1","t2"))
axis(2,at=1:2,labels=c("within benchmark","out of bench"))
})
If the first condition is TRUE Assign the position of (1,1) in the graph to the point.witch will be in the position of (t1,Within benchmark) in the axis of of x and y respectively.
But it does not assign it.
If you want to change the value of currentstatus from within a reactive component, it should be a reactive value itself. Here is an example where a reactiveValues element is used to store currentstatus. It is updated from within one renderPlot and used in another, as in your code.
In this example, the value of currentstatus changes when the line crosses the color barrier.
## Sample data
dat <- mtcars
library(shiny)
shinyApp(
shinyUI(
fluidPage(
wellPanel(
radioButtons('column', 'Column:', choices=names(dat),
selected='mpg', inline=TRUE),
uiOutput('ui')
),
mainPanel(
fluidRow(column(8, plotOutput('plotmahal')),
column(4, plotOutput('dss')))
)
)
),
shinyServer(function(input, output){
## Reactive values
vals <- reactiveValues(currentstatus = 'Within')
## The input options
output$ui <- renderUI({
list(
sliderInput('inp', 'Range:', min=0, max=max(dat[[input$column]]),
value=mean(dat[[input$column]])),
helpText('Example: when the line crosses the color barrier, currenstatus changes.',
align='center', style='font-weight:800;')
)
})
output$plotmahal <- renderPlot({
## Update the value of currentstatus when the input is < or > the mean
mu <- mean(dat[[input$column]])
vals$currentstatus <- if (input$inp < mu) 'Within' else 'Out'
## Make a random graph
counts <- hist(dat[[input$column]], plot=FALSE)
image(x=seq(0, mu, length=20), (y=seq(0, max(counts$counts), length=20)),
(z=matrix(rnorm(400), 20)), col=heat.colors(20, alpha=0.5),
xlim=c(0, max(counts$breaks)), xlab='', ylab='')
image(x=seq(mu, max(counts$breaks), length=20), y=y, z=z,
col=colorRampPalette(c('lightblue', 'darkblue'), alpha=0.5)(20), add=TRUE)
abline(v = input$inp, lwd=4, col='firebrick4')
})
output$dss <- renderPlot({
## This prints the currentstatus variable to RStudio console
print(vals$currentstatus)
if(is.element("Out", vals$currentstatus))
x <- y <- 1
if(is.element("Within", vals$currentstatus)) {
x <- 1
y <- 2
}
plot(x, y, xaxt='n',yaxt='n',cex=1,pch=19,
col=ifelse(x==1,"red","green"),ylab="status",xlab="period",
xlim=c(0,3), ylim=c(0,3))
axis(1,at=1:2,labels=c("t1","t2"))
axis(2,at=1:2,labels=c("within benchmark","out of bench"))
})
})
)