library(tidyverse)
library(scales)
library(corrr)
library(patchwork)
library(widyr)
library(igraph)
library(ggnetwork)
library(ggforce)
library(chilemapas)
# sobresicribir los parámetros por default
# lo hago pues no me gusta el color tan claro
# _amarillo chillón_ en la escala viridis.
scale_fill_viridis_d <- partial(
ggplot2::scale_fill_viridis_d ,
begin = 0.05, end = 0.9
)
scale_color_viridis_d <- partial(
ggplot2::scale_color_viridis_d,
begin = 0.05, end = 0.9
)Introducción
De vez en cuando tengo que hacer clases de visualización o análisis de datos, enseñar ggplot2 o dplyr, y en dichas ocaciones especiales siempre retomo un ya muy viejo ejemplo con datos del metro de Santiago de Chile en el cual intento motivar el hecho de que a través de distintas visualización uno va aprediendo de los datos.
La idea es ejemplificar el ejercicio de visualización tanto para consumir o generar conocimiento además de presentar la información. Todo esto bajo un contexto de análsis de datos generando/derivando nuevas variables como se verá más adelante.
Setup: paquetes y otras cosas
Datos
Los datos a trabajar corresponden a ingresos de usuarios a estaciones de metros cada media hora. No recuerdo muy bien de donde los obtuve, pero existen y los llevo a cada repo en donde necesito mostrar este ejemplo.
# A tibble: 358,543 × 3
paraderosubida mediahora subidas_laboral_promedio
<chr> <chr> <dbl>
1 ALCANTARA 05:30:00 2.6
2 ALCANTARA 06:00:00 13.2
3 ALCANTARA 06:30:00 53.4
4 ALCANTARA 07:00:00 185.
5 ALCANTARA 07:30:00 364
6 ALCANTARA 08:00:00 546.
7 ALCANTARA 08:30:00 479.
8 ALCANTARA 09:00:00 359
9 ALCANTARA 09:30:00 275.
10 ALCANTARA 10:00:00 230.
# ℹ 358,533 more rows
En el ejemplo estudiamos solamente las estaciones de metro, por lo que eliminamos todo lo que parezca código.
data <- data |>
filter(!str_detect(paraderosubida, "[0-9]+-[0-9]")) |>
filter(paraderosubida != "-")Ahora parseamos mediahora para transformarla en el tipo de dato adecuado.
data <- data |>
mutate(mediahora = readr::parse_guess(mediahora)) |>
filter(hour(mediahora) > 0) Y por último nos aseguramos de que cada estación posea todas las mediahora.
# A tibble: 3,922 × 3
paraderosubida mediahora subidas_laboral_promedio
<chr> <time> <dbl>
1 ALCANTARA 05:30 2.6
2 ALCANTARA 06:00 13.2
3 ALCANTARA 06:30 53.4
4 ALCANTARA 07:00 185.
5 ALCANTARA 07:30 364
6 ALCANTARA 08:00 546.
7 ALCANTARA 08:30 479.
8 ALCANTARA 09:00 359
9 ALCANTARA 09:30 275.
10 ALCANTARA 10:00 230.
# ℹ 3,912 more rows
Lo simple, de menos a más
Visualicemos los datos de una estación:
dplazamaipu <- data |>
filter(paraderosubida == "PLAZA MAIPU")
p0 <- ggplot(dplazamaipu) +
geom_point(aes(subidas_laboral_promedio, mediahora, color = paraderosubida), size = 1.5) +
scale_x_continuous(labels = comma) +
scale_color_viridis_d(guide = "none")
p0Es bueno equivocarse! Vamos por otro intento.
ggplot(dplazamaipu) +
geom_path(aes(subidas_laboral_promedio, mediahora, color = paraderosubida), size = 1.5) +
scale_x_continuous(labels = comma) +
scale_color_viridis_d(guide = "none")Es importante seguir convenciones. En un gráfico done una de las variables es temporal usualmente se mapea al eje X.
ggplot(dplazamaipu) +
geom_path(aes(mediahora, subidas_laboral_promedio, color = paraderosubida), size = 1.5) +
scale_y_continuous(labels = comma) +
scale_color_viridis_d(name = NULL)Buscando patrones
Importante observar patrones o relaciones en los datos.
d1 <- data |>
filter(paraderosubida %in% c("PLAZA MAIPU", "LAGUNA SUR"))
c <- d1 |>
pivot_wider(
names_from = paraderosubida,
values_from = subidas_laboral_promedio
) |>
corrr::correlate(quiet = TRUE) |>
select(2) |>
pull() |>
na.omit() |>
as.numeric()
p1 <- ggplot(d1) +
geom_line(
aes(mediahora, subidas_laboral_promedio,
color = paraderosubida,
group = paraderosubida),
size = 1.2
) +
scale_y_continuous(label = scales::comma) +
scale_color_viridis_d()
p1Correlación
Métrica de asociación lineal. Nos ayuda a cuantificar la asociación lineal.
set.seed(123)
n <- 100
x <- rnorm(n)
e <- rnorm(n)
pc <- tibble(
beta = c(0, 1, 1, -1, -1, 0),
beta2 = c(0, 0, 0, 1, 0, 1),
sd = c(1, 1, 0, 0, 1, 1),
) |>
pmap_df(function(beta = 1, beta2 = 1, sd = 1){
tibble(
x = x,
y = beta * x + beta2 * x^2 + sqrt(sd) * e,
cor = cor(x, y)
)
}) |>
mutate(
cor = round(cor, 3),
cor = str_glue("{cor} ({ percent(cor)})"),
cor = fct_inorder(as.character(cor))
) |>
ggplot(aes(x, y)) +
geom_point(alpha = 0.25) +
geom_smooth(method = "lm", color = "darkred", size = 1.2,
formula = y ~ x, se = FALSE) +
facet_wrap(vars(cor), scales = "free")
pcDe esta forma, con los datos los podemos visualizar de otra forma.
lab_dates <- d1 |>
spread(paraderosubida, subidas_laboral_promedio) |>
pull(mediahora) |>
as_datetime() |>
pretty(6)
lab_dates_lbls <- str_extract(lab_dates, "[0-9]{2}:[0-9]{2}")
p2 <- d1 |>
spread(paraderosubida, subidas_laboral_promedio) |>
mutate(mediahora = as_datetime(mediahora)) |>
ggplot(aes(`LAGUNA SUR`, `PLAZA MAIPU`)) +
geom_point(aes(color = as.numeric(mediahora)), size = 3) +
scale_y_continuous(label = scales::comma) +
scale_x_continuous(label = scales::comma) +
scale_color_viridis_c(name = NULL, breaks = as.numeric(lab_dates), labels = lab_dates_lbls) +
labs(subtitle = str_glue("Correlación { percent(c, , accuracy = 0.01) }"))
p1 | p2 Otro par de estaciones
d1 <- data |>
filter(paraderosubida %in% c("UNIVERSIDAD DE CHILE", "PLAZA DE PUENTE ALTO"))
c <- d1 |>
pivot_wider(
names_from = paraderosubida,
values_from = subidas_laboral_promedio
) |>
corrr::correlate(quiet = TRUE) |>
select(2) |>
pull() |>
na.omit() |>
as.numeric()
p1 <- ggplot(d1) +
geom_line(
aes(mediahora, subidas_laboral_promedio, color = paraderosubida, group = paraderosubida),
size = 1.2
) +
scale_y_continuous(label = scales::comma) +
scale_color_viridis_d(name = NULL)
p2 <- d1 |>
spread(paraderosubida, subidas_laboral_promedio) |>
mutate(mediahora = as_datetime(mediahora)) |>
ggplot(aes(`PLAZA DE PUENTE ALTO`, `UNIVERSIDAD DE CHILE`)) +
geom_point(aes(color = as.numeric(mediahora)), size = 3) +
scale_y_continuous(label = scales::comma) +
scale_x_continuous(label = scales::comma) +
scale_color_viridis_c(name = NULL, breaks = as.numeric(lab_dates), labels = lab_dates_lbls) +
labs(subtitle = str_glue("Correlación { percent(c, , accuracy = 0.01) }"))
p1 | p2 Todas las correlaciones
Calculamos con 106 estaciones 11,130 correlaciones. Visualización vía heatmap.
ggplot(dcor) +
geom_tile(aes(item1, item2, fill = correlation)) +
scale_fill_viridis_c(limits = c(-1, 1), breaks = seq(-1, 1, length.out = 5), labels = percent) +
theme(
axis.text.y = element_text(size = 5),
axis.text.x = element_text(size = 5, angle = 90, hjust = 1),
legend.position = "right",
legend.key.width = unit(0.5, "cm")
) +
labs(x = NULL, y = NULL)El orden, en este caso, importa.
M <- data |>
spread(paraderosubida, subidas_laboral_promedio) |>
select(-1) |>
mutate_all(replace_na, 0) |>
cor()
order <- corrplot::corrMatOrder(M, order = "hclust")
M <- M[order, order]
lvls <- colnames(M)
dcor <- dcor |>
mutate(across(where(is.character), ~ factor(.x, levels = lvls)))
pcors <- ggplot(dcor) +
geom_tile(aes(item1, item2, fill = correlation)) +
scale_fill_viridis_c(limits = c(-1, 1), breaks = seq(-1, 1, length.out = 5), labels = percent) +
theme(
axis.text.y = element_text(size = 5),
axis.text.x = element_text(size = 5, angle = 90, hjust = 1),
legend.position = "right",
legend.key.width = unit(0.5, "cm")
) +
labs(x = NULL, y = NULL) +
theme(
panel.grid.major = element_blank(),
panel.grid.minor = element_blank()
)
pcorsRedes y comunidades
Selecciones las correlaciones más altas.
ncors <- 250
dcorf <- dcor |>
filter(as.character(item1) < as.character(item2)) |>
arrange(desc(correlation)) |>
mutate(w = correlation*correlation) |>
head(ncors)Considerando las 250 correlaciones más altas realizamos una red donde el peso del vértice es la correlación y el tamaño es la cantidad de subidas.
Código
g <- graph_from_data_frame(dcorf, directed = FALSE)
E(g)$weight <- dcorf$w
wc <- cluster_fast_greedy(g)
nc <- length(unique(membership(wc)))
dvert <- tibble(paraderosubida = V(g)$name) |>
mutate(comm = as.numeric(membership(wc))) |>
left_join(
data |>
group_by(paraderosubida) |>
summarise(n = sum(subidas_laboral_promedio)),
by = "paraderosubida"
) |>
left_join(
data |>
group_by(paraderosubida) |>
summarise(tend = cor(seq(1, n()), subidas_laboral_promedio)),
by = "paraderosubida"
) |>
ungroup()
# dvert
V(g)$label <- dvert$paraderosubida
V(g)$size <- dvert$n
V(g)$subidas_totales_miles <- round(dvert$n/1000, 2)
V(g)$comm <- as.numeric(membership(wc))
V(g)$tendencia <- round(dvert$tend, 2)
V(g)$color <- dvert$comm
set.seed(123)
dfnet <- ggnetwork(g)
dfnet2 <- dfnet |>
as.matrix() |>
as.data.frame() |>
as_tibble() |>
select(x, y, name, weight, size, color) |>
mutate_all(as.character) |>
mutate_at(vars(x, y, weight, size), as.numeric) |>
filter(is.na(weight))
pnet <- ggplot(dfnet) +
geom_edges(
aes(-x, -y, size = width, color = factor(comm), xend = -xend, yend = -yend),
color = "gray50", size = 1, alpha = 0.25
) +
geom_point(
aes(-x, -y, size = size, color = factor(comm), fill = factor(comm)), shape = 21
) +
ggrepel::geom_text_repel(
aes(-x, -y, label = name), size = 2,
data = dfnet2, color = "#666666",
force = 10,
family = "main_font"
) +
scale_fill_viridis_d(name = "Comunidad") +
# scale_color_viridis_d() +
scale_size(guide = "none") +
theme(
panel.grid.major = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
legend.position = "right"
) +
guides(
color = guide_legend(override.aes = list(size = 5)),
fill = guide_legend(override.aes = list(size = 5))
) +
labs(
x = NULL,
y = NULL,
size = "Subidas",
color = "Comunidad"
) +
theme(
panel.grid.major = element_blank(),
panel.grid.minor = element_blank()
)
pnetReducción de Dimensionalidad
Hasta ahora hemos usado solamente correlaciones que miden asociacion lineal y no es un indicador necesariamente robusto para usarlo como métrica de distancia.
Usaremos todos los datos por estacion y usaremos UMAP, un algoritmo para resumir toda la información en 2 columnas/variables. Lleva de una tabla con muchas columnas por observación a 2 columnas:
De esto:
# A tibble: 8 × 8
paraderosubida `05:30:00` `06:00:00` `06:30:00` `07:00:00` `07:30:00`
<chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 ALCANTARA 2.6 13.2 53.4 185. 364
2 BAQUEDANO L1 86.6 125. 208. 398. 522.
3 BAQUEDANO L5 1.4 7.2 26.2 95.6 200.
4 BARRANCAS 37 195. 406. 737. 800.
5 BELLAS ARTES 6.6 37 92.2 178. 282.
6 BELLAVISTA DE LA FLORI… 158. 298. 566. 1114. 1163.
7 BLANQUEADO 35.8 200. 411. 646. 737.
8 CAL Y CANTO 103 575. 1281 2309 2640.
# ℹ 2 more variables: `08:00:00` <dbl>, `08:30:00` <dbl>
A esto:
library(uwot)
set.seed(123)
um <- umap(datas, verbose = TRUE, n_threads = 3, n_neighbors = 20)
dumap <- as.data.frame(um) |>
as_tibble() |>
mutate(paraderosubida = pull(datas, paraderosubida)) |>
select(paraderosubida, everything())
dumap# A tibble: 106 × 3
paraderosubida V1 V2
<chr> <dbl> <dbl>
1 ALCANTARA 0.784 0.619
2 BAQUEDANO L1 -0.0766 2.33
3 BAQUEDANO L5 1.07 0.171
4 BARRANCAS 1.81 -2.34
5 BELLAS ARTES 0.796 0.594
6 BELLAVISTA DE LA FLORIDA -1.91 0.356
7 BLANQUEADO 1.63 -3.01
8 CAL Y CANTO -0.266 3.78
9 CAMINO AGRICOLA 0.262 0.229
10 CARLOS VALDOVINOS 0.788 -0.988
# ℹ 96 more rows
De esta forma:
set.seed(1234)
pumap <- ggplot(dumap) +
geom_point(aes(V1, V2), alpha = 0.3) +
ggrepel::geom_text_repel(
aes(V1, V2, label = paraderosubida),
data = dumap |> sample_n(30),
size = 3,
force = 10
)
pumapClustering
K-means
De vez en cuando es relevante agrupar información/perfilar. Diagramas de Voronoi.
withins <- map_dbl(1:15, function(k = 4){
km <- kmeans(
dumap |> select(-paraderosubida),
centers = k,
nstart = 50,
iter.max = 150
)
km$tot.withinss
})
# plot(withins)
km <- kmeans(
dumap |> select(-paraderosubida),
centers = 4,
nstart = 50,
iter.max = 150
)
dumap <- dumap |>
mutate(cluster = as.character(km$cluster))
dcenters <- km$centers |>
as.data.frame() |>
as_tibble() |>
mutate(cluster = as.character(row_number()))
# xmin, xmax, ymin, ymax.
bnd <- c(-4, 4, -4, 4)
set.seed(1234)
pumapkm <- ggplot(dumap, aes(V1, V2, fill = cluster, group = -1)) +
geom_voronoi_tile(data = dcenters, alpha = 0.2, bound = bnd) +
geom_voronoi_segment(data = dcenters, color = "gray70", bound = bnd) +
geom_point(aes(V1, V2, fill = cluster), alpha = 0.3) +
ggrepel::geom_text_repel(
aes(V1, V2, label = paraderosubida),
data = dumap |> sample_n(30),
size = 3,
force = 10
) +
scale_fill_viridis_d() +
xlim(c(-4, 4)) + ylim(c(-4, 4)) +
theme(legend.position = "none")
pumapkm¿Por qué 4 grupos? El algoritmo para encontrar grupos usado fue K-means. Uno usualmente da el número de grupos y determina que observación queda en que grupo. Una regla para decidir el número de cluster es realizando el proceso varias veces con distintas cantidad de centros.
dkm <- tibble(
tot.withinss = withins,
cluster = seq(length(withins))
)
pkm <- ggplot(dkm, aes(cluster, tot.withinss, fill = "1", color = "1")) +
geom_line(size = 2) +
geom_point(size = 3, shape = 21, color = "white") +
scale_x_continuous(breaks = dkm$cluster) +
labs(
y = "Suma de los cuadrados dentro de cada grupo",
x = "Grupos"
) +
scale_fill_viridis_d() +
scale_color_viridis_d() +
theme(legend.position = "none")
pkmClustering Jerárquico
Dendograma visualiza la forma de agrupación a medida que aumento distancia.
library(ggdendro)
dhclust <- dumap |>
column_to_rownames("paraderosubida") |>
select(V1, V2)
hc <- hclust(dist(dhclust), "ave") # heirarchal clustering
dendr <- dendro_data(hc, type="rectangle") # convert for ggplot
clust <- cutree(hc, k = 4) # find 4 clusters
clust.df <- data.frame(label = names(clust), cluster = factor(clust))
dendr[["labels"]] <- merge(dendr[["labels"]], clust.df, by = "label")
pdend <- ggplot() +
geom_segment(
data = segment(dendr),
aes(x = x, y = y, xend = xend, yend = yend)
) +
geom_text(
data = label(dendr),
aes(x, y, label = label, hjust = 1, color = cluster),
size = 1.8
) +
coord_flip() +
scale_color_viridis_d() +
# scale_y_continuous(limits = c(-0.10, NA)) +
# scale_y_reverse(expand=c(0.2, 0)) +
theme(
axis.line.y = element_blank(),
axis.ticks.y = element_blank(),
axis.text.y = element_blank(),
axis.title.y = element_blank(),
panel.grid.major.y = element_blank(),
panel.grid = element_blank(),
legend.position = "right"
) +
guides(color = guide_legend(override.aes = list(size = 5)))
pdendÁrbol filogenético
Otra visualización con los mismos datos del dendograma. Árbol filogenético.
L <- get("last_plot.phylo", envir = .PlotPhyloEnv)
dedges <- tibble(x = L$xx, y = L$yy) |>
mutate(id = row_number())
dedges2 <- as.data.frame(L$edge) |>
as_tibble() |>
left_join(dedges, by = c("V1" = "id")) |>
left_join(dedges, by = c("V2" = "id"), suffix = c("", "_end"))
dnodes <- dedges |>
head(length(clust)) |>
mutate(
paraderosubida = names(clust),
cluster = as.character(clust)
)pphylo <- ggplot(dedges2) +
geom_segment(
aes(x = x, y = y, xend = x_end, yend = y_end, group = -1L),
color = "gray70",
size = .9
) +
ggrepel::geom_text_repel(
aes(x, y, label = paraderosubida),
data = dnodes,
size = 1.5,
max.overlaps = 1000,
segment.colour = "gray80"
) +
geom_point(
aes(x, y, fill = cluster),
data = dnodes,
shape = 21, color = "white", size = 4
) +
scale_color_viridis_d() +
scale_fill_viridis_d() +
theme(
panel.grid.major = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
legend.position = "right"
) +
labs(x = NULL, y = NULL) +
scale_y_continuous(expand = expansion(mult = 0.25)) +
scale_x_continuous(expand = expansion(mult = 0.25))
pphylo¿Significan algo los cluster generados?
Revisamos nuestro primer gráfico para cada estación y separados por grupo.
dataf <- data |>
left_join(dumap |> select(paraderosubida, cluster), by = "paraderosubida")
pclus <- ggplot(dataf, aes(mediahora, subidas_laboral_promedio)) +
geom_line(aes(group = paraderosubida), alpha = 0.8, size = 0.8, color = "gray90") +
geom_smooth(
aes(color = cluster),
se = FALSE, size = 2,
method = 'gam',
formula = y ~ s(x, bs = "cs")
) +
scale_color_viridis_d() +
facet_wrap(vars(cluster)) +
scale_y_continuous(labels = comma)
pclusSe observan comportamientos distintos para cada grupo.
Contextualizar
Vamos con información geofráfica.
Código
routes <- read_csv(fs::path(ruta_datos, "routes.txt"))
trips <- read_csv(fs::path(ruta_datos, "trips.txt"))
stops <- read_csv(fs::path(ruta_datos, "stops.txt"),
col_types = cols(stop_url = col_character()))
shapes <- data.table::fread(fs::path(ruta_datos, "shapes.csv.gz"))
shapes <- as_tibble(shapes)
stops_metro <- stops |>
filter(!grepl("\\d", stop_id)) |>
mutate(stop_url = basename(stop_url))
routes_metro <- routes |>
filter(grepl("^L\\d",route_id))
shapes_metro <- routes |>
filter(grepl("^L\\d",route_id)) %>%
semi_join(trips, ., by = "route_id") %>%
semi_join(shapes, ., by = "shape_id") |>
### IMPORTANTE
filter(str_detect(shape_id, "-I")) |>
mutate(shape_id2 = str_replace(shape_id, "-I", ""))
colors_metro <- distinct(shapes, shape_id) |>
left_join(distinct(trips, shape_id, route_id), by = "shape_id") |>
left_join(distinct(routes, route_id, route_color), by = "route_id") |>
semi_join(shapes_metro, by = "shape_id") |>
mutate(route_color = paste0("#", route_color))
str_to_id2 <- function(x) {
x |>
as.character() |>
str_trim() |>
str_to_lower() |>
str_replace_all("\\\\s+", "_") |>
str_replace_all("\\\\\\\\|/", "_") |>
str_replace_all("\\\\[|\\\\]", "_") |>
str_replace_all("_+", "_") |>
str_replace_all("_$|^_", "") |>
str_replace_all("á", "a") |>
str_replace_all("é", "e") |>
str_replace_all("í", "i") |>
str_replace_all("ó", "o") |>
str_replace_all("ú", "u") |>
str_replace_all("ñ", "n") |>
str_replace_all("`", "") |>
str_replace_all("_de_", "_")
}
dumap <- mutate(dumap, id = str_to_id2(paraderosubida))
data4 <- dataf |>
group_by(paraderosubida, cluster) |>
summarise(median = median(subidas_laboral_promedio), .groups = "drop") |>
ungroup() |>
mutate(id = str_to_id2(paraderosubida))
stops_metro_data <- stops_metro |>
mutate(id = str_to_id2(stop_name)) |>
left_join(data4, by = "id") |>
filter(!is.na(cluster))
rm(shapes, routes, stops, trips, data4)
colors_metro_manual <- colors_metro |>
select(name = route_id, value = route_color) |>
deframe()
pmetro <- ggplot() +
geom_path(
data = shapes_metro,
aes(shape_pt_lon, shape_pt_lat, color = shape_id2),
size = 2
) +
geom_point(
data = stops_metro_data,
aes(stop_lon, stop_lat, size = log(median), fill = cluster),
shape = 21, color = "white"
) +
scale_color_manual(name = "Línea", values = colors_metro_manual) +
scale_size(guide = "none") +
scale_fill_viridis_d(name = "Clúster") +
coord_equal() +
guides(
color = guide_legend(override.aes = list(size = 5)),
fill = guide_legend(override.aes = list(size = 5))
) +
theme(
panel.grid.major = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
legend.position = "right"
) +
facet_wrap(vars(cluster))
pmetroU otra versión utilizando chilemapas.
pmetro2 <- ggplot() +
geom_sf(
data = chilemapas::mapa_zonas |> filter(codigo_region == "13"),
aes(geometry = geometry),
alpha = 0.5,
color = "white"
) +
geom_path(
data = shapes_metro,
aes(shape_pt_lon, shape_pt_lat, color = shape_id2),
size = 2
) +
geom_point(
data = stops_metro_data,
aes(stop_lon, stop_lat, size = log(median), fill = cluster),
shape = 21, color = "white"
) +
scale_color_manual(name = "Línea", values = colors_metro_manual) +
scale_size(guide = "none") +
scale_fill_viridis_d(name = "Clúster") +
coord_sf(xlim = c(-70.8, -70.5), ylim = c(-33.3, -33.65)) +
guides(
color = guide_legend(override.aes = list(size = 5)),
fill = guide_legend(override.aes = list(size = 5))
) +
theme(legend.position = "right")
pmetro2Resumiendo
Mostrar código del resumen
# https://stackoverflow.com/a/46221054/829971
remove_geom <- function(ggplot2_object, geom_type) {
# Delete layers that match the requested type.
layers <- lapply(ggplot2_object$layers, function(x) {
if (class(x$geom)[1] == geom_type) {
NULL
} else {
x
}
})
# Delete the unwanted layers.
layers <- layers[!sapply(layers, is.null)]
ggplot2_object$layers <- layers
ggplot2_object
}
pdend_summary <- remove_geom(pdend, "GeomText")
list(p0, p1, p2, pcors, pnet, pumapkm, pkm,
pdend_summary, pphylo, pclus, pmetro, pmetro2) |>
map(function(p){
pb <- ggplot_build(p)
nfacets <- pb$layout$layout$PANEL |> length()
p <- remove_geom(p, "GeomTextRepel") +
theme(
panel.grid.major = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
legend.position = "none",
strip.text = element_blank(),
panel.spacing = unit(0.1, "lines")
) +
labs(x = NULL, y = NULL, title = NULL, subtitle = NULL)
if(!class(pb$layout$coord)[1] == "CoordSf") {
p <- p + coord_cartesian()
}
if(nfacets != 1){
p <- p +
# ggforce::facet_grid_paginate(vars(cluster), ncol = 1, nrow = 1, page = 1)
facet_wrap(vars(cluster), ncol = 2, scales = "free")
}
p
}) |>
reduce(`+`)Quitar títulos, ejes o leyendas deja más espacio para el panel y puede hacer que el gráfico parezca más grande, pero esa limpieza solo ayuda cuando esos elementos no son necesarios para interpretar la figura. En series, dispersiones y barras, los ejes suelen ser indispensables para leer magnitudes y unidades; en matrices y mapas de calor, además identifican las categorías comparadas.
En mapas temáticos, las coordenadas de longitud y latitud normalmente aportan poco si el objetivo es reconocer patrones espaciales, aunque una escala gráfica sí es útil cuando importan las distancias. En redes y proyecciones como UMAP, las coordenadas suelen ser relativas y sus valores numéricos pueden omitirse. En un PCA, en cambio, los componentes y la varianza explicada sí tienen interpretación. Un título dentro del gráfico también puede omitirse cuando el encabezado y el texto del artículo ya entregan el contexto necesario.