Chapter 3 Data
In this chapter, you will learn how to add data to your Shiny applications, and allow your users to upload and download data.
3.1 Adding data to your app
3.1.1 The easy part
You can load data into a Shiny app using any programmatic method you use in normal R code (i.e. point and click use of the interface can’t be replicated in Shiny). This includes:
- CSVs and other flat files
- R data files
- Excel files
- Web scraping
- APIs
- Data from BigQuery or SQL (Our BigQuery to R training course provides information to get your data from BigQuery into R)
- Data stored in cloud buckets
Any call which allows you to read in data can be replicated in Shiny.
3.1.2 The tricky part
Your Shiny app’s working directory may be different from the directories you usually work with in other projects. Some file types may also be slower to load in Shiny, so consider which data formats to use. Where you read data in the app affects its loading speed, how the data is used, and which parts of the app can access it. Additionally, if your app needs to access data directly from an online source, it may require special permissions (though this is beyond the scope of this course).
3.1.3 The Shiny working directory
In Shiny apps, relative file paths must use the correct working directory, which by default is the app’s root folder (the folder containing server.R
and ui.R
). This isn’t necessarily the same as your project or R’s current working directory, so it’s important to specify paths carefully.
For example, if you have a Data
folder in your project:
Project Root
├── Data/
│ └── file_name_here
├── test_shiny/
├── server.R
├── ui.R
- from a standard R code in the same project, you’d access files in
Data
like this:"Data/file_name_here"
- if your Shiny app (e.g.
test_shiny
) is in a subfolder of the project, you’ll need to go up one level to access theData
folder:"../Data/file_name_here"
3.1.4 Choosing sensible data storage
Your Shiny app won’t load completely until it’s finished doing all the data reading and processing. If this is slow, it will be frustrating for users!
Therefore, to improve loading speed:
- choose file types which have fast reading options. The ideal is flat files such as CSVs (using
data.table::fread()
), Rdata files and parquet files - data tidying and processing steps are slow, especially if they need to be done every time to app opens. Use a pre-processing script where possible and save your data in a clean, tidy data format
- if you reliant on external data sources, run pre-app QA check there are breaking changes introduced into the data
3.1.5 Where to load data
Data is almost always loaded and used inside the server
. You can’t do any data processing within inputs or create outputs in the UI
.
There are three different ways you can load data into the server, depending on what you want to do with it:
- Before the server call
# Load data before the server call
data <- readr::read_csv("Data/mydata.csv")
server <- function(input, output) {
output$table <- renderTable({
# Data is available directly without reloading
data
})
}
Useful if the data does not change based on user input and is needed across multiple functions.
- Inside the server call, in a reactive function
server <- function(input, output) {
# Reactive function to load data or filter it based on user input
filtered_data <- reactive({
data <- readr::read_csv("Data/mydata.csv")
data[data$category == input$category, ]
})
output$table <- renderTable({
filtered_data()
})
}
Useful when data loading or processing depends on user selections, as it recalculates only when needed.
- Inside the server call, in a render function
server <- function(input, output) {
output$table <- renderTable({
data <- readr::read_csv("Data/mydata.csv")
data[data$category == input$category, ]
})
}
Typically used for simpler data processing or one-off calculations related to specific outputs.
Each method fits specific scenarios:
Ways to load in data | Type of loading | Used for |
---|---|---|
Before the server call | Global loading | Static data - only happens once (when the app is first loaded), so it’s fast and less processing power is required |
Inside the server call, in a reactive function | Reactive loading | Dynamic data - happens many times (every time the data is called or an input is updated from the user), therefore is slower and more processing power is required. |
Inside the server call, in a render function | Render-based loading | Producing a visual object (e.g. text, chart or table) |
A summary of the two types of reactive environments in a Shiny app:
reactive()
: Used to create intermediate data objects for the server, such as a vector or data frame, that can be used across multiple outputs.- ideal for processing data that might feed into multiple outputs.
- cannot be used to create output objects directly for the UI
render()
: Used to create visual objects for the UI, like charts or tables, which will display data to the user.- suitable for final data processing, focused on producing a single output.
- contains the code to actually generate the output (e.g., a plot or table) and is used to create UI-facing objects only.
A reminder that the reactive()
and render()
functions need to be wrap in both round and curly brackets: reactive({})
and render({})
3.1.6 Exercise
10:00
Open Chapter 3 > Exercise-3.1.6
in the training repository and complete the following tasks:
- In your Shiny app, replace the
gapminder
data witheurovision
data. This data can be read in directly from ‘https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2022/2022-05-17/eurovision.csv’. - Read the data in inside the
renderDataTable({})
. - Adjust the year filter so you can still filter the data on the year input widget, and have a go at filtering the dataset a few times.
3.1.6 Solution
UI
library(shiny)
library(lubridate)
library(gapminder)
library(dplyr)
shinyUI(fluidPage(
titlePanel("My Shiny App"),
sidebarLayout(
sidebarPanel(
selectInput("year",
label = "Select Year(s):",
### Question 3: Adjust the year filter so you can still filter the data
## on the year input widget
##START HERE ----
choices = c(1956:2022),
selected = 2007,
multiple = TRUE
)
),
mainPanel(
textOutput("welcomeText"),
#Optional:
## - You may want to change the input `name` to something more appropriate
## - If you do, make sure it matches the output$`name` in the renderDataTable in the server
dataTableOutput("eurovisionTable")
)
)
))
Server
library(shiny)
library(lubridate)
library(gapminder)
library(dplyr)
shinyServer(function(input, output) {
output$welcomeText <- renderText({
paste("Welcome to the app. Showing data for", paste(input$year, collapse = ", "))
})
### Question 1: Replace the gapminder data with eurovision.
### Question 2: Ensure to read in the data inside the renderDataTable({}) function
## Optional:
# - You may want to change the output$`name` to something more appropriate
# - If you do, make sure it matches the input `name` in the dataTableOutput() in the UI
output$eurovisionTable <- renderDataTable({
##START HERE ----
eurovision_data <-
read.csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2022/2022-05-17/eurovision.csv") %>%
dplyr::filter(year %in% input$year)
})
})
Training repository: Go to Chapter 3
> Exercise-3.1.6
> Solution
Run either the ui.R
or server.R
file to see the app in action.
3.2 Creating reactives
Reactive environments are created using the reactive({})
function, and MUST be assigned to an R object. As mentioned before, you will want to wrap the code inside the reactive in both round ()
and curly {}
brackets.
eurovision_filtered <- reactive({
eurovision_data %>%
dplyr::filter(year %in% as.numeric(input$year))
})
You can then write any normal R code inside the reactive call, similar to how you write code inside a function.
3.2.1 Calling reactives
When calling and using reactives, you can treat them as if they were a special case of function with no arguments. You can refer to them by their assigned name followed by empty brackets e.g. my_data()
.
If you do not include the brackets, you will get an error!
When you use a reactive, it behaves like a function and gives you back the most recent result it calculated. You can use one reactive inside another reactive or in functions that create outputs (like plots or tables) in Shiny. However, you can’t use a reactive outside of Shiny’s reactive system (like in code that runs only once when the app starts) because it won’t update properly.
3.2.2 Naming in reactive environments
Naming objects works slightly differently for reactive environments
- objects used inside the reactive are created and destroyed inside there
- named objects can’t be used outside the reactive environment they are created in
eurovision_filtered <- reactive({
data <- eurovision_data %>%
dplyr::filter(year %in% as.numeric(input$year))
})
As a result, names can be reused in different reactive environments for different objects.
3.3 Reactive efficiency
Like a function, reactives are not updated until they are called. This happens:
- when they are used for the first time in another
reactive({})
orrender({})
call - when an input that feeds into them changes
This means that every time a user changes an input variable, the data (and outputs) will automatically update. This means the whole reactive will run every time.
Structuring your reactives can therefore improve the efficiency and speed of your dashboard:
eurovision_filtered <- reactive({
readr::read_csv("eurovision.csv") %>%
dplyr::filter(year %in% as.numeric(input$year))
})
3.3.1 Exercise
10:00
Open Chapter 3 > Exercise-3.3.1
in the training repository and complete the following tasks:
- Create a new reactive environment inside your app, and give it a sensible name
- Move the reading for the
Eurovision
data into this reactive environment - Call the reactive environment inside your
renderDataTable()
so that the table still works
3.3.1 Solution
UI
library(shiny)
library(lubridate)
library(gapminder)
library(dplyr)
shinyUI(fluidPage(
titlePanel("My Shiny App"),
sidebarLayout(
sidebarPanel(
selectInput("year",
label = "Select Year(s):",
choices = c(1956:2022),
selected = 2007,
multiple = TRUE
)
),
mainPanel(
textOutput("welcomeText"),
dataTableOutput("eurovisionTable")
)
)
))
Server
library(shiny)
library(lubridate)
library(gapminder)
library(dplyr)
shinyServer(function(input, output) {
output$welcomeText <- renderText({
paste("Welcome to the app. Showing data for", paste(input$year, collapse = ", "))
})
### Question 1: Create a new reactive environment, using an appropriate `name`
## Hint: use the reactive({})
### Question 2: Move the reading for the Eurovision data into the reactive function
## Hint: remember to add the filter() so users can continue to filter by their chosen years
## START HERE ----
eurovision_filtered <- reactive({
eurovision_data <-
read.csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2022/2022-05-17/eurovision.csv") %>%
dplyr::filter(year %in% input$year)
})
output$eurovisionTable <- renderDataTable({
### Question 3: Call the reactive environment inside your renderDataTable
## Hint: do not forget the brackets reactive_name() when calling the reactive environment
## START HERE ----
eurovision_filtered()
})
})
Training repository: Go to Chapter 3
> Exercise-3.3.1
> Solution
Run either the ui.R
or server.R
file to see the app in action.
3.4 Data downloads and uploads
3.4.2 Allowing data upload
As well as making use of data you choose, you can also produce dashboards which allow users to upload their own data.
This can be done using the fileInput()
function in the UI. This works in the same way as any other input; the first argument is a name
and the parameters of the input can be set using the other arguments.
To access and use the contents of the file that was uploaded, you can call input$name
in the server using the name you provided.
3.4.3 Exercise
10:00
Open Chapter 3 > Exercise-3.4.3
in the training repository and complete the following tasks:
- Add a download button to your app to allow users to download the filtered
Eurovision
data. - Set the name of the downloaded file to include
"Eurovision_data"
and the date that the data was downloaded.
3.4.3 Solution
UI
library(shiny)
library(lubridate)
library(gapminder)
library(dplyr)
shinyUI(fluidPage(
titlePanel("My Shiny App"),
sidebarLayout(
sidebarPanel(
selectInput("year",
label = "Select Year(s):",
choices = c(1956:2022),
selected = 2007,
multiple = TRUE
),
### Question 1: Add a download button to the app
## START HERE ----
downloadButton("downloadData", "Download the data")
),
mainPanel(
textOutput("welcomeText"),
dataTableOutput("eurovisionTable")
)
)
))
Server
library(shiny)
library(lubridate)
library(gapminder)
library(dplyr)
shinyServer(function(input, output) {
output$welcomeText <- renderText({
paste("Welcome to the app. Showing data for", paste(input$year, collapse = ", "))
})
eurovision_filtered <- reactive({
eurovision_data <-
read.csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2022/2022-05-17/eurovision.csv") %>%
dplyr::filter(year %in% input$year)
})
output$eurovisionTable <- renderDataTable({
eurovision_filtered()
})
### Question 2: Set the name of the downloaded file to include "Eurovision_data"
### and the date that the data was downloaded
## Hint:
# - remember the output$`name` needs to be the same as the input `name` in the UI
# - you can use the Sys.Date() or lubridate::today() to get today's date
## START HERE ----
output$downloadData <- downloadHandler(
filename = function() {
paste("Eurovision_data-", lubridate::today(), ".csv", sep = "")
},
content = function(con) {
write.csv(eurovision_filtered(), con)
}
)
})
Training repository: Go to Chapter 3
> Exercise-3.4.3
> Solution
Run either the ui.R
or server.R
file to see the app in action.
This is a good opportunity to take a 10-minute break away from the computer to refresh your mind, stretch, and reset before continuing onto Chapter 4.