У меня есть карта Северной Каролины. Я случайным образом присвоил каждому округу разные цвета (так, чтобы в каждом округе был только 1 цвет) и создал следующую карту (R: Использование функции ПОИСКПОЗ в качестве СОЕДИНЕНИЯ?):
library(sf)
library(dplyr)
library(ggplot2)
nc <- st_read(system.file("gpkg/nc.gpkg", package = "sf"), quiet = TRUE) %>%
st_transform(st_crs(4326)) %>%
st_cast("POLYGON")
set.seed(123)
colors <- c("red", "blue", "green", "yellow", "purple", "orange")
favorite_color <- sample(colors, nrow(nc), replace = TRUE)
my_df <- data.frame(NAME = nc$NAME, favorite_color)
nc_merged <- merge(nc, my_df, by = "NAME", all.x = TRUE)
ggplot(data = nc_merged) +
geom_sf(aes(fill = favorite_color)) +
scale_fill_manual(values = colors) +
theme_minimal() +
labs(title = "Favorite Color by County",
fill = "Favorite Color")
Затем я создал карту, на которой у округа может быть более одного любимого цвета. В данном случае эти округа остаются белыми:
set.seed(123)
colors <- c("red", "blue", "green", "yellow", "purple", "orange")
favorite_color <- replicate(nrow(nc), {
sample_size <- sample(1:3, 1)
sample(colors, sample_size, replace = FALSE)
})
my_df <- data.frame(NAME = nc$NAME, favorite_color = sapply(favorite_color, function(x) paste(x, collapse = ", ")))
my_df$favorite_color[sapply(favorite_color, length) > 1] <- NA
nc_merged <- merge(nc, my_df, by = "NAME", all.x = TRUE)
ggplot(data = nc_merged) +
geom_sf(aes(fill = favorite_color)) +
scale_fill_manual(values = c(colors, "white"), na.value = "white") +
theme_minimal() +
labs(title = "Favorite Color by County (with Ties)",
fill = "Favorite Color")
Мой вопрос: В случае, если в округах цвета связаны (например, 2 ничьи, 3 ничьи), можно ли каким-то образом смешать эти цвета вместе, чтобы получить новый цвет? Т.е. где нет связей, раскрасьте в один цвет – а там, где есть связи, смешайте цвета между собой?
Я попытался сделать это с помощью ggpattern:
set.seed(123)
colors <- c("red", "blue", "green", "yellow", "purple", "orange")
favorite_color <- replicate(nrow(nc), {
sample_size <- sample(1:3, 1)
sample(colors, sample_size, replace = FALSE)
})
my_df <- data.frame(NAME = nc$NAME,
favorite_color = sapply(favorite_color, function(x) paste(x, collapse = ", ")))
unique_patterns <- unique(my_df$favorite_color)
pattern_colors <- setNames(rainbow(length(unique_patterns)), unique_patterns)
nc_merged <- merge(nc, my_df, by = "NAME", all.x = TRUE)
ggplot(data = nc_merged) +
geom_sf_pattern(aes(fill = favorite_color, pattern_fill = favorite_color),
pattern = "stripe", color = "black", size = 0.2) +
scale_fill_manual(values = pattern_colors) +
scale_pattern_fill_manual(values = pattern_colors) +
theme_minimal() +
labs(title = "Favorite Color by County (with Patterns for Ties)",
fill = "Favorite Color")
Но мне кажется, что это очень трудно читать. Может кто-нибудь подсказать, что делать?
Спасибо!
Обновлено: Альтернативный способ, все еще не идеальный...
library(ggpattern)
set.seed(123)
colors <- c("red", "blue", "green", "yellow", "purple", "orange")
favorite_color <- replicate(nrow(nc), {
sample_size <- sample(1:3, 1)
sample(colors, sample_size, replace = FALSE)
})
my_df <- data.frame(
NAME = nc$NAME,
stringsAsFactors = FALSE
)
for(i in 1:3) {
my_df[[paste0("color", i)]] <- sapply(favorite_color, function(x) if (length(x) >= i) x[i] else NA)
}
my_df$color_count <- sapply(favorite_color, length)
nc_merged <- merge(nc, my_df, by = "NAME", all.x = TRUE)
nc_merged$pattern <- ifelse(nc_merged$color_count == 2, "stripe", "crosshatch")
ggplot() +
# Layer for counties with a single color
geom_sf(data = subset(nc_merged, color_count == 1),
aes(fill = color1),
color = "white") +
# Layer for counties with two colors
geom_sf_pattern(data = subset(nc_merged, color_count == 2),
aes(fill = color1,
pattern_fill = color2),
pattern = "stripe",
pattern_angle = 45,
color = "white",
pattern_density = 0.5,
pattern_spacing = 0.01,
pattern_key_scale_factor = 0.6) +
# Layer for counties with three colors
geom_sf_pattern(data = subset(nc_merged, color_count == 3),
aes(fill = color1,
pattern_fill = color2),
pattern = "crosshatch",
pattern_angle = 45,
color = "white",
pattern_density = 0.5,
pattern_spacing = 0.01,
pattern_key_scale_factor = 0.6) +
geom_sf_pattern(data = subset(nc_merged, color_count == 3),
aes(pattern_fill = color3),
pattern = "crosshatch",
pattern_angle = 135,
color = NA,
pattern_density = 0.5,
pattern_spacing = 0.01) +
scale_fill_identity() +
theme_minimal() +
labs(title = "Favorite Color by County (with Patterns for Ties)")
@ Кэт: Большое спасибо за ответ! Есть ли у вас идеи, как я мог бы это сделать? Очень хотелось бы увидеть ваш ответ...
Какие-то идеи? Coolbutuseless.github.io/package/ggpattern/articles/…
@ Кэт: Завтра я планирую назначить награду за этот вопрос.
Выбор подходящих элементов для любого картографического представления должен основываться на данных. Однако, исходя из вашего примера, не совсем ясно, каким будет ваш вариант использования в «реальном мире» (см. комментарий Аллана). Вероятно, существует гораздо более подходящий метод представления ваших данных, но без дальнейшего контекста это будет всего лишь предположение. Не могли бы вы отредактировать свой вопрос и объяснить немного больше о ваших фактических данных?





Эта небольшая функция смешает вектор цветов в один цвет:
mixer <- function(colors) {
paste0("#",
col2rgb(colors) |>
rowMeans() |>
round() |>
as.raw() |>
as.character() |>
paste0(collapse = ""))
}
Итак, если вы возьмете фрейм данных со связанными цветами, вы можете сделать:
my_df <- my_df %>%
summarise(actual_color = mixer(favorite_color),
favorite_color = paste(favorite_color, collapse = " + "),
.by = NAME)
nc_merged <- merge(nc, my_df, by = "NAME", all.x = TRUE)
ggplot(data = nc_merged) +
geom_sf(aes(fill = favorite_color)) +
scale_fill_manual(values = setNames(unique(my_df[-1])[[1]],
unique(my_df[-1])[[2]])) +
theme_minimal() +
labs(title = "Favorite Color by County",
fill = "Favorite Color")
@ Аллан Кэмерон: большое спасибо за ответ!
Как вы думаете, ggpattern может быть хорошим выбором?
@stats_noob, честно говоря, нет. ggpattern отлично подходит для некоторых случаев использования, но с точки зрения данных изображение уже сложное и трудно интерпретируемое. Я думаю, что узорчатая заливка сделает ситуацию скорее хуже, чем лучше. Возможно, знание реального варианта использования позволит лучше порекомендовать, что здесь будет хорошо работать.
Другой вариант — градиентировать цвета в общих цветовых состояниях, но его все равно сложно читать или интерпретировать.
Вам нужно будет добавить библиотеку ggnewscale
Вы заметили, что в вашей легенде красный отмечен синим? (Ни один из цветов не выровнен правильно.) Это проблема сортировки (сортировка цветов перед назначением их
values). Вы увидите это измененное в моем коде.
Когда были созданы favorite_color и, следовательно, my_df, любые округа с разделенной геометрией (например, Крейвен, Картерет и т. д.) были перечислены более одного раза. Эти округа имеют противоречивые цветовые обозначения. У вас 108 строк в my_df, но в Северной Каролине всего 100 округов.
Вот как я решил эту проблему в вашем коде для этих двух кадров:
favorite_color <- replicate(length(unique(nc$NAME)), { # note `length...`
sample_size <- sample(1:3, 1)
sample(colors, sample_size, replace = FALSE)
})
my_df <- data.frame(NAME = unique(nc$NAME), # note `unique...`
favorite_color = sapply(
favorite_color, function(x) paste(x, collapse = ", ")))
Поскольку удаление цветов с помощью NA не сработает для этого кода, я добавил еще один столбец, в котором сохраняются исходные данные цвета, прежде чем они будут заменены на NA в вашем коде.
my_df$fc2 <- my_df$favorite_color # keep full color data
Подход, который я использую здесь, немного неуклюж, и обработка графика перед рендерингом занимает немало времени.
Постройте сетки для градиентов в состояниях с более чем одним цветом.
Это получено из другого ответа ТАК.
В этой пользовательской функции первая функция включает аргумент cellsize. Чем меньше вы сделаете эти значения, тем лучше будет выглядеть градиент. Однако чем меньше вы сделаете эти значения, тем больше времени потребуется для визуализации графика.
gradPoly <- function(mp) { # create grid squares within shape to gradient
my_mp <- st_make_grid(mp, cellsize = c(.05, .05))
long <- st_coordinates(st_centroid(my_mp, warn = F))[, 1]
return(st_sf(longitude = long, geometry = my_mp) %>%
st_intersection(st_geometry(mp)))
} # this code will render a warning in the console
Создайте градиентные цветовые шкалы для состояний с более чем одним цветом.
scaler <- function(mp, plt) {
wds = strsplit(mp$fc2, ', ')[[1]] # extract colors assigned
val = length(wds) # quantity of colors assigned
ifelse(val < 3, # 2 colors or 3 colors in the gradient
return(plt + scale_fill_gradient(low = wds[1], high = wds[2], guide = "none")),
return(plt + scale_fill_gradient2(low = wds[1], mid = wds[2], high = wds[3], guide = "none",
midpoint = median(gradPoly(mp)$longitude))))
}
Определите округа, которым присвоено более одного цвета.
# which counties have more than one fave?
these <- filter(my_df, is.na(favorite_color)) %>% select(NAME) %>%
arrange(NAME) %>% unlist()
Постройте пустой участок для строительства. В конце вы добавите границы к geom_sf, так что это будет верхний слой.
plt <- ggplot(data = nc_merged) + # essentially the code from the question
theme_minimal() +
labs(title = "Favorite Color by County (with Ties)",
fill = "Favorite Color")
Добавьте градиент округа для каждого применимого округа. Выполнение этого кода займет горячую минуту; также потребуется некоторое время для рендеринга сюжета.
for(j in unique(these)) { # for each county with more than one favorite
dta <- filter(nc_merged, NAME == j) # extract county data # more than one color
plt <- plt + new_scale_fill() + # add to existing plot
geom_sf(data = gradPoly(dta), # call the function gradPoly()
aes(fill = longitude), color = NA) # build gradient grid
plt <- scaler(dta, plt) # add gradient for grid with scaler()
}
Добавьте на график оставшиеся одноцветные округа и границы.
(plt <- plt + new_scale_fill() + new_scale_color() +
geom_sf(aes(fill = favorite_color), color = "black") + # from question
scale_fill_manual(values = sort(colors), na.value = NA)) # values modified
График (ячейки сетки 0,05 x 0,05) (1,08 минуты на рендеринг)
Чтобы дать вам представление о том, что дает изменение размера ячейки сетки, вот тот же график, но с cellsize = c(.01, .01) (1,9 минуты на рендеринг).
(проще скопировать + вставить)
library(sf)
library(dplyr)
library(ggplot2)
library(ggnewscale)
# code from question
nc <- st_read(system.file("gpkg/nc.gpkg", package = "sf"), quiet = TRUE) %>%
st_transform(st_crs(4326)) %>%
st_cast("POLYGON")
set.seed(123)
colors <- c("red", "blue", "green", "yellow", "purple", "orange")
favorite_color <- replicate(length(unique(nc$NAME)), { # <- I ADDED: unique NAME only
sample_size <- sample(1:3, 1)
sample(colors, sample_size, replace = FALSE)
})
my_df <- data.frame(
NAME = unique(nc$NAME), # <---- I ADDED: unique NAME only
favorite_color = sapply(favorite_color,
function(x) paste(x, collapse = ", ")))
my_df$fc2 <- my_df$favorite_color # <---- I ADDED: keep full color data
my_df$favorite_color[sapply(favorite_color, length) > 1] <- NA
nc_merged <- merge(nc, my_df, by = "NAME", all.x = TRUE)
gradPoly <- function(mp) { # create grid squares for gradient
my_mp <- st_make_grid(mp, cellsize = c(.05, .05))
long <- st_coordinates(st_centroid(my_mp, warn = F))[, 1]
return(st_sf(longitude = long, geometry = my_mp) %>%
st_intersection(st_geometry(mp)))
}
scaler <- function(mp, plt) {
wds = strsplit(mp$fc2, ', ')[[1]] # extract colors assigned
val = length(wds) # quantity of colors assigned
ifelse(val < 3, # 2 colors or 3 colors in the gradient
return(plt + scale_fill_gradient(low = wds[1], high = wds[2], guide = "none")),
return(plt + scale_fill_gradient2(low = wds[1], mid = wds[2], high = wds[3], guide = "none",
midpoint = median(gradPoly(mp)$longitude))))
}
# which counties have more than one fave?
these <- filter(my_df, is.na(favorite_color)) %>% select(NAME) %>%
arrange(NAME) %>% unlist()
plt <- ggplot(data = nc_merged) + # essentially the code from the question
theme_minimal() +
labs(title = "Favorite Color by County (with Ties)",
fill = "Favorite Color")
for(j in unique(these)) { # for each county with more than one favorite
dta <- filter(nc_merged, NAME == j) # extract county data # more than one color
plt <- plt + new_scale_fill() + # add to existing plot
geom_sf(data = gradPoly(dta), # call the function gradPoly()
aes(fill = longitude), color = NA) # build gradient grid
plt <- scaler(dta, plt) # add gradient for grid with scaler()
}
(plt <- plt + new_scale_fill() + new_scale_color() +
geom_sf(aes(fill = favorite_color), color = "black") + # from question
scale_fill_manual(values = sort(colors), na.value = NA)) # values modified
Я думаю, что, учитывая количество округов, за всем, что вы делаете, будет сложно следить. Однако, возможно, изменение непрозрачности сработает?