The case is adapted from how the R Shiny App for a Library Survey was created.
This post is the second of the three in a series to make an interactive data visualization web app:
Previously, as the first step, we did a lot with data cleaning, especially reshaping for later visualization. With the cleaned data, now let’s build the jigsaw puzzle pieces, the single plots that later will be assembled to form the Shiny App.
Along the way, we will encounter chart types of stacked bars (count/percentage), grouped bars, lollipop chart, dot plot, heatmap and word cloud. Because the goal of the survey is to find out the distribution of service usage by groups, the plots we heavily rely on mostly concern graphing the distribution of categories.
So nothing unusual or fancy. This post is really not about fancy plots, and we will see how we make use of the data we reshaped in the last post to tell stories.
We will use the ggplot2 package for visualization throughout unless otherwise stated.
library(dplyr)
library(stringr)
library(data.table)
library(ggplot2)
library(wordcloud2)
library(gridExtra)
library(grid)
Let’s load the sample survey data.
## setwd()
load("~/Desktop/r sample files/survey")
head(survey)
## # A tibble: 6 x 17
## status country major Q1.1 Q1.2 Q1.3 top_reason place_options
## <fct> <fct> <fct> <chr> <chr> <chr> <chr> <chr>
## 1 Freshm… U.S. Undef… Never Never Never Find a quiet … quiet (occasio…
## 2 Freshm… China Undef… Occas… Often Often Find a quiet … crowded,focused
## 3 Freshm… U.S. Undef… Never Occa… Often Meet up with … (close to) sil…
## 4 Freshm… China Undef… Occas… Occa… Occa… Meet up with … focused,(close…
## 5 Freshm… U.S. Undef… Never Occa… Occa… Find a quiet … (close to) sil…
## 6 Freshm… China Undef… Occas… Often Often Find a quiet … (close to) sil…
## # ... with 9 more variables: space_lib <chr>, rank_crowded <dbl>,
## # rank_modpop <dbl>, rank_noisy <dbl>, rank_quiet <dbl>,
## # rank_silent <dbl>, rank_relaxed <dbl>, rank_focused <dbl>,
## # workshops <chr>
Let’s create a list of a color palette. This will make working with colors more easily later in plotting.
palette <- list(purple = c("#351F39", "#351C4D", "#6c1f55", "#765285", "#8a6899"),
turquoise = c("#709FB0", "#849974", "#A0C1B8"),
golden = c("#D1A827", "#f3da4c"))
We did quite some cleaning in the last post to get the data structure ready for plotting. We don’t need to do that all the time, of course, if the current data structure supports the graphing needs.
Here if we want to present how often respondents of different majors use the library website to search for articles, we can do that right away. The data on website usage is Q1.3
(“I use our library website to (how often) - Search for articles”). The data for major is major
.
Q1.3
and major
both contain discrete values. The data structure is ready for visualizing the distribution of how students of each major use the library website to search for articles. Bar plots and their variations can easily achieve this goal.
tail(survey[c("Q1.3","major")])
## # A tibble: 6 x 2
## Q1.3 major
## <chr> <fct>
## 1 Often Data Science & Interactive Media Business
## 2 Occasionally Business, Finance & Economics
## 3 Often Interactive Media Arts
## 4 Often Business, Finance & Economics
## 5 Occasionally Humanities & Social Sciences
## 6 Occasionally CS & Engineering
table(survey$Q1.3)
##
## Never Occasionally Often
## 77 126 117
table(survey$major)
##
## Business, Finance & Economics
## 118
## Humanities & Social Sciences
## 32
## Data Science & Interactive Media Business
## 24
## Interactive Media Arts
## 20
## Science
## 21
## CS & Engineering
## 27
## Mathematics
## 21
## Undefined
## 57
ggplot(survey, aes(major)) +
geom_bar(aes(fill = Q1.3), position = position_stack(reverse = TRUE), width = 0.4, alpha = 0.75) +
## geom_bar() adds a layer of stacked bars to the plot
## aes(fill = Q1.3) fills the stacked bars with the counts of each value of Q1.3 (never/occasionally/often)
## position = position_stack(reverse = TRUE) reverses the order of the stacked bars
scale_fill_manual(values = c(palette[[1]][4], palette[[2]][1], palette[[3]][1])) +
## manually fills the bars with the preset color scheme
scale_x_discrete(limits = rev(levels(survey$major))) +
## reverse the order of levels of x axis
coord_flip() +
## flip the coordinates
theme(axis.text.x = element_text(size = 12),
axis.text.y = element_text(size = 12, margin = margin(0,3,0,0)),
axis.title.y = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_line(size = 0),
legend.title = element_blank(),
legend.text = element_text(size = 10),
plot.margin = unit(c(1,1,1,1), "cm"))
Alternatively, we can fill the stacked bars with the percentage of each value of Q1.3.
ggplot(survey, aes(major)) +
geom_bar(aes(fill = Q1.3), position = "fill", width = 0.4, alpha = 0.75) +
## position = "fill" sets the plot as stacked bars with filled percentages instead of counts
scale_fill_manual(values = c(palette[[1]][4], palette[[2]][1], palette[[3]][1])) +
scale_x_discrete(limits = rev(levels(survey$major))) +
coord_flip() +
theme(axis.text.x = element_text(size = 12),
axis.text.y = element_text(size = 12, margin = margin(0,3,0,0)),
axis.title.y = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_line(size = 0),
legend.title = element_blank(),
legend.text = element_text(size = 10),
plot.margin = unit(c(1,1,1,1), "cm"))
Or we can group the bars rather than stack the bars.
ggplot(survey, aes(major)) +
geom_bar(aes(fill = Q1.3), position = "dodge", width = 0.4, alpha = 0.75) +
## position = "dodge" sets the plot as grouped bars
scale_fill_manual(values = c(palette[[1]][4], palette[[2]][1], palette[[3]][1])) +
scale_x_discrete(limits = rev(levels(survey$major))) +
coord_flip() +
theme(axis.text.x = element_text(size = 12),
axis.text.y = element_text(size = 12, margin = margin(0,3,0,0)),
axis.title.y = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_line(size = 0),
legend.title = element_blank(),
legend.text = element_text(size = 10),
plot.margin = unit(c(1,1,1,1), "cm"))
We made many frequency tables in the previous post. Time to put them to use.
If we want to plot the distribution of major, that could be easily done with a bar chart. But we could also be easily bored by a bar chart. A lollipop chart, which is a variation of bar chart, can do the same thing for us with some visual diversity.
To make a lollipop chart, we need a frequency table that summarizes the counts of each major. We already did this in the last post, which we repeat below.
tb <- survey %>% count(major) %>% data.frame() # generates a frequency table
tb <- tb %>% arrange(-n) %>% filter(major != "Undefined") # reorder by frequency; remove undefined majors
tb
## major n
## 1 Business, Finance & Economics 118
## 2 Humanities & Social Sciences 32
## 3 CS & Engineering 27
## 4 Data Science & Interactive Media Business 24
## 5 Science 21
## 6 Mathematics 21
## 7 Interactive Media Arts 20
Now let’s plot the lollipop chart from the frequency table.
ggplot(tb, aes(n, reorder(major, -n), label = n)) +
geom_segment(aes(x = 0, y = reorder(major, -n), xend = n, yend = reorder(major, -n)),
size = 0.5, color = "grey50") +
## reorder Group2 by its frequency in descending order
geom_point(size = 8) +
## geom_point() creates scatterplots that display the relationship between two variables, which takes the values of both x and y axes.
## Here, if we add geom_point() before geom_segment(), we'll see the sticks stabbing the points.
## So the sequence actually matters here.
geom_text(color = "white", size = 3) +
coord_flip() +
theme(axis.text.x = element_text(size = 12, angle = 90, hjust = 1),
## The label of x axis would be too wide to lay out horizontally. hjust = 1 means right-justified.
axis.text.y = element_text(size = 12),
axis.title.y = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_line(size = 0))
Another graph type/package that needs a frequency table is the word cloud, at least for the wordcloud2
package that we will be using. The good thing about the graphs created by the wordcloud2 package is that if we hover over the graphs, we will be able to view the frequency of the chosen word in a tooltip.
We have data from student submissions on “My favorite place to study in Library”.
head(survey$space_lib, n = 25)
## [1] "desk" "windows" "428"
## [4] "428" "talking" "silent"
## [7] "windows" "400" "silent"
## [10] "428" "NULL" "silent"
## [13] "silent" "windows" "group study room"
## [16] "400" "group study room" "group study room"
## [19] "400" "407" "407, silent"
## [22] "other" "other" "group study room"
## [25] "NULL"
Extracting the strings and putting them into a frequency table before plotting, which we elaborated in the previous post.
lib <- unlist(strsplit(survey$space_lib, ","))
lib <- str_trim(lib, side = "both")
lib <- lib[!lib %in% c("NULL")]
lib <- data.frame(lib) %>% count(lib) %>% data.frame() %>% arrange(-n)
lib
## lib n
## 1 silent 77
## 2 407 58
## 3 windows 47
## 4 other 31
## 5 group study room 23
## 6 400 22
## 7 428 17
## 8 desk 14
## 9 sofa 11
## 10 large desk 7
## 11 corner 6
## 12 small desk 4
## 13 4F 2
## 14 table 2
## 15 outlet 1
## 16 privacy 1
## 17 reserve 1
## 18 talking 1
Now we can plot the word cloud with two lines of codes.
color1 <- rep(c(palette[[1]][4], palette[[2]][1], palette[[3]][1], palette[[2]][2], palette[[2]][3], palette[[3]][2]),
length.out=nrow(lib))
## sets the color scheme to be applied in the wordcloud2() function
wordcloud2(lib, size=1.2, shape = "diamond", color = color1, ellipticity = 0.9)
In our survey data, we have several variables rank_crowded
, rank_modpop
, rank_noisy
, rank_quiet
, and rank_silent
asking if library users would like to study in an environment that is “crowded”, “moderately populated”, “noisy”, “quiet (occasional whispers)”, or “(close to) silent”.
head(survey[c("rank_crowded","rank_modpop", "rank_noisy", "rank_quiet", "rank_silent")])
## # A tibble: 6 x 5
## rank_crowded rank_modpop rank_noisy rank_quiet rank_silent
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 NA NA NA 1 NA
## 2 1 NA NA NA NA
## 3 NA 3 NA NA 1
## 4 NA NA NA NA 2
## 5 NA NA NA 3 1
## 6 NA NA 2 3 1
When plotting, we’d like to present the results altogether in a matrix. We did some preprocessing in order to bind the columns by group and to reshape them to the long format that a plot type supports.
rank_status <-
data.frame(survey %>% group_by(status) %>% summarise(rank_crowded = round(mean(rank_crowded, na.rm =TRUE),2))) %>%
left_join(data.frame(survey %>% group_by(status) %>% summarise(rank_modpop = round(mean(rank_modpop, na.rm =TRUE),2)))) %>%
left_join(data.frame(survey %>% group_by(status) %>% summarise(rank_noisy = round(mean(rank_noisy, na.rm =TRUE),2)))) %>%
left_join(data.frame(survey %>% group_by(status) %>% summarise(rank_quiet = round(mean(rank_quiet, na.rm =TRUE),2)))) %>%
left_join(data.frame(survey %>% group_by(status) %>% summarise(rank_silent = round(mean(rank_silent, na.rm =TRUE),2)))) %>%
rename(`crowded` = rank_crowded, `moderately populated` = rank_modpop, `noisy` = rank_noisy, `quiet (occasional whispers)` = rank_quiet, `(close to) silent` = rank_silent)
rank_status <- rank_status %>% melt()
rank_status
## status variable value
## 1 Freshman crowded 1.52
## 2 Sophomore crowded 1.60
## 3 Junior crowded 1.25
## 4 Senior crowded 3.42
## 5 Other Programs crowded 4.00
## 6 Freshman moderately populated 2.43
## 7 Sophomore moderately populated 2.36
## 8 Junior moderately populated 1.67
## 9 Senior moderately populated 3.21
## 10 Other Programs moderately populated 3.50
## 11 Freshman noisy 3.08
## 12 Sophomore noisy 3.40
## 13 Junior noisy 2.00
## 14 Senior noisy 4.93
## 15 Other Programs noisy 7.00
## 16 Freshman quiet (occasional whispers) 1.90
## 17 Sophomore quiet (occasional whispers) 2.13
## 18 Junior quiet (occasional whispers) 1.45
## 19 Senior quiet (occasional whispers) 1.97
## 20 Other Programs quiet (occasional whispers) 1.91
## 21 Freshman (close to) silent 1.76
## 22 Sophomore (close to) silent 1.71
## 23 Junior (close to) silent 1.89
## 24 Senior (close to) silent 1.58
## 25 Other Programs (close to) silent 1.93
Now let’s plot the heat map.
ggplot(data = rank_status, aes(x = variable, y = status)) +
geom_tile(aes(fill = value), alpha = 0.95)+
scale_fill_gradient(high = "white", low = palette[[2]][1]) +
geom_text(aes(label = value), size = 2.5, alpha = 0.9)+
scale_y_discrete(limits = rev(levels(rank_status$status))) +
theme(axis.text.x = element_text(size = 12, angle = 90, hjust = 1),
axis.text.y = element_text(size = 12, margin = margin(0,3,0,0)),
axis.title.y = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_line(size = 0),
plot.subtitle = element_text(size = 10, margin = margin(0,0,15,0)),
legend.title = element_blank(),
legend.position = "none",
plot.margin = unit(c(1,1,1,1), "cm")) +
labs(subtitle = " * Average scores of ranking (1 to 6). Note: limited cases in each group.")
Let’s say we want to visualize the distribution of top reason of visiting library (top_reason
) by groups. There are multiple ways to do that. We will use this subsample as a demo to show ways of visually grouping data.
Previously we’ve created a data frame dtset
summarizing top reason of visiting library by country. We did quite some work of reshaping.
Reason <- c("Work on a class assignment/paper", "Watch video or listen audio", "Use specialized databases \\(e.g. Bloomberg, Wind\\)", "Use a library computer", "Use a group study room", "Print, photocopy, scan", "Other", "Meet up with friends", "Hang out between classes", "Get readings from Course Reserve", "Get help from a librarian", "Find a quiet place to study", "Borrow books and materials", "Attend a library workshop")
r <- data.frame(survey$country)
for (m in 1: length(Reason)){
r[,Reason[m]] <- str_extract(survey$top_reason, Reason[m])
}
dtset <- data.frame(Reason)
levels(dtset$Reason)[levels(dtset$Reason) == "Use specialized databases \\(e.g. Bloomberg, Wind\\)"] <- "Use specialized databases (e.g. Bloomberg, Wind)"
g <- c("China", "U.S.", "Other")
for (n in 1:length(g)){
dtset[,g[n]] <- apply(r[r[] == g[n], 2:15], 2, function(x) length(which(!is.na(x))))
}
dtset$Total<- rowSums(dtset[,2:4], na.rm = TRUE, dims = 1)
dtset <- dtset[order(dtset$Total,decreasing = T),]
dtset
## Reason China U.S. Other Total
## 12 Find a quiet place to study 123 47 50 220
## 6 Print, photocopy, scan 104 45 52 201
## 1 Work on a class assignment/paper 89 46 31 166
## 13 Borrow books and materials 63 15 28 106
## 10 Get readings from Course Reserve 30 6 8 44
## 4 Use a library computer 11 20 10 41
## 5 Use a group study room 32 4 5 41
## 8 Meet up with friends 23 4 9 36
## 9 Hang out between classes 16 6 5 27
## 11 Get help from a librarian 13 2 7 22
## 3 Use specialized databases (e.g. Bloomberg, Wind) 12 3 2 17
## 14 Attend a library workshop 9 5 3 17
## 2 Watch video or listen audio 7 2 2 11
## 7 Other 2 2 1 5
We will use this readily available data for all the demos below.
Let’s say we want to plot the distribution of top visits and make the contrast more visible among groups. One way to achieve the goal is to color the levels in groups. Specifically, we decide to group the frequencies as “high”, “medium” and “low”.
We first code the levels.
Reason2 <- c("Work on a class assignment/paper", "Watch video or listen audio", "Use specialized databases (e.g. Bloomberg, Wind)", "Use a library computer", "Use a group study room", "Print, photocopy, scan", "Other", "Meet up with friends", "Hang out between classes", "Get readings from Course Reserve", "Get help from a librarian", "Find a quiet place to study", "Borrow books and materials", "Attend a library workshop")
l <- data.frame(Reason = Reason2, Level = c("High","Low","Low","Medium","Medium","High","Low","Medium","Low","Medium","Low","High","High","Low"))
## label the frequency level
## the standard may be a bit arbitrary
dtset <- dtset %>% left_join(l, by = "Reason")
The rest is standard plotting.
ggplot(dtset, aes(x = reorder(Reason, Total), y = Total, fill = factor(Level, levels = c("High","Medium","Low")))) +
geom_bar(stat = "identity", alpha = 0.75) +
scale_fill_manual(values = c(palette[[1]][4], palette[[2]][1], palette[[3]][1]), name="Level of\nFrequency") +
## \n in "Level of\nFrequency" breaks the line
coord_flip() +
theme(axis.text.x = element_text(size = 12),
axis.text.y = element_text(size = 12, margin = margin(0,3,0,0)),
axis.title.y = element_blank(),
axis.title.x = element_text(size = 12, margin = margin(15,0,0,0)),
axis.ticks.x = element_line(size = 0),
legend.title = element_text(size = 12),
legend.text = element_text(size = 12),
plot.margin = unit(c(0,0,1,0), "cm"))
Now let’s say we want to plot the distribution of top visits by country. This can be done with “facets” - we will get panels within one plot for each subgroup.
To support the graphing needs, we need to reshape the dtset
data frame into long format.
dtset2 <- dtset %>% melt()
dtset2
## Reason Level variable value
## 1 Find a quiet place to study High China 123
## 2 Print, photocopy, scan High China 104
## 3 Work on a class assignment/paper High China 89
## 4 Borrow books and materials High China 63
## 5 Get readings from Course Reserve Medium China 30
## 6 Use a library computer Medium China 11
## 7 Use a group study room Medium China 32
## 8 Meet up with friends Medium China 23
## 9 Hang out between classes Low China 16
## 10 Get help from a librarian Low China 13
## 11 Use specialized databases (e.g. Bloomberg, Wind) Low China 12
## 12 Attend a library workshop Low China 9
## 13 Watch video or listen audio Low China 7
## 14 Other Low China 2
## 15 Find a quiet place to study High U.S. 47
## 16 Print, photocopy, scan High U.S. 45
## 17 Work on a class assignment/paper High U.S. 46
## 18 Borrow books and materials High U.S. 15
## 19 Get readings from Course Reserve Medium U.S. 6
## 20 Use a library computer Medium U.S. 20
## 21 Use a group study room Medium U.S. 4
## 22 Meet up with friends Medium U.S. 4
## 23 Hang out between classes Low U.S. 6
## 24 Get help from a librarian Low U.S. 2
## 25 Use specialized databases (e.g. Bloomberg, Wind) Low U.S. 3
## 26 Attend a library workshop Low U.S. 5
## 27 Watch video or listen audio Low U.S. 2
## 28 Other Low U.S. 2
## 29 Find a quiet place to study High Other 50
## 30 Print, photocopy, scan High Other 52
## 31 Work on a class assignment/paper High Other 31
## 32 Borrow books and materials High Other 28
## 33 Get readings from Course Reserve Medium Other 8
## 34 Use a library computer Medium Other 10
## 35 Use a group study room Medium Other 5
## 36 Meet up with friends Medium Other 9
## 37 Hang out between classes Low Other 5
## 38 Get help from a librarian Low Other 7
## 39 Use specialized databases (e.g. Bloomberg, Wind) Low Other 2
## 40 Attend a library workshop Low Other 3
## 41 Watch video or listen audio Low Other 2
## 42 Other Low Other 1
## 43 Find a quiet place to study High Total 220
## 44 Print, photocopy, scan High Total 201
## 45 Work on a class assignment/paper High Total 166
## 46 Borrow books and materials High Total 106
## 47 Get readings from Course Reserve Medium Total 44
## 48 Use a library computer Medium Total 41
## 49 Use a group study room Medium Total 41
## 50 Meet up with friends Medium Total 36
## 51 Hang out between classes Low Total 27
## 52 Get help from a librarian Low Total 22
## 53 Use specialized databases (e.g. Bloomberg, Wind) Low Total 17
## 54 Attend a library workshop Low Total 17
## 55 Watch video or listen audio Low Total 11
## 56 Other Low Total 5
The facet_wrap()
option allows us to plot by groups.
ggplot(dtset2, aes(value, reorder(Reason, value))) +
geom_segment(aes(x = 0, y = reorder(Reason, value), xend = value, yend = reorder(Reason, value)), size = 0.3, color = "grey50") +
geom_point(color = palette[[1]][4], size = 2) +
facet_wrap(~variable, nrow = 2) +
## produces the facets in two rows
theme(axis.text.x = element_text(size = 10),
axis.text.y = element_text(size = 10, margin = margin(0,5,0,0)),
axis.title.y = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_line(size = 0),
strip.text = element_text(size=12))
Another scenario of grouping plots is “combining graphs”. Facets work on subgroups, but combining puts plots of different topics and types into one graph rather than plots subgroups on one graph.
For instance, we can combine “top reason of visiting library” and “distribution of survey participants”; but we will use faceting for plotting “top reason of visiting library (U.S.)” and “top reason of visiting library (other international students)”.
Below we will create a combined plot on student submissions of “My favorite place to study in Library” with user preference on study environment.
rank_status <-
data.frame(survey %>% group_by(status) %>% summarise(rank_crowded = round(mean(rank_crowded, na.rm =TRUE),2))) %>%
left_join(data.frame(survey %>% group_by(status) %>% summarise(rank_modpop = round(mean(rank_modpop, na.rm =TRUE),2)))) %>%
left_join(data.frame(survey %>% group_by(status) %>% summarise(rank_noisy = round(mean(rank_noisy, na.rm =TRUE),2)))) %>%
left_join(data.frame(survey %>% group_by(status) %>% summarise(rank_quiet = round(mean(rank_quiet, na.rm =TRUE),2)))) %>%
left_join(data.frame(survey %>% group_by(status) %>% summarise(rank_silent = round(mean(rank_silent, na.rm =TRUE),2)))) %>%
rename(`crowded` = rank_crowded, `moderately populated` = rank_modpop, `noisy` = rank_noisy, `quiet (occasional whispers)` = rank_quiet, `(close to) silent` = rank_silent)
rank_status <- rank_status %>% melt()
c1 <-
ggplot(rank_status, aes(x = variable, y = status)) +
geom_tile(aes(fill = value), alpha = 0.95)+
scale_fill_gradient(high = "white", low = palette[[2]][1]) +
geom_text(aes(label = value), size = 2.5, alpha = 0.9)+
scale_y_discrete(limits = rev(levels(rank_status$status))) +
theme(axis.text.x = element_text(size = 12, angle = 90, hjust = 1),
axis.text.y = element_text(size = 12, margin = margin(0,3,0,0)),
axis.title.y = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_line(size = 0),
plot.subtitle = element_text(size = 10, margin = margin(0,0,15,0)),
legend.title = element_blank(),
legend.position = "none",
plot.margin = unit(c(1,1,1,1), "cm")) +
labs(subtitle = " * Average scores of ranking (1 to 6). Note: limited cases in each group.")
c1
lib <- unlist(strsplit(survey$space_lib, ","))
lib <- str_trim(lib, side = "both")
lib <- lib[!lib %in% c("NULL")]
lib <- data.frame(lib) %>% count(lib) %>% data.frame() %>% arrange(-n)
c2 <-
ggplot(lib, aes(n, reorder(lib, -n), label = n)) +
geom_segment(aes(x = 0, y = reorder(lib, -n), xend = n, yend = reorder(lib, -n)),
size = 0.5, color = "grey50") +
geom_point(size = 5) +
geom_text(color = "white", size = 2) +
coord_flip() +
theme(axis.text.x = element_text(size = 12, angle = 90, hjust = 1),
axis.text.y = element_text(size = 12),
axis.title.y = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_line(size = 0))
c2
grid.arrange(c1, c2, nrow = 1, top = textGrob("Study Space Preference", gp = gpar(fontsize = 10)))
As you may say, the combination is a bit arbitrary and the combined plot is not so beautiful. But I want to show you the rationale of using “combining graphs” rather than “facets”.