14 Geomarketing

The solutions assume the following packages are attached (other packages will be attached when needed):

E1. Download the csv file containing inhabitant information for a 100 m cell resolution (https://www.zensus2011.de/SharedDocs/Downloads/DE/Pressemitteilung/DemografischeGrunddaten/csv_Bevoelkerung_100m_Gitter.zip?__blob=publicationFile&v=3). Please note that the unzipped file has a size of 1.23 GB. To read it into R you can use readr::read_csv. This takes 30 seconds on a machine with 16 GB RAM. data.table::fread() might be even faster, and returns an object of class data.table(). Use dplyr::as_tibble() to convert it into a tibble. Build an inhabitant raster, aggregate it to a cell resolution of 1 km, and compare the difference with the inhabitant raster (inh) we have created using class mean values.

# Coarse inhabitant raster (1 km resolution)
#*******************************************

# inhabitant raster (coarse resolution); this is one of the results of the 
# previous exercise
data("census_de", package = "spDataLarge")
input = select(census_de, x = x_mp_1km, y = y_mp_1km, pop = Einwohner,
                      women = Frauen_A, mean_age = Alter_D, hh_size = HHGroesse_D)
input_tidy = dplyr::mutate(input, dplyr::across(.fns = ~ifelse(. %in% c(-1, -9), NA, .)))
input_ras = terra::rast(input_tidy, type = "xyz", crs = "EPSG:3035")
inh_coarse = input_ras$pop
# reclassify, i.e. convert the classes into inhabitant numbers using class means
rcl = matrix(c(1, 1, 125, 2, 2, 375, 3, 3, 1250, 4, 4, 3000, 5, 5, 6000,
               6, 6, 8000), ncol = 3, byrow = TRUE)
inh_coarse = terra::classify(inh_coarse, rcl = rcl, right = NA)

# Fine inhabitant raster (100 m resolution)
#******************************************
url =
  paste0("https://www.zensus2011.de/SharedDocs/Downloads/DE/Pressemitteilung/",
         "DemografischeGrunddaten/csv_Bevoelkerung_100m_Gitter.zip", 
         "?__blob=publicationFile&v=3")
# download fine raster
download.file(url = url, destfile = file.path(tempdir(), "census.zip"),
              method = "auto", mode = "wb")
# list the file names
nms = unzip(file.path(tempdir(), "census.zip"), list = TRUE)
# unzip only the csv file
base_name = grep(".csv$", nms$Name, value = TRUE)
unzip(file.path(tempdir(), "census.zip"), files = base_name, exdir = tempdir())
# read in the csv file
input = data.table::fread(file.path(tempdir(), base_name)) |>
  dplyr::as_tibble()
input = select(input, x = starts_with("x_mp_1"),
                      y = starts_with("y_mp_1"), inh = Einwohner)
# set -1 and -9 to NA
input = dplyr::mutate(input,
                      dplyr::across(.fns = ~ifelse(. %in% c(-1, -9), NA, .)))
# convert table into a raster (x and y are cell midpoints)
inh_fine = terra::rast(input, type = "xyz", crs = "EPSG:3035")
# Note that inh_fine contains the actual number of inhabitants per raster cell
# instead of mean class values as was the case with its coarse 1km counterpart

# Comparing the coarse with the fine raster
#******************************************

# aggregate to the resolution of the coarse raster
inh_fine = 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) = terra::origin(inh_coarse)
# make the comparison
summary(inh_fine - inh_coarse)
plot(inh_fine - inh_coarse)
plot(abs(inh_fine - inh_coarse) > 1000)
# the biggest deviations can be found in big cities like Berlin
terra::global((abs(inh_fine - inh_coarse) > 1000), fun = "sum", na.rm = TRUE)
# 18,121 cells have a deviation > 1000 inhabitants
terra::global((abs(inh_fine - inh_coarse) > 5000), fun = "sum", na.rm = TRUE)
# 338 cells have a deviation > 5000

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.

# Here, we assue that you have already created `input_ras` in the first exercise.
# 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.
rcl_pop = matrix(c(1, 1, 127, 2, 2, 375, 3, 3, 1250, 
                   4, 4, 3000, 5, 5, 6000, 6, 6, 8000), 
                 ncol = 3, byrow = TRUE)
rcl_women = matrix(c(1, 1, 3, 2, 2, 2, 3, 3, 1, 4, 5, 0), 
                   ncol = 3, byrow = TRUE)
# here we are giving the classes (3 to 5) containing the oldest people the
# highest weight
rcl_age = matrix(c(1, 1, 1, 2, 2, 1, 3, 5, 3),
                 ncol = 3, byrow = TRUE)
rcl_hh = rcl_women
rcl = list(rcl_pop, rcl_women, rcl_age, rcl_hh)

reclass = input_ras
for (i in 1:terra::nlyr(reclass)) {
  reclass[[i]] = terra::classify(x = reclass[[i]], rcl = rcl[[i]], right = NA)
}
names(reclass) = names(input_ras)

# The rest of the analysis follows exactly the code presented in the book. 

# Add metro names to metros sf object
#************************************
metro_names = dplyr::pull(metro_names, city) |>
  as.character() |>
  {\(x) ifelse(x == "Velbert", "Düsseldorf", x)}() |>
  {\(x) gsub("ü", "ue", x)}()

pop_agg = terra::aggregate(reclass$pop, 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

# 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"
# remove population raster and add poi raster
reclass = reclass[[names(reclass) != "pop"]] |>
  c(poi)

# Identify suitable locations
#****************************
# calculate the total score
result = sum(reclass)

# have a look at suitable bike shop locations in Berlin
berlin = 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")