Р: Раскрасить ggplot в два цвета, чтобы обозначить ничью?

У меня есть карта Северной Каролины. Я случайным образом присвоил каждому округу разные цвета (так, чтобы в каждом округе был только 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)")

Я думаю, что, учитывая количество округов, за всем, что вы делаете, будет сложно следить. Однако, возможно, изменение непрозрачности сработает?

Kat 18.08.2024 20:21

@ Кэт: Большое спасибо за ответ! Есть ли у вас идеи, как я мог бы это сделать? Очень хотелось бы увидеть ваш ответ...

stats_noob 18.08.2024 20:39

Какие-то идеи? Coolbutuseless.github.io/package/ggpattern/articles/…

stats_noob 18.08.2024 20:41

@ Кэт: Завтра я планирую назначить награду за этот вопрос.

stats_noob 19.08.2024 15:10

Выбор подходящих элементов для любого картографического представления должен основываться на данных. Однако, исходя из вашего примера, не совсем ясно, каким будет ваш вариант использования в «реальном мире» (см. комментарий Аллана). Вероятно, существует гораздо более подходящий метод представления ваших данных, но без дальнейшего контекста это будет всего лишь предположение. Не могли бы вы отредактировать свой вопрос и объяснить немного больше о ваших фактических данных?

L Tyrone 19.08.2024 23:45
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Эта небольшая функция смешает вектор цветов в один цвет:

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")

@ Аллан Кэмерон: большое спасибо за ответ!

stats_noob 19.08.2024 15:10

Как вы думаете, ggpattern может быть хорошим выбором?

stats_noob 19.08.2024 15:11

@stats_noob, честно говоря, нет. ggpattern отлично подходит для некоторых случаев использования, но с точки зрения данных изображение уже сложное и трудно интерпретируемое. Я думаю, что узорчатая заливка сделает ситуацию скорее хуже, чем лучше. Возможно, знание реального варианта использования позволит лучше порекомендовать, что здесь будет хорошо работать.

Allan Cameron 19.08.2024 17:49
Ответ принят как подходящий

Другой вариант — градиентировать цвета в общих цветовых состояниях, но его все равно сложно читать или интерпретировать.

Вам нужно будет добавить библиотеку 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

Подход, который я использую здесь, немного неуклюж, и обработка графика перед рендерингом занимает немало времени.

Шаг 1:

Постройте сетки для градиентов в состояниях с более чем одним цветом.

Это получено из другого ответа ТАК.

В этой пользовательской функции первая функция включает аргумент 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

Шаг 2:

Создайте градиентные цветовые шкалы для состояний с более чем одним цветом.

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))))
}

Шаг 3:

Определите округа, которым присвоено более одного цвета.

# which counties have more than one fave?
these <- filter(my_df, is.na(favorite_color)) %>% select(NAME) %>% 
  arrange(NAME) %>% unlist()

Шаг 4:

Постройте пустой участок для строительства. В конце вы добавите границы к 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")

Шаг 5:

Добавьте градиент округа для каждого применимого округа. Выполнение этого кода займет горячую минуту; также потребуется некоторое время для рендеринга сюжета.

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()
}

Шаг 6:

Добавьте на график оставшиеся одноцветные округа и границы.

(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

Другие вопросы по теме