I'm trying to create a map in R using ggplot2 and the newggscale packages, where I show proportional symbols for both urban and rural locations, with varying color gradients depicting the size of the proportional symbol. My goal is to have:
Currently, I'm facing an issue when using new_scale_color() or new_scale_fill() to manage multiple color gradients. Instead of showing just one legend for the proportional symbols and count, I'm getting that plus two DMA legends and no propoer color gradient for the urban and rural locations.
Here's what I've tried:
ok_cities_correct_clean <- ok_cities_correct_clean %>%
mutate(
color_gradient = case_when(
# For Urban cities, custom color ranges based on COUNT
CLASSIF == "Urban" & COUNT <= 10 ~ "lightblue", # Urban: Counts 1-10
CLASSIF == "Urban" & COUNT <= 20 ~ "lightskyblue", # Urban: Counts 10-20
CLASSIF == "Urban" & COUNT <= 30 ~ "dodgerblue", # Urban: Counts 20-30
CLASSIF == "Urban" & COUNT > 30 ~ "darkblue", # Urban: Counts 30+
# For Rural cities, custom color ranges based on COUNT
CLASSIF == "Rural" & COUNT <= 10 ~ "lightpink", # Rural: Counts 1-10
CLASSIF == "Rural" & COUNT <= 20 ~ "lightcoral", # Rural: Counts 10-20
CLASSIF == "Rural" & COUNT <= 30 ~ "firebrick", # Rural: Counts 20-30
CLASSIF == "Rural" & COUNT > 30 ~ "darkred", # Rural: Counts 30+
TRUE ~ "gray" # Default color for any unmatched cases
)
)
ggplot() +
# Add OK counties (light grey fill, black borders)
geom_sf(
data = ok_counties_reproj,
fill = "#f5f5f5",
color = "black",
size = 0 # No borders for counties in this layer
) +
# Add DMA boundaries (Tulsa and Oklahoma City)
geom_sf(
data = ok_DMA,
aes(fill = DMA, color = DMA),
linewidth = 1.5, # Set line width for borders
alpha = 0.75 # Transparency for DMAs
) +
# Set fill colors for Tulsa and Oklahoma City
scale_fill_manual(
values = c("Tulsa, OK" = "#4575b4",
"Oklahoma City, OK" = "#5ab4ac")
) +
# Set border colors for Tulsa and Oklahoma City
scale_color_manual(
values = c("Tulsa, OK" = "#4575b4",
"Oklahoma City, OK" = "#5ab4ac")
) +
# Add Oklahoma counties again with borders, but no fill
geom_sf(
data = ok_counties_reproj,
fill = NA,
color = "black", # Keep the black border for counties
size = 1 # Make the county borders visible
) +
new_scale_color() +
# Add cities with color_gradient based on urban/rural classification and their counts
geom_sf(
data = ok_cities_correct_clean %>%
filter(COUNT > 0), # Only cities with counts greater than 0
aes(size = COUNT, fill = color_gradient), # **Fill by `color_gradient`**
show.legend = TRUE
) +
# Adjust size scale for proportional symbols
scale_size_continuous(
range = c(1, 10),
name = "Frequency of Mentions"
)[![enter image description here][1]][1]
What I expect:
What I'm getting:
Question: How can I properly create a map with:
UPDATE:
The code below (provided by @stefan) seemed to address my issues. However, I am now seeing duplicate DMA legends. One is correct (bottom of figure) and the other has the correct border but a gray fill (top of figure). I'd like to remove the duplicate DMA legend (top of figure), but when I do, it messes up other things in my map. Suggestions?
# Filter out zero counts and apply binning
ok_cities_correct_clean <- ok_cities_correct_clean %>%
filter(COUNT > 0) %>%
mutate(
color_gradient = cut(
COUNT, # Cut the COUNT values into the specified ranges
breaks = c(0, 10, 20, 30, Inf), # Include 0 as the lower bound for first bin
labels = c("1 - 10", "11 - 20", "21 - 30", "> 30"), # Correct number of labels
right = TRUE # Ensure the upper bound is included in each bin
)
)
# Define color palettes
cities_urban <- c("lightblue", "lightskyblue", "dodgerblue", "darkblue")
names(cities_urban) <- labels
cities_rural <- c("lightpink", "lightcoral", "firebrick", "darkred")
names(cities_rural) <- labels
ggplot() +
# Add OK counties
geom_sf(
data = ok_counties_reproj,
fill = "#f5f5f5",
color = "black",
size = 0
) +
# Add DMA
geom_sf(
data = ok_DMA,
aes(fill = DMA, color = DMA),
linewidth = 1.5,
alpha = 0.75,
show.legend = c(fill = TRUE, color = FALSE)
) +
# OK counties (border, no fill)
geom_sf(
data = ok_counties_reproj,
fill = NA,
color = "black",
size = 1
) +
# Set fill colors for DMA
scale_fill_manual(
values = c(
"Tulsa, OK" = "#4575b4",
"Oklahoma City, OK" = "#5ab4ac"
),
aesthetics = c("fill", "color"),
guide = guide_legend(override.aes = list(
color = c("#5ab4ac", "#4575b4")
))
) +
new_scale_color() +
# Add cities with color_gradient based on urban/rural classification
geom_sf(
data = ok_cities_correct_clean %>%
filter(COUNT > 0, CLASSIF == "Urban"),
aes(size = COUNT, color = color_gradient)
) +
scale_color_manual(
values = cities_urban, na.value = "gray", name = "Urban"
) +
new_scale_color() +
# Add cities with color g radient based on urban/rural classification
geom_sf(
data = ok_cities_correct_clean %>%
filter(COUNT > 0, CLASSIF == "Rural"),
aes(size = COUNT, color = color_gradient)
) +
scale_color_manual(
values = cities_rural, na.value = "gray", name = "Rural"
) +
scale_size_continuous(
range = c(1, 10),
name = "Frequency of Mentions"
)
'''
[1]: .png
[2]: .png
I'm trying to create a map in R using ggplot2 and the newggscale packages, where I show proportional symbols for both urban and rural locations, with varying color gradients depicting the size of the proportional symbol. My goal is to have:
Currently, I'm facing an issue when using new_scale_color() or new_scale_fill() to manage multiple color gradients. Instead of showing just one legend for the proportional symbols and count, I'm getting that plus two DMA legends and no propoer color gradient for the urban and rural locations.
Here's what I've tried:
ok_cities_correct_clean <- ok_cities_correct_clean %>%
mutate(
color_gradient = case_when(
# For Urban cities, custom color ranges based on COUNT
CLASSIF == "Urban" & COUNT <= 10 ~ "lightblue", # Urban: Counts 1-10
CLASSIF == "Urban" & COUNT <= 20 ~ "lightskyblue", # Urban: Counts 10-20
CLASSIF == "Urban" & COUNT <= 30 ~ "dodgerblue", # Urban: Counts 20-30
CLASSIF == "Urban" & COUNT > 30 ~ "darkblue", # Urban: Counts 30+
# For Rural cities, custom color ranges based on COUNT
CLASSIF == "Rural" & COUNT <= 10 ~ "lightpink", # Rural: Counts 1-10
CLASSIF == "Rural" & COUNT <= 20 ~ "lightcoral", # Rural: Counts 10-20
CLASSIF == "Rural" & COUNT <= 30 ~ "firebrick", # Rural: Counts 20-30
CLASSIF == "Rural" & COUNT > 30 ~ "darkred", # Rural: Counts 30+
TRUE ~ "gray" # Default color for any unmatched cases
)
)
ggplot() +
# Add OK counties (light grey fill, black borders)
geom_sf(
data = ok_counties_reproj,
fill = "#f5f5f5",
color = "black",
size = 0 # No borders for counties in this layer
) +
# Add DMA boundaries (Tulsa and Oklahoma City)
geom_sf(
data = ok_DMA,
aes(fill = DMA, color = DMA),
linewidth = 1.5, # Set line width for borders
alpha = 0.75 # Transparency for DMAs
) +
# Set fill colors for Tulsa and Oklahoma City
scale_fill_manual(
values = c("Tulsa, OK" = "#4575b4",
"Oklahoma City, OK" = "#5ab4ac")
) +
# Set border colors for Tulsa and Oklahoma City
scale_color_manual(
values = c("Tulsa, OK" = "#4575b4",
"Oklahoma City, OK" = "#5ab4ac")
) +
# Add Oklahoma counties again with borders, but no fill
geom_sf(
data = ok_counties_reproj,
fill = NA,
color = "black", # Keep the black border for counties
size = 1 # Make the county borders visible
) +
new_scale_color() +
# Add cities with color_gradient based on urban/rural classification and their counts
geom_sf(
data = ok_cities_correct_clean %>%
filter(COUNT > 0), # Only cities with counts greater than 0
aes(size = COUNT, fill = color_gradient), # **Fill by `color_gradient`**
show.legend = TRUE
) +
# Adjust size scale for proportional symbols
scale_size_continuous(
range = c(1, 10),
name = "Frequency of Mentions"
)[![enter image description here][1]][1]
What I expect:
What I'm getting:
Question: How can I properly create a map with:
UPDATE:
The code below (provided by @stefan) seemed to address my issues. However, I am now seeing duplicate DMA legends. One is correct (bottom of figure) and the other has the correct border but a gray fill (top of figure). I'd like to remove the duplicate DMA legend (top of figure), but when I do, it messes up other things in my map. Suggestions?
# Filter out zero counts and apply binning
ok_cities_correct_clean <- ok_cities_correct_clean %>%
filter(COUNT > 0) %>%
mutate(
color_gradient = cut(
COUNT, # Cut the COUNT values into the specified ranges
breaks = c(0, 10, 20, 30, Inf), # Include 0 as the lower bound for first bin
labels = c("1 - 10", "11 - 20", "21 - 30", "> 30"), # Correct number of labels
right = TRUE # Ensure the upper bound is included in each bin
)
)
# Define color palettes
cities_urban <- c("lightblue", "lightskyblue", "dodgerblue", "darkblue")
names(cities_urban) <- labels
cities_rural <- c("lightpink", "lightcoral", "firebrick", "darkred")
names(cities_rural) <- labels
ggplot() +
# Add OK counties
geom_sf(
data = ok_counties_reproj,
fill = "#f5f5f5",
color = "black",
size = 0
) +
# Add DMA
geom_sf(
data = ok_DMA,
aes(fill = DMA, color = DMA),
linewidth = 1.5,
alpha = 0.75,
show.legend = c(fill = TRUE, color = FALSE)
) +
# OK counties (border, no fill)
geom_sf(
data = ok_counties_reproj,
fill = NA,
color = "black",
size = 1
) +
# Set fill colors for DMA
scale_fill_manual(
values = c(
"Tulsa, OK" = "#4575b4",
"Oklahoma City, OK" = "#5ab4ac"
),
aesthetics = c("fill", "color"),
guide = guide_legend(override.aes = list(
color = c("#5ab4ac", "#4575b4")
))
) +
new_scale_color() +
# Add cities with color_gradient based on urban/rural classification
geom_sf(
data = ok_cities_correct_clean %>%
filter(COUNT > 0, CLASSIF == "Urban"),
aes(size = COUNT, color = color_gradient)
) +
scale_color_manual(
values = cities_urban, na.value = "gray", name = "Urban"
) +
new_scale_color() +
# Add cities with color g radient based on urban/rural classification
geom_sf(
data = ok_cities_correct_clean %>%
filter(COUNT > 0, CLASSIF == "Rural"),
aes(size = COUNT, color = color_gradient)
) +
scale_color_manual(
values = cities_rural, na.value = "gray", name = "Rural"
) +
scale_size_continuous(
range = c(1, 10),
name = "Frequency of Mentions"
)
'''
[1]: https://i.sstatic.net/3EVCeDlD.png
[2]: https://i.sstatic.net/JCcIMo2C.png
The first issue with your code is that you have to use new_scale_fill
instead of new_scale_color
to add additional fill
scales. Second, you also have to filter your data for urban and rural cities or counties. Third, concerning the issue with the DMA legends. Not sure about that. This looks like an issue with ggnewscale
and perhaps is a bug, i.e. both legends get merged into one when not using ggnewscale
. To fix that I dropped the color
legend and use the override.aes
argument of guide_legend
to set the colors for the fill
legend. Finally, instead of mapping color codes on the aesthetics I bin the COUNT
variable using cut
and set the colors via scale_fill_manual
.
Using a minimal reproducible example based on the default example from ?geom_sf
:
library(tidyverse)
library(ggnewscale)
## Make example data
set.seed(123)
nc_counties <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE)
dma <- sample(nrow(nc_counties), 2)
ok_counties_reproj <- nc_counties
ok_DMA <- nc_counties[dma, ]
ok_DMA$DMA <- c("Tulsa, OK", "Oklahoma City, OK")
ok_cities_correct_clean <- nc_counties[-dma, ]
ok_cities_correct_clean$CLASSIF <- sample(
c("Urban", "Rural"),
nrow(ok_cities_correct_clean),
replace = TRUE
)
ok_cities_correct_clean$COUNT <- sample(
seq(0, 40),
nrow(ok_cities_correct_clean),
replace = TRUE
)
###
breaks <- c(-Inf, 10, 20, 30, Inf)
labels <- labels <- c("1 - 10", "11 - 20", "21 - 30", "> 30")
## Bin the COUNT variable
ok_cities_correct_clean$color_gradient <- cut(ok_cities_correct_clean$COUNT, breaks, labels)
## Define color palettes
pal_urban <- c("lightblue", "lightskyblue", "dodgerblue", "darkblue")
names(pal_urban) <- labels
pal_rural <- c("lightpink", "lightcoral", "firebrick", "darkred")
names(pal_rural) <- labels
ggplot() +
# Add OK counties (light grey fill, black borders)
geom_sf(
data = ok_counties_reproj,
fill = "#f5f5f5",
color = "black",
size = 0 # No borders for counties in this layer
) +
# Add DMA boundaries (Tulsa and Oklahoma City)
geom_sf(
data = ok_DMA,
aes(fill = DMA, color = DMA),
linewidth = 1.5, # Set line width for borders
alpha = 0.75, # Transparency for DMAs,
show.legend = c(fill = TRUE, color = FALSE)
) +
# Add Oklahoma counties again with borders, but no fill
geom_sf(
data = ok_counties_reproj,
fill = NA,
color = "black", # Keep the black border for counties
size = 1 # Make the county borders visible
) +
# Set fill colors for Tulsa and Oklahoma City
scale_fill_manual(
values = c(
"Tulsa, OK" = "#4575b4",
"Oklahoma City, OK" = "#5ab4ac"
),
aesthetics = c("fill", "color"),
guide = guide_legend(override.aes = list(
color = c("#5ab4ac", "#4575b4")
))
) +
new_scale_fill() +
# Add cities with color_gradient based on urban/rural classification and their counts
geom_sf(
data = ok_cities_correct_clean %>%
filter(COUNT > 0, CLASSIF == "Urban"),
aes(size = COUNT, fill = color_gradient)
) +
scale_fill_manual(
values = pal_urban, na.value = "gray", name = "Urban"
) +
new_scale_fill() +
# Add cities with color_gradient based on urban/rural classification and their counts
geom_sf(
data = ok_cities_correct_clean %>%
filter(COUNT > 0, CLASSIF == "Rural"),
aes(size = COUNT, fill = color_gradient)
) +
scale_fill_manual(
values = pal_rural, na.value = "gray", name = "Rural"
) +
scale_size_continuous(
range = c(1, 10),
name = "Frequency of Mentions"
)