I am going to build a web page that clusters iris data based on the number of clusters the user enters. It uses K means algorithm to cluster the data and shows a plot of clustered data.
It does not work and I do not know why. I started from this link:
http://rstudio.github.io/shiny/tutorial/#sending-images
Here are my files:
ui.R
library(shiny)
shinyUI(pageWithSidebar(
headerPanel("Clustering iris Data"),
sidebarPanel(
sliderInput("k", "Number of clusters:",
min = 1, max = 5, value = 3)
),
mainPanel(
# Use imageOutput to place the image on the page
imageOutput("myImage")
)
))
and server.R
library(shiny)
library(caret)
library(ggplot2)
data(iris)
inTrain <- createDataPartition(y=iris$Species, p=0.7, list=FALSE)
training <- iris[inTrain,]
testing <- iris[-inTrain,]
shinyServer(function(input, output, session) {
output$myImage <- renderImage({
# A temp file to save the output.
# This file will be removed later by renderImage
outfile <- tempfile(fileext='.png')
kMeans1 <- kmeans(subset(training,select=-c(Species)),centers=input$k)
training$clusters <- as.factor(kMeans1$cluster)
# Generate the PNG
png(outfile, width=400, height=600)
qplot(Petal.Width,Petal.Length,colour=clusters,data=training,main="iris Data Clusters")
print(qplot)
#plot(training$Petal.Width,training$Petal.Length,colour=clusters,data=training,main="iris Data Clusters")
#hist(rnorm(input$k), main="Generated in renderImage()")
#myImage
dev.off()
# Return a list containing the filename
list(src = outfile,
contentType = 'image/png',
width = 400,
height = 600,
alt = "This is alternate text")
}, deleteFile = TRUE)
})
I think you just have to change
qplot(Petal.Width,Petal.Length,colour=clusters,data=training,main="iris Data Clusters")
print(qplot)
to something like this:
qP <- qplot(
Petal.Width,Petal.Length,
colour=clusters,data=training,
main="iris Data Clusters")
print(qP)
Because your call to qplot() was not actually creating an object; which is why print(qplot) was printing the function definition of qplot in the console.
Related
I am using Shiny and R to visualize my data interactively. I would like to draw an interactive scatter plot of Petal.Width versus Petal.Length in Iris dataset and cluster the points based on k clusters (user input) and p, the percentage of data rows dedicated to the training dataset (user input). I added a hover feature to the scatterplot so that by clicking on each point, the whole dataset for that point will be demonstrated.
The output should look like this:
# Loading Libraries
library(shiny)
library(caret)
library(ggplot2)
data(iris)
ui <- pageWithSidebar(
headerPanel("Clustering iris Data"),
sidebarPanel(
sliderInput("k", "Number of clusters:",
min = 1, max = 5, value = 3),
sliderInput("prob", "Training percentage:",
min=0.5, max=0.9, value = 0.7)),
mainPanel(
# img(src='iris_types.jpg', align = "center", height="50%", width="50%"),
plotOutput("plot1", click = "plot_click"),
verbatimTextOutput("info")
)
)
server <- function(input, output) {
inTrain <- createDataPartition(y=iris$Species,
p=input$prob,
list=FALSE)
training <- iris[ inTrain,]
testing <- iris[-inTrain,]
kMeans1 <- kmeans(subset(training,
select=-c(Species)),
centers=input$k)
training$clusters <- as.factor(kMeans1$cluster)
output$plot1 <- renderPlot({
qplot(Petal.Width,
Petal.Length,
colour = clusters,
data = training,
xlab="Petal Width",
ylab="Petal Length")
})
output$info <- renderPrint({
# With ggplot2, no need to tell it what the x and y variables are.
# threshold: set max distance, in pixels
# maxpoints: maximum number of rows to return
# addDist: add column with distance, in pixels
nearPoints(iris, input$plot_click, threshold = 10, maxpoints = 1,
addDist = FALSE)
})
}
shinyApp(ui, server)
When I run the app in R Studio, I receive the following error:
Error in .getReactiveEnvironment()$currentContext() :
Operation not allowed without an active reactive context. (You tried to do something that can only be done from inside a reactive expression or observer.)
As mentioned by #Clemsang you are using reactives values outside an oberserver/render* function.
What you want to do is to create a reactive environment, where you use your inputs. That is, something which gets recalculated whenever the inputs change. Thus, you need to wrap your training calculation in reactive and when you want to use it in your render function, you "call" it by adding (). I like to name my reactives with a verb, to highlight the fact that I actually do something when these functions are called, hence the name get_training_data:
server <- function(input, output) {
get_training_data <- reactive({ ## now your inputs are in a reactive environment
inTrain <- createDataPartition(y=iris$Species,
p=input$prob,
list=FALSE)
training <- iris[ inTrain,]
testing <- iris[-inTrain,]
kMeans1 <- kmeans(subset(training,
select=-c(Species)),
centers=input$k)
training$clusters <- as.factor(kMeans1$cluster)
training
})
output$plot1 <- renderPlot({
qplot(Petal.Width,
Petal.Length,
colour = clusters,
data = get_training_data(),
xlab="Petal Width",
ylab="Petal Length")
})
output$info <- renderPrint({
# With ggplot2, no need to tell it what the x and y variables are.
# threshold: set max distance, in pixels
# maxpoints: maximum number of rows to return
# addDist: add column with distance, in pixels
nearPoints(iris, input$plot_click, threshold = 10, maxpoints = 1,
addDist = FALSE)
})
}
I have an r script includes a Identify_IP() that returns a list of dataframe and a ggplot. I want to call the script and render both the dataframe and the plot.
This is Identify_IP() function. I took off unrelative code and kept only the plot, lines and ggplot code to give a clear example of my type of ggplot.
library(ggplot2)
library(matrixStats)
library(fda.usc)
#df <- read.table("name.XLS", header = FALSE)
Identify_IP = function(df1){
mlearn <- df1[,'V7']
formul <- plot(blue_curve$x, blue_curve$y * 30, type = 'l', col = 'blue')
formula_deriv <- lines(blue_curve$x, red_curve$y1 * 30, col = 'red')
p <- ggplot(df1, aes(blue_curve$x)) +
geom_line(aes(y = blue_curve$y, colour = "0 Deriv")) +
geom_line(aes(y = red_curve$y1, colour = "1st Deriv")) +
geom_vline(xintercept = x_loc) + geom_hline(yintercept = 0)
return(list(df1,p))
}
Now, this is a modified Shiny code based on amrr and micstr suggestion.
source('InflectionP2.R', local = TRUE)
library(ggplot2)
library(shiny)
runApp(
list(
ui = fluidPage(
titlePanel("Upload your file"),
sidebarLayout(
sidebarPanel(
fileInput('file1', 'Choose xls file',
accept = c(".XLS")),
actionButton("btn", "Update Table"),
actionButton("btn1", "Display Plot")
),
mainPanel(
tableOutput('what'),
plotOutput('pl'))
)
)
,
server = function(input, output, session){
dataOP <- reactive({
inFile <- input$file1
if (is.null(input$file1))
return(NULL)
dfs <- Identify_IP(read.table(inFile$datapath))
return(dfs)
})
observeEvent(input$btn, output$what <- renderTable({
dataOP()[[1]]
}))
observeEvent(input$btn1, output$pl <- renderPlot({
pp <- dataOP()
pp[[2]]
}))
}))
This was really helpful in teaching me how to call r script in reactive(). And it makes sense to me. Yet, it render the table but the Display Plot button is not rendering the plot. Does my ggplot in Identify_IP function has anything to do with not being able to display the plot? I also tried print(ggplot(pp[[2]])) and still the same.
I managed to get this working.
Note I used the internal data set iris and made a toy Identify_IP function as I do not have your code.
Note you still need to choose a file to trigger the events but it will ignore that file and use iris data.
Workaround I used [[1]] to get the table not dataOP()$tble
CODE
library(shiny)
library(ggplot2)
# source('InflectionP2.R', local = TRUE)
# MAKE TEST FUNCTION
Identify_IP <- function(mydata) {
#shrink data
tble <- head(mydata)
plt <- ggplot(data = head(mydata),
mapping = aes(y = Sepal.Length,
x = Petal.Length)) + geom_point()
return(list(tble, plt))
}
runApp(
list(
ui = fluidPage(
titlePanel("Upload your file"),
sidebarLayout(
sidebarPanel(
fileInput('file1', 'Choose xls file',
accept = c(".XLS")),
actionButton("btn", "Update Table"),
actionButton("btn1", "Display Plot")
),
mainPanel(
tableOutput('what'),
plotOutput('pl'))
)
)
,
server = function(input, output, session){
dataOP <- reactive({
inFile <- input$file1
if (is.null(input$file1))
return(NULL)
# ORIGINAL dfs <- Identify_IP(read.table(inFile$datapath))
# using internal dataset for example
dfs <- Identify_IP(iris)
# ORIGINAL list(tble = dfs, plt = dfs)
# lets just return your dfs, its already a list in code above
return(dfs)
})
observeEvent(input$btn, output$what <- renderTable({
#print(dataOP()) # debug line that led to [[1]] idea
# ORIGINAL dataOP()$tble
# just say first in list
dataOP()[[1]]
}))
observeEvent(input$btn1, output$pl <- renderPlot({
#ggplot(dataOP()$plt)
# since already a plot just need to index it
# I found [[2]] worked better than explicit dataOP()$plt
pp <- dataOP()
pp[[2]]
}))
}))
RESULT
Voila!
1) Try print (ggplot(dataOP()$plt))
Take a look at this answer I wrote.
2) Sorry its hard to interpret without your ggplot code bit and data. Given #amrrs questions can you try debug in your Shiny code with print() and str() temporary lines to see what your data is returning. i.e.
print(dataOP()$plt)
str(dataOP())
Worse case, try split your code in two. So Identify_IP code to do the data leg and then make a Print_IP with the ggplot code that just returns the plot. It might rule out your chart is not the problem.
3) Take a look at reactiveValues()
https://shiny.rstudio.com/reference/shiny/0.11/reactiveValues.html
It "bakes" a result that was reactive. The type coming out of your chart may be a reactive type not a chart type. Perhaps share any error messages you are getting.
I am trying to displaying different 3D plots in different tab panels, but I found that 3D plot only displayed in the first tab panel. According to this post, plotOutputs parameter outputId should be unique, and in my case IDs are unique over the entail shiny app. Some lines of my app are as follow:
ui.R
shinyUI(fluidPage(
mainPanel(
tabsetPanel(
tabPanel("VWC", webGLOutput("VWC3DPlot")),
tabPanel("TEMP", webGLOutput("TEMP3DPlot"))
)
)
))
server.R
shinyServer(function(input, output) {
# set dir;
dataDir <- "C:/Users/PH/Desktop/data/DATA/"
# store dataframe of attribute to list;
dfList <- readIntoDF(dataDir) # readIntoDF() is function that return a list
# extract dataframe from list
dfVWC <- dfList$VWC
dfTEMP <- dfList$TEMP
# processing of dataframes
dfVWC <- transformDF(dfVWC)
dfTEMP <- transformDF(dfTEMP)
# prepare grid for kriging;
grd <- expand.grid(x=seq(from=0, to=600, by=200),
y=seq(from=0, to=500, by=200))
# Kriging;
dfVWCkrige <- krigingFun(dfVWC, grd)
dfTEMPKrige <- krigingFun(dfTEMP, grd)
krigeList <- list("VWCKrige" = dfVWCkrige, "TEMPKrige" = dfTEMPKrige)
return(krigeList)
}) # end of dataInput
### create cubs;
output$VWC3DPlot <- renderWebGL({
createCubes(dataInput()$VWCKrige) # createCubes() is a function that use output of kriging and shinyrgl pkg to create cubes;
})
output$TEMP3DPlot <- renderWebGL({
createCubes(dataInput()$TEMPKrige)
})
})
Since there are hundreds of lines, I could not to post all of them.
According to this post, I updated the version of shiny, but had no effect on my case.
You appear to be using shinyRGL. Don't use it, rgl has what you need. Here's an example that works for me:
ui.R:
library(shiny)
shinyUI(fluidPage(
mainPanel(
tabsetPanel(
tabPanel("red",
rglwidgetOutput('thewidget1')),
tabPanel("green",
rglwidgetOutput('thewidget2'))
))
))
server.R:
library(shiny)
library(rgl)
options(rgl.useNULL = TRUE)
shinyServer(function(input, output, session) {
x <- rnorm(100)
y <- 2*rnorm(100)
z <- 10*rnorm(100)
open3d()
plot3d(x, y, z, col = "red")
scene1 <- scene3d()
plot3d(z, y, x, col = "green")
scene2 <- scene3d()
rgl.close()
save <- options(rgl.inShiny = TRUE)
on.exit(options(save))
output$thewidget1 <- renderRglwidget(
rglwidget(scene1)
)
output$thewidget2 <- renderRglwidget(
rglwidget(scene2)
)
})
By the way, we would have got here a lot sooner if you had posted a reproducible example as requested.
I have part of a shiny function where the user selects the download image type (.png, .tiff etc) and clicks a button to download it. But all options download in .png format and I can't seem to find out what is wrong.
Note that the preview is always png. The download function creates different file types upon clicking the button. Another point to note is the use of file.copy() in downloadhandler rather than something like
png(name)
plot()
dev.off()
This is because my plotting function is complex and file.copy() is more practical.
The code is below.
#ui.R ----------------------------------------------------------
shinyUI(fluidPage(
titlePanel("Download test"),
sidebarLayout(
sidebarPanel(
numericInput("fheight", "Height (cm)", min=2, max=15, step=1, value = 10),
numericInput("fwidth", "Width (cm)", min=2, max=15, step=1, value = 10),
selectInput("fres", "Res", choices=c("100","200","300"), selected = "100"),
selectInput("fformat", "File type", choices=c("png","tiff","jpeg","pdf"), selected = "png", multiple = FALSE, selectize = TRUE),
downloadButton('bn_download', 'Download Plot')
),
# Show a plot of the generated distribution
mainPanel(
imageOutput("plotoutput")
)
)
))
# server.R ----------------------------------------------------------
shinyServer(function(input, output) {
# store some values
store <- reactiveValues(dname="AwesomeDownload")
# data creation
fn_data <- reactive({
df <- data.frame(x=rnorm(50),y=rnorm(50))
})
# create filename
fn_downloadname <- reactive({
if(input$fformat=="png") filename <- paste0(store$dname,".png",sep="")
if(input$fformat=="tiff") filename <- paste0(store$dname,".tif",sep="")
if(input$fformat=="jpeg") filename <- paste0(store$dname,".jpg",sep="")
if(input$fformat=="pdf") filename <- paste0(store$dname,".pdf",sep="")
return(filename)
})
# render png preview
output$plotoutput <- renderImage({
df <- fn_data()
fheight <- input$fheight
fwidth <- input$fwidth
fres <- as.numeric(input$fres)
png(paste0(store$dname,".png",sep=""), height=fheight, width=fwidth, res=fres, units="cm")
plot(df)
dev.off()
return(list(src = paste0(store$dname,".png",sep=""),
contentType = "image/png",
width = round((input$fwidth*as.numeric(input$fres))/2.54, 0),
height = round((input$fheight*as.numeric(input$fres))/2.54, 0),
alt = "plot"))
},deleteFile=TRUE)
# download function
fn_download <- function()
{
df <- fn_data()
fheight <- input$fheight
fwidth <- input$fwidth
fres <- as.numeric(input$fres)
if(input$fformat=="pdf") fheight <- round(fheight*0.3937,2)
if(input$fformat=="pdf") fwidth <- round(fwidth*0.3937,2)
if(input$fformat=="png") png(fn_downloadname(), height=fheight, width=fwidth, res=fres, units="cm")
if(input$fformat=="tiff") tiff(fn_downloadname(), height=fheight, width=fwidth, res=fres, units="cm",compression="lzw")
if(input$fformat=="jpeg") jpeg(fn_downloadname(), height=fheight, width=fwidth, res=fres, units="cm",quality=100)
if(input$fformat=="pdf") pdf(fn_downloadname(), height=fheight, width=fwidth)
plot(df)
dev.off()
}
# download handler
output$bn_download <- downloadHandler(
filename = fn_downloadname(),
content = function(file) {
fn_download()
file.copy(fn_downloadname(), file, overwrite=T)
}
)
})
Removing parenthesis in the download filename fixed the issue. Yeah, don't even ask. I have no idea why this is so either. But it works.
Joe Cheng's answer:
Change this line:
filename = fn_downloadname(),
to this:
filename = fn_downloadname,
Given the following ui.R and server.R and circuit.csv; I can produce a simple plot which reacts to the user input (power in this case).
However, not all values for power are returned. For example, .5 produces a plot whereas .6 does not, so on and so forth at random occurrence throughout the power range.
If i plot as a table instead, to check my work, same thing, certain power inputs work as expected and others produce no table, and also no plot when asking to plot.
ui.R
library(shiny)
library(ggplot2)
shinyUI(fluidPage(
hr(),
sidebarLayout(
sidebarPanel(
sliderInput("power",label = "Power",
min = 0, max = 5, value = .5, step = .1)
),
mainPanel(
p("Lum vs Distance by Power"),
plotOutput('plot1')
)
)
))
server.R
library(shiny)
library(ggplot2)
df <- read.table(file = "circuit.csv", sep=",", header = TRUE)
shinyServer(function(input, output) {
output$plot1 <- renderPlot({
df2 <- subset(df,df$pow==input$power)
p <- ggplot(df2)+
geom_point(aes(x=dist, y=lum))
print(p)
})
})
Link to github (for csv data)
I would post images but am not allowed to do so at this time.