library(readr)
library(ggplot2)
library(dplyr)
library(patchwork)
# Load data
<- "https://raw.githubusercontent.com/simoneSantoni/data-viz-smm635/refs/heads/master/data/googleplaystore.csv"
data_source <- read_csv(data_source)
apps
# Clean data
<- apps %>%
apps mutate(
Reviews = as.numeric(Reviews),
Rating = as.numeric(Rating)
%>%
) filter(Type %in% c("Free", "Paid"))
Faceting and Multi-Panel Layouts with ggplot2
Setup
Faceting in ggplot2
Faceting creates multiple panels based on categorical variables, allowing comparison across subgroups. ggplot2 provides two main faceting functions:
facet_wrap()
facet_wrap()
wraps a 1D sequence of panels into 2D, useful for faceting by a single variable.
%>%
apps ggplot(aes(x = Rating)) +
geom_histogram(bins = 30, fill = "steelblue", alpha = 0.7) +
facet_wrap(~Type) +
theme_minimal() +
labs(title = "Rating Distribution by App Type",
x = "Rating", y = "Count")
%>%
apps filter(`Content Rating` %in% c("Everyone", "Teen", "Mature 17+", "Everyone 10+")) %>%
ggplot(aes(x = Rating)) +
geom_density(fill = "coral", alpha = 0.6) +
facet_wrap(~`Content Rating`, ncol = 2) +
theme_minimal() +
labs(title = "Rating Density by Content Rating",
x = "Rating", y = "Density")
%>%
apps filter(`Content Rating` %in% c("Everyone", "Teen", "Mature 17+")) %>%
ggplot(aes(x = Reviews)) +
geom_histogram(bins = 20, fill = "darkgreen", alpha = 0.7) +
facet_wrap(~`Content Rating`, scales = "free") +
scale_x_log10() +
theme_minimal() +
labs(title = "Review Distribution with Independent Scales",
x = "Reviews (log scale)", y = "Count")
facet_grid()
facet_grid()
forms a matrix of panels defined by row and column faceting variables. Perfect for examining interactions between two categorical variables.
%>%
apps filter(`Content Rating` %in% c("Everyone", "Teen")) %>%
ggplot(aes(x = Reviews, y = Rating)) +
geom_point(alpha = 0.3, color = "purple") +
geom_smooth(method = "lm", color = "red", se = FALSE) +
facet_grid(Type ~ `Content Rating`) +
scale_x_log10() +
theme_minimal() +
labs(title = "Reviews vs Rating: Type � Content Rating",
x = "Reviews (log scale)", y = "Rating")
%>%
apps ggplot(aes(x = Rating, fill = Type)) +
geom_density(alpha = 0.5) +
facet_grid(Type ~ .) +
theme_minimal() +
scale_fill_brewer(palette = "Set1") +
labs(title = "Rating Density by App Type (Row Facets)",
x = "Rating", y = "Density")
%>%
apps ggplot(aes(x = Rating, fill = Type)) +
geom_histogram(bins = 30, alpha = 0.7, position = "identity") +
facet_grid(. ~ Type) +
theme_minimal() +
scale_fill_brewer(palette = "Set2") +
labs(title = "Rating Distribution by App Type (Column Facets)",
x = "Rating", y = "Count")
Patchwork: Flexible Multi-Panel Layouts
The patchwork package extends ggplot2’s capabilities by allowing you to combine independent plots with intuitive operators.
Basic Patchwork Operators
<- apps %>%
p1 ggplot(aes(x = Type, fill = Type)) +
geom_bar() +
theme_minimal() +
scale_fill_brewer(palette = "Pastel1") +
labs(title = "App Count by Type", x = "", y = "Count")
<- apps %>%
p2 ggplot(aes(x = Type, y = Rating, fill = Type)) +
geom_boxplot() +
theme_minimal() +
scale_fill_brewer(palette = "Pastel1") +
labs(title = "Rating Distribution", x = "", y = "Rating")
+ p2 p1
<- apps %>%
p3 filter(Reviews > 0) %>%
ggplot(aes(x = Reviews)) +
geom_histogram(bins = 50, fill = "skyblue") +
scale_x_log10() +
theme_minimal() +
labs(title = "Review Count Distribution", x = "Reviews (log scale)", y = "Count")
<- apps %>%
p4 ggplot(aes(x = Rating)) +
geom_histogram(bins = 30, fill = "salmon") +
theme_minimal() +
labs(title = "Rating Distribution", x = "Rating", y = "Count")
/ p4 p3
<- apps %>%
p5 ggplot(aes(x = `Content Rating`, fill = Type)) +
geom_bar(position = "dodge") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
scale_fill_brewer(palette = "Set3") +
labs(title = "Apps by Content Rating & Type", x = "", y = "Count")
+ p2) / p5 (p1
Advanced Patchwork Features
<- p1 + p2 + p3 + p4
combined
+
combined plot_annotation(
title = 'Google Play Store Apps Analysis',
subtitle = 'Distribution of key metrics across app types',
tag_levels = 'A',
theme = theme(plot.title = element_text(size = 16, face = "bold"))
)
+ p2 + p3 + p4 +
p1 plot_layout(ncol = 2, heights = c(1, 2))
<- apps %>%
p6 filter(Reviews > 0, !is.na(Rating)) %>%
ggplot(aes(x = Reviews, y = Rating, color = Type)) +
geom_point(alpha = 0.4) +
geom_smooth(method = "lm", se = FALSE) +
scale_x_log10() +
scale_color_brewer(palette = "Set1") +
theme_minimal() +
labs(title = "Reviews vs Rating", x = "Reviews (log)", y = "Rating")
<- apps %>%
p7 count(Type, `Content Rating`) %>%
filter(`Content Rating` %in% c("Everyone", "Teen", "Mature 17+")) %>%
ggplot(aes(x = `Content Rating`, y = n, fill = Type)) +
geom_col(position = "dodge") +
theme_minimal() +
scale_fill_brewer(palette = "Set1") +
labs(title = "Distribution by Content Rating", x = "", y = "Count")
| (p1 / p2)) + plot_layout(widths = c(2, 1)) (p6
Collecting Guides
When plots share legends, patchwork can collect them into a single legend:
<- apps %>%
p8 filter(Reviews > 0) %>%
ggplot(aes(x = Reviews, fill = Type)) +
geom_histogram(bins = 30, alpha = 0.7) +
scale_x_log10() +
scale_fill_brewer(palette = "Dark2") +
theme_minimal() +
labs(title = "Review Distribution", x = "Reviews (log)", y = "Count")
<- apps %>%
p9 filter(!is.na(Rating)) %>%
ggplot(aes(x = Rating, fill = Type)) +
geom_density(alpha = 0.6) +
scale_fill_brewer(palette = "Dark2") +
theme_minimal() +
labs(title = "Rating Density", x = "Rating", y = "Density")
+ p9 + plot_layout(guides = "collect") p8
Faceting vs Patchwork: When to Use Which?
Feature | facet_wrap / facet_grid | patchwork |
---|---|---|
Use case | Same plot type, same variables, different subgroups | Different plot types, different variables, or different data |
Data source | Single data frame | Multiple data frames or sources |
Aesthetics | Shared across all panels | Independent per plot |
Scales | Can be fixed or freed | Fully independent |
Layout | Regular grid | Flexible, asymmetric layouts |
Legends | Automatically unified | Can be collected or kept separate |
Annotations | Standard ggplot2 labels | Advanced with plot_annotation() |
Example: Complete Analysis Dashboard
# Overview panel
<- apps %>%
overview count(Type, `Content Rating`) %>%
filter(`Content Rating` %in% c("Everyone", "Teen", "Mature 17+", "Everyone 10+")) %>%
ggplot(aes(x = `Content Rating`, y = n, fill = Type)) +
geom_col(position = "dodge") +
theme_minimal() +
scale_fill_brewer(palette = "Set1") +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
labs(title = "App Distribution", x = "", y = "Count")
# Rating analysis with facets
<- apps %>%
rating_facet filter(`Content Rating` %in% c("Everyone", "Teen")) %>%
ggplot(aes(x = Rating, fill = Type)) +
geom_density(alpha = 0.6) +
facet_wrap(~`Content Rating`) +
scale_fill_brewer(palette = "Set1") +
theme_minimal() +
labs(title = "Rating Density by Content Rating", x = "Rating", y = "Density")
# Reviews analysis
<- apps %>%
reviews_scatter filter(Reviews > 0, !is.na(Rating)) %>%
ggplot(aes(x = Reviews, y = Rating, color = Type)) +
geom_point(alpha = 0.3, size = 0.8) +
geom_smooth(method = "lm", se = FALSE, size = 1) +
scale_x_log10() +
scale_color_brewer(palette = "Set1") +
theme_minimal() +
labs(title = "Reviews vs Rating", x = "Reviews (log scale)", y = "Rating")
# Combine with patchwork
| rating_facet) / reviews_scatter +
(overview plot_layout(heights = c(1, 1.5), guides = "collect") +
plot_annotation(
title = 'Google Play Store Apps: Comprehensive Analysis',
subtitle = 'Combining faceting and patchwork for effective data storytelling',
tag_levels = 'A',
theme = theme(
plot.title = element_text(size = 18, face = "bold"),
plot.subtitle = element_text(size = 12)
)&
) theme(legend.position = "bottom")
Summary
This document demonstrated:
- facet_wrap(): For wrapping panels in a ribbon
- facet_grid(): For creating rectangular grids
- Patchwork operators:
+
(side-by-side),/
(stacked),|
(compose) - plot_layout(): Fine control over dimensions and arrangement
- plot_annotation(): Adding titles, tags, and themes
- Collecting guides: Unifying legends across plots