搞出來一個加拿大人民喝過的酒的交互可視化統(tǒng)計
NOTE ON Building Shiny apps - an interactive tutorial
YOU DO NOT NEED TO KNOW ANY HTML/CSS/JavaScript
1. Shiny app basics
Every Shiny app is composed of 2 parts:
- a web page
- a computer
In Shiny terminology,
- UI (user interface)
- server
UI
a web document that the user gets to see
-
responsible for
- creating the layout of the app
- telling Shiny exactly where things go
server
- responsible for the logic of the app
- the set of instructions that tell the web page what to show
2. An empty Shiny app
library(shiny)
ui <- fluidPage()
server <- function(input, output) {}
shinyApp(ui = ui, server = server)
All Shiny apps follow the same template ??
A few things you should keep in mind:
- It is very important that the name of the file is
app.R
, otherwise it would not be recognized as a Shiny app.- You should not have any R code after the
shinyApp(ui = ui, server = server)
line. That line needs to be the last line in your file.- It is good practice to place this app in its own folder, and not in a folder that already has other R scripts or files, unless those other files are used by your app.
runApp()
Listening on http://127.0.0.1:5360
Click the stop button to stop the app, or press the Escape key.
2.1 Alternate way: separate UI and server files
When the app is complex and involves more code,
separate the UI and server code into two files:
ui.R
server.R
When RStudio sees these two files in the same folder, it will know you’re writing a Shiny app.
Do not need to include the shinyApp(ui = ui, server = server)
line.
2.2 Let RStudio fill out a Shiny app template
#
# This is a Shiny web application. You can run the application by clicking
# the 'Run App' button above.
#
# Find out more about building applications with Shiny here:
#
# http://shiny.rstudio.com/
#
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Old Faithful Geyser Data"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$distPlot <- renderPlot({
# generate bins based on input$bins from ui.R
x <- faithful[, 2]
bins <- seq(min(x), max(x), length.out = input$bins + 1)
# draw the histogram with the specified number of bins
hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
}
# Run the application
shinyApp(ui = ui, server = server)
3. Load the dataset
就直接用作者的測試數(shù)據(jù)吧 raw data, 大概就是想從國企報表里康一康大家喝了多少酒什么酒花了多少錢這個亞子。
bcl <- read.csv("bcl-data.csv", stringsAsFactors = FALSE)
## you will see a summary of the dataset which should let you know that the dataset was indeed loaded correctly
print(str(bcl))
# 'data.frame': 6132 obs. of 7 variables:
# $ Type : chr "WINE" "WINE" "WINE" "WINE" ...
# $ Subtype : chr "TABLE WINE RED" "TABLE WINE WHITE" "TABLE WINE RED" "TABLE WINE WHITE" ...
# $ Country : chr "CANADA" "CANADA" "CANADA" "CANADA" ...
# $ Name : chr "COPPER MOON - MALBEC" "DOMAINE D'OR - DRY" "SOMMET ROUGE" "MISSION RIDGE - PREMIUM DRY WHITE" ...
# $ Alcohol_Content: num 14 11.5 12 11 13.5 11 12.5 12 11.5 40 ...
# $ Price : num 31 33 30 34 37 ...
# $ Sweetness : int 0 0 0 1 0 0 0 0 0 NA ...
# NULL
做個練習(xí):
Exercise: Load the data file into R and get a feel for what’s in it. How big is it, what variables are there, what are the normal price ranges, etc.
dim(bcl)
# [1] 6132 7
## Alcohol Content
alc_mean <- mean(bcl$Alcohol_Content)
alc_mean
# [1] 17.16615
alc_max <- max(bcl$Alcohol_Content)
alc_max
# [1] 75.5
alc_min <- min(bcl$Alcohol_Content)
alc_min
# [1] 2.5
寫個函數(shù)求眾數(shù):
getthemost <- function(x) {
tail(sort(table(x)), n=1)
}
alc_mode <- getthemost(bcl$Alcohol_Content)
alc_mode
# 13
# 799
## Price
price_mean <- mean(bcl$Price, na.rm = TRUE)
price_mean
# [1] 141.4914
price_max <- max(bcl$Price, na.rm = TRUE)
price_max
# [1] 30250
price_min <- min(bcl$Price, na.rm = TRUE)
price_min
# [1] 1.99
price_mode <- getthemost(bcl$Price)
price_mode
# 14.99
# 217
getthetops <- function(x) {
tail(sort(table(x)), n=5)
}
## Country
topcountries <- getthetops(bcl$Country)
topcountries
# x
# AUSTRALIA ITALY
# 364 570
# UNITED STATES OF AMERICA FRANCE
# 703 1357
# CANADA
# 1374
## Type 后面發(fā)現(xiàn)其實一共也就這四種
toptypes <- getthetops(bcl$Type)
toptypes
# x
# REFRESHMENT BEER SPIRITS WINE
# 111 683 1147 4191
## Subtype
topsubts <- getthetops(bcl$Subtype)
topsubts
# x
# SPARKLING WINE WHITE SCOTCH - MALT # BEER
# 181 208 689
# TABLE WINE WHITE TABLE WINE RED
# 1119 2564
看了半天第煮,回到 shiny
4. Build the basic UI
4.1 Add plain text to the UI
The first thing you do when writing a Shiny app - add elements to the UI.
Place R strings inside fluidPage()
to render text:
ui <- fluidPage("BC Liquor Store", "prices")
server <- function(input, output) {}
shinyApp(ui = ui, server = server)
一步兩步、
ui <- fluidPage("BC Liquor Store", "prices", "Alcohol Content")
server <- function(input, output) {}
shinyApp(ui = ui, server = server)
4.2 Add formatted text and other HTML elements
-
h1()
: a top-level header -
h2()
: a secondary header -
strong()
: make text bold -
em()
: make text italicized -
br()
: a line break -
img()
: an image -
a()
: a hyperlink
ui <- fluidPage(h1("My app"),
"BC",
"Liquor",
br(),
"Store",
strong("prices"))
server <- function(input, output) {}
shinyApp(ui = ui, server = server)
4.3 Add a title
Shiny also has a special function titlePanel()
.
Using titlePanel()
not only adds a visible big title-like text to the top of the page, but it also sets the “official” title of the web page.
ui <- fluidPage(titlePanel("BC Liquor Store prices",
windowTitle = "BC Liquor Store prices"))
server <- function(input, output) {}
shinyApp(ui = ui, server = server)
4.4 Add a layout
Use sidebarLayout()
to add a simple structure.
ui <- fluidPage(titlePanel("BC Liquor Store prices",
windowTitle = "BC Liquor Store prices"),
sidebarLayout(
sidebarPanel("our inputs will go here"),
mainPanel("the results will go here")
))
server <- function(input, output) {}
shinyApp(ui = ui, server = server)
## 隨便試一下另外兩個函數(shù)
ui <- fluidPage(titlePanel("BC Liquor Store prices",
windowTitle = "BC Liquor Store prices"),
sidebarLayout(
sidebarPanel("our inputs will go here"),
mainPanel("the results will go here")),
column(width = 5, "5_1", offset = 3),
column(width = 4, "4_1", offset = 3),
fluidRow(
column(width = 5, "5_2"),
column(width = 4, "4 offset 2", offset = 2)
))
server <- function(input, output) {}
shinyApp(ui = ui, server = server)
5. Add inputs to the UI
Inputs are what gives users a way to interact with a Shiny app.
-
textInput()
: enter text -
numericInput()
: select a number -
dateInput()
: selecta date -
selectInput()
: create a select box (aka a dropdown menu)
All input functions have the same first two arguments: inputId
and label
.
- The
inputId
will be the name. Every input must have a uniqueinputId
. - The
label
argument specifies the text in the display label that goes along with the input widget.
## 隨便瞎試一下
ui <- fluidPage(titlePanel("BC Liquor Store prices",
windowTitle = "BC Liquor Store prices"),
sidebarLayout(
sidebarPanel(
numericInput("Price",
"in USD",
"1.99",
min = 0,
max = NA)),
mainPanel("the results will go here")))
5.1 Input for price
The first input we want to have is for specifying a price range (minimum and maximum price). The most sensible types of input for this are either numericInput()
or sliderInput()
.
-
numericInput()
, two inputs, one for the minimum value and one for the maximum. -
sliderInput()
, by supplying a vector of length two as thevalue
argument, it can be used to specify a range rather than a single number.
說了這么多牵素,就是 sliderInput()
的效果更好请琳,拋棄前面的 numericInput()
.
前面求出最貴的酒是30250刀俄精,顯然買不起竖慧,買不起我可以假裝看不見,所以把最大值設(shè)為100——當然砍的,從四分位數(shù)也可以看出??
fivenum(bcl$Price)
# [1] 1.99 14.99 24.99 62.99 30250.00
最大值設(shè)為100是很合理的。
ui <- fluidPage(titlePanel("BC Liquor Store prices",
windowTitle = "BC Liquor Store prices"),
sidebarLayout(
sidebarPanel(
sliderInput("priceInput", "Price",
min = 0,
max = 100,
value = c(25, 40),
pre = "$")),
mainPanel("the result will go here")))
5.2 Input for product type
We could either use radio buttons or a select box for our purpose. Let’s use radio buttons for now since there are only a few options.
ui <- fluidPage(titlePanel("BC Liquor Store prices",
windowTitle = "BC Liquor Store prices"),
sidebarLayout(
sidebarPanel(
sliderInput("priceInput", "Price",
min = 0,
max = 100,
value = c(25, 40),
pre = "$"),
radioButtons("typeInput", "Product type",
choices = c("BEER", "REFRESHMENT",
"SPIRITS", "WINE"),
selected = "WINE")),
mainPanel("the result will go here")))
5.3 Input for country
紅酒就要喝 fà國 的榄审。
selectInput()
: create a select box.
ui <- fluidPage(titlePanel("BC Liquor Store prices",
windowTitle = "BC Liquor Store prices"),
sidebarLayout(
sidebarPanel(
sliderInput("priceInput", "Price",
min = 0,
max = 100,
value = c(25, 40),
pre = "$"),
radioButtons("typeInput", "Product type",
choices = c("BEER", "REFRESHMENT",
"SPIRITS", "WINE"),
selected = "WINE"),
selectInput("countryInput", "Country",
choices = c("CANADA", "FRANCE", "UNITED STATES OF AMERICA",
"ITALY", "AUSTRALIA"))),
mainPanel("the result will go here")))
這時候的網(wǎng)頁已經(jīng)有點人樣了(霧,輸入部分已完成篮撑,把所有模塊整理好應(yīng)該是這樣的:
bcl <- read.csv("bcl-data.csv", stringsAsFactors = FALSE)
library(shiny)
ui <- fluidPage(titlePanel("BC Liquor Store prices",
windowTitle = "BC Liquor Store prices"),
sidebarLayout(
sidebarPanel(
sliderInput("priceInput", "Price",
min = 0,
max = 100,
value = c(25, 40),
pre = "$"),
radioButtons("typeInput", "Product type",
choices = c("BEER", "REFRESHMENT",
"SPIRITS", "WINE"),
selected = "WINE"),
selectInput("countryInput", "Country",
choices = c("CANADA", "FRANCE", "UNITED STATES OF AMERICA",
"ITALY", "AUSTRALIA"))),
mainPanel("the result will go here")))
server <- function(input, output) {}
shinyApp(ui = ui, server = server)
6. Add placeholders for outputs
Outputs can be any object that R creates and that we want to display in our app - such as a plot, a table, or text.
All the ouput functions have a outputId
argument that is used to identify each output, and this argument must be unique for each output.
這時候可以把"the result will go here"用代碼替換了。
We’ll have a plot showing some visualization of the results.
We will have a table that shows all the results. To get a table, we use the tableOutput()
function.
所以 mainPanel()
內(nèi)部是這樣的:
mainPanel(
plotOutput("coolplot"),
br(), br(),
tableOutput("results"))
7. Implement server logic to create outputs
Now we have to write the server
function, which will be responsible for listening to changes to the inputs and creating outputs to show in the app.
Server function is always defined with two arguments: input
and output
.
input
: is a list you will read values from and will contain the values of all the different inputs at any given time.output
is a list you will write values to, and it is where you will save output objects (such as tables and plots) to display in your app.
There are three rules to build an output in Shiny.
- Save the output object into the
output
list (remember the app template - every server function has anoutput
argument) - Build the object with a
render*
function, where*
is the type of output - Access input values using the
input
list (every server function has aninput
argument)
7.1 Making an output react to an input
2 rules:
- we’re creating a plot inside the
renderPlot()
function, and assigning it to coolplot in theoutput
list. - Remember that every output created in the UI must have a unique ID, now we see why. In order to attach an R object to an output with ID x, we assign the R object to
output$x
.
server <- function(input, output) {
output$coolplot <- renderPlot({
plot(rnorm(input$priceInput[1]))
})
}
The variable input
contains a list of all the inputs that are defined in the UI. input$priceInput
return a vector of length 2 containing the miminimum and maximum price.
We are saving to the output
list (output$coolplot <-
), we are using a render*
function to build the output (renderPlot({})
), and we are accessing an input value (input$priceInput[1]
).
7.2 Building the plot output
上面的只是試手纸型,正式的——用 ggplot2
畫一個酒精含量的直方圖狰腌。
# library(ggplo2)
server <- function(input, output) {
output$coolplot <- renderPlot({
ggplot(bcl, aes(Alcohol_Content)) +
geom_histogram()
})
}
這時候的圖是不隨旁邊的輸入數(shù)據(jù)改變的,所以 the next step is to actually filter the dataset based on the inputs.
# library(ggplot2)
# library(dplyr)
server <- function(input, output) {
output$coolplot <- renderPlot({
filtered <-
bcl %>%
filter(Price >= input$priceInput[1],
Price <= input$priceInput[2],
Type == input$typeInput,
Country == input$countryInput)
ggplot(filtered, aes(Alcohol_Content)) +
geom_histogram()
})
}
7.3 Building the table output
The other output we have was called results
(as defined in the UI) and should be a table of all the products that match the filters.
We should use the renderTable()
function. We’ll do the exact same filtering on the data, and then simply return the data as a data.frame.
在 server <- function(input, output) {}
下加上:
output$results <- renderTable({
filtered <-
bcl %>%
filter(Price >= input$priceInput[1],
Price <= input$priceInput[2],
Type == input$typeInput,
Country == input$countryInput)
8. Reactivity
Shiny uses a concept called reactive programming. This is what enables your outputs to react to changes in inputs.
Only reactive variables behave this way, and in Shiny all inputs are automatically reactive.
8.1 Creating and accessing reactive variables
One very important thing to remember about reactive variables (such as the input
list) is that they can only be used inside reactive contexts.
Any render*
function is a reactive context, so you can always use input$x
or any other reactive variable inside render functions.
2 other common reactive contexts:
reactive({})
observe({})
observe({})
statement depends on input$priceInput
.
Remember to wrap the cat(input$x)
or print(input$x)
by an observe({})
.
在 server <- function(input, output) {}
下加上:
observe({ print(input$priceInput) })
You can also create your own reactive variables using the reactive({})
function.
The difference between reactive({})
and observe({})
is that reactive({})
returns a value.
Create a variable called priceDiff
that will be the difference between the maximum and minimum price selected.
在 server <- function(input, output) {}
下加上:
priceDiff <- reactive({
diff(input$priceInput)
})
observe({print(priceDiff())})
If you want to access a reactive variable defined with reactive({})
, you must add parentheses after the variable name, as if it’s a function.
8.2 Using reactive variables to reduce code duplication
again, 上面的都是試手。
we have the exact same code filtering the dataset in two places, once in each render function. We can solve that problem by defining a reactive variable that will hold the filtered dataset, and use that variable in the render functions.
在 server <- function(input, output) {}
下加上:
filtered <- reactive({
bcl %>%
filter(Price >= input$priceInput[1],
Price <= input$priceInput[2],
Type == input$typeInput,
Country == input$countryInput
)
})
將 7.2 和 7.3 的代碼換成:
output$coolplot <- renderPlot({
ggplot(filtered(), aes(Alcohol_Content)) +
geom_histogram()
})
output$results <- renderTable({
filtered()
})
9. Using uiOutput() to create UI elements dynamically
9.1 Basic example of uiOutput()
One of the output functions you can add in the UI is uiOutput()
, this is an output used to render more UI. It’s usually used to create inputs (or any other UI) from the server.
ui <- fluidPage(
numericInput("num", "Maximum slider value", 5),
uiOutput("slider")
)
server <- function(input, output) {
output$slider <- renderUI({
sliderInput("slider" , "Slider", min = 0,
max = input$num , value = 0)
})
}
shinyApp(ui = ui, server = server)
slider 的最大值是隨著 input 變動的尸诽。
9.2 Use uiOutput() in our app to populate the countries
現(xiàn)在通過 uiOutput()
renderUI({})
這個組合把對國家的選擇放在 server <- function(input, output) {}
下甥材。
把 ui <- fluidPage()
下的 selectInput("countryInput", ...)
換成:
uiOutput("countryOutput")
在 server <- function(input, output) {}
下加上:
output$countryOutput <- renderUI({
selectInput("countryInput", "Country",
sort(unique(bcl$Country)),
selected = "CANADA")
這樣就可以選擇所有國家了。
9.3 Errors showing up and quickly disappearing
-
The problem is that when the app initializes,
filtered
is trying to access the country input, but the country input hasn’t been created yet.Inside the
filtered
reactive function, we should check if the country input exists, and if not then just returnNULL
. The ggplot function will not work with a
NULL
dataset, so we also need to make a similar check in therenderPlot()
function.
修復(fù)一閃而過的報錯逊谋,在 filtered <- reactive({})
下加上:
if (is.null(input$countryInput)) {
return(NULL)
}
output$coolplot <- renderPlot({})
下加上:
if (is.null(filtered())) {
return()
}
FINALLY
大致粗糙做完了擂达,現(xiàn)在這個網(wǎng)頁長這樣:
ui.R 長這樣:
## bc_liquor_store_ui
bcl <- read.csv("bcl-data.csv", stringsAsFactors = FALSE)
library(shiny)
ui <- fluidPage(titlePanel("BC Liquor Store prices",
windowTitle = "BC Liquor Store prices"),
sidebarLayout(
sidebarPanel(
sliderInput("priceInput", "Price",
min = 0,
max = 100,
value = c(25, 40),
pre = "$"),
radioButtons("typeInput", "Product type",
choices = c("BEER", "REFRESHMENT",
"SPIRITS", "WINE"),
selected = "WINE"),
uiOutput("countryOutput")),
mainPanel(plotOutput("coolplot"),
br(), br(),
tableOutput("results")
)))
server.R 長這樣:
## bc_liquor_store_server
library(ggplot2)
library(dplyr)
server <- function(input, output) {
filtered <- reactive({
if (is.null(input$countryInput)) {
return(NULL)
}
bcl %>%
filter(Price >= input$priceInput[1],
Price <= input$priceInput[2],
Type == input$typeInput,
Country == input$countryInput)
})
output$coolplot <- renderPlot({
if (is.null(filtered())) {
return()
}
ggplot(filtered(), aes(Alcohol_Content)) +
geom_histogram()
})
output$results <- renderTable({
filtered()
})
output$countryOutput <- renderUI({
selectInput("countryInput", "Country",
sort(unique(bcl$Country)),
selected = "CANADA")
})
}
最后究恤,向大家隆重推薦生信技能樹的一系列干貨理张!
- 生信技能樹全球公益巡講:https://mp.weixin.qq.com/s/E9ykuIbc-2Ja9HOY0bn_6g
- B站公益74小時生信工程師教學(xué)視頻合輯:https://mp.weixin.qq.com/s/IyFK7l_WBAiUgqQi8O7Hxw
- 招學(xué)徒:https://mp.weixin.qq.com/s/KgbilzXnFjbKKunuw7NVfw