En la sección anterior de la práctica, se estudiaron los conceptos generales relacionados con el Agrupamiento de datos, así como de su variante espacial en la Regionalización. En esta parte, se analizarán con un poco más de detalle alguna de las particularidades de los procesos estudiados; en específico, se estudiarán algunas formas de discernir las variables correctas para realizar una Regionalización, así como algunos parámetros para conocer si los resultados obtenidos son lo suficientemente aceptables para ser considerados como válidos o ciertos.
# Librerías a Utilizar
import seaborn as sns
import pandas as pd
import pysal as ps
import geopandas as gpd
import numpy as np
import matplotlib.pyplot as plt
from sklearn import cluster
Cuando se ejecuta algún algoritmo de Regionalización, en el transfondo se está realizando una generalización sobre el espacio, esto es, se está asignando un mismo valor o atributo a áreas que resultan más grandes que las que se tenían en un inicio.
Esta Generalización puede resultar positiva, pues permite visualizar algunos patrones reproducibles en el espacio y, por ende, permitir deducir si la componente espacial es significativa sobre el fenómeno estudiado, dando otra herramienta para explicarlo. Por otra parte, el generalizar provoca que se pierda el detalle que se tenía inicialmente con áreas más pequeñas y, por ende, la explicación que pueda derivarse también pierde de profundidad. Como tal, es importante encontrar un balance entre el poder explicativo que se gana al momento de Regionalizar y la pérdida de profundidad implícita en la Generalización.
Para estudiar este fenómeno, se tomarán nuevamente los datos utilizados en la sección anterior de la práctica, esto es, las calificaciones de los AirBnb contenidos en las AGEB's de la Alcaldía Cuauhtémoc de la CDMX. Como tal, es necesario importar estos datos:
airbnb = gpd.read_file('data/agebs_airbnb.shp') # Importar el ShapeFile
airbnb = airbnb.set_index('ageb') # Establecer la Clave Geográfica del AGEB como Índice
airbnb.head() # Observar el GeoDataFrame importado
En la siguiente celda se repetirán los pasos ejecutados en la sección anterior de la práctica para generar una Regionalización; en este caso, se generarán 15 Regiones utilizando el Algoritmo AZP. Asimismo, se utilizará sólo una de las variables anteriores, la de Calificación General (calif
), para facilitar el análisis posterior:
# Importar la librería local 'clusterpy'
import clusterpy
# Generar el objeto de tipo 'layer' que la librería necesita
layer = clusterpy.importArcData('data/agebs_airbnb')
# Ejecutar el Algoritmo AZP de Regionalización
layer.cluster('azp', ['calif'], 15, wType='queen')
# Indicar a qué región pertenece cada AGEB como una nueva columna
airbnb['azpcls_15'] = layer.region2areas
# Visualizar el resultado en la tabla original
airbnb.head()
Como se estudió en la práctica anterior, es posible obtener Estadísticos Descriptivos tanto de la variable original, utilizada para la Regionalización, como de las regiones estudiadas. En el caso de la variable original, sus estadísticos pueden obtenerse a través de la función .describe()
:
airbnb['calif'].describe()
Para el análisis, únicamente resulta de interés obtener la Desviación Estándar ($\sigma$ o std
) de la variable. Para esto, sólo es necesario llamar la función .std()
, que permite aislar a este estadístico en específico:
airbnb['calif'].std()
Lo que el estadístico anterior pretende comunicar es el rango de valores en el cual se encuentran la mayoría de los datos, tomando como referencia la media de los mismos. En el ejemplo de trabajo, teniendo una Desviación Estándar de $4.18$ y una Media de $94.67$ para la variable, puede asegurarse que la mayoría de las Calificaciones Generales de los AirBnb se encuentran en un rango de $94.67 \pm 4.18$, en otras palabras, entre $90.49$ y $98.85$. Lo anterior ayuda a tener una idea de qué tan dispersos se encuentran los datos; mientras más dispersos, mayor será la Desviación Estándar y, por ende, menos similares son los datos entre sí.
Para el caso de las regiones, es necesario utilizar primero la función .groupby()
, agrupando a través de la etiqueta, para después poder obtener los estadísticos descriptivos de interés; cabe notar el uso del doble corchete para aislar únicamente a la variable utilizada en la regionalización y la de referencia para el agrupamiento:
airbnb[['calif' , 'azpcls_15']].groupby('azpcls_15').describe()
Nuevamente, si se desea obtener únicamente la Desviación Estándar, basta con llamar sólo a .std()
:
airbnb[['calif' , 'azpcls_15']].groupby('azpcls_15').std()
Comparando los valores anteriores con la Desviación Estándar original de la variable ($4.18$), puede notarse que la gran mayoría de éstos tienden a ser menores, o por lo menos muy cercanos, a la original; lo anterior significa no sólo que la Regionalización esta logrando el objetivo buscado, de agrupar las cosas similares entre sí, sino también que el nivel de detalle que contiene ésta se asemeja al detalle que se tenía originalmente. Esto resulta positivo, pues se han disminuído el número total de Unidades Espaciales (de 153 AGEB's a sólo 15 Regiones) con las que se está trabajando, sin sacrificar enormemente la cantidad de información que se podría derivar de los datos originales.
Pero, ¿qué ocurre si el número de regiones se quiere disminuir? En otras palabras, si se desea generalizar aun más la información contenida, dado los objetivos de la investigación; a través de clusterPy
, es posible disminuir estas regiones únicamente modificando el argumento pertinente:
# Ejecutar el Algoritmo AZP de Regionalización
layer.cluster('azp', ['calif'], 10, wType='queen')
# Indicar a qué región pertenece cada AGEB como una nueva columna
airbnb['azpcls_10'] = layer.region2areas
Y, al igual que con el caso anterior, obtener la Desviación Estándar para cada una de las regiones creadas:
airbnb[['calif' , 'azpcls_10']].groupby('azpcls_10').std()
Comparando con la Desviación Estándar del caso en el que se generaron 15 Regiones, se notará que el estadístico derivado de únicamente 10 Regiones tiende a ser mucho más grande, lo cual significa que las regiones generadas son más heterogéneas que en el caso anterior y, por ende, se ha perdido detalle en los datos. Lo anterior no es necesariamente negativo; el hecho de que se hayan agrupado las AGEB's de la forma anterior indica que existe suficiente similitud entre ellas como para que la Componente Espacial sea significativa sobre el comportamiento de la variable, aunque la variable en sí ya no se encuentre tan detallada y especificada como se tenía en las 15 Regiones, y mucho menos con la variable original.
Puede estudiarse qué es lo que ocurriría si se disminuyen aún más las regiones; por ejemplo, si se generaran únicamente 5 de éstas:
# Ejecutar el Algoritmo AZP de Regionalización
layer.cluster('azp', ['calif'], 5, wType='queen')
# Indicar a qué región pertenece cada AGEB como una nueva columna
airbnb['azpcls_5'] = layer.region2areas
Y, al igual que en los casos anteriores, obtener la Desviación Estándar de todas las regiones generadas:
airbnb[['calif' , 'azpcls_5']].groupby('azpcls_5').std()
Nuevamente, el reducir el número de regiones aumentó los valores de la Desviación Estándar; en otras palabras, generó regiones más heterogéneas con un menor nivel de detalle de los datos. Como tal, aunque el número de Unidades Espaciales se ha reducido enormemente, y el trabajo con otro tipo de herramientas de análisis puede simplificarse de sobremanera, también se ha perdido una gran cantidad de detalle en los datos, eliminándose la posibilidad de identificar AGEB's muy particulares que puedan estar generando problemas.
Como tal, es importante considerar la influencia de la Regionalización sobre el nivel de detalle buscado; si no resulta vital para los objetivos del proyecto el considerar todas las particularidades de cada área sobre el resultado final, entonces puede recurrirse a generar pocas regiones muy generalizadas; por el contrario, si estos aspectos específicos si resultan importantes, entonces tal vez resulte más útil generar una gran cantidad de regiones, pero todas bien detalladas. Después de todo, es necesario encontrar un balance para asegurar que los resultados obtenidos no sólo sean útiles, sino también acertados y pertinentes a lo buscado.
Al momento de Regionalizar no sólo resulta pertinente el asegurarse que el Número de Regiones generado ofrezca la suficiente cantidad de detalle en los datos al mismo tiempo que simplifique a un número de Unidades Espaciales fácil de manejar; también es importante verificar que las regiones generadas sean lo suficientemente válidad por si mismas. Para esto existe el estadístico de Homogeneidad, que pretende medir el grado de similitud entre las áreas que componen a la región; en el ejemplo trabajado hasta ahora, el estadístico mediría el grado de semejanza de Calificaciones de los AirBnb's de los AGEB's que componen a cada una de las regiones.
La Homogeneidad se obtiene sencillamente siguiendo la fórmula:
\begin{equation*} h_i = \frac{\sigma _i^ 2}{\sigma_t ^ 2} \end{equation*}Donde $h_i$ es la Homogeneidad de la $i$-ésima región, $\sigma_i^2$ el cuadrado de la Desviación Estándar (también conocida como Varianza) de esa misma región, y $\sigma_t^2$ es la varianza original de la variable, es decir, el valor del estadístico en la variable original antes de la Regionalización.
De forma similar a los estadísticos anteriores, la Varianza ($\sigma^2$) puede ser obtenida fácilmente a través de la función .var()
:
airbnb['calif'].var()
Como tal, si se desea obtener la Varianza de cada una de las regiones generadas (tomando como referencia el ejercicio anterior con 15 Regiones):
airbnb[['calif' , 'azpcls_15']].groupby('azpcls_15').var()
De modo que, para obtener la Homogeneidad de cada una de estas regiones, únicamente se necesita dividir los valores anteriores entre la Varianza de la variable original:
# Almacenar la Varianza Inicial en una variable
var_inicial = airbnb['calif'].var()
# Obtener la Homogeneidad
homogeneidad = airbnb[['calif' , 'azpcls_15']].groupby('azpcls_15').var() / var_inicial
# Visualizar el resultado
homogeneidad
Mientras menor sea el valor del estadístico, más homogéneas son las regiones, es decir, más se asemejan las AGEB's que la componen entre sí; de lo contrario, mientras mayor sea el valor del estadístico, más heterogéneas son, es decir, las AGEB's que le componen no son tan semejantes entre sí.
Esto puede comprobarse sencillamente comparando los valores de la variable de regionalización (en este caso, Calificación General de los AirBnb, o calif
) entre cada uno de los integrantes de la región. La siguiente celda muestra automáticamente los elementos de la región más Homogénea (es decir, con un valor menor del estadístico):
# Ordenar los valor de Homogeneidad de menor a mayor con '.sort_values()', y almacenar sólo el primer elemento
homogeneidad_menor = homogeneidad.sort_values(by = 'calif').head(1)['calif']
# Registrar el valor de la homogeneidad para la región almacenada
value = homogeneidad_menor.values[0]
# Registrar el índice (o Número de Región)
index = homogeneidad_menor.index[0]
# Imprimir un mensaje que establezca la región con la que se está trabajando
print("AGEB's que conforman a la Región No. "+str(index)+", con Homogeneidad = "+str(value))
# Utilizar '.loc()' para localizar las AGEB's que pertenecen a dicha región.
airbnb.loc[airbnb['azpcls_15']==index]
Asimismo, la siguiente celda muestra autmoáticamente los elementos de la región más Heterogénea (es decir, con un valor mayor del estadístico):
# Ordenar los valor de Homogeneidad de menor a mayor con '.sort_values()', y almacenar sólo el primer elemento
homogeneidad_mayor = homogeneidad.sort_values(by = 'calif', ascending = False).head(1)['calif']
# Registrar el valor de la homogeneidad para la región almacenada
value = homogeneidad_mayor.values[0]
# Registrar el índice (o Número de Región)
index = homogeneidad_mayor.index[0]
# Imprimir un mensaje que establezca la región con la que se está trabajando
print("AGEB's que conforman a la Región No. "+str(index)+", con Homogeneidad = "+str(value))
# Utilizar '.loc()' para localizar las AGEB's que pertenecen a dicha región.
airbnb.loc[airbnb['azpcls_15']==index]
Tomando en cuenta que la variable de Calificación General de los Airbnb (calif
) fue la única utilizada para la Regionalización, ésta es la única considerada al momento de calcular la Homogeneidad; como tal, puede notarse que los valores de esta variable en la región más homogénea son muy similares, mientras que en la más heterogénea tienen una gran diferencia entre sí.
Dependiendo del objetivo de la investigación, es importante considerar lo anterior en cuenta; si cierta región no cumple con un mínimo de homogeneidad, determinado por los objetivos buscados, entonces es un buen indicio para modificar el número de regiones generadas o, inclusive, el algoritmo utilizado. De una u otra forma, el estadístico de Homogeneidad permite brindar de mayor certeza a los resultados obtenidos.
Lo anterior también puede comprobarse de forma visual, representando las regiones en un mapa a través de las funciones estudiadas anteriormente:
# Almacenar las columnas del 'GeoDataFrame' que son de interés para '.dissolve()'
tmp = airbnb[['azpcls_15','geometry']]
# Unir las AGEB's de cada región en una sola geometría a través de '.dissolve()'
regiones = tmp.dissolve(by = 'azpcls_15')
# Adicionar columna con valores de Homogeneidad para cada región
regiones['homogeneidad'] = homogeneidad
# Convertir los valores nulos en ceros (Función estudiada en Práctica posterior)
regiones = regiones.fillna(0)
# Crear la figura y sus ejes
fig, ejes = plt.subplots(1, figsize=(12, 12))
# Graficar las Regiones
regiones.plot(column = 'homogeneidad', scheme = 'Fisher_Jenks', cmap='RdYlGn_r' , ax = ejes , legend = True, edgecolor = 'black')
# Remover los ejes del mapa
ejes.set_axis_off()
# Asignar un título
plt.title('Homogeneidad para las Regiones generadas con AZP')
# Mostrar el resultado
plt.show()
Aunque muchos de los comandos anteriores ya han sido estudiados a lo largo del curso, vale la pena resaltar algunos detalles:
.dissolve()
(llamado regiones
) la columna con el cálculo de Homogeneidad. Debido a que el resultado de este cálculo ya posee como índices el Número de Región, y la función .dissolve()
asigna el mismo índice a su resultado, no es necesario realizar ninguna específicación adicional, pues a través de éste el comando automáticamente identifica qué valor va para cada región..fillna()
para convertir los valores nulos de Homogeneidad a ceros, ya que se asocian directamente a regiones donde sólo existe 1 AGEB y, por ende, son completamente homogéneas. El uso detallado de esta función se analizará más adelante..plot()
, el argumento cmap
, que indica qué colores debe de utilizar la gráfica, es llenado con una secuencia de colores de Rojo-Amarillo-Verde (RdYlGn
). Si se dejara este nombre como tal, en automático los valores más bajos del estadístico se colorearían de rojo, y los más altos de verde; sin embargo, esto no es completamente correcto pues, dado que los valores más altos se asocian a las regiones menos Homogéneas, es preferible asignar el color rojo a éstos. Como tal, se adiciona el sufijo _r
al nombre de la paleta para generar este cambio.Ejecuta el algoritmo de regionalización múltiples veces, modificando el Número de Regiones generadas y la Variable de Regionalización, calculando el estadístico de Homogeneidad ($h_i$) en cada caso. Con esto, responde a la pergunta: ¿Qué variable es la más útil para separar los datos?
Por ahora, en esta sección de la práctica, se ha regionalizado utilizando únicamente una de las variables disponibles; sin embargo, en la sección anterior, la regionalización se hizo utilizando un vector de todas las variables disponibles. Aunque los resultados son interesantes, no es posible asegurar que sean lo suficientemente válidos, mucho menos si se consideran los conceptos estudiados hasta ahora.
Como tal, resulta pertinente estudiar más a detalle la relación que guardan entre sí las variables utilizadas para la Regionalización. En la sección anterior, esta relación se estudió rápidamente de forma visual a través de Diagramas de Dispersión e Histogramas generados con la función .pairplot()
de la librería seaborn
. En este caso, se recurrirá a un estadístico, la Correlación Lineal, para realizar esta comparación, la cual puede ser obtenida sencillamente a través de la función .corr()
:
# Almacenar el nombre de las variables en una lista
calificaciones = ['calif', 'expec', 'limp', 'checkin', 'com', 'ubi', 'precio']
# Obtener la Correlación Lineal únicamente entre los valores de interés
airbnb[calificaciones].corr()
La Correlación Lineal únicamente puede obtener valores en un rango de entre -1 y 1; mientras más cercano se encuentre el estadístico a cualquiera de estos dos, mayor la correlación que existe entre las dos variables comparadas (es decir, más influencia tienen entre sí), siendo Negativa si se acerca al -1 o Positiva si se acerca al 1. Por otra parte, cuando el estadístico se acerca al 0, significa que las variables comparadas no se encuentran correlacionadas (es decir, tienen poca influencia entre sí).
Lo anterior puede comprobarse generando las gráficas correspondientes. De la tabla anterior, eliminando la diagonal principal, se tiene que la correlación más grande existe entre la Calificación de Limpieza del Airbnb (limp
) y su Calificación de Precio (precio
), siendo ésta de tipo positivo; esto significa que, en general, mientras más alta sea una de estas calificaciones, más alta será la otra. Graficamente la relación entre ambas variables puede visualizarse a través de la función .regplot()
de la librería seaborn
:
_ = sns.regplot(x = 'limp' , y = 'precio' , data = airbnb)
Por otra parte, la correlación más pequeña es la que existe entre la Calificación de Comunicación con el anfitrión (com
) y la Calificación de Ubicación del AirBnb (ubi
); esto significa que muy poco tienen que ver ambas calificaciones entre sí. Gráficamente, se tiene:
_ = sns.regplot(x = 'com' , y = 'ubi' , data = airbnb)
En la primera gráfica, con las variables más correlacionadas, puede observarse cierta dispersión de los datos, pero ésta permite deducir una relación lineal lo suficientemente acetable entre ambas, marcada por el hecho de que la línea que representa esta relación es lo suficientemente inclinada y el intervalo de confianza de ésta (marcada por el área azul) es relativamente pequeño. Por otra parte, la segunda gráfica, de las variables menos correlacionadas, muestra una dispersión que poco sigue un patrón lineal, siendo la recta generada casi horizontal, y teniendo un intervalo de confianza demasiado grande.
Lo anterior es de gran importancia la momento de regionalizar. Mientras más correlacionadas se encuentren dos variables, más similitud existe en la información que son capaces de explicar; en otras palabras, si dos variables se encuentran muy correlacionadas, el fenómeno que las originó muy probablemente es el mismo y, por ende, la cantidad de información que puede propoprcionar una es muy similar a la otra. Por otra parte, entre menos se encuentren correlacionadas dos variables, mayor será la cantidad de información que se pueda derivar de ellas pues, dado que el proceso que los originó muy probablemente es distinto, existe gran probabilidad de abarcar mayor información con ambas.
Como tal, al momento de Regionalizar, dado lo que se desea es abarcar la mayor diversidad de fenómenos posibles para agrupar correctamente las Unidades Espaciales, lo correcto es seleccionar un conjunto de variables que posean una baja correlación entre sí. En el caso de este ejercicio, sería incorrecto colocar en un mismo algoritmo de regionalización a las variables de limp
y precio
, dada su alta correlación, y los resultados más enriquecedores se tendrían utilizando juntas a las variables de com
y ubi
, dada su baja correlación.
Para ejemplificar lo anterior, resulta pertinente visualizar los resultados de estos casos. Primero, se realiza la regionalización utilizando las variables altamente correlacionadas:
# Ejecutar el Algoritmo AZP de Regionalización con variables altamente correlacionadas
layer.cluster('azp', ['limp' , 'precio'], 33, wType='queen')
# Indicar a qué región pertenece cada AGEB como una nueva columna
airbnb['azpcls_altacorr'] = layer.region2areas
# Almacenar las columnas del 'GeoDataFrame' que son de interés para '.dissolve()'
tmp = airbnb[['azpcls_altacorr','geometry']]
# Unir las AGEB's de cada región en una sola geometría a través de '.dissolve()'
regiones_altacorr = tmp.dissolve(by = 'azpcls_altacorr')
Se realiza lo mismo, pero esta vez con las variables que se encuentran menos correlacionadas:
# Ejecutar el Algoritmo AZP de Regionalización
layer.cluster('azp', ['com' , 'ubi'], 33, wType='queen')
# Indicar a qué región pertenece cada AGEB como una nueva columna
airbnb['azpcls_bajacorr'] = layer.region2areas
# Almacenar las columnas del 'GeoDataFrame' que son de interés para '.dissolve()'
tmp = airbnb[['azpcls_bajacorr','geometry']]
# Unir las AGEB's de cada región en una sola geometría a través de '.dissolve()'
regiones_bajacorr = tmp.dissolve(by = 'azpcls_bajacorr')
Y, para poder comparar los resultados obtenidos con lo que se observa en la realidad, se importar las colonias que conforman a la Alcaldía Cuauhtémoc:
colonias_reales = gpd.read_file('data/colonias_cuauh.shp')
colonias_reales = colonias_reales.set_index('id')
Por último, se pueden graficar estos tres últimos conjuntos de regiones de forma paralela para facilitar la comparación entre ellas, utilizando el mismo método estudiado en la sección anterior:
# Crear la figura y sus ejes
fig, ejes = plt.subplots(nrows = 1, ncols = 3, figsize=(20, 20))
# Aislar los ejes (cuadros) en sus propias variables
eje1 = ejes[0]
eje2 = ejes[1]
eje3 = ejes[2]
# Para el primer eje (cuadro)
# Graficar las regiones derivadas de las variables con Alta Correlación
regiones_altacorr.plot(figsize = (10,10), edgecolor = 'black', facecolor = '#50C878', ax = eje1)
# Remover los ejes del mapa
eje1.set_axis_off()
# Asignar un título
eje1.set_title('Colonias utilizando Alta Correlación')
# Para el segundo eje (cuadro)
# Graficar las regiones derivadas de las variables con Baja Correlación
regiones_bajacorr.plot(figsize = (10,10), edgecolor = 'black', facecolor = '#50C878', ax = eje2)
# Remover los ejes del mapa
eje2.set_axis_off()
# Asignar un título
eje2.set_title('Colonias utilizando Baja Correlación')
# Para el segundo eje (cuadro)
# Graficar las colonias originales
colonias_reales.plot(figsize = (10,10), edgecolor = 'black', facecolor = '#50C878', ax = eje3)
# Remover los ejes del mapa
eje3.set_axis_off()
# Asignar un título
eje3.set_title('Colonias Administrativas de la Alcaldía Cuauhtémoc')
plt.show()
Aunque ninguna de las regionalizaciones es idéntica a lo que se observa en la realidad, si pueden notarse diferencias fundamentales entre ambas. Por un lado, las regiones generadas con variables altamente correlacionadas aún guardan las características de caos, desorden y falta de proporción observadas en las colonias en la primera parte de la práctica; por otra parte, las regiones de variables poco correlacionadas, aunque no idénticas a las colonias verdaderas de la alcaldía, si empiezan a guardar entre sí cierto orden y proporción, aún mejor que el de las regiones de la sección anterior, llegando inclusive a delimitar ciertas zonas características de la Alcaldía.
Claro está que las variables utilizadas en la Regionalización no son necesariamente las más ideales para obtener una regionalización tan detallada y socialmente marcada como la que generan las colonias en la realidad; sin embargo, el ejercicio permite visualizar con bastante claridad la importancia seleccionar las variables correctas para obtener resultados con mayor validez.
En el Ejercicio Final de la sección anterior de la práctica utilizaste las variables de educación disponibles en el archivo agebs_educacion.shp
para practicar los conceptos estudiados. Esta vez, repite el ejercicio de regionalización, pero ahora utilizando los conceptos aprendidos para generar regiones lo suficientemente adecuadas para que, a tu criterio, sean consideradas válidas.
Como tal, además de presentar las regiones finales obtenidas, debes de responder lo siguiente:
No debes de generar regiones perfectas; es suficiente conque utilices los conceptos aprendidos a lo largo de la práctica para justificar tus resultados, de modo que no sean fiel representación de lo que ocurre en la realidad, pero tampoco lo suficientemente descabellados como para alejarse totalmente de ella.