lunes, 4 de mayo de 2020

Teoría de Redes Complejas (Parte 1). Conceptos básicos


Para continuar con nuestro proyecto vamos a hacer un estudio aplicando teoría de grafos y de redes complejas con el objetivo de demostrar si las redes de bicicletas pueden asemejarse a una red compleja o no.  Para el análisis estructural de la red, necesitaremos calcular algunos atributos como:

  • Número total de aristas.
  • Secuencia de grados.
  • Grado medio.
  • Distribución de probabilidad de los grados.
  • Camino mínimo entre dos nodos.
  • Distancia, longitud promedio de caminos y diámetro del grafo.
  • Número de agrupaciones conexas.
  • Coeficiente de agrupamiento.
  • Transitividad.

Empezaremos explicando la estructura de datos que almacenará el grafo y luego los métodos que calculan estos atributos.

ESTRUCTURA DE DATOS


La estructura que usaremos para almacenar un grafo será una matriz de adyacencia de dimensiones NxN siendo N el número de nodos del grafo. 
  • Cada posición (j,i) de la matriz tendrá un valor numérico (peso) que será infinito cuando no haya arista entre los nodos j e i. Si el grafo es no pesado, los valores posibles serán 1 o infinito y si es pesado, podrán contener cualquier valor mayor que 0 (no consideramos pesos negativos).
  • No se considerarán lazos, es decir, aristas que tengan origen y destino en el mismo nodo.

Un ejemplo de grafo dirigido no pesado podría ser el siguiente:




NÚMERO TOTAL DE ARISTAS


El cálculo del número de aristas consiste contar cuántas celdas de la matriz de adyacencia almacenan un valor distinto a infinito. En caso de que el grafo sea no dirigido, debemos tener en cuenta que hay que dividir el valor resultante a la mitad. El código que usaría un grafo dirigido es:





SECUENCIA DE GRADOS


El grado de un nodo es el número de aristas en las que participa. Si el grafo es dirigido, distinguimos entre grado entrante (número de aristas en las que el nodo participa como destino) y grado saliente (número de aristas en las que el nodo participa como origen). La secuencia de grados es la lista que contiene los grados de todos los nodos de la red. Si grafo es dirigido, tenemos que diferenciar entre secuencia de grados entrante y secuencia de grados saliente. Si el grafo es no dirigido, las secuencia entrante y saliente coinciden.



El código que hace esto es:




GRADO MEDIO


El grado medio es la sumatoria de la secuencia de grados entre el número de nodos del grafo. Si grafo es dirigido, tendremos que diferenciar entre grado medio entrante y grado medio saliente. El código que calcula esto es: 




DISTRIBUCIÓN DE PROBABILIDAD DE LOS GRADOS


La distribución de probabilidad de los grados es una lista que contiene en la posición i-ésima la probabilidad de que un nodo de la red escogido al azar sea de grado i. Podemos hacer el cálculo como si estuviéramos haciendo un histograma de frecuencias y después dividir cada posición por el número de nodos de la red. El código en cuestión es:




CAMINO MÍNIMO ENTRE DOS NODOS


El camino entre dos nodos j e i se define como la secuencia de nodos/aristas que hay que visitar para llegar desde el nodo j al i. El camino mínimo será aquel que visite menos nodos/aristas. Para calcular el camino entre cada par de nodos del grafo, hemos implementado el algoritmo de Floyd.

Se trata de un algoritmo que utiliza dos matrices como memoria dinámica. Una matriz se usa para determinar cuál es el coste mínimo entre dos nodos (la llamaremos D de distancia) y otra para la secuencia de nodos que se deben visitar para ir de uno a otro (la llamaremos C de camino).

En la Wikipedia podéis obtener más datos sobre el algoritmo, incluso un pseudocódigo, un código en C++ y Java y una traza de ejecución. (enlace).


DISTANCIA, LONGITUD PROMEDIO DE CAMINOS Y DIÁMETRO DE UNA RED


Los tres conceptos que presentamos ahora están muy relacionados entre ellos y se apoyan todos en el cálculo de camino mínimo entre dos nodos de la red:

  • La distancia entre dos nodos es la longitud del camino mínimo entre ambos. 
  • La longitud promedio es el promedio de las distancias más pequeñas entre cualquier par de nodos de la red.
  • El diámetro de la red es el máximo de las distancias más cortas entre cualquier par de nodos de la red.

En nuestro código, para obtener estos atributos, solo tendremos que consultar la matriz D (la definimos antes como la matriz que contiene el coste mínimo de camino entre cada par de nodos de la red).

Para la distancia entre dos nodos, solo tenemos que indexar en la matriz D, para la longitud promedio, hacemos el promedio de los valores de D y para el diámetro de la red, buscamos el máximo valor de D.

El código de la longitud promedio será:



Y el del diámetro de la red:




NÚMERO DE AGRUPACIONES CONEXAS


Una agrupación conexa es un subconjunto de los nodos donde cada nodo está conectado al menos a otro nodo del subconjunto. La forma de calcularlo consiste en considerar inicialmente que todos los nodos están desconectados e ir agrupando en base a los vecinos de cada nodo. El código que permite hacer esto es el siguiente:




COEFICIENTE DE AGRUPAMIENTO


Es una medida del grado en el que los nodos de una red tienden a agruparse entre ellos. El coeficiente de agrupamiento del nodo i-ésimo se calcula como el número de pares (l,m) de nodos vecinos de i conectados por aristas dividido por el número total de pares que podrían existir de nodos vecinos de i. En definitiva, se puede asimilar al número de triángulos que se pueden formar con el nodo i.

A modo de ejemplo, vamos a mostrar un grafo con los triángulos que se pueden formar en él.


Los triángulos se forman considerando aristas dirigidas, es decir, si hay dos aristas entre dos nodos, como ocurre entre el nodo 4 y 2, se contabiliza como dos triángulos. Los triángulos para este grafo son:



El código que permite hacer este cálculo es:





TRANSITIVIDAD


También se puede llamar como coeficiente de agrupamiento global y se calcula como el número de triángulos que existen en la red dividido por el número de tripletes. Un triplete es un subgrafo con tres nodos y dos aristas.

El número de triángulos se puede calcular como:



El número de tripletes se puede obtener como:



Una vez tenemos todos estos atributos, podremos empezar nuestro análisis estructural. En los siguiente post, pondremos a prueba algunas propiedades de redes complejas y redes aleatorias y aplicaremos los mismos tests sobre nuestras redes de bicicletas.



viernes, 27 de marzo de 2020

Estudio estático. Comparación de ciudades según la evolución del número de agrupaciones conexas

En el último post, explicamos en qué consistía una agrupación conexa dentro de un grafo, visualizamos de forma gráfica cómo evolucionaba el número de agrupaciones conexas conforme aumentábamos el radio de influencia de cada nodo y estudiamos si había correlación lineal al aplicar logaritmos a ambos ejes. 

En esta ocasión, vamos a comprobar si hay ciudades que presenten un comportamiento similar en este experimento para tratar de establecer similitudes entre las redes de bicicletas. Para ello, vamos a estudiar la correlación que presentan las curvas de cada una de las ciudades.

Para el cálculo de la correlación, es necesario que las curvas estén normalizadas tanto en el eje X (número de agrupaciones conexas) como en el eje Y (porcentaje de diámetro que se toma como radio). Por tanto, será necesario hacer un procesamiento previo de los datos.


NORMALIZAR EL EJE Y


Normalizar el eje Y es una tarea sencilla. Solo tenemos que dividir el número de agrupaciones conexas entre el número total de estaciones que tiene la red. Si el número de agrupaciones conexas es igual al número de estaciones, el resultado de la división será 1 (valor máximo posible) y, a medida que el número de agrupaciones disminuya, el resultado de la división tenderá a 0 (valor mínimo posible).



NORMALIZAR EL EJE X


Para normalizar el eje X necesitamos conocer cuál es el porcentaje del diámetro que hace que el número de agrupaciones conexas sea 1. Este valor no lo tenemos, así que, debemos diseñar un algoritmo que lo calcule. 

Para la investigación que estamos haciendo, no es necesario saber el valor exacto, nos basta con una buena aproximación. Si nos fijamos en cómo son las curvas, veremos que siempre son decrecientes. Podemos apoyarnos en esto para construir nuestro algoritmo.



La idea consiste en hacer un algoritmo en dos pasadas:
  • Primera pasada (de derecha a izquierda): escogemos un valor de porcentaje que sea muy alto, de manera que nos aseguremos de que, con ese porcentaje, el número de agrupaciones conexas es 1, por ejemplo, 60%. A continuación, vamos reduciendo el porcentaje una cantidad constante (step 1), por ejemplo, 1%, hasta que el número de agrupaciones conexas es mayor que 1.
  • Segunda pasada (de izquierda a derecha): incrementamos el valor de porcentaje que hemos obtenido de la primera pasada una cantidad constante (step 2), por ejemplo, 0.01%, hasta que el número de agrupaciones conexas sea 1. 
Cuanto menor sea el valor de step 2, más preciso será el resultado que se obtiene. Podríamos haber planteado un algoritmo de una única pasada empezando desde el 0% e incrementando una cantidad pequeña, si bien, el coste computacional es mucho más alto. 

Para que poder establecer la correlación, todas las ciudades deben tener la misma cantidad de valores en el eje X. Así que, vamos a tomar N valores comprendidos entre (0, Pmax] siendo Pmax el porcentaje del diámetro donde el número de agrupaciones conexas es 1. 

Supongamos que N = 100. Esto quiere decir que para cada ciudad tenemos 100 muestras en el eje X y que la primera de estas muestras es el 1% del porcentaje que hace que el número de agrupaciones conexas sea 1.

De esta forma, ya tendríamos todos nuestros datos normalizados.


MATRIZ DE CORRELACIÓN DE PEARSON


Para el calculo de la correlación, vamos a usar una función incorporada dentro de la librería de Pandas en Python. Para usarla, tenemos que crear un DataFrame que tenga una columna por cada ciudad y una fila para cada valor de agrupaciones conexas. Si tenemos 83 ciudades y hemos tomado un valor de N = 100, tendremos un DataFrame de 83 ciudades y 100 filas. En el resultado que se va a mostrar, se ha tomado un valor de N = 1000.

Importante: debemos ordenar las ciudades según el número de estaciones para que podamos estudiar si la correlación depende del número de estaciones. 

Si nuestro DataFrame se llama df, la función a aplicar sería:

df.corr(method='pearson')

El resultado es otro DataFrame que representa una matriz cuadrada que tendrá tantas filas y columnas como ciudades, es decir, 83x83. Si visualizamos el resultado como un mapa de calor, lo que se obtiene es:


Las ciudades están ordenadas según el número de estaciones de menor a mayor, las columnas situadas más a la izquierda tienen menor número de estaciones que las situadas a la derecha.

A primera vista, hay correlación alta entre muchas ciudades pero, no hay un dependencia aparente con el número de estaciones de la ciudad.



domingo, 1 de marzo de 2020

Estudio estático. Recopilación de datasets


Para llevar a cabo un estudio de un sistema, ya sea de bicicletas o de cualquier otra índole, es necesario tener a nuestra disposición una gran cantidad de datos con los que trabajar. Para nuestro proyecto, necesitamos conocer la localización geográfica de las estaciones de bici de diferentes ciudades. 


DATOS EN FORMATO .CSV


Si buscamos "bikesharing data" en Internet, encontraremos varios portales web de empresas de alquiler de bicicletas con una sección de Open Data donde podemos obtener estos datos de forma gratuita y podemos usarlos sin ningún tipo de restricción. Algunos de estos sitios web son:
Estos datos suelen aparecer en formato .csv y muchos de ellos siguen el formato especificado por la GBFS (General Bikeshare Feed Specification). Algunos de estos sitios no solo nos facilitan la localización de las estaciones de bici sino que además mantienen un registro abierto y accesible de la actividad de cada una de estas estaciones. Podemos saber cuándo y dónde un usuario alquiló una bici, cuánto tiempo la ha estado usando y dónde la ha depositado al terminar, entre otras cosas.

Además, existe una página web llamada bikeshare.com que nos redirige a diferentes repositorios donde podemos obtener también información de la actividad de las estaciones que puede ser útil para hacer un estudio dinámico de la red. El sitio en cuestión es este. En alguno de los enlaces que hemos mostrado antes, también es posible conseguir datos de los trayectos que han hecho los usuarios.



TRADUCCIÓN DE DIRECCIONES CON GEOPY


El primer problema que aparece con estos datos es que no todos nos indican las coordenadas de longitud y latitud de las estaciones sino que utilizan el nombre de la calle en la que se encuentran. Debemos mapear las direcciones a coordenadas de latitud y longitud para nuestro estudio. 

Para ello, vamos a usar una librería de Python llamada Geopy que permite la traducción de nombres de calles a coordenadas y viceversa. Además, podemos solicitar a Geopy que nos calcule la distancia entre dos puntos. Esto puede ser interesante porque así podemos saber con mayor exactitud la distancia entre dos estaciones en lugar de basarnos en la distancia euclidiana.

La documentación oficial de Geopy la podemos encontrar en el siguiente enlace. En ella se nos enseña a utilizar las funciones de Geopy mediante ejemplos que podemos copiar en nuestro programa para hacer las traducciones de direcciones. Cabe destacar que, en nuestro caso, queremos traducir un dataset completo de direcciones, es decir, vamos a tener que hacer muchas peticiones al servidor de Geopy. 

Como Geopy es abierto para toda la comunidad, no podemos bombardear sus servidores con peticiones muy seguidas, debemos dejar un intervalo de tiempo entre ellas o el servidor nos bloqueará. La documentación oficial contempla esta situación y explica la forma correcta de hacer muchas peticiones consecutivas en el apartado Usage with Pandas donde se explica el uso de la clase RateLimit.

Si tenemos alguna duda y preferimos el clásico tutorial de Youtube, aquí dejo el que usé yo (click aquí).


DATOS EN FORMATO .JSON


Hasta ahora, la mayoría de los sitios de los que habíamos conseguido información se encontraban en EEUU. Para obtener información de sistemas de bicicletas por todo el mundo, podemos visitar la página de bikeshare-research.org. Este sitio web recopila información de redes de alquiler de bicicletas y la ofrece a través de una API.


Dentro de esta página, si seleccionamos un país y una ciudad, podemos ver un apartado llamado CityBikes feed donde puede aparecer un enlace a un fichero json con la información geoespacial de las estaciones de bici de la ciudad. En todas las ciudades no aparece este enlace pero en muchas de ellas sí.

Es posible que a través de la API se pueda obtener más información de la que aparece en el json. De hecho, algunos atributos indican el número de bicicletas disponibles en la estación y el número de huecos. Así que, esta API también puede ser interesante para hacer un estudio dinámico. Además, también cabe la posibilidad de que podamos obtener los datos de las ciudades que no tienen un json vinculado mediante la API.

Cabe mencionar que la información geoespacial de las estaciones aparece expresada en un formato con el que no estamos familiarizados y será necesario estudiar cómo traducir los valores de latitud y longitud para poder representarlos y estudiarlos.

RESULTADOS


Después de extraer toda la información, hemos realizado un conteo del número de estaciones por ciudad y hemos elaborado un ranking con todas las ciudades. El resultado se muestra a continuación:

En total hay 84 ciudades.

place num_stations
Paris_Francia 1398
NewYork_EEUU 936
Londres_ReinoUnido 781
Boston_Canada 619
Chicago_EEUU 612
Washington_EEUU 579
Toronto_Canada 463
Helsinki_Finlandia 450
Barcelona_Spain 444
SanFrancisco_EEUU 430
Lyon_Francia 415
Bruselas_Belgica 354
Warsaw_Polonia 344
Boston_EEUU 330
Milan_Italia 302
Antwerp_Belgica 301
Toulouse_Francia 288
RioDeJaneiro_Brasil 262
Sevilla_Spain 260
Madrid_Spain 214
Turin_Italia 206
Vancouver_Canada 200
Nice_Francia 176
StPetesburg_EEUU 174
SaoPaulo_Brasil 154
Brisbane_Australia 151
Miami_EEUU 146
Budapest_Hungria 144
Philadelphia_EEUU 141
Zaragoza_Spain 130
Marseille_Francia 123
Nantes_Francia 121
Viena_Austria 120
Dublin_Irlanda 110
Houston_EEUU 108
Pittsburgh_EEUU 101
Edimburgo_ReinoUnido 97
Luxemburgo_Luxemburgo 92
Columbus_EEUU 83
Recife_Brasil 77
Denver_EEUU 76
Goteborg_Suecia 74
AustinTexas_EEUU 74
Omaha_EEUU 70
Glasgow_ReinoUnido 67
Ljubljana_Eslovenia 61
SanAntonio_EEUU 61
Calais_Francia 56
ClermontFerrand_Francia 52
Caen_Francia 50
AbuDhabi_EmiratosArabes 50
Indianapolis_EEUU 50
Belfast_ReinoUnido 47
Madison_EEUU 46
FortWorth_EEUU 46
Boulder_EEUU 45
SaltLake_EEUU 45
CergyPontoise_Francia 43
Valence_Francia 43
Chattanooga_EEUU 42
Mulhouse_Francia 41
Kansas_EEUU 41
Vilnius_Lithuania 36
Cork_Irlanda 33
SaintEtienne_Francia 31
Besacon_Francia 30
Charlotte_EEUU 29
Namur_Belgica 28
Nancy_Francia 28
Amiens_Francia 27
Dayton_EEUU 27
Rouen_Francia 25
FortLauderdale_EEUU 23
Limerick_Irlanda 23
Toyama_Japon 22
DesMoines_EEUU 22
ElPaso_EEUU 18
Lund_Suecia 17
Santander_Spain 16
Fargo_EEUU 11
Creteil_Francia 10
Kazan_Rusia 7
Spartanburg_EEUU 6
Lillestrom_Noruega 6




domingo, 1 de diciembre de 2019

Introducción a Cartopy


Cartopy es una librería de Python que permite mostrar mapas de todas las partes del mundo con distintos niveles de detalle y en diferentes sistemas de coordenadas. En este post presentaremos muy por encima esta herramienta y mostraremos resultados sencillos que se pueden obtener con unas pocas líneas de código.

Preparativos

Para usar esta librería os recomiendo hacer lo siguiente:
  • Crear un nuevo entorno virtual en Anaconda para instalar los paquetes que se requieren (opcional).
  • Instalar Cartopy con el comando: conda install -c conda-forge cartopy
  • Instalar nb_conda con el comando: conda install nb_conda para usar Jupyter Notebook.

Importación de librerías

Para mostrar los mapas, vamos a necesitar Matplotlib y Cartopy:


¡Descubriendo América!

Vamos a visualizar el mapa de EEUU con características muy concretas. Queremos:
  • Que se muestre el suelo.
  • Que se muestre el océano alrededor.
  • Que se resalte la línea de costa.
  • Que añada un borde punteado entre los países.
  • Que se muestren los lagos.
El código que hace esto es:



El método add_feature es lo que nos permite incluir los detalles de nuestro mapa. Os dejo el siguiente enlace donde podréis encontrar otras características que se pueden añadir de esta forma: link. El resultado que se obtiene es:



En cartopy.feature tenemos algunas características que podemos añadir simplemente con llamar a una constante como hemos hecho antes pero, estas características son muy limitadas y simples. Podemos personalizar los mapas entrando más a fondo en cartopy.feature y llamando a alguno de sus métodos de diseño. Por ejemplo, vamos a mostrar las divisiones entre estados de EEUU. Al código anterior le tenemos que añadir:



Hemos decidido separar los estados con líneas punteadas ('dotted') de color negro. Los resultados que hemos obtenido son:



Podemos también centrarnos en una región en concreto para estudiarla, es decir, hacer un zoom en el mapa. Vamos a hacerlo pero debemos tener en cuenta que nuestro mapa tiene muy poco detalle, así que, lo que veremos será la separación entre los estados. Lo que debemos hacer es usar el método set_extent con una ventana más pequeña manteniendo el tamaño del figure. Por ejemplo:




Hemos incluido una malla que va a extenderse por todo nuestro mapa. El resultado que se obtiene es:



Estos mapas que estamos mostrando no dejan de ser gráficos sobre ventanas tipo figure de la librería Matplotlib, así que, podemos pintar sobre ellos lo que queramos: puntos, líneas, áreas... Para ilustrar esto, vamos a pintar un punto encima. Lo único que tenemos que añadir es:


Los parámetros que se usan son:
  • Los valores de longitud y latitud del punto.
  • El sistema de coordenadas.
  • La forma que debe tener el punto ('marker'). En este caso una estrella '*'.
  • El tamaño del punto ('s' de 'size'). En este caso 400.
  • El color del punto ('c' de 'color'). En este caso 'y' de 'yellow'.
El resultado que se obtiene es:



Como se puede ver, Cartopy es una librería que permite dibujar mapas muy rápidamente y que puede ser muy útil para estudios geográficos, meteorológicos, climáticos... Si queréis tener más información sobre la librería, os recomiendo visitar la documentación oficial en el siguiente link: click aquí.

Un saludo!

lunes, 11 de noviembre de 2019

Investigación de librerías de geolocalización

INVESTIGACIÓN DE LIBRERÍAS DE GEOLOCALIZACIÓN

Uno de los problemas más comunes a la hora de trabajar con grandes volúmenes de datos es saber interpretarlos correctamente y darles un tratamiento adecuado. Por ello, los ingenieros requieren del uso de gráficos y estadísticos que les faciliten la tarea de análisis. A día de hoy, existen multitud de librerías creadas para el procesamiento y visualización de datos y en este post vamos a investigar algunas de ellas.

En nuestro proyecto estudiamos subsistemas de movilidad, en concreto, la movilidad urbana con bicis. De este sistema, tenemos información de la posición que ocupa cada estación en el mapa geográfico. Así que, nos será de mucha ayuda mostrar cada estación en un mapa de calles y sobre él, empezar a visualizar más datos. Por ejemplo: área de influencia de una estación, movimientos de bicis, zonas con mayor concentración de bicis...



BASEMAP

Basemap no es exactamente una librería sino que se trata de una extensión de Matplotlib. Facilita el trabajo a la hora de mostrar información geográfica, hacer transformaciones entre diferentes tipos de proyecciones geográficas y trabajar con gráficos vectoriales. Sin embargo, se trata de una extensión que va a ser declarada obsoleta para dejar paso a un nuevo proyecto llamado Cartopy. 

Si queréis investigar cómo funciona Basemap, os muestro a continuación un ejemplo de gráfico que se puede generar con Basemap y un enlace a un tutorial (click aquí).



CARTOPY

Cartopy es una librería que entra para sustituir a Basemap. Ofrece las funcionalidades que ya ofrecía su antecesor y añade mejoras en el procesamiento de información vectorial gracias a la integración de Shapely, una librería diseñada para el análisis y manipulación de objetos geométricos planos. Cartopy se encuentra todavía en desarrollo pero ya se pueden generar imágenes como la que se muestra a continuación.

Esta imagen ha sido generada como ejemplo de introducción a Cartopy. Podéis visitar el tutorial que desarrolla este gráfico en el siguiente enlace (click aquí).


OPEN STREET MAP (OSM)

Hasta ahora, los dos ejemplos que hemos visto trabajan con mapas geográficos. Sin embargo, ninguno de ellos se centra en ciudades y calles concretas sino que se dedican a un estudio más global. Para realizar una investigación exhaustiva de las calles de una ciudad, existe una librería llamada Open Street Map.

Open Street Map es un proyecto de software libre a escala global que pretende mapear todos los aspectos importantes de las ciudades: calles, carreteras, carriles bici, edificios, puntos de interés, colegios, hospitales... Cuenta con miles de personas involucradas que se dedican a mantener información actualizada y a facilitar su uso en aplicaciones y proyectos de investigación.



OPEN STREET MAP NX (OSMnx)

Open Street Map NX se trata de una librería auxiliar que nos va a facilitar el trabajo con Open Street Map. Permite obtener datos de las ciudades desde los repositorios oficiales de OSM de forma muy sencilla con unas pocas líneas de código. 

Después de investigar varias alternativas, vemos que ésta es la que mejor se adapta a nuestras necesidades, así que, será la que utilicemos a lo largo del proyecto. Como ejemplo, os muestro un mapa de calles extraído desde OSM usando OSMnx. Os dejo también el tutorial que se ha seguido para generar esta imagen (click aquí) y una página web donde se explican más ejemplos (click aquí).



domingo, 3 de noviembre de 2019

Comenzando en el análisis y extracción de datos. Diagrama de Voronoi

¿QUÉ DATOS TENGO A MI DISPOSICIÓN?

Cuando empezamos un proyecto de análisis de datos debemos estructurar la información disponible y fijar los aspectos que queremos observar. En esta ocasión, estudiaremos las estaciones de bici de diferentes ciudades del mundo. La información de cada estación es la siguiente:
  • Ciudad a la que pertenece.
  • Identificador único para cada estación dentro de una ciudad.
  • Dirección de la calle en la que se encuentra.
  • Valores de longitud y latitud para situarla geográficamente.
  • Capacidad máxima de bicis que puede albergar.
  • Bicis disponibles.
De todos los atributos que hemos mencionado, lo único que varía a lo largo del tiempo son las bicis disponibles. Cada cinco minutos, las estaciones envían a una base de datos cuántas bicis tienen en ese momento. De esta forma, podemos saber cómo evoluciona el número de bicis a lo largo de un día, una semana, un mes, un año...




¿QUÉ PRETENDEMOS OBSERVAR?

Según lo que queramos observar, tendremos que procesar los datos de una forma u otra. Vamos a empezar con una pequeña aproximación a nuestro sistema de bicis. En primer lugar, podría ser de interés saber qué zonas de la ciudad tienen más estaciones de bicis. Es de esperar que en las zonas céntricas halla más estaciones, pero, ¿serán suficientes? ¿Habrá zonas sin estaciones cerca? Vamos a comprobarlo.

Empezaremos segmentando el mapa de la ciudad y para ello, necesitaremos implementar algunas herramientas informáticas que nos faciliten la tarea. Vamos a utilizar un diagrama de Voronoi.

DIAGRAMA DE VORONOI

Un diagrama de Voronoi es una malla como la que se muestra a continuación:


Los puntos azules son los puntos de Voronoi y se utilizan para crear las diferentes regiones de la malla. El proceso que se sigue para crear la malla es el siguiente:
  1. Se genera una nube de puntos. 
  2. Se trazan las mediatrices entre los puntos de la nube. 
  3. Los vértices de las regiones de la malla son los puntos donde corten las mediatrices.
Nota: Dados dos puntos A y B, la mediatriz es la recta que divide por la mitad el segmento que une  A y B. Se cumple que los puntos de la mediatriz equidistan de A y B.

La nube de puntos constituye el conjunto de puntos de Voronoi. Entre cada par de puntos (P1, P2) existe una frontera que equidista de (P1, P2). De esta manera, si estudiamos una muestra dentro de una región de Voronoi, sabemos que el punto de Voronoi más cercano es aquel que se encuentra también dentro de esa región. Veamoslo gráficamente:



P1 es el punto de Voronoi más cercano a cualquier punto de la región coloreada. Si utilizamos los barrios de una ciudad como puntos de Voronoi, podemos averiguar cuántas estaciones de bici hay por cada barrio y así conocer cuáles son las zonas con más y menos densidad de bicis. Del mismo modo, si consideramos las estaciones de bici como puntos de Voronoi, podemos ver el área de influencia de cada estación. 

HERRAMIENTAS INFORMÁTICAS 

Para construir el diagrama de Voronoi vamos a usar:
  • Python como lenguaje de programación. 
  • Jupyter como entorno de trabajo.
  • Anaconda como gestor de paquetes.
  • scipy.spatial.Voronoi como librería.
No vamos a implementar el diagrama desde cero, vamos a apoyarnos en una librería que ya lo genera y vamos a utilizarlo para nuestra aplicación. Tendremos que añadir algunas líneas de código para el análisis que queremos llevar a cabo, pero partimos de una base.

Nota: si no estás familiarizado con Python, Jupyter o Anaconda, tengo un post dedicado para hacer la configuración del entorno de desarrollo que vamos a usar a lo largo del proyecto. Link: click aquí.

Nota (II): para el código que se va a mostrar, se recomienda estar familiarizado con matplotlib (tutorial: click aquí) y con numpy (tutorial: click aquí).


IMPLEMENTAR DIAGRAMA VORONOI

Vamos a crear un entorno virtual en Anaconda para trabajar con la librería scipy. Para ello:
  1. Abrimos una terminal de Anaconda. Podemos hacerlo escribiendo en la barra de búsqueda Anaconda prompt.
  2. Creamos un entorno virtual: conda create -n voronoienv python=3.7
  3. Activamos el entorno virtual: conda activate voronoienv
  4. Instalamos los paquetes que vamos a usar: conda install scipy
  5. Instalamos la librería que enlaza Anaconda con Jupyter: conda install nb_conda
  6. Abrimos Jupyter: jupyter notebook
  7. Creamos un nuevo proyecto dentro del entorno virtual voronoienv.
CÓDIGO FUENTE: Todo el código que se va a mostrar a continuación está subido en mi cuenta de GitHub: click aquí.

PASO 1. Importación de librerías

Cuando hayamos creado el proyecto en Jupyter, vamos a importar las siguientes librerías:

Si nos da algún error porque no encuentre alguna de las librerías que estamos importando, la forma de solucionarlo es instalar la librería que falte usando Anaconda y el comando conda install nombreLibreria.

PASO 2. Graficar diagrama de Voronoi sencillo

Vamos a crear un diagrama de Voronoi aleatorio para probar que la librería funciona como esperamos.


Los puntos azules son los puntos de Voronoi y los puntos naranjas son los vérticos de las regiones de Voronoi. Podemos mostrar este mismo diagrama sin que se vean los puntos azules y naranjas añadiendo parámetros a la función voronoi_plot_2d.


Para conocer todos los parámetros que se pueden usar para configurar el diagrama, os recomiendo visitar la documentación oficial: click aquí.


PASO 3. Pintar puntos encima de un diagrama de Voronoi

Vamos a generar una nube de puntos aleatorio y la vamos a pintar encima de un diagrama de Voronoi usando la librería matplotlib. De ahora en adelante, vamos a referirnos a los puntos que pintamos sobre el diagrama de Voronoi como puntos de interés.


PASO 4. Densidad de puntos en una región

Ahora que somos capaces de mostrar los puntos de interés y el diagrama de Voronoi, sería interesante conocer la densidad de puntos de interés dentro de cada región de Voronoi. Para ello, tenemos que hacer lo siguiente:
  1. Extraer las regiones de Voronoi.
  2. Crear un objeto Path por cada región que no sea infinita. Un objeto Path define un polígono irregular que es capaz de determinar si contiene o no un punto del espacio. Si vemos el diagrama, hay regiones cuyas fronteras son líneas discontinuas. Esto quiere decir que la región es infinita porque no se corta con ninguna otra recta. Los vértices de este tipo de regiones almacenan el valor -1 para indicar que es una región infinita.
  3. Estudiar cuántos puntos de interés caen dentro de cada Path.
Nota: el código Python que se va a mostrar a continuación es avanzado. Si no estás familiarizado con el lenguaje, no te centres en qué hace cada sentencia y trata de quedarte con la idea general.

Vamos a mostrar el código y luego lo explicamos:


Explicación del código:
  • Líneas 1-5: creamos el diagrama de Voronoi y los puntos de interés.
  • Línea 8: extraemos las regiones del diagrama de Voronoi.
  • Línea 9: inicializamos a vacío el array que usaremos para almacenar los objetos Path.
  • Líneas 10-14: por cada región, si no tiene un vértice con -1 (es decir, si no es una región infinita) y no es una región sin vértices, se utilizan los vértices para crear un polígono. Con el polígono, creamos un objeto Path y, finalmente, almacenamos el Path en el array.
  • Línea 17: creamos un array con tantas posiciones como regiones de Voronoi e inicializamos todas sus posiciones a cero. Este array contendrá las densidades de puntos de interés de cada región.
  • Líneas 17-25: por cada punto de interés, buscamos el Path que lo contiene y sumamos 1 a la posición que le corresponda en el array de densidades.

PASO 5. Colorear cada región en función de la densidad de puntos

Vamos a colorear con tonos más oscuros las regiones que tengan mayor concentración de puntos de interés. Para ello, primero debemos graficar el diagrama de Voronoi y los puntos y después colorear las regiones. Es importante hacerlo en este orden porque el método que vamos a usar para colorear las regiones se apoya en que ya haya un diagrama pintado. 

Para el código, vamos a suponer que ya tenemos el cálculo de las densidades hecho. Al igual que en el paso anterior, mostramos el código y después lo explicamos:



Explicación del código:
  • Líneas 2-3: graficamos el diagrama de Voronoi y los puntos.
  • Línea 5: definimos un valor alpha. Este valor va a ser el que indique la intensidad del color. Debe tener un valor comprendido entre 0 y 1, así que, lo vamos a normalizar atendiendo al valor máximo de densidad para evitar que supere el valor 1 y vamos a añadir un porcentaje al cociente para evitar que la región con mayor densidad tenga un color completamente negro.
  • Líneas 7-11: por cada región, si no tiene un vértice con -1 (es decir, si no es una región infinita) y no es una región sin vértices, se utilizan los vértices para crear un polígono. Basándonos en el polígono y la densidad correspondiente a esa región, coloreamos el diagrama.
Podemos observar que hay una región muy oscura que se corresponde con la mayor concentración de puntos de interés. Además, cabe destacar que los puntos situados en regiones infinitas no han sido contabilizados


PASO 6. Añadir peso a los puntos de interés

Cada punto que pintamos sobre el diagrama de Voronoi lo contabilizamos como +1. Sin embargo, podemos modificar nuestro código para que se tenga en cuenta que hay puntos con más valor que otros. Para no alejarnos mucho de lo que queremos conseguir, darle peso a los puntos lo podemos asociar con el tamaño de nuestras estaciones de bici. Cuanto mayor sea la estación y más bicis pueda tener, mayor influencia ejerce.

Para implementar esto, vamos a considerar que los puntos de interés vienen dados con tres valores: (coordenada x, coordenada y, peso). Lo único que tenemos que hacer es añadir este factor al cálculo de densidades. Solo se modifican dos líneas:



Líneas modificadas:
  • Línea 1: a la hora de crear los points_of_interest debemos generar tres valores en lugar de 2.
  • Línea 9: la densidad se actualiza sumando el valor del peso asociado al punto de interés.
Si queremos que nuestro código admita entradas con peso y sin peso, podemos añadir un fragmento de código que, en caso de que los puntos de interés no tengan peso, se les asocie un peso de 1.



PASO 7. Adaptar la nube de puntos a coordenadas no normalizadas y añadir bordes

Cuando generamos la nube de puntos usando la función random de la librería Numpy, los valores que se obtienen están en coordenadas normalizadas entre 0 y 1. Sin embargo, en la realidad, las coordenadas de los puntos de interés no tienen por qué estar entre 0 y 1.

Además, vimos en el paso 5 que había puntos que se situaban en regiones de Voronoi infinitas y que estas regiones se sitúan en los bordes del diagrama de Voronoi. Podemos conseguir que todos los puntos de interés queden en regiones finitas si generamos los puntos de Voronoi más dispersos, ya que, así, los puntos de interés se alejarán de los bordes y caerán en regiones finitas.

El grado de dispersión de los puntos de Voronoi debe ser relativo al tamaño de la nube de puntos de interés. Para expresar el grado de dispersión vamos a utilizar un porcentaje de borde que actuará de la siguiente forma: si las coordenadas de los puntos de interés están entre 0 y 100 y usamos un porcentaje de borde del 20%, los puntos de Voronoi se generarán entre -20 y 120.

En definitiva, tenemos que desplazar y escalar los puntos de Voronoi para que se adapten a la nube de puntos de interés. El código es el siguiente:


Paso 8. Crear función

Finalmente, después de probar todos los bloques de código, podemos unirlos en una función. El resultado que se obtiene es:



Como vemos, las regiones se colorean, los puntos de interés tienen pesos asociados, todos caen sobre regiones finitas del diagrama y, además, están en coordenadas no normalizadas. Esta función es la primera herramienta informática que hemos creado y que nos va a permitir el análisis de datos posterior.

CÓDIGO FUENTE: Todo el código que se va a mostrar a continuación está subido en mi cuenta de GitHub: click aquí.


Configurando el entorno de trabajo


Para nuestro proyecto vamos a usar:
  • Python como lenguaje de programación. 
  • Jupyter como entorno de trabajo.
  • Anaconda como gestor de paquetes.
A continuación, vamos a describir qué es cada elemento que hemos mencionado y cómo los instalamos para poder llevar a cabo el proyecto.

¿QUÉ ES PYTHON?

Python es un lenguaje de programación interpretado, imperativo y orientado a objetos. Tiene una extensa variedad de librerías que facilitan enormemente el trabajo del programador y, por ello, se ha convertido en uno de los lenguajes más populares del momento.

Para aquellos que quieran iniciarse en la programación en Python, yo recomiendo este tutorial. Es un tutorial en inglés de unas seis horas que te introduce los fundamentos básicos del lenguaje. Si estás interesado en profundizar aún más, os recomiendo investigar las siguientes librerías:
  • Matplotlib: es una librería para dibujar gráficos en 2D. Tutorial: click aquí.
  • Numpy: es una librería que facilita el trabajo con vectores y matrices. Tutorial: click aquí.
  • Pandas: es una extensión de Numpy para trabajar con tablas de datos. Tutorial: click aquí.
Link para descargar Python: click aquí.



¿QUÉ ES JUPYTER?

Jupyter es un entorno de trabajo interactivo que permite escribir bloques de código y visualizar gráficos de forma muy dinámica. Podemos ejecutar bloques de código de forma aislada y comprobar lo que estamos haciendo.

Link para descargar Jupyter: click aquí.



¿QUÉ ES ANACONDA?

Anaconda es un gestor de paquetes que nos va a facilitar el uso de librerías y de entornos virtuales. Un entorno virtual es un espacio aislado donde podemos instalar los paquetes que necesitemos para nuestros proyectos. Estos paquetes estarán instalados únicamente dentro del entorno virtual y no interferirán de ninguna forma con los paquetes de otro entorno. De esta forma, podemos probar librerías sin el riesgo de provocar efectos colaterales con algún paquete que ya estemos usando.

Link para descargar Anaconda: click aquí.




PREPARANDO EL ENTORNO DE TRABAJO

Lo primero que tenemos que hacer para empezar a trabajar es instalar Python, Jupyter y Anaconda. Una vez hayamos hecho esto, lo que haremos a continuación será:

  1. Crear un entorno virtual.
  2. Instalar los paquetes que necesitemos en ese entorno virtual.
  3. Vincular Jupyter con Anaconda.
  4. Crear un proyecto en Jupyter dentro de un entorno virtual de Anaconda.
  5. Ejecutar código en Python dentro del proyecto de Jupyter.

PASO 1. Crear entorno virtual.

Debemos abrir un terminal de Anaconda. Podemos escribir en la barra de búsqueda Anaconda Prompt y, si hemos realizado correctamente la instalación de Anaconda, nos aparecerá una terminal como esta:



Si vemos que pone (base) delante del prompt, quiere decir que estamos en el entorno virtual raiz (root). El entorno base es un entorno que se crea por defecto y que está activo si no hay otro en uso. Para crear un entorno virtual, la sintaxis del comando es:

conda create -n nombreEntorno python=x.x


El nombre del entorno lo decidimos nosotros y x.x es la versión de Python que tenemos instalada. Si no recordamos nuestra versión de Python, podemos abrir una terminal de Windows y escribir python.



Vamos a probar creando un entorno virtual con el siguiente comando:

conda create -n mientorno python=3.7


Cuando lo ejecutemos, nos aparecerá la lista de paquetes que se va a instalar y nos preguntan si queremos aceptar la instalación [y/n]. Para ver los entornos virtuales que tenemos creados, podemos usar el comando:

conda env list

La salida que se obtiene es de la siguiente forma:



En mi caso, tengo varios entornos creados y entre ellos se encuentra el que acabamos de crear (el * indica el entorno virtual activo). Para poder usar el entorno que hemos creado, debemos ejecutar el comando:

conda activate mientorno

Después de esto, veremos que el prompt ha cambiado.


Para salir del entorno y volver al entorno base, debemos ejecutar:

conda deactivate

PASO 2. Instalando paquetes

Vamos a probar instalando una librería en este entorno. La sintaxis del comando es:

conda install nombreLibreria

Por ejemplo, vamos a instalar la librería matplotlib para poder hacer gráficos en 2D.

conda install matplotlib

Al igual que ocurrió antes, nos mostrará una lista con los paquetes que se van a instalar. La ventaja de usar Anaconda es que si matplotlib tiene dependencias con otra librería, Anaconda las busca y las instala automáticamente. 

PASO 3. Vincular Jupyter con Anaconda

Para poder usar Jupyter con Anaconda, debemos instalar un paquete llamado nb_conda dentro del entorno virtual de trabajo, en nuestro caso, dentro de mientorno.

conda install nb_conda

Una vez lo hayamos instalado, podemos abrir Jupyter desde Anaconda usando el comando:

jupyter notebook

Importante: si vamos a trabajar en un entorno virtual, debemos asegurarnos de que tenemos ese entorno activado con conda activate nombreEntorno.

PASO 4. Crear un proyecto en Jupyter

Cuando tengamos abierto Jupyter, veremos que se ha abierto en el navegador y que nos muestra las carpetas que tenemos en nuestro equipo. Seleccionamos la carpeta en la que queremos crear nuestro proyecto y abrimos la pestaña new que se encuentra a la derecha de la pantalla. 

Veremos que se despliega una lista que muestra los entornos virtuales que tenemos creados. Seleccionamos el entorno que nos interese y se creará el proyecto. Debemos asegurarnos de que el entorno que seleccionemos esté acompañado de un * para asegurarnos de que estamos creando el proyecto en el entorno virtual activo.



PASO 5. Ejecutar código en Python

Para ejecutar el código, solo tenemos que escribirlo dentro del cuadro y dar al botón Run. Por ejemplo, vamos a visualizar un gráfica sencilla usando la librería que hemos instalado:



Llegados a este punto, ya podemos empezar a ejecutar todo el código que veamos a lo largo del proyecto. Os animo a experimentar con Jupyter y con las librerías que os he mencionado al principio del post. 

Un saludo!


Teoría de Redes Complejas (Parte 1). Conceptos básicos

Para continuar con nuestro proyecto vamos a hacer un estudio aplicando teoría de grafos y de redes complejas con el objetivo de demostrar...