forked from cpsievert/plotly_book
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patharranging.Rmd
More file actions
200 lines (158 loc) · 15.2 KB
/
arranging.Rmd
File metadata and controls
200 lines (158 loc) · 15.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# Arranging multiple views
One technique essential to high-dimensional data analysis is the ability to arrange multiple views. Ideally, these views are linked in some way to foster comparisons (the next chapter discusses linking techniques). The next section, [Arranging htmlwidgets](#arranging-htmlwidgets) describes techniques for arranging htmlwidget objects, which many R packages for creating web-based data visualizations build upon, including **plotly**. Typically interactivity is isolated _within_ an htmlwidget object, but [Linking views without shiny](#linking-views-without-shiny) explores some more recent work on enabling interactivity _across_ htmlwidget objects. The following section, [Subplots](#subplots) describes the `subplot()` function, which is useful for _merging_ multiple plotly objects into a single htmlwidget object. The main benefit of merging (rather than arranging) plotly objects is that it gives us the ability to synchronize zoom and pan events across multiple axes. The last section, [Navigating many views](#navigating-many-views) discusses some useful tools for restricting focus on interesting views when there are more views than you can possibly digest visually.
## Arranging htmlwidgets
Since plotly objects inherit properties from an htmlwidget object, any method that works for arranging htmlwidgets also works for plotly objects. In some sense, an htmlwidget object is just a collection of HTML tags, and the **htmltools** package provides some useful functions for working with HTML tags [@htmltools]. The `tagList()` function gathers multiple HTML tags into a tag list, and when printing a tag list inside of a **knitr**/**rmarkdown** document [@knitr]; [@rmarkdown], it knows to render as HTML. When printing outside of this context (e.g., at the command line), a tag list prints as a character string by default. In order to view the rendered HTML, provide the tag list to the `browsable()` function.
```{r, echo = FALSE}
set.seed(100)
```
```{r multiple-htmlwidgets-fake, eval = FALSE}
library(htmltools)
library(plotly)
p <- plot_ly(x = rnorm(100))
tagList(p, p)
```
```{r multiple-htmlwidgets, echo = FALSE, fig.cap = "Printing multiple htmlwidget objects with `tagList()`. To render tag lists at the command line, wrap them in `browsable()` ", screenshot.alt = "screenshots/multiple-htmlwidgets"}
# resort to this until this gets resolved -- https://github.com/rstudio/bookdown/issues/74
knitr::include_graphics("images/multiple-htmlwidgets")
```
Figure \@ref(fig:multiple-htmlwidgets) renders two plots, each in its own row spanning the width of the page, because each htmlwidget object is an HTML `<div>` tag. More often than not, it is desirable to arrange multiple plots in a given row, and there are a few ways to do that. A very flexible approach is to wrap all of your plots in a [flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) (i.e., an HTML `<div>` with `display: flex` Cascading Style Sheets (CSS) property). The `tags$div()` function from **htmltools** provides a way to wrap a `<div>` around both tag lists and htmlwidget objects, and set attributes, such as `style`. As Figure \@ref(fig:flexbox) demonstrates, this approach also provides a nice way to add custom styling to the page, such as borders around each panel.
```{r flexbox-fake, eval = FALSE}
tags$div(
style = "display: flex; flex-wrap: wrap",
tags$div(p, style = "width: 50%; padding: 1em; border: solid;"),
tags$div(p, style = "width: 50%; padding: 1em; border: solid;"),
tags$div(p, style = "width: 100%; padding: 1em; border: solid;")
)
```
```{r flexbox, echo = FALSE, fig.cap = "Arranging multiple htmlwidgets with flexbox"}
knitr::include_graphics("images/flexbox")
```
Another way to arrange multiple htmlwidget objects on a single page is to leverage the `fluidPage()`, `fluidRow()`, and `column()` functions from the **shiny** package.
```{r fluid-fake, eval = FALSE}
library(shiny)
fluidPage(
fluidRow(p),
fluidRow(
column(6, p), column(6, p)
)
)
```
```{r fluid, echo = FALSE, fig.cap = "Arranging multiple htmlwidgets with `fluidPage()` from the **shiny** package."}
knitr::include_graphics("images/fluid")
```
All the arrangement approaches discussed thus far are agnostic to output format, meaning that they can be used to arrange htmlwidgets within _any_ **knitr**/**rmarkdown** document.^[Although HTML can not possibly render in a pdf or word document, **knitr** can automatically detect a non-HTML output format and embed a static image of the htmlwidget via the **webshot** package [@webshot].] If the htmlwidgets do not need to be embedded within a larger document that requires an opinionated output format, the **flexdashboard** package provides a **rmarkdown** template for generating dashboards, with a convenient syntax for arranging views [@flexdashboard].
## Merging plotly objects
The `subplot()` function provides a flexible interface for merging multiple plotly objects into a single object (i.e., view). It is more flexible than most trellis display frameworks (e.g., ggplot2's `facet_wrap()`) as you don't have to condition on a value of common variable in each display [@trellis]. Its capabilities and interface is similar to the `grid.arrange()` function from the **gridExtra** package, which allows you to arrange multiple **grid** grobs in a single view, effectively providing a way to arrange (possibly unrelated) **ggplot2** and/or **lattice** plots in a single view [@RCore]; [@gridExtra]; [@lattice]. Figure \@ref(fig:simple) shows the most simple way to use `subplot()` which is to directly supply plotly objects.
```{r simple, fig.cap = "The most basic use of `subplot()` to merge multiple plotly objects into a single plotly object.", screenshot.alt = "screenshots/simple"}
library(plotly)
p1 <- plot_ly(economics, x = ~date, y = ~unemploy) %>%
add_lines(name = "unemploy")
p2 <- plot_ly(economics, x = ~date, y = ~uempmed) %>%
add_lines(name = "uempmed")
subplot(p1, p2)
```
Although `subplot()` accepts an arbitrary number of plot objects, passing a _list_ of plots can save typing and redundant code when dealing with a large number of plots. Figure \@ref(fig:economics) shows one time series for each variable in the `economics` dataset and share the x-axis so that zoom/pan events are synchronized across each series:
```{r economics, fig.cap = "Five different economic variables on different y scales and a common x scale. Zoom and pan events in the x-direction are synchronized across plots.", screenshot.alt = "screenshots/economics"}
vars <- setdiff(names(economics), "date")
plots <- lapply(vars, function(var) {
plot_ly(economics, x = ~date, y = as.formula(paste0("~", var))) %>%
add_lines(name = var)
})
subplot(plots, nrows = length(plots), shareX = TRUE, titleX = FALSE)
```
A plotly subplot is a single plotly graph with multiple traces anchored on different axes. If you pre-specify an [axis ID](https://plot.ly/r/reference/#scatter-yaxis) for each trace, `subplot()` will respect that ID. Figure \@ref(fig:prepopulate) uses this fact in correspondence with the fact that mapping a discrete variable to `color` creates one trace per value. In addition to providing more control over trace placement, this provides a convenient way to control coloring (we could have `symbol`/`linetype` to achieve the same effect).
```{r prepopulate, fig.cap = "Pre-populating y axis IDs.", screenshot.alt = "screenshots/prepopulate"}
economics %>%
tidyr::gather(variable, value, -date) %>%
transform(id = as.integer(factor(variable))) %>%
plot_ly(x = ~date, y = ~value, color = ~variable, colors = "Dark2",
yaxis = ~paste0("y", id)) %>%
add_lines() %>%
subplot(nrows = 5, shareX = TRUE)
```
Conceptually, `subplot()` provides a way to place a collection of plots into a table with a given number of rows and columns. The number of rows (and, by consequence, the number of columns) is specified via the `nrows` argument. By default each row/column shares an equal proportion of the overall height/width, but as shown in Figure \@ref(fig:proportions) the default can be changed via the `heights` and `widths` arguments.
```{r proportions, echo = FALSE, fig.cap = "A visual diagram of controling the `heights` of rows and `widths` of columns."}
knitr::include_graphics("images/proportions")
```
This flexibility is quite useful for a number of visualizations, for example, as shown in Figure \@ref(fig:joint), a joint density plot is really of subplot of joint and marginal densities. The **heatmaply** package is great example of leveraging `subplot()` in a similar way to create interactive dendrograms [@heatmaply].
```{r joint, fig.cap = "A joint density plot with synchronized axes.", screenshot.alt = "screenshots/joint"}
x <- rnorm(100)
y <- rnorm(100)
s <- subplot(
plot_ly(x = x, color = I("black")),
plotly_empty(),
plot_ly(x = x, y = y, color = I("black")),
plot_ly(y = y, color = I("black")),
nrows = 2, heights = c(0.2, 0.8), widths = c(0.8, 0.2),
shareX = TRUE, shareY = TRUE, titleX = FALSE, titleY = FALSE
)
layout(s, showlegend = FALSE)
```
### Recursive subplots
The `subplot()` function returns a plotly object so it can be modified like any other plotly object. This effectively means that subplots work recursively (i.e., you can have subplots within subplots). This idea is useful when your desired layout doesn't conform to the table structure described in the previous section. In fact, you can think of a subplot of subplots like a spreadsheet with merged cells. Figure \@ref(fig:recursive) gives a basic example where each row of the outer-most subplot contains a different number of columns.
```{r recursive, fig.cap = "Recursive subplots.", screenshot.alt = "screenshots/recursive"}
plotList <- function(nplots) {
lapply(seq_len(nplots), function(x) plot_ly())
}
s1 <- subplot(plotList(6), nrows = 2, shareX = TRUE, shareY = TRUE)
s2 <- subplot(plotList(2), shareY = TRUE)
subplot(
s1, s2, plot_ly(), nrows = 3,
margin = 0.04, heights = c(0.6, 0.3, 0.1)
)
```
The concept is particularly useful when you want plot(s) in a given row to have different widths from plot(s) in another row. Figure \@ref(fig:map-subplot) uses this recursive behavior to place many bar charts in the first row, and a single choropleth in the second row.
```{r map-subplot, fig.cap = "Multiple bar charts of US statistics by state in a subplot with a choropleth of population density", screenshot.alt = "screenshots/map-subplot"}
# specify some map projection/options
g <- list(
scope = 'usa',
projection = list(type = 'albers usa'),
lakecolor = toRGB('white')
)
# create a map of population density
density <- state.x77[, "Population"] / state.x77[, "Area"]
map <- plot_geo(z = ~density, text = state.name,
locations = state.abb, locationmode = 'USA-states') %>%
layout(geo = g)
# create a bunch of horizontal bar charts
vars <- colnames(state.x77)
barcharts <- lapply(vars, function(var) {
plot_ly(x = state.x77[, var], y = state.name) %>%
add_bars(orientation = "h", name = var) %>%
layout(showlegend = FALSE, hovermode = "y",
yaxis = list(showticklabels = FALSE))
})
subplot(
subplot(barcharts, margin = 0.01), map,
nrows = 2, heights = c(0.3, 0.7), margin = 0.1
)
```
### ggplot2 subplots
Underneath the hood, ggplot2 facets are implemented as subplots, which enables the synchronized zoom events on shared axes. Since subplots work recursively, it is also possible to have a subplot of ggplot2 faceted plots, as Figure \@ref(fig:ggplot2-subplots) shows. Moreover, `subplot()` can understand ggplot objects, so there is no need to translate them to plotly object via `ggplotly()` (unless you want to leverage some of the `ggplotly()` arguments, such as `tooltip` for customizing information displayed on hover).
```{r ggplot2-subplots, fig.cap = "Arranging multiple faceted ggplot2 plots into a plotly subplot.", screenshot.alt = "screenshots/ggplot2-subplots"}
e <- tidyr::gather(economics, variable, value, -date)
gg1 <- ggplot(e, aes(date, value)) + geom_line() +
facet_wrap(~variable, scales = "free_y", ncol = 1)
gg2 <- ggplot(e, aes(factor(1), value)) + geom_violin() +
facet_wrap(~variable, scales = "free_y", ncol = 1) +
theme(axis.text = element_blank(), axis.ticks = element_blank())
subplot(gg1, gg2) %>% layout(margin = list(l = 50))
```
## Navigating many views
Sometimes you have to consider way more views than you can possibly digest visually. In [Multiple linked views](#multiple-linked-views), we explore some useful techniques for implementing the popular visualization mantra from @details-on-demand:
> "Overview first, zoom and filter, then details-on-demand."
In fact, Figure \@ref(fig:plotlyLinkedClick) from that section provides an example of this mantra put into practice. The correlation matrix provides an overview of the correlation structure between all the variables, and by clicking a cell, it populates a scatterplot between those two specific variables. This works fine with tens or hundreds or variables, but once you have thousands or tens-of-thousands of variables, this technique begins to fall apart. At that point, you may be better off defining a range of correlations that you're interested in exploring, or better yet, incorporating another measure (e.g., a test statistic), then focusing on views that match a certain criteria.
@scagnostics-tukey first described the idea of using quantitative measurements of scatterplot characteristics (e.g. correlation) to help guide exploratory analysis of many variables. This idea, coined scagnostics (short for scatterplot diagnostics), has since been made explicit, and many measures have been explored, even measures specifically useful for time-series have been proposed [@Wilkinson:2005b]; [@Wilkinson:2008]; [@Wilkinson:2012]. Probably the most universally useful scagnostic is the outlying measure which helps identify projections of the data space that contain outlying observations. Of course, the idea of associating quantitative measures with a graphical display of data can be generalized to include more that just scatterplots, and in this more general case, these measures are sometimes referred to as cognostics.
The same problems and principles that inspired scagnostics has inspired work on more general divide & recombine technique(s) for working with navigating through many statistical artifacts [@divide-recombine]; [@RHIPE], including visualizations [@trelliscope]. The **trelliscope** package provides a system for computing arbitrary cognostics on each panel of a trellis display as well as an interactive graphical user interface for defining (and navigating through) interesting panels based on those cognostics [@trelliscope-pkg]. This system also allows users to define the graphical method for displaying each panel, so **plotly** graphs can easily be embedded. The **trelliscope** package is currently built upon **shiny**, but as Figure \@ref(fig:trelliscope) demonstrates, the **trelliscopejs** package provides lower-level tools that allow one to create trelliscope displays without **shiny** [@trelliscopejs].
```{r trelliscope-fake, eval = FALSE}
library(trelliscopejs)
qplot(cty, hwy, data = mpg) +
xlim(7, 37) + ylim(9, 47) + theme_bw() +
facet_trelliscope(
~ manufacturer + class, nrow = 2, ncol = 4,
as_plotly = TRUE, plotly_args = list(dynamicTicks = T)
)
```
```{r trelliscope, echo = FALSE, fig.cap = "Using plotly within a trelliscope"}
knitr::include_graphics("images/trelliscope")
```