14 Geomarketing
The solutions assume the following packages are attached (other packages will be attached when needed):
E1. This exercise requires the z22 package for accessing 100 m resolution data.
Install it with remotes::install_github("JsLth/z22").
Load the population data at 100 m cell resolution using z22::z22_data("population", res = "100m", year = 2022).
Aggregate it to a cell resolution of 1 km using terra::aggregate() with fun = sum, and compare the result with the 1 km resolution data from census_de.
Note that the 100 m data is much larger and may take some time to download.
# Coarse inhabitant raster (1 km resolution)
#*******************************************
# Load 1 km population data from spDataLarge
data("census_de", package = "spDataLarge")
pop_1km = census_de |>
select(x, y, pop) |>
mutate(pop = ifelse(pop < 0, NA, pop))
inh_coarse = terra::rast(pop_1km, type = "xyz", crs = "EPSG:3035")
# Fine inhabitant raster (100 m resolution)
#******************************************
# Load 100 m population data using z22 (this may take some time)
pop_100m = z22::z22_data("population", res = "100m", year = 2022, as = "df")
pop_100m = pop_100m |>
rename(pop = cat_0) |>
mutate(pop = ifelse(pop < 0, NA, pop))
inh_fine = terra::rast(pop_100m, type = "xyz", crs = "EPSG:3035")
# Comparing the coarse with the fine raster
#******************************************
# aggregate to the resolution of the coarse raster
inh_fine_agg = terra::aggregate(
inh_fine, fact = terra::res(inh_coarse)[1] / terra::res(inh_fine)[1],
fun = sum, na.rm = TRUE)
# origin has to be the same
terra::origin(inh_fine_agg) = terra::origin(inh_coarse)
# make the comparison
summary(inh_fine_agg - inh_coarse)
plot(inh_fine_agg - inh_coarse)
# Note: Since Census 2022 provides actual counts at both resolutions,
# differences should be minimal (mainly due to rounding or edge effects)
terra::global((abs(inh_fine_agg - inh_coarse) > 100), fun = "sum", na.rm = TRUE)E2. Suppose our bike shop predominantly sold electric bikes to older people. Change the age raster accordingly, repeat the remaining analyses and compare the changes with our original result.
# Load data from spDataLarge
data("census_de", package = "spDataLarge")
input_tidy = census_de |>
mutate(across(c(pop, women, mean_age, hh_size), ~ifelse(.x < 0, NA, .x)))
input_ras = terra::rast(input_tidy, type = "xyz", crs = "EPSG:3035")
# attach further necessary data
data("metro_names", "shops", package = "spDataLarge")
# Basically, we are assuming that especially older people will use an electric
# bike, therefore, we increase the weights for raster cells where predominantly
# older people are living.
# Reclassification matrices for continuous values
rcl_women = matrix(c(
0, 40, 3,
40, 47, 2,
47, 53, 1,
53, 60, 0,
60, 100, 0
), ncol = 3, byrow = TRUE)
# For elderly electric bikes: give highest weights to older age groups
rcl_age = matrix(c(
0, 40, 0, # Young -> low weight
40, 42, 0, # Young-ish -> low weight
42, 44, 1, # Middle-aged -> some weight
44, 47, 2, # Older -> higher weight
47, 120, 3 # Elderly -> highest weight
), ncol = 3, byrow = TRUE)
rcl_hh = matrix(c(
0, 1.5, 3,
1.5, 2.0, 2,
2.0, 2.5, 1,
2.5, 3.0, 0,
3.0, 100, 0
), ncol = 3, byrow = TRUE)
rcl = list(rcl_women, rcl_age, rcl_hh)
# Separate population (used as counts for metro detection) from variables to reclassify
pop_ras = input_ras$pop
demo_vars = c("women", "mean_age", "hh_size")
reclass = input_ras[[demo_vars]]
for (i in seq_len(terra::nlyr(reclass))) {
reclass[[i]] = terra::classify(x = reclass[[i]], rcl = rcl[[i]], right = NA)
}
names(reclass) = demo_vars
# The rest of the analysis follows exactly the code presented in the book.
# Add metro names to metros sf object
#************************************
metro_names_vec = dplyr::pull(metro_names, city) |>
as.character() |>
{\(x) ifelse(x == "Velbert", "Düsseldorf", x)}() |>
{\(x) gsub("ü", "ue", x)}()
pop_agg = terra::aggregate(pop_ras, fact = 20, fun = sum, na.rm = TRUE)
pop_agg = pop_agg[pop_agg > 500000, drop = FALSE]
polys = pop_agg |>
terra::patches(directions = 8) |>
terra::as.polygons() |>
sf::st_as_sf()
metros = polys |>
dplyr::group_by(patches) |>
dplyr::summarize()
metros$metro_names = metro_names_vec
# Create shop/poi density raster
#*******************************
shops = sf::st_transform(shops, sf::st_crs(reclass))
# create poi raster
poi = terra::rasterize(x = shops, y = reclass, field = "osm_id", fun = "length")
# construct reclassification matrix
int = classInt::classIntervals(values(poi), n = 4, style = "fisher")
int = round(int$brks)
rcl_poi = matrix(c(int[1], rep(int[-c(1, length(int))], each = 2),
int[length(int)] + 1), ncol = 2, byrow = TRUE)
rcl_poi = cbind(rcl_poi, 0:3)
# reclassify
poi = terra::classify(poi, rcl = rcl_poi, right = NA)
names(poi) = "poi"
# add poi raster to demographic weights
reclass = c(reclass, poi)
# Identify suitable locations
#****************************
# calculate the total score
result = sum(reclass)
# have a look at suitable bike shop locations in Berlin
berlin = metros[metros$metro_names == "Berlin", ]
berlin_raster = terra::crop(result, berlin)
# summary(berlin_raster)
# berlin_raster
berlin_raster = berlin_raster > 9
berlin_raster[berlin_raster == 0] = NA
# make the plot
leaflet::leaflet() |>
leaflet::addTiles() |>
leaflet::addRasterImage(raster::raster(berlin_raster), colors = "darkgreen", opacity = 0.8) |>
leaflet::addLegend("bottomright", colors = c("darkgreen"),
labels = c("potential locations"), title = "Legend")