(1) Open the slides in Firefox: bit.ly/R_Pharma
(2) Go to this address https://rstudio.cloud/project/446255. This hosted RStudio instance contains materials for today's workshop.
(3) Ask me any question at any time by going to slido.com and enter event code #8464 (or use this link). I'll try to check these questions periodically (upvote questions if you'd like them answered!)
05:00
plot_ly()
is designed to give more "R-like" defaultslibrary(plotly)plot_ly(x = c("A", "B"), y = c(1, 2))
What actually happens when a plotly object is printed and rendered locally?
A Plotly figure is made up of trace(s) and a layout. A trace is a mapping from data to visual.
var trace1 = { x: ['giraffes', 'orangutans', 'monkeys'], y: [20, 14, 23], name: 'SF Zoo', type: 'bar'};var trace2 = { x: ['giraffes', 'orangutans', 'monkeys'], y: [12, 18, 29], name: 'LA Zoo', type: 'bar'};var data = [trace1, trace2];Plotly.newPlot('myDiv', data);
plot_ly()
In R, use plot_ly()
(or ggplotly()
) to create a plotly object, then add_trace()
to add any trace you want.
plot_ly() %>% add_trace( x = c('giraffes', 'orangutans', 'monkeys'), y = c(20, 14, 23), name = 'SF Zoo', type = 'bar' ) %>% add_trace( x = c('giraffes', 'orangutans', 'monkeys'), y = c(12, 18, 29), name = 'LA Zoo', type = 'bar' )
plot_ly()
syntaxUse ~
to reference a data column
zoo_dat <- tibble::tibble( animal = rep(c('giraffes', 'orangutans', 'monkeys'), 2), count = c(20, 14, 23, 12, 18, 29), zoo = rep(c('SF Zoo', 'LA Zoo'), each = 3))zoo_dat#> # A tibble: 6 x 3#> animal count zoo #> <chr> <dbl> <chr> #> 1 giraffes 20 SF Zoo#> 2 orangutans 14 SF Zoo#> 3 monkeys 23 SF Zoo#> 4 giraffes 12 LA Zoo#> 5 orangutans 18 LA Zoo#> 6 monkeys 29 LA Zoo
plot_ly(zoo_dat) %>% add_trace( x = ~animal, y = ~count, name = ~zoo, type = "bar" )
plot_ly()
syntaxFor attributes that require a scalar value (e.g. name
), plot_ly()
generates one trace per level.
zoo_dat <- tibble::tibble( animal = rep(c('giraffes', 'orangutans', 'monkeys'), 2), count = c(20, 14, 23, 12, 18, 29), zoo = rep(c('SF Zoo', 'LA Zoo'), each = 3))zoo_dat#> # A tibble: 6 x 3#> animal count zoo #> <chr> <dbl> <chr> #> 1 giraffes 20 SF Zoo#> 2 orangutans 14 SF Zoo#> 3 monkeys 23 SF Zoo#> 4 giraffes 12 LA Zoo#> 5 orangutans 18 LA Zoo#> 6 monkeys 29 LA Zoo
plot_ly(zoo_dat) %>% add_trace( x = ~animal, y = ~count, name = ~zoo, type = "bar" )
plot_ly()
syntaxThe split
argument is a more explicit way to split data into multiple traces
zoo_dat <- tibble::tibble( animal = rep(c('giraffes', 'orangutans', 'monkeys'), 2), count = c(20, 14, 23, 12, 18, 29), zoo = rep(c('SF Zoo', 'LA Zoo'), each = 3))zoo_dat#> # A tibble: 6 x 3#> animal count zoo #> <chr> <dbl> <chr> #> 1 giraffes 20 SF Zoo#> 2 orangutans 14 SF Zoo#> 3 monkeys 23 SF Zoo#> 4 giraffes 12 LA Zoo#> 5 orangutans 18 LA Zoo#> 6 monkeys 29 LA Zoo
plot_ly(zoo_dat) %>% add_trace( x = ~animal, y = ~count, split = ~zoo, type = "bar" )
plotly_json()
to view underlying JSONzoo_dat <- tibble::tibble( animal = rep(c('giraffes', 'orangutans', 'monkeys'), 2), count = c(20, 14, 23, 12, 18, 29), zoo = rep(c('SF Zoo', 'LA Zoo'), each = 3))zoo_dat#> # A tibble: 6 x 3#> animal count zoo #> <chr> <dbl> <chr> #> 1 giraffes 20 SF Zoo#> 2 orangutans 14 SF Zoo#> 3 monkeys 23 SF Zoo#> 4 giraffes 12 LA Zoo#> 5 orangutans 18 LA Zoo#> 6 monkeys 29 LA Zoo
plot_ly(zoo_dat) %>% add_trace( x = ~animal, y = ~count, name = ~zoo, type = "bar" ) %>% # Verify we get one trace per zoo plotly_json(FALSE)
"data": [ { "x": ["giraffes", "orangutans", "monkeys"], "y": [12, 18, 29], "name": "LA Zoo", "type": "bar" }, { "x": ["giraffes", "orangutans", "monkeys"], "y": [20, 14, 23], "name": "SF Zoo", "type": "bar" }]
plot_ly()
syntaxMore common trace types have a dedicated "layer" function (e.g., add_bars()
).
zoo_dat <- tibble::tibble( animal = rep(c('giraffes', 'orangutans', 'monkeys'), 2), count = c(20, 14, 23, 12, 18, 29), zoo = rep(c('SF Zoo', 'LA Zoo'), each = 3))zoo_dat#> # A tibble: 6 x 3#> animal count zoo #> <chr> <dbl> <chr> #> 1 giraffes 20 SF Zoo#> 2 orangutans 14 SF Zoo#> 3 monkeys 23 SF Zoo#> 4 giraffes 12 LA Zoo#> 5 orangutans 18 LA Zoo#> 6 monkeys 29 LA Zoo
plot_ly(zoo_dat) %>% add_bars( x = ~animal, y = ~count, name = ~zoo )
plot_ly()
syntaxDiscrete axis ordering: use factor levels (character strings appear alphabetically)
zoo_dat <- tibble::tibble( animal = rep(c('giraffes', 'orangutans', 'monkeys'), 2), count = c(20, 14, 23, 12, 18, 29), zoo = rep(c('SF Zoo', 'LA Zoo'), each = 3))zoo_dat#> # A tibble: 6 x 3#> animal count zoo #> <chr> <dbl> <chr> #> 1 giraffes 20 SF Zoo#> 2 orangutans 14 SF Zoo#> 3 monkeys 23 SF Zoo#> 4 giraffes 12 LA Zoo#> 5 orangutans 18 LA Zoo#> 6 monkeys 29 LA Zoo
plot_ly(zoo_dat) %>% add_bars( x = ~factor(animal, c("monkeys", "giraffes", "orangutans")), y = ~count, name = ~zoo )
plot_ly()
color mappingMapping data to color
will impose it's own color scheme
zoo_dat <- tibble::tibble( animal = rep(c('giraffes', 'orangutans', 'monkeys'), 2), count = c(20, 14, 23, 12, 18, 29), zoo = rep(c('SF Zoo', 'LA Zoo'), each = 3))zoo_dat#> # A tibble: 6 x 3#> animal count zoo #> <chr> <dbl> <chr> #> 1 giraffes 20 SF Zoo#> 2 orangutans 14 SF Zoo#> 3 monkeys 23 SF Zoo#> 4 giraffes 12 LA Zoo#> 5 orangutans 18 LA Zoo#> 6 monkeys 29 LA Zoo
plot_ly(zoo_dat) %>% add_bars( x = ~animal, y = ~count, color = ~zoo )
plot_ly()
color mappingSpecify the range via colors
(here a colorbrewer palette name, see RColorBrewer::brewer.pal.info
for options)
zoo_dat <- tibble::tibble( animal = rep(c('giraffes', 'orangutans', 'monkeys'), 2), count = c(20, 14, 23, 12, 18, 29), zoo = rep(c('SF Zoo', 'LA Zoo'), each = 3))zoo_dat#> # A tibble: 6 x 3#> animal count zoo #> <chr> <dbl> <chr> #> 1 giraffes 20 SF Zoo#> 2 orangutans 14 SF Zoo#> 3 monkeys 23 SF Zoo#> 4 giraffes 12 LA Zoo#> 5 orangutans 18 LA Zoo#> 6 monkeys 29 LA Zoo
plot_ly(zoo_dat) %>% add_bars( x = ~animal, y = ~count, color = ~zoo, colors = "Accent" )
plot_ly()
color mappingProvide your own palette if you want. See ?plot_ly
for more details.
zoo_dat <- tibble::tibble( animal = rep(c('giraffes', 'orangutans', 'monkeys'), 2), count = c(20, 14, 23, 12, 18, 29), zoo = rep(c('SF Zoo', 'LA Zoo'), each = 3))zoo_dat#> # A tibble: 6 x 3#> animal count zoo #> <chr> <dbl> <chr> #> 1 giraffes 20 SF Zoo#> 2 orangutans 14 SF Zoo#> 3 monkeys 23 SF Zoo#> 4 giraffes 12 LA Zoo#> 5 orangutans 18 LA Zoo#> 6 monkeys 29 LA Zoo
plot_ly(zoo_dat) %>% add_bars( x = ~animal, y = ~count, color = ~zoo, colors = c( "SF Zoo" = "black", "LA Zoo" = "red" ) )
plot_ly()
syntaxUse color
/colors
for fill-color and stroke
/strokes
for outline-color
zoo_dat <- tibble::tibble( animal = rep(c('giraffes', 'orangutans', 'monkeys'), 2), count = c(20, 14, 23, 12, 18, 29), zoo = rep(c('SF Zoo', 'LA Zoo'), each = 3))zoo_dat#> # A tibble: 6 x 3#> animal count zoo #> <chr> <dbl> <chr> #> 1 giraffes 20 SF Zoo#> 2 orangutans 14 SF Zoo#> 3 monkeys 23 SF Zoo#> 4 giraffes 12 LA Zoo#> 5 orangutans 18 LA Zoo#> 6 monkeys 29 LA Zoo
plot_ly(zoo_dat) %>% add_bars( x = ~animal, y = ~count, split = ~zoo, stroke = ~zoo, strokes = c( "SF Zoo" = "black", "LA Zoo" = "red" ) )
zoo_dat <- tibble::tibble( animal = rep(c('giraffes', 'orangutans', 'monkeys'), 2), count = c(20, 14, 23, 12, 18, 29), zoo = rep(c('SF Zoo', 'LA Zoo'), each = 3))zoo_dat#> # A tibble: 6 x 3#> animal count zoo #> <chr> <dbl> <chr> #> 1 giraffes 20 SF Zoo#> 2 orangutans 14 SF Zoo#> 3 monkeys 23 SF Zoo#> 4 giraffes 12 LA Zoo#> 5 orangutans 18 LA Zoo#> 6 monkeys 29 LA Zoo
plot_ly(zoo_dat) %>% add_bars( x = ~animal, y = ~count, color = ~zoo, stroke = "black", span = I(5) )
span
argument control? span
be a function of count
?stroke
on this plot black?05:00
Use plotly_json()
to inspect the underlying JSON
color
, stroke
, ggplotly()
).plotly_json()
should align with the figure reference https://plot.ly/javascript/reference
The online figure reference doesn't always reflect the version of plotly.js used in the R package, but schema()
does!
plotly_json()
: focus on data
and layout
# If no plot is provided, it shows JSON of the previously printed plotplotly_json()
plotly_json()
should match up with plotly.js' schema()
?plot_ly
documents R-specific arguments (e.g., color
, stroke
, etc). schema()
documents the actual plotly.js attributes (e.g., marker.color
, marker.line.color
, etc).plot_ly(zoo_dat) %>% add_bars( x = ~animal, y = ~count, text = ~count, textposition = "auto", split = ~zoo, stroke = I("black") )
layout()
& config()
plot_ly() %>% layout( xaxis = list(range = c(-5, 5)), yaxis = list(range = c(-5, 5)), annotations = list( text = "Drag the rect", x = 0.5, xref = "paper", y = 0.5, yref = "paper" ), # shapes types: rect, circle, line shapes = list( type = "rect", x0 = 0, x1 = 1, y0 = 0, y1 = -1, fillcolor = "blue" ) ) %>% # Two lesser known gems: editable & displayModeBar config( editable = TRUE, displayModeBar = FALSE )
Later on, we'll see some cool ways to leverage edit events in Shiny.
Too much to list, but here's a start!
ggplotly()
drug_outcomes()
acquires reaction outcomes for a given drug name
library(openfda) # remotes::install_github("ropenhealth/openfda")library(dplyr)drug_outcomes <- function(name) { fda_query("/drug/event.json") %>% fda_filter("patient.drug.openfda.generic_name", name) %>% fda_count("patient.reaction.reactionoutcome") %>% fda_exec() %>% mutate(reaction = recode(term, `1` = "Recovered/resolved", `2` = "Recovering/resolving", `3` = "Not recovered/not resolved", `4` = "Recovered/resolved with sequelae", `5` = "Fatal", `6` = "Unknown")) }drug_outcomes("fentanyl")#> term count reaction#> 1 5 31515 Fatal#> 2 6 30624 Unknown#> 3 1 14641 Recovered/resolved#> 4 3 13691 Not recovered/not resolved#> 5 2 4248 Recovering/resolving#> 6 4 406 Recovered/resolved with sequelae
drugs <- c( "fentanyl", "oxycodone", "morphine")lapply(drugs, drug_outcomes) %>% setNames(drugs) %>% bind_rows(.id = "drug") %>% plot_ly( y = ~reaction, x = ~count, color = ~drug ) %>% add_bars()
Not only is the visual technique more scalable, but with heatmaply, it's easy to reorder columns so that "similar" drugs next to each other.
Ben Shneiderman
Sound advice for any high-dimensional datavis problem
Here's an Shiny app with a plotly graph that updates in response to a dropdown:
file.edit("shiny/00/app.R")shiny::runApp("shiny/00")
Many htmlwidget packages allow you to listen to user interaction with the widget. Just to name a few, plotly, leaflet, and DT all have this functionality. Here's a simple DT example:
file.edit("shiny/01/app.R")shiny::runApp("shiny/01")
The following Shiny app shows how to access click events tied to the scatterplot.
file.edit("shiny/02/app.R")shiny::runApp("shiny/02")
Follow the "Your Turn" directions to extend the functionality of this app (to see the "solution", run shiny::runApp("shiny/02-solution")
)
05:00
The following Shiny app prints the time difference between button clicks to the R console.
file.edit("shiny/03/app.R")shiny::runApp("shiny/03")
Note that:
reactiveVal()
provides mechanism for doing so).The following Shiny app shows how to accumulate (i.e., track the history) of click events
file.edit("shiny/04/app.R")shiny::runApp("shiny/04")
Follow the "Your Turn" directions to extend the functionality of this app. There are many ways to go about this, so there are two "solutions".
05:00
The following Shiny app shows how to color points on click.
file.edit("shiny/05/app.R")shiny::runApp("shiny/05")
Follow the "Your Turn" directions to extend the functionality of this app (to see the "solution", run shiny::runApp("shiny/05-solution")
)
05:00
Click on the bar chart of the previous app.
?event_data
)?03:00
Everytime we click a point, the scatterplot is regenerated on the server. This means:
renderPlotly()
is executed.But, we only need to change the size/color of the clicked marker!
For 1,000 points, this 'naive' approach is fine, but updates will become unnecessarily responsive with larger data.
To acheive faster updates, we can trigger partial modifications to a plotly graph.
plotlyProxy()
.plotlyProxy()
is difficult to use!It interfaces directly with plotly.js, so you need to be careful about:
color
/stroke
argument)When things go wrong, it can be difficult to debug...hopefully we'll make it easier in the future!
When things go right, it makes your app logic a bit more difficult to reason about (because, side effects!)
Plotly.restyle
The following Shiny apps use plotly.js' restyle
function to flip marker.color
to black/red
file.edit("shiny/06/01-all-colors.R")file.edit("shiny/06/02-single-color.R")
The following Shiny app uses plotlyProxy()
to avoid a full redraw of the scatterplot when a point is clicked.
file.edit("shiny/06/app.R")shiny::runApp("shiny/06")
Your Turn: find the code that updates the clicked point, how does it work?
03:00
restyle
: modify trace(s).relayout
: modify the layout.addTraces
/deleteTraces
: add/remove tracesrestyle()
if you can!)react
: supply a new figure.extendTraces
: add data to a trace.plotly_example("shiny", "stream")
We're not going to learn these, just know they are available to you!
reactiveVal()
, you can implement your own drill-down!sales <- readr::read_csv("data/sales.csv")select(sales, category, sub_category, sales)#> # A tibble: 9,994 x 3#> category sub_category sales#> <chr> <chr> <dbl>#> 1 Furniture Bookcases 262. #> 2 Furniture Chairs 732. #> 3 Office Supplies Labels 14.6 #> 4 Furniture Tables 958. #> 5 Office Supplies Storage 22.4 #> 6 Furniture Furnishings 48.9 #> 7 Office Supplies Art 7.28#> 8 Technology Phones 907. #> 9 Office Supplies Binders 18.5 #> 10 Office Supplies Appliances 115. #> # … with 9,984 more rows
sales %>% count(category, wt = sales)#> # A tibble: 3 x 2#> category n#> <chr> <dbl>#> 1 Furniture 742000.#> 2 Office Supplies 719047.#> 3 Technology 836154.
sales %>% count(category, wt = sales)#> # A tibble: 3 x 2#> category n#> <chr> <dbl>#> 1 Furniture 742000.#> 2 Office Supplies 719047.#> 3 Technology 836154.
sales %>% count(category, wt = sales) %>% plot_ly() %>% add_pie(labels = ~category, values = ~n)
sales <- readr::read_csv("data/sales.csv")sales %>% filter(category == "Furniture") %>% count(sub_category, wt = sales)#> # A tibble: 4 x 2#> sub_category n#> <chr> <dbl>#> 1 Bookcases 114880.#> 2 Chairs 328449.#> 3 Furnishings 91705.#> 4 Tables 206966.
sales %>% filter(category == "Furniture") %>% count(sub_category, wt = sales) %>% plot_ly() %>% add_pie(labels = ~sub_category, values = ~n)
The following Shiny app implements a drill-down for sales category/sub-category
file.edit("shiny/07/app.R")shiny::runApp("shiny/07")
Let's add a title to the sub-category pie chart showing what category is currently selected.
sales
dataset listed herehttps://plotly-r.com/linking-views-with-shiny.html#drill-down
(Hint: remember that one reactiveVal()
was used to remember which category was clicked in the pie drill-down)
05:00
event_data()
supports many event types!plotly_example("shiny", "event_data")
plotly_brushing
, event scoping, and Plotly.restyle()
to efficiently alter bar heights plotly_example("shiny", "crossfilter")
Brushing events aren't (yet) supported, but most everything else is!
plotly_example("shiny", "event_data_3D")
ui <- fluidPage( plotlyOutput("p"), verbatimTextOutput("info"))server <- function(input, output) { output$p <- renderPlotly({ plot_ly(x = 1, y = 1) }) output$info <- renderPrint({ event_data("plotly_relayout") })}shinyApp(ui, server)
ui <- fluidPage( plotlyOutput("p"), verbatimTextOutput("info"))server <- function(input, output) { output$p <- renderPlotly({ plot_ly(x = 1, y = 1) %>% layout( shapes = list( type = "line", x0 = 0.5, y0 = 0, x1 = 0.5, y1 = 1, xref = "paper", yref = "paper" ) ) %>% config(edits = list(shapePosition = TRUE)) }) output$info <- renderPrint({ event_data("plotly_relayout") })}
plotly_example("shiny", "drag_lines")
plotly_example("shiny", "drag_markers")
Choose one of the following:
Ask me a question (through sli.do)
Apply something you learned to a dataset of your choice.
Run a plotly_example()
from the slides and dissect how it works.
Resources for more learning:
https://plotly-r.com
https://plot.ly/r
https://talks.cpsievert.me
https://vimeo.com/cpsievert
Find me online:
Web: http://cpsievert.me/
Twitter: @cpsievert
GitHub: @cpsievert
Email: cpsievert1@gmail.com
(1) Open the slides in Firefox: bit.ly/R_Pharma
(2) Go to this address https://rstudio.cloud/project/446255. This hosted RStudio instance contains materials for today's workshop.
(3) Ask me any question at any time by going to slido.com and enter event code #8464 (or use this link). I'll try to check these questions periodically (upvote questions if you'd like them answered!)
05:00
Keyboard shortcuts
↑, ←, Pg Up, k | Go to previous slide |
↓, →, Pg Dn, Space, j | Go to next slide |
Home | Go to first slide |
End | Go to last slide |
Number + Return | Go to specific slide |
b / m / f | Toggle blackout / mirrored / fullscreen mode |
c | Clone slideshow |
p | Toggle presenter mode |
t | Restart the presentation timer |
?, h | Toggle this help |
Esc | Back to slideshow |