Las cafeterías de Buenos Aires forman parte de la cultura de la ciudad, de las costumbres de sus habitantes y su circuito turístico. Han sido objeto de estudio de distintos escritores, inspiradores de innumerables creaciones artísticas y tradicional encuentro de unión de los porteños. En este proyecto, la idea es encontrar una ubicación óptima para una nueva cafetería, basados en algoritmos de aprendizaje automático tomados del curso "The Battle of Neighborhoods: Coursera Capstone Project" (1). Partiendo de que existe una asociación de las cafeterías con los restaurantes, primero intentaremos detectar ubicaciones en función de la definición de factores que influirán en nuestra decisión:
1- Lugares que aún no están llenos de restaurantes.
2- Áreas con poco o ninguna cafetería cercana.
3- Cerca del centro, si es posible, suponiendo que se cumplan las dos primeras condiciones.
Con estos sencillos parámetros vamos a programar un algoritmo para descubrir que soluciones se pueden obtener.
Se necesitarán las siguientes fuentes de datos para extraer y generar la información requerida:
1.- Los centros de las áreas candidatas serán generados automáticamente siguiendo el algoritmo y las direcciones aproximadas de los centros de estas áreas se obtendrán usando uno de los paquetes de Geopy Geocoders. (2) 2-El número de restaurantes, su tipo y ubicación en cada vecindario se obtendrá utilizando la API de Foursquare. (3)
Los datos se utilizarán en los siguientes escenarios:
1- Para descubrir la densidad de todos los restaurantes y las cafeterías a partir de los datos extraídos.
2- Para identificar áreas que no están muy densas y no son muy competitivas.
3- Para calcular las distancias entre restaurantes competidores.
El área objetivo será el centro de la ciudad, donde las atracciones turísticas son + numerosas en comparación con otros lugares. A partir de ello vamos a crear una cuadrícula de celdas que cubra el área de interés la cual sera unos 12x12 Kilómetros centrados alrededor del centro de la ciudad de Buenos Aires.
import requests
from geopy.geocoders import Nominatim
address = 'Sarmiento 999, Buenos Aires, Argentina'
geolocator = Nominatim(user_agent="lyon_explorer")
location = geolocator.geocode(address)
lat = location.latitude
lng = location.longitude
caba_center = [lat, lng]
print('Coordinate of {}: {}'.format(address, lyon_center), ' location : ', location)
Creamos una cuadrícula de las áreas candidatas equidistantes, centradas alrededor del centro de la ciudad y que sea 6 km alrededor de este punto, para ello calculamos las distancias que necesitamos para crear nuestra cuadrícula de ubicaciones en un sistema de coordenadas cartesianas 2D que nos permitirá luego calcular distancias en metros.
A continuación, proyectaremos estas coordenadas en grados de latitud / longitud para que se muestren en los mapas con Mapbox y Folium (3).
#!pip install shapely
import shapely.geometry
#!pip install pyproj
import pyproj
import math
def lonlat_to_xy(lon, lat):
proj_latlon = pyproj.Proj(proj='latlong',datum='WGS84')
proj_xy = pyproj.Proj(proj="utm", zone=33, datum='WGS84')
xy = pyproj.transform(proj_latlon, proj_xy, lon, lat)
return xy[0], xy[1]
def xy_to_lonlat(x, y):
proj_latlon = pyproj.Proj(proj='latlong',datum='WGS84')
proj_xy = pyproj.Proj(proj="utm", zone=33, datum='WGS84')
lonlat = pyproj.transform(proj_xy, proj_latlon, x, y)
return lonlat[0], lonlat[1]
def calc_xy_distance(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
return math.sqrt(dx*dx + dy*dy)
print('Verificación de las Coordenadas')
print('-------------------------------')
print('Centro de Buenos Aires longitud={}, latitud={}'.format(lyon_center[1], lyon_center[0]))
x, y = lonlat_to_xy(lyon_center[1], lyon_center[0])
print('Centro de Buenos Aires UTM X={}, Y={}'.format(x, y))
lo, la = xy_to_lonlat(x, y)
print('Centro de Buenos Aires longitud={}, latitud={}'.format(lo, la))
Creamos una cuadrícula hexagonal de celdas: desplazamos todas las líneas y ajustamos el espaciado de las líneas verticales para que cada centro de celda sea equidistante de todos sus vecinos.
caba_center_x, caba_center_y = lonlat_to_xy(caba_center[1], caba_center[0]) # City center in Cartesian coordinates
k = math.sqrt(3) / 2 # Vertical offset for hexagonal grid cells
x_min = caba_center_x - 6000
x_step = 600
y_min = caba_center_y - 6000 - (int(21/k)*k*600 - 12000)/2
y_step = 600 * k
latitudes = []
longitudes = []
distances_from_center = []
xs = []
ys = []
for i in range(0, int(21/k)):
y = y_min + i * y_step
x_offset = 300 if i%2==0 else 0
for j in range(0, 21):
x = x_min + j * x_step + x_offset
distance_from_center = calc_xy_distance(caba_center_x, caba_center_y, x, y)
if (distance_from_center <= 6001):
lon, lat = xy_to_lonlat(x, y)
latitudes.append(lat)
longitudes.append(lon)
distances_from_center.append(distance_from_center)
xs.append(x)
ys.append(y)
print(len(latitudes), 'Grilla del centro de la Ciudad de Buenos Aires - CABA')
Veamos los datos que tenemos hasta ahora: ubicación en el centro y los centros vecinales candidatos:
import folium
tileset = r'https://api.mapbox.com'
attribution = (r'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a>'
' contributors, Imagery © <a href="http://mapbox.com">MapBox</a>')
map_caba = folium.Map(location=caba_center, zoom_start=14, tiles=tileset, attr=attribution)
folium.Marker(caba_center, popup='Ciudad de Buenos Aires').add_to(map_caba)
for lat, lon in zip(latitudes, longitudes):
#folium.CircleMarker([lat, lon], radius=2, color='blue', fill=True, fill_color='blue', fill_opacity=1).add_to(map_lyon)
folium.Circle([lat, lon], radius=200, color='purple', fill=False).add_to(map_caba)
#folium.Marker([lat, lon]).add_to(map_caba)
map_caba
En este punto, ahora tenemos las coordenadas de los centros / áreas locales a evaluar, a la misma distancia (la distancia entre cada punto y sus vecinos es exactamente la misma) y aproximadamente a 4 km del centro de Buenos Aires.
def get_address(lat, lng):
#print('entering get address')
try:
#address = '{},{}'.format(lat, lng)
address = [lat, lng]
geolocator = Nominatim(user_agent="lyon_explorer")
location = geolocator.geocode(address)
#print(location[0])
return location[0]
except:
return 'nothing found'
addr = get_address(lyon_center[0], lyon_center[1])
print('Reverse geocoding check')
print('-----------------------')
print('Address of [{}, {}] is: {}'.format(caba_center[0], caba_center[1], addr))
print(type(location[0]))
print('Obteniendo Ubicaciones: ', end='')
addresses = []
for lat, lon in zip(latitudes, longitudes):
address = get_address(lat, lon)
if address is None:
address = 'NO ADDRESS'
address = address.replace(', Argentina', '')
addresses.append(address)
print(' .', end='')
print(' done.')
import pandas as pd
df_locations = pd.DataFrame({'Dirección': addresses,
'Latitud': latitudes,
'Longitud': longitudes,
'X': xs,
'Y': ys,
'Distancia desde el centroide': distances_from_center})
df_locations.head()
df_locations.shape
df_locations.to_pickle('./Dataset/caba_locations.pkl')
Ahora vamos a utilizar el API de Foursquare para explorar la cantidad de restaurantes disponibles dentro de estas cuadriculas y limitaremos la búsqueda a las categorías de alimentos para recuperar los datos de latitud y longitud de los restaurantes y de las cafeterías.
client_id = 'xxxxx'
client_secret = 'xxxxx'
VERSION = 'xxxxx'
Utilizamos la API de Foursquare para explorar la cantidad de restaurantes disponibles dentro de los 4 km del centro de Buenos Aires y limitar la búsqueda a todos los locales asociados con la categoría de restaurantes y especialmente aquellos que corresponden a las Cafeterías.
food_category = '4d4b7105d754a06374d81259' # 'Food' Catégorie de restaurant
caba_cafe_categories = ['4bf58dd8d48988d143941735', '4bf58dd8d48988d128941735', '4bf58dd8d48988d16d941735',
'4bf58dd8d48988d1e0931735', '4bf58dd8d48988d147941735'] # 'Food' Catégorie de restaurants cafe
def is_restaurant(categories, specific_filter=None):
restaurant_words = ['restaurant']
restaurant = False
specific = False
for c in categories:
category_name = c[0].lower()
category_id = c[1]
for r in restaurant_words:
if r in category_name:
restaurant = True
if 'Restaurante' in category_name:
restaurant = False
if not(specific_filter is None) and (category_id in specific_filter):
specific = True
restaurant = True
return restaurant, specific
def get_categories(categories):
return [(cat['name'], cat['id']) for cat in categories]
def format_address(location):
address = ', '.join(location['formattedAddress'])
address = address.replace(', Argentina', '')
address = address.replace(', Argentina', '')
return address
def get_venues_near_location(lat, lon, category, client_id, client_secret, radius=500, limit=1000):
version = '20180724'
url = 'https://api.foursquare.com/v2/venues/explore?client_id={}&client_secret={}&v={}&ll={},{}&categoryId={}&radius={}&limit={}'.format(
client_id, client_secret, version, lat, lon, category, radius, limit)
try:
results = requests.get(url).json()['response']['groups'][0]['items']
venues = [(item['venue']['id'],
item['venue']['name'],
get_categories(item['venue']['categories']),
(item['venue']['location']['lat'], item['venue']['location']['lng']),
format_address(item['venue']['location']),
item['venue']['location']['distance']) for item in results]
except:
venues = []
return venues
# Passons maintenant aux emplacements de nos quartiers et trouvons des restaurants à proximité. nous allons également maintenir un dictionnaire de tous les restaurants trouvés et de tous les restaurants français trouvés
import pickle
def get_restaurants(lats, lons):
restaurants = {}
caba_cafe = {}
location_restaurants = []
print('Obtención de los candidatos', end='')
for lat, lon in zip(lats, lons):
# En utilisant rayon = 350 mts, assurez-vous que nous avons des recouvrements / une couverture complète afin que nous ne manquions aucun restaurant (nous utilisons des dictionnaires pour supprimer tout doublon résultant de chevauchements de zones).
venues = get_venues_near_location(lat, lon, food_category, client_id, client_secret, radius=350, limit=100)
area_restaurants = []
for venue in venues:
venue_id = venue[0]
venue_name = venue[1]
venue_categories = venue[2]
venue_latlon = venue[3]
venue_address = venue[4]
venue_distance = venue[5]
is_res, is_cafe = is_restaurant(venue_categories, specific_filter=caba_cafe_categories)
if is_res:
x, y = lonlat_to_xy(venue_latlon[1], venue_latlon[0])
restaurant = (venue_id, venue_name, venue_latlon[0], venue_latlon[1], venue_address, venue_distance, is_cafe, x, y)
if venue_distance<=300:
area_restaurants.append(restaurant)
restaurants[venue_id] = restaurant
if is_cafe:
caba_cafe[venue_id] = restaurant
location_restaurants.append(area_restaurants)
print(' .', end='')
print(' done.')
return restaurants, caba_cafe, location_restaurants
# Essayez de charger à partir du système de fichiers local au cas où nous l'avions déjà fait
restaurants = {}
caba_cafe = {}
location_restaurants = []
loaded = False
try:
with open('/Dataset/restaurants_350.pkl', 'rb') as f:
restaurants = pickle.load(f)
print('Restaurant data loaded.')
with open('/Dataset/caba_cafe_350.pkl', 'rb') as f:
caba_cafe = pickle.load(f)
print('Descargando Datos de las Cafeterías')
with open('/Dataset/location_restaurants_350.pkl', 'rb') as f:
location_restaurants = pickle.load(f)
print('Descargando datos de Restaurantes de la Ciudad de Buenos Aires')
loaded = True
except:
print('Datos de Restaurantes Descargandose')
pass
# Si le chargement échoue, utilisez l'API Foursquare pour obtenir les données.
if not loaded:
restaurants, caba_cafe, location_restaurants = get_restaurants(latitudes, longitudes)
print('**Resultados**',)
print('Número Total de Restaurantes:', len(restaurants))
print('Número Total de Cafeterías:', len(caba_cafe))
print('Porcentaje de Cafeterías: {:.2f}%'.format(len(caba_cafe) / len(restaurants) * 100))
print('Promedio de locales por cuadrícula:', np.array([len(r) for r in location_restaurants]).mean())
print('Lista de Todos los Restaurantes')
print('-----------------------')
for r in list(restaurants.values())[:10]:
print(r)
print('...')
print('Total:', len(restaurants))
print('Lista de todas las Cafeterías')
print('---------------------------')
for r in list(caba_cafe.values())[:10]:
print(r)
print('...')
print('Total:', len(caba_cafe))
print('Restaurantes de Autor')
print('---------------------------')
for i in range(100, 110):
rs = location_restaurants[i][:8]
names = ', '.join([r[1] for r in rs])
print('Restaurants around location {}: {}'.format(i+1, names))
Todos los restaurantes en la ciudad de Buenos Aires están indicados en gris y aquellos asociados a cafeterías los vamos a resaltar en rojo.
tileset = r'https://api.mapbox.com/styles/v1/roqueleal08/cjyaey84d07zq1crze5r08yg1/tiles/256/{z}/{x}/{y}@2x?access_token=pk.eyJ1Ijoicm9xdWVsZWFsMDgiLCJhIjoiY2ppZzl5NWo2MTVmMTNrcGU0enR0ZTU2MyJ9.4ZWYdzUlqvIQwwSIR50xZA'
attribution = (r'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a>'
' contributors, Imagery © <a href="http://mapbox.com">MapBox</a>')
map_caba = folium.Map(location=caba_center, zoom_start=13, tiles=tileset, attr=attribution)
folium.Marker(caba_center, popup='Buenos Aires').add_to(map_caba)
for res in restaurants.values():
lat = res[2]; lon = res[3]
is_cafe = res[6]
color = 'red' if is_cafe else 'grey'
folium.CircleMarker([lat, lon], radius=3, color=color, fill=True, fill_color=color, fill_opacity=1).add_to(map_caba)
map_caba