Web Scraping for Data Analysis in Python (Parte IV): Geoencoding


En el artículo anterior obtuvimos todos los datos estructurados que el portal web nos podía ofrecer. No obstante, podemos ir mucho (mucho) más allá aprovechando el resto de la web como fuente de datos. Una de las características más importantes que podemos desear conocer es la posición espacial que ocupa el item. Conocer las coordenadas exactas (o aproximadas) de un dato nos abre la puerta a un océano de datos espaciales con los que cruzar nuestros datos.

En nuestro caso de estudio, los pisos no cuentan con dicha información de forma fácilmente extraíble, pero si conocemos el nombre de la calle o la zona. Esto nos permite aplicar una técnica conocida como geoencoding: traducir de nombres a lugares.

Presentando: Nomatim (by OSM)

Nomatim es un servicio abierto de geoencoding basado en Open Street Maps. El servicio ofrece búsquedas ilimitadas y gratuitas siempre que se respeten unos acuerdos de uso, que restringen, entre otras cosas, la frecuencia de las peticiones.

Configuración

Los pre-requisitos en este caso serán:

  • python==3.6
  • requests==2.22.0

La librería requests, de hecho, no sería estrictamente necesaria, pero facilitará enormemente la realización de las peticiones al servidor.

Funcionamiento

La API de Nomatim

El servicio expone una API sencilla con la que podremos consultar por nombres de calles y plazas siguiendo un formato específico:

GET https://nominatim.openstreetmap.org/search?q=
	${Nombre+De+La+Calle}
	&polygon_geojson=1
	&viewbox=${latlon_left}%${latlon_top}%2C${latlon_right}%2${latlon_bot}
	&format=json

El resultado será un objeto JSON (ya que así ha sido solicitado en la API) con varios campos de interés como el display_name que permite supervisar que el resultado es el esperado, o los campos lat y lon que, como sus nombres indican, contienen las coordenadas del centroide de la geometría. Esto puede suponer un problema en el caso de calles largas, pero es la mejor aproximación que se puede realizar sin tener los números de los portales.

El código python para llamar a la API para nuestro caso de estudio:

url = 'https://nominatim.openstreetmap.org/search?q=' + \
	query.strip().replace(' ', '+')+'+Zaragoza+Aragon+Spain' + \
	'&polygon_geojson=1' + \
	'&viewbox=-1.30875%2C41.77592%2C-0.43327%2C41.51937' + \
	'&format=json'

headers = {
	'User-Agent': 'Python Script for Economics Study',
}

results = requests.get(url, headers=headers).json()

Algoritmo

El algoritmo a ejecutar no entraña misterio alguno:

  1. Cargar el fichero CSV con todos los items
  2. Iterar, para cada item 1.
    1. Extraer su dirección
      1. “Limpiar” dicha dirección
      2. Realizar una búsqueda en nominatim
      3. Comprobar si, entre la lista de resultados, hay un resultado válido (dentro de un área razonable, por ejemplo)
      4. Si lo hay, continuar con el siguiente item
      5. Si no la hay, probar con otra “limpieza” y volver al principio de este bucle

Limpieza de datos

El filtrado de nombres es uno de los pasos más artesanales y más guiados por la prueba-error de todo el proceso. Se han detectado que los términos que más empeoran la búsqueda son ‘calle’, ‘plaza’, ‘avenida’ y ‘paseo’. La estrategia es detectar estos términos y eliminarlos del nombre.

También se han observado problemas con el artículo ‘de’, por lo que otro de los “niveles de limpieza” implica eliminar dichos artículos.

El código de limpieza de nombres se puede condensar en las siguientes dos funciones.

def rip(query):
	for pattern in ['calle', 'plaza', 'avenida', 'paseo']:
		if pattern in query:
			return _rip_pattern(pattern, query)
	return query

def _rip_pattern(pattern, query):
		if pattern+' de' not in query:
			return query.replace(pattern, pattern+' de')
		if pattern+' de' in query:
			return query.replace(pattern+' de', '')
		return query

Al final del proceso, se han logrado ubicar cerca del 80% de los pisos de nuestro caso práctico combinando las distintas técnicas de “limpieza”.