Identificación de núcleos gastronómicos en el distrito de Miraflores de la ciudad de Lima mediante análisis de densidad y clustering espacial con datos de OpenStreetMap.
Analizar la distribución espacial de restaurantes en Miraflores para identificar zonas de alta concentración (hotspots) utilizando datos de OpenStreetMap y técnicas de visualización y análisis espacial.
#install.packages("tidyverse")
library(tidyverse)
#install.packages("sf")
library(sf)
#install.packages("ggmap")
library(ggmap)
#install.packages("osmdata")
library(osmdata)
Miraflores se encuentra situado en el corazón de Lima moderna, este distrito limita al norte con el centro financiero del distrito de San Isidro y al sur con el bohemio distrito de Barranco. Su frontera más espectacular es la del oeste, con los acantilados verdes que dan paso a las playas del Océano Pacífico.
#Activación de API key
ggmap::register_stadiamaps(key = "0bf2d96e-58f7-444b-911a-3d02b8d0b013")
bbox_miraflores <- getbb("Miraflores, Lima, Peru")
#Revisión de los resultados obtenidos
bbox_miraflores
## min max
## x -77.05602 -77.00129
## y -12.14015 -12.10285
#Revisión de los resultados obtenidos
mapa_miraflores <- get_stadiamap(bbox=bbox_miraflores,
maptype="alidade_smooth",
zoom=14)
#Ver resultados generados
ggmap(mapa_miraflores)
#Cargar la extensión del territorio de Miraflores
polygon_miraflores <- getbb("Miraflores, Lima, Peru",
format_out = "sf_polygon")
polygon_miraflores <- polygon_miraflores[1, ]
#Ver resultado del polígono
polygon_miraflores
#Ver resultado del polígono
ggplot()+
geom_sf(data=polygon_miraflores)
#Ver polígono en OSM
ggmap(mapa_miraflores)+
geom_sf(data=polygon_miraflores, inherit.aes = FALSE)
Ajustes para mejorar la visualización: - Quitemos el relleno del
polígono y demos algún color al borde. - Pongamos título, subtítulo y
fuente. - Ajustemos el theme .
ggmap(mapa_miraflores)+
geom_sf(data=polygon_miraflores, fill=NA, color="black", lwd=0.75, inherit.aes = FALSE)+
labs(title="Miraflores, Lima, Perú",
caption="Fuente: OpenStreetMap")+
theme_void()
### Analizar la información existente
available_features()
## [1] "4wd_only" "abandoned"
## [3] "abutters" "access"
## [5] "addr" "addr:*"
## [7] "addr:city" "addr:conscriptionnumber"
## [9] "addr:country" "addr:county"
## [11] "addr:district" "addr:flats"
## [13] "addr:full" "addr:hamlet"
## [15] "addr:housename" "addr:housenumber"
## [17] "addr:inclusion" "addr:interpolation"
## [19] "addr:place" "addr:postbox"
## [21] "addr:postcode" "addr:province"
## [23] "addr:state" "addr:street"
## [25] "addr:subdistrict" "addr:suburb"
## [27] "addr:unit" "admin_level"
## [29] "aeroway" "agricultural"
## [31] "alcohol" "alt_name"
## [33] "amenity" "area"
## [35] "atv" "backward"
## [37] "barrier" "basin"
## [39] "bdouble" "bicycle"
## [41] "bicycle_road" "biergarten"
## [43] "boat" "border_type"
## [45] "boundary" "brand"
## [47] "bridge" "bridge:name"
## [49] "building" "building:architecture"
## [51] "building:colour" "building:fireproof"
## [53] "building:flats" "building:levels"
## [55] "building:material" "building:min_level"
## [57] "building:part" "building:prefabricated"
## [59] "building:soft_storey" "bus"
## [61] "bus:lanes" "bus_bay"
## [63] "busway" "capacity"
## [65] "carriage" "castle_type"
## [67] "change" "charge"
## [69] "clothes" "construction"
## [71] "construction#Railways" "construction_date"
## [73] "covered" "craft"
## [75] "crossing" "crossing:island"
## [77] "cuisine" "cutting"
## [79] "cycle_rickshaw" "cycleway"
## [81] "cycleway:left" "cycleway:left:oneway"
## [83] "cycleway:right" "cycleway:right:oneway"
## [85] "denomination" "destination"
## [87] "diet:*" "direction"
## [89] "dispensing" "disused"
## [91] "dog" "drinking_water"
## [93] "drinking_water:legal" "drive_in"
## [95] "drive_through" "ele"
## [97] "electric_bicycle" "electrified"
## [99] "embankment" "embedded_rails"
## [101] "emergency" "end_date"
## [103] "energy_class" "entrance"
## [105] "est_width" "fee"
## [107] "female" "fire_hydrant"
## [109] "fire_object:type" "fire_operator"
## [111] "fire_rank" "food"
## [113] "foot" "footway"
## [115] "ford" "forestry"
## [117] "forward" "frequency"
## [119] "frontage_road" "fuel"
## [121] "full_name" "gauge"
## [123] "gender_segregated" "golf_cart"
## [125] "goods" "gutter"
## [127] "hand_cart" "hazard"
## [129] "hazmat" "healthcare"
## [131] "healthcare:counselling" "healthcare:speciality"
## [133] "height" "hgv"
## [135] "highway" "historic"
## [137] "horse" "hot_water"
## [139] "hov" "ice_road"
## [141] "incline" "industrial"
## [143] "inline_skates" "inscription"
## [145] "int_name" "internet_access"
## [147] "junction" "kerb"
## [149] "landuse" "lane_markings"
## [151] "lanes" "lanes:bus"
## [153] "lanes:psv" "layer"
## [155] "leaf_cycle" "leaf_type"
## [157] "leisure" "lhv"
## [159] "lit" "loc_name"
## [161] "location" "male"
## [163] "man_made" "max_age"
## [165] "max_level" "maxaxleload"
## [167] "maxheight" "maxlength"
## [169] "maxspeed" "maxstay"
## [171] "maxweight" "maxwidth"
## [173] "military" "min_age"
## [175] "min_level" "minspeed"
## [177] "mofa" "moped"
## [179] "motor_vehicle" "motorboat"
## [181] "motorcar" "motorcycle"
## [183] "motorroad" "mountain_pass"
## [185] "mtb:description" "mtb:scale"
## [187] "name" "name:left"
## [189] "name:right" "name_1"
## [191] "name_2" "narrow"
## [193] "nat_name" "natural"
## [195] "nickname" "noexit"
## [197] "non_existent_levels" "nudism"
## [199] "office" "official_name"
## [201] "old_name" "oneway"
## [203] "oneway:bicycle" "oneway:bus"
## [205] "openfire" "opening_hours"
## [207] "opening_hours:drive_through" "operator"
## [209] "orientation" "oven"
## [211] "overtaking" "parking"
## [213] "parking:condition" "parking:lane"
## [215] "passenger_lines" "passing_places"
## [217] "place" "power"
## [219] "power_supply" "priority"
## [221] "priority_road" "produce"
## [223] "proposed" "proposed:name"
## [225] "protected_area" "psv"
## [227] "psv:lanes" "public_transport"
## [229] "railway" "railway:preserved"
## [231] "railway:track_ref" "recycling_type"
## [233] "ref" "ref_name"
## [235] "reg_name" "religion"
## [237] "religious_level" "rental"
## [239] "residential" "roadtrain"
## [241] "route" "sac_scale"
## [243] "sauna" "service"
## [245] "service_times" "shelter_type"
## [247] "shop" "short_name"
## [249] "shoulder" "shower"
## [251] "side_road" "sidewalk"
## [253] "site" "ski"
## [255] "smoking" "smoothness"
## [257] "social_facility" "sorting_name"
## [259] "speed_pedelec" "sport"
## [261] "start_date" "step_count"
## [263] "substation" "surface"
## [265] "tactile_paving" "tank"
## [267] "taxi" "tidal"
## [269] "toilets" "toilets:wheelchair"
## [271] "toll" "topless"
## [273] "tourism" "tourist_bus"
## [275] "tower:type" "tracks"
## [277] "tracktype" "traffic_calming"
## [279] "traffic_sign" "trail_visibility"
## [281] "trailblazed" "trailblazed:visibility"
## [283] "trailer" "tunnel"
## [285] "tunnel:name" "turn"
## [287] "type" "unisex"
## [289] "usage" "vehicle"
## [291] "vending" "voltage"
## [293] "water" "wheelchair"
## [295] "wholesale" "width"
## [297] "winter_road" "wood"
# Configurar un servidor alternativo
set_overpass_url("https://overpass.kumi.systems/api/interpreter")
# Consultar restaurantes en Miraflores
restaurantes_query <- opq(bbox = bbox_miraflores) %>%
add_osm_feature(key = "amenity", value = "restaurant") %>%
osmdata_sf()
# Extraer solo los puntos (restaurantes como puntos)
restaurantes <- restaurantes_query$osm_points
# Ver cuántos restaurantes encontramos
nrow(restaurantes)
## [1] 1036
# Ver las primeras filas
head(restaurantes)
# Consultar restaurantes en Miraflores
gastronomia_query <- opq(bbox = bbox_miraflores) %>%
add_osm_feature(key = "amenity",
value = c("restaurant", "bar", "cafe", "pub")) %>%
osmdata_sf()
# Extraer los puntos (restaurantes)
gastronomia <- gastronomia_query$osm_points
# Ver cuántos encontramos
nrow(gastronomia)
## [1] 1338
En base a la información del open street map se puede observar que se identifican 1036 restaurantes.A continuación, los visualizamos en el mapa para explorar su distribución en el territorio.
ggmap(mapa_miraflores)+
geom_sf(data=polygon_miraflores, fill=NA, color="blue", lwd=0.75, inherit.aes = FALSE)+ geom_sf(data = gastronomia, inherit.aes = FALSE)+
labs(title="Locales gastronómicos",
subtitle="Miraflores, Lima, Perú",
caption="Fuente: OpenStreetMap")+
theme_void()+
theme(title=element_text(size=10, face = "bold"), #tamaño de titulo del mapa
plot.caption=element_text(face = "italic", colour = "gray35",size=7)) #tamaño de nota al pie
ggmap(mapa_miraflores)+
geom_sf(data=polygon_miraflores, fill=NA, color="black", lwd=0.75, inherit.aes = FALSE)+
geom_sf(data = gastronomia, aes(color=amenity), inherit.aes = FALSE)+
labs(title="Locales gastronómicos",
subtitle="Miraflores, Lima, Perú",
caption="Fuente: OpenStreetMap",
color="")+
scale_color_manual(values=c("deeppink", "tomato", "skyblue", "yellow2"))+
theme_void()+
theme(title=element_text(size=10, face = "bold"), #tamaño de titulo del mapa
plot.caption=element_text(face = "italic", colour = "gray35",size=7)) #tamaño de nota al pie
Se realiza el cálculo para identificar la cantidad de NA que existen y
que no se están visibilizando en la categorías mostradas
previamente,
gastronomia %>%
group_by(amenity) %>%
summarise(cantidad=n())
Como se puede ver en los resultados, existen 721 NA que superan en cantidad a los restaurantes.Sin embargo, para fines del presente trabajo, se optará por eliminar los NA.
gastronomia <- gastronomia %>%
filter(!is.na(amenity))
# Ver resumen
gastronomia %>%
group_by(amenity) %>%
summarise(cantidad = n())
#install.packages("leaflet")
library(leaflet)
leaflet(gastronomia) %>%
addTiles() %>%
addCircleMarkers()
#Creación de paletas de colores según categoría gastronómica
factpal <- colorFactor(palette = c("pink","tomato","skyblue","yellow2"),
levels = gastronomia$amenity)
leaflet(gastronomia) %>%
addTiles() %>%
addCircleMarkers(popup = paste("Tipo:", gastronomia$amenity, "<br>",
"Nombre:", gastronomia$name),
color = ~factpal(amenity))%>%
addLegend("bottomright", pal = factpal, values = ~amenity,
title = "Tipo",
opacity = 1) %>%
addMiniMap()
#install.packages("leaflet.extras")
library(leaflet.extras)
leaflet(gastronomia) %>%
addTiles() %>%
addHeatmap(radius = 15,
max = 0.05) %>%
addMiniMap()
library(dbscan)
gastronomia <- gastronomia %>%
st_transform(32718)
modelo <- dbscan(gastronomia %>% st_coordinates(.), eps = 250, minPts = 8)
modelo
## DBSCAN clustering for 617 objects.
## Parameters: eps = 250, minPts = 8
## Using euclidean distances and borderpoints = TRUE
## The clustering contains 6 cluster(s) and 96 noise points.
##
## 0 1 2 3 4 5 6
## 96 441 23 22 11 11 13
##
## Available fields: cluster, eps, minPts, metric, borderPoints
gastronomia<- gastronomia %>%
mutate(cluster=modelo$cluster)
gastronomia <- gastronomia %>%
st_transform(4326)
ggmap(mapa_miraflores)+
geom_sf(data=gastronomia %>%
filter(cluster!=0), aes(color=as.factor(cluster)), inherit.aes = FALSE)+
theme_void()
### Identificar polígonos clusters
ggmap(mapa_miraflores)+
geom_sf(data=gastronomia %>%
filter(cluster!=0), aes(color=as.factor(cluster)), inherit.aes = FALSE)+
theme_void()
gastronomia_clusters <- gastronomia %>%
filter(cluster != 0) %>%
group_by(cluster) %>%
summarise(geometry = st_union(geometry)) %>%
st_convex_hull()
ggmap(mapa_miraflores)+
geom_sf(data=gastronomia %>%
filter(cluster!=0), aes(color=as.factor(cluster)), inherit.aes = FALSE)+
geom_sf(data=gastronomia_clusters, aes(fill=as.factor(cluster)), color=NA,
alpha=0.5, inherit.aes = FALSE)+
labs(title="Clústers de establecimientos gastronómicos",
subtitle="Miraflores, Lima, Perú",
color="",
fill="")+
theme_void()
leaflet(gastronomia) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addPolygons(data=gastronomia_clusters,
color = ~case_when(
cluster == 0 ~ "gray",
cluster == 1 ~ "#005f73",
cluster == 2 ~ "#0a9396",
cluster == 3 ~ "#06d6a0",
cluster == 4 ~ "#ee9b00",
cluster == 5 ~ "#ca6702",
cluster == 6 ~ "#ae2012")) %>%
addCircleMarkers(
popup = paste("Tipo:", gastronomia$amenity, "<br>",
"Nombre:", gastronomia$name, "<br>",
"Clúster:", gastronomia$cluster),
radius=4,
color = ~case_when(
cluster == 0 ~ "gray",
cluster == 1 ~ "#005f73",
cluster == 2 ~ "#0a9396",
cluster == 3 ~ "#06d6a0",
cluster == 4 ~ "#ee9b00",
cluster == 5 ~ "#ca6702",
cluster == 6 ~ "#ae2012")) %>%
addMiniMap(tiles = providers$CartoDB.Positron)
# Extraer coordenadas lon/lat
gastronomia_coords <- gastronomia %>%
st_transform(4326) %>% # en WGS84
mutate(
lon = st_coordinates(.)[,1],
lat = st_coordinates(.)[,2]
)
ggplot() +
stat_density2d(
data = gastronomia_coords,
aes(x = lon, y = lat, fill = after_stat(level)),
geom = "polygon",
alpha = 0.75,
) +
labs(
title = "Densidad de establecimientos gastronómicos",
subtitle = "Miraflores, Lima, Perú",
fill = "Densidad"
) +
scale_fill_distiller(palette = "Spectral") +
theme_void()
#Agregar polígono de Miraflores encima
ggplot() +
stat_density2d(
data = gastronomia_coords,
aes(x = lon, y = lat, fill = after_stat(level)),
geom = "polygon",
alpha = 0.75
) +
geom_sf(data = polygon_miraflores, fill = NA, color = "black", lwd = 1) +
labs(
title = "Densidad de establecimientos gastronómicos",
subtitle = "Miraflores, Lima, Perú",
fill = "Densidad"
) +
scale_fill_distiller(palette = "Spectral") +
theme_void()
### Densidad de establecimientos gastronómicos con
maps
#Incluir mapa
ggmap(mapa_miraflores) +
stat_density2d(
data = gastronomia_coords,
aes(x = lon, y = lat, fill = after_stat(level)),
geom = "polygon",
alpha = 0.5
) +
geom_sf(data = polygon_miraflores, fill = NA, color = "black", lwd = 0.5, inherit.aes = FALSE) +
labs(
title = "Densidad de establecimientos gastronómicos",
subtitle = "Miraflores, Lima, Perú",
fill = "Densidad"
) +
scale_fill_distiller(palette = "Spectral") +
theme_void()
### Densidad de establecimientos gastronómicos
ggmap(mapa_miraflores) +
stat_density2d(
data = gastronomia_coords,
aes(x = lon, y = lat, fill = after_stat(level)),
geom = "polygon",
alpha = 0.5
) +
geom_sf(data = polygon_miraflores, fill = NA, color = "black", lwd = 0.5, inherit.aes = FALSE) +
labs(
title = "Densidad de establecimientos gastronómicos",
subtitle = "Miraflores, Lima, Perú",
fill = "Densidad",
x = "Longitud",
y = "Latitud"
) +
scale_fill_distiller(palette = "Spectral") +
theme_void() +
theme(
axis.text = element_text(size = 8), # Mostrar coordenadas
axis.title.x = element_text(size = 10, face = "bold"),
axis.title.y = element_text(size = 10, face = "bold", angle = 90),
axis.title = element_text(size = 10, face = "bold") # Títulos de ejes
)
### Cargar los tipos de vías de OSM
# Descargar vías principales de Miraflores
vias_principales <- opq(bbox = bbox_miraflores) %>%
add_osm_feature(key = "highway",
value = c("primary", "secondary", "trunk", "motorway")) %>%
osmdata_sf()
# Extraer líneas
vias <- vias_principales$osm_lines
# Intersección con Miraflores
vias <- st_intersection(vias, polygon_miraflores)
# Ver cuántas vías
nrow(vias)
## [1] 563
ggmap(mapa_miraflores) +
stat_density2d(
data = gastronomia_coords,
aes(x = lon, y = lat, fill = after_stat(level)),
geom = "polygon",
alpha = 0.5
) +
geom_sf(data = vias, color = "red", lwd = 0.1, alpha = 0.7, inherit.aes = FALSE) + # ✅ Vías
geom_sf(data = polygon_miraflores, fill = NA, color = "black", lwd = 0.5, inherit.aes = FALSE) +
labs(
title = "Densidad gastronómica y vías principales",
subtitle = "Miraflores, Lima, Perú",
fill = "Densidad",
x = "Longitud",
y = "Latitud",
caption = "Fuente: OpenStreetMap"
) +
scale_fill_distiller(palette = "Spectral") +
theme_void() +
theme(
axis.text = element_text(size = 8),
axis.title = element_text(size = 10, face = "bold"),
plot.caption = element_text(face = "italic", colour = "gray35", size = 7)
)
### Mapa de densidad de establecimientos gastronómicos con tipos
de vías
ggmap(mapa_miraflores) +
stat_density2d(
data = gastronomia_coords,
aes(x = lon, y = lat, fill = after_stat(level)),
geom = "polygon",
alpha = 0.5
) +
geom_sf(data = vias, aes(color = highway), lwd = 0.2, inherit.aes = FALSE) + # Color por tipo
geom_sf(data = polygon_miraflores, fill = NA, color = "black", lwd = 0.5, inherit.aes = FALSE) +
labs(
title = "Densidad gastronómica y vías principales",
subtitle = "Miraflores, Lima, Perú (hora punta típica)",
fill = "Densidad",
color = "Tipo de vía",
x = "Longitud",
y = "Latitud",
caption = "Fuente: OpenStreetMap"
) +
scale_fill_distiller(palette = "Spectral") +
scale_color_manual(values = c(
"primary" = "red",
"secondary" = "orange",
"trunk" = "darkred",
"motorway" = "purple"
),
guide = guide_legend(
override.aes = list(
linetype = 1,
linewidth = 0.2,
shape = NA,
fill = NA
)
)
) +
theme_void() +
theme(
axis.text = element_text(size = 8),
axis.title.x = element_text(size = 10, face = "bold"),
axis.title.y = element_text(size = 10, face = "bold", angle = 90),
plot.caption = element_text(face = "italic", colour = "gray35", size = 7)
)