Geoinformática - Práctica 8

Problema de la Unidad de Área Modificable (MUAP)

Parte I

En esta práctica vamos a explorar la forma en la que agregación de los datos influye en los resultados de un análisis. Para esto vamos a trabajar con los datos de desaparecidos en la República Mexicana entre 2006 y 2014.

Los datos vienen en dos ShapeFiles, uno a nivel estatal y otro a nivel municipal. Importemos los archivos con GeoPandas para analizarlos:

In [1]:
from geopandas import GeoDataFrame
estatal = GeoDataFrame.from_file('data/des_rezago_estado.shp')
municipal = GeoDataFrame.from_file('data/muns_geo_des.shp')

Analicemos los datos un poco:

In [2]:
estatal.head()
Out[2]:
cvegeo estado POB1 2006 2007 2008 2009 2010 2011 2012 2013 2014 pob_alimen pob_capaci pob_patrim analfabeta no_escuela ed_basica rezago geometry
0 01 Aguascalientes 1184996 0 25 7 20 18 30 14 15 62 14.9 23.6 51.1 4.15 4.53 41.83 1.14451 POLYGON ((-102.2878651817759 22.41649003941765...
1 02 Baja California 3155070 0 7 25 11 8 19 128 177 381 1.3 2.3 9.2 3.07 4.77 38.94 0.66364 (POLYGON ((-115.2104851485454 28.3722493563768...
2 03 Baja California Sur 637026 0 0 2 1 3 2 3 8 3 4.7 8.0 23.5 3.60 4.03 38.92 0.48199 (POLYGON ((-109.8006324469839 24.1492608586424...
3 04 Campeche 822441 0 0 5 0 1 0 0 10 59 20.0 27.3 51.4 10.17 5.11 49.00 0.32493 POLYGON ((-90.37935699678101 20.84832728853007...
4 05 Coahuila de Zaragoza 2748391 1 55 102 117 227 258 136 233 103 8.6 15.2 41.0 3.28 3.84 38.13 1.25058 POLYGON ((-102.3107926469074 29.87694857356086...

Debido a que estos datos poseen 22 columnas, no es posible verlos sencillamente; como tal, vamos a listar las columnas para ver qué datos se tienen:

In [3]:
estatal.info()
<class 'geopandas.geodataframe.GeoDataFrame'>
RangeIndex: 32 entries, 0 to 31
Data columns (total 20 columns):
cvegeo        32 non-null object
estado        32 non-null object
POB1          32 non-null int64
2006          32 non-null int64
2007          32 non-null int64
2008          32 non-null int64
2009          32 non-null int64
2010          32 non-null int64
2011          32 non-null int64
2012          32 non-null int64
2013          32 non-null int64
2014          32 non-null int64
pob_alimen    32 non-null float64
pob_capaci    32 non-null float64
pob_patrim    32 non-null float64
analfabeta    32 non-null float64
no_escuela    32 non-null float64
ed_basica     32 non-null float64
rezago        32 non-null float64
geometry      32 non-null object
dtypes: float64(7), int64(10), object(3)
memory usage: 5.1+ KB

En las columnas 2006 a 2014 tenemos los datos de desaparecidos para cada año. En las demás columnas tenemos alguna información sobre las condiciones socioeconómicas de cada Unidad Espacial:

  • pob_alimen - Porcentaje de la Población en Pobreza Alimentaria
  • pob_capaci - Porcentaje de la Población en Pobreza de Capacidades
  • pob_patrim - Porcentaje de la Población en Pobreza Patrimonial
  • analfabeta - Porcentaje de la Población que no sabe leer ni escribir
  • no_escuela - Porcentaje de niños en edad escolar que no asiste a la escuela
  • ed_basica - Porcentaje de la Población que sólo terminó la Educación Básica
  • rezago - Índice de Rezago

El mismo proceso puede ser repetido para el GeoDataFrame en la variable municipal; primero, analizando la apariencia del mismo:

In [4]:
municipal.head()
Out[4]:
cvegeo_x POB1 cve_estado nom_estado cve_mun nom_mun pob_alimen pob_capaci pob_patrim no_escuela ... 2006 2007 2008 2009 2010 2011 2012 2013 2014 geometry
0 01001 797010 1 Aguascalientes 1001 Aguascalientes 12.1 20.4 48.0 3.19 ... 0 22 6 19 17 26 11 15 48 POLYGON ((-102.1064122399267 22.06035441303033...
1 01002 45492 1 Aguascalientes 1002 Asientos 19.9 28.9 56.8 6.75 ... 0 0 0 0 0 0 2 0 1 POLYGON ((-102.051893439036 22.29143529350413,...
2 01003 54136 1 Aguascalientes 1003 Calvillo 24.9 35.2 62.5 8.15 ... 0 0 0 0 0 0 0 0 4 POLYGON ((-102.6856884472506 22.09962730886253...
3 01004 15042 1 Aguascalientes 1004 Cosío 14.8 22.6 49.8 6.71 ... 0 1 0 0 0 0 0 0 0 POLYGON ((-102.287865181776 22.41649003941679,...
4 01005 99590 1 Aguascalientes 1005 Jesús María 18.7 28.4 55.8 5.33 ... 0 1 0 1 0 3 1 0 4 POLYGON ((-102.3356775711373 22.05066521496391...

5 rows × 22 columns

Y después, a través de la función .info(), que nos permite observar que se tienen el mismo tipo de variables:

In [5]:
municipal.info()
<class 'geopandas.geodataframe.GeoDataFrame'>
RangeIndex: 2454 entries, 0 to 2453
Data columns (total 22 columns):
cvegeo_x      2454 non-null object
POB1          2454 non-null int64
cve_estado    2454 non-null int64
nom_estado    2454 non-null object
cve_mun       2454 non-null int64
nom_mun       2454 non-null object
pob_alimen    2454 non-null float64
pob_capaci    2454 non-null float64
pob_patrim    2454 non-null float64
no_escuela    2454 non-null float64
ed_basica     2454 non-null float64
rezago        2454 non-null float64
2006          2454 non-null int64
2007          2454 non-null int64
2008          2454 non-null int64
2009          2454 non-null int64
2010          2454 non-null int64
2011          2454 non-null int64
2012          2454 non-null int64
2013          2454 non-null int64
2014          2454 non-null int64
geometry      2454 non-null object
dtypes: float64(6), int64(12), object(4)
memory usage: 421.9+ KB

En el primer ejercicio, vamos a realizar una Regresión Lineal del Total de Desaparecidos (sobre cada uno de los años), contra alguna variable socioeconómica para observar cómo cambia el resultado con la escala del análisis.

El primer paso es crear y calcular una columna con el Total de Desaparecidos; para esto, primero se aislan en un DataFrame nuevo las columnas que nos interesan:

In [6]:
des_estado = estatal[['cvegeo','2006','2007','2008','2009','2010','2011','2012','2013','2014']]
des_estado.head()
Out[6]:
cvegeo 2006 2007 2008 2009 2010 2011 2012 2013 2014
0 01 0 25 7 20 18 30 14 15 62
1 02 0 7 25 11 8 19 128 177 381
2 03 0 0 2 1 3 2 3 8 3
3 04 0 0 5 0 1 0 0 10 59
4 05 1 55 102 117 227 258 136 233 103

Para después realizar la suma por fila utilizando la función .sum() y guardando el resultado en una nueva columna llamada total_des:

In [7]:
des_estado['total_des'] = des_estado.sum(axis=1)
des_estado.head()
/home/datalab/miniconda3/envs/geoinf/lib/python3.7/site-packages/ipykernel_launcher.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  """Entry point for launching an IPython kernel.
Out[7]:
cvegeo 2006 2007 2008 2009 2010 2011 2012 2013 2014 total_des
0 01 0 25 7 20 18 30 14 15 62 191
1 02 0 7 25 11 8 19 128 177 381 756
2 03 0 0 2 1 3 2 3 8 3 22
3 04 0 0 5 0 1 0 0 10 59 75
4 05 1 55 102 117 227 258 136 233 103 1232

Esta nueva columna puede ser añadida a los datos originales, a través de la función .merge() de la librería pandas:

In [8]:
import pandas as pd

estatal = pd.merge(estatal, des_estado[['total_des','cvegeo']] , on = 'cvegeo' )
estatal.head()
Out[8]:
cvegeo estado POB1 2006 2007 2008 2009 2010 2011 2012 ... 2014 pob_alimen pob_capaci pob_patrim analfabeta no_escuela ed_basica rezago geometry total_des
0 01 Aguascalientes 1184996 0 25 7 20 18 30 14 ... 62 14.9 23.6 51.1 4.15 4.53 41.83 1.14451 POLYGON ((-102.2878651817759 22.41649003941765... 191
1 02 Baja California 3155070 0 7 25 11 8 19 128 ... 381 1.3 2.3 9.2 3.07 4.77 38.94 0.66364 (POLYGON ((-115.2104851485454 28.3722493563768... 756
2 03 Baja California Sur 637026 0 0 2 1 3 2 3 ... 3 4.7 8.0 23.5 3.60 4.03 38.92 0.48199 (POLYGON ((-109.8006324469839 24.1492608586424... 22
3 04 Campeche 822441 0 0 5 0 1 0 0 ... 59 20.0 27.3 51.4 10.17 5.11 49.00 0.32493 POLYGON ((-90.37935699678101 20.84832728853007... 75
4 05 Coahuila de Zaragoza 2748391 1 55 102 117 227 258 136 ... 103 8.6 15.2 41.0 3.28 3.84 38.13 1.25058 POLYGON ((-102.3107926469074 29.87694857356086... 1232

5 rows × 21 columns

Lo mismo se repite para los municipios:

In [9]:
# Crear un DataFrame nuevo con sólo las columnas para el cálculo
des_mun = municipal[['cvegeo_x','2006','2007','2008','2009','2010','2011','2012','2013','2014']]

# Realizar la suma
des_mun['total_des'] = des_mun.sum(axis=1)

# Generar la unión de la nueva columna a los datos originales
municipal = pd.merge(municipal, des_mun[['total_des','cvegeo_x']] , on = 'cvegeo_x' )

# Analizar los datos para verificar el proceso
municipal.head()
/home/datalab/miniconda3/envs/geoinf/lib/python3.7/site-packages/ipykernel_launcher.py:5: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  """
Out[9]:
cvegeo_x POB1 cve_estado nom_estado cve_mun nom_mun pob_alimen pob_capaci pob_patrim no_escuela ... 2007 2008 2009 2010 2011 2012 2013 2014 geometry total_des
0 01001 797010 1 Aguascalientes 1001 Aguascalientes 12.1 20.4 48.0 3.19 ... 22 6 19 17 26 11 15 48 POLYGON ((-102.1064122399267 22.06035441303033... 164
1 01002 45492 1 Aguascalientes 1002 Asientos 19.9 28.9 56.8 6.75 ... 0 0 0 0 0 2 0 1 POLYGON ((-102.051893439036 22.29143529350413,... 3
2 01003 54136 1 Aguascalientes 1003 Calvillo 24.9 35.2 62.5 8.15 ... 0 0 0 0 0 0 0 4 POLYGON ((-102.6856884472506 22.09962730886253... 4
3 01004 15042 1 Aguascalientes 1004 Cosío 14.8 22.6 49.8 6.71 ... 1 0 0 0 0 0 0 0 POLYGON ((-102.287865181776 22.41649003941679,... 1
4 01005 99590 1 Aguascalientes 1005 Jesús María 18.7 28.4 55.8 5.33 ... 1 0 1 0 3 1 0 4 POLYGON ((-102.3356775711373 22.05066521496391... 10

5 rows × 23 columns

Para empezar, podemos suponer que la cantidad de desaparecidos puede estar correlacionada con la cantidad de habitantes de una zona. Probemos modelar esto a nivel estatal con una Regresión Lineal Simple, utilizando la librería statsmodels para esto:

In [10]:
# Importar biblioteca a utilizar
import statsmodels.formula.api as sm

# Generar el modelo a través de Mínimos Cuadrador Ordinarios
model = sm.ols('total_des ~ POB1', data = estatal)

# Realizar formalmente la Regresión Lineal
result = model.fit()

# Observar los resultados
result.summary()
Out[10]:
OLS Regression Results
Dep. Variable: total_des R-squared: 0.092
Model: OLS Adj. R-squared: 0.062
Method: Least Squares F-statistic: 3.043
Date: Tue, 06 Aug 2019 Prob (F-statistic): 0.0913
Time: 17:14:13 Log-Likelihood: -263.53
No. Observations: 32 AIC: 531.1
Df Residuals: 30 BIC: 534.0
Df Model: 1
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept 328.7103 259.796 1.265 0.216 -201.864 859.284
POB1 9.905e-05 5.68e-05 1.744 0.091 -1.69e-05 0.000
Omnibus: 56.722 Durbin-Watson: 2.358
Prob(Omnibus): 0.000 Jarque-Bera (JB): 401.590
Skew: 3.757 Prob(JB): 6.25e-88
Kurtosis: 18.643 Cond. No. 7.13e+06


Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 7.13e+06. This might indicate that there are
strong multicollinearity or other numerical problems.

El primero comando importa la biblioteca a utilizar para realizar las regresiones; el segundo genera el modelo de Mínimos Cuadrados Ordinarios (OLS, por sus siglas en inglés) a través de la función .ols(); después, se genera formalmente la regresión lineal a través de la función .fit(), para finalizar con la visualización del modelo utilizando .summary():

Analizando los resultados es posible observar que, aunque la relación entre el Total de Desaparecidos (total_des) y la Población del Estado (POB1) es significativa, pues la significancia del coeficiente asociada es menor al 10% (Observable en P>|t|), la regresión explica muy poco del fenómeno, dado el valor del estadístico $R^2$ (Adj. R-squared).

Resulta pertinente preguntarse, ¿sucederá lo mismo a nivel municipal? Pueden repetirse el mismo grupo de comandos que en el caso estatal:

In [11]:
# Importar biblioteca a utilizar
import statsmodels.formula.api as sm

# Generar el modelo a través de Mínimos Cuadrador Ordinarios
model = sm.ols('total_des ~ POB1', data = municipal)

# Realizar formalmente la Regresión Lineal
result = model.fit()

# Observar los resultados
result.summary()
Out[11]:
OLS Regression Results
Dep. Variable: total_des R-squared: 0.360
Model: OLS Adj. R-squared: 0.360
Method: Least Squares F-statistic: 1379.
Date: Tue, 06 Aug 2019 Prob (F-statistic): 7.32e-240
Time: 17:14:13 Log-Likelihood: -12613.
No. Observations: 2454 AIC: 2.523e+04
Df Residuals: 2452 BIC: 2.524e+04
Df Model: 1
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept -1.8531 0.882 -2.100 0.036 -3.583 -0.123
POB1 0.0002 6.28e-06 37.133 0.000 0.000 0.000
Omnibus: 5164.231 Durbin-Watson: 1.819
Prob(Omnibus): 0.000 Jarque-Bera (JB): 22564372.775
Skew: 17.656 Prob(JB): 0.00
Kurtosis: 471.435 Cond. No. 1.49e+05


Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 1.49e+05. This might indicate that there are
strong multicollinearity or other numerical problems.

En este caso, no sólo el coeficiente de POB1 se vuelve mucho más significativo, siéndolo al 1%, sino también el valor del estadístico $R^2$, indica que esta relación, modelada a nivel municipal, explica mucho más que al momento de modelarla a nivel estatal.


Ejercicio

Realicen múltiples regresiones, utilizando diferentes variables que consideren puedan explicar la cantidad de desaparecidos. Comparen los resultados a nivel Estatal y Municipal


También podemos pensar en modelos un poco más complicaos; por ejemplo, las Regresiones Multivariadas. En un primer experimento, podemos modelar la cantidad de desaparecidos a partir de la Población Total y el Índice de Rezago. Realizando esto a nivel estatal:

In [12]:
model = sm.ols('total_des ~ POB1 + rezago', data = estatal)
result = model.fit()
result.summary()
Out[12]:
OLS Regression Results
Dep. Variable: total_des R-squared: 0.094
Model: OLS Adj. R-squared: 0.031
Method: Least Squares F-statistic: 1.503
Date: Tue, 06 Aug 2019 Prob (F-statistic): 0.239
Time: 17:14:13 Log-Likelihood: -263.50
No. Observations: 32 AIC: 533.0
Df Residuals: 29 BIC: 537.4
Df Model: 2
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept 364.9309 303.269 1.203 0.239 -255.325 985.187
POB1 0.0001 5.9e-05 1.730 0.094 -1.86e-05 0.000
rezago -63.8026 263.004 -0.243 0.810 -601.706 474.100
Omnibus: 56.834 Durbin-Watson: 2.381
Prob(Omnibus): 0.000 Jarque-Bera (JB): 403.695
Skew: 3.767 Prob(JB): 2.18e-88
Kurtosis: 18.685 Cond. No. 9.42e+06


Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 9.42e+06. This might indicate that there are
strong multicollinearity or other numerical problems.

Es posible observar que, entre las advertencias arrojadas por statsmodels, se nos avisa que existe un Número de Condición demasiado grande, lo cual puede relacionarse a un problema de Multicolinealidad entre las variables; esto es, algunas de nuestras variables se encuentran muy correlacionadas y, por lo tanto, no deberían de utilizarse juntas en este modelo.

Para determinar si éste es el caso, podemos examinar la Matriz de Correlación de la variables; en particular, los eigenvalores de la misma, para verificar si tenemos Multicolinealidad:

In [13]:
# Importar la nueva librería a utilizar
import numpy as np

# Generar la Matriz de Colinearidad (Notar que existen dos pares de corchetes)
corr = estatal[['POB1', 'rezago', 'total_des']].corr()

# Obtener los Eigenvalores de la matriz (w son Eigenvalores, v Eigenvectores)
w , v = np.linalg.eig(corr)

# Visualizar el resultado
w
Out[13]:
array([1.37833882, 0.64155697, 0.98010421])

Lo que hicimos aquí fue importar la librería numpy que incluye métodos de Álgebra Líneal; después, calculamos la Matriz de Correlación a través de pandas y, finalmente, calculamos los eigenvalores de dicha matriz.

Debido a que ninguno de los eigenvalores es cercano a cero, podemos concluir que no tenemos problemas de multicolinealidad y, por lo tanto, la advertencia de un Número de Condición grande se debe a problemas numéricos

En este caso, podemos pensar que estos problemas se derivan del hecho de que la variable POB1 posee valores mucho más grandes que el resto. Para resolver este problema, intentemos reescalar las variables:

In [14]:
# Aislar en un nuevo DataFrame las variables de interés
vars_estatal =  estatal[['POB1','rezago','total_des']]

# Normalizar las variables
vars_estatal_norm = (vars_estatal - vars_estatal.mean()) / (vars_estatal.std())

# Obtener los estadísticos descriptivos a través de .describe()
vars_estatal_norm.describe()
Out[14]:
POB1 rezago total_des
count 3.200000e+01 3.200000e+01 3.200000e+01
mean 1.387779e-17 1.665335e-16 6.938894e-18
std 1.000000e+00 1.000000e+00 1.000000e+00
min -9.638119e-01 -1.046468e+00 -6.828026e-01
25% -6.416989e-01 -6.580102e-01 -5.828646e-01
50% -2.696107e-01 -2.037151e-01 -3.847870e-01
75% 3.072822e-01 3.360195e-01 3.361025e-01
max 3.912732e+00 2.716219e+00 4.602350e+00

Lo que hicimos aquí fue reescalar los valores de nuestras variables utilizando la fórmula:

$$ z_i = \dfrac{y - \bar{y}}{\sigma_y} $$

Donde $z_i$ es la versión estandarizada de nuestra variable, $\bar{y}$ es el promedio de la misma y $\sigma$ su desviación estándar; esto consigue que la media se encuentre centrada en cero, y lo que cuantificamos es la desviación de esta media con respecto al rango.

Ahora, veamos si esta transformación de datos nos ayuda a resolver el problema del Número de Condición:

In [15]:
model = sm.ols("total_des ~ POB1 + rezago", data = vars_estatal_norm)
result = model.fit()
result.summary()
Out[15]:
OLS Regression Results
Dep. Variable: total_des R-squared: 0.094
Model: OLS Adj. R-squared: 0.031
Method: Least Squares F-statistic: 1.503
Date: Tue, 06 Aug 2019 Prob (F-statistic): 0.239
Time: 17:14:13 Log-Likelihood: -43.320
No. Observations: 32 AIC: 92.64
Df Residuals: 29 BIC: 97.04
Df Model: 2
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept 2.776e-17 0.174 1.6e-16 1.000 -0.356 0.356
POB1 0.3126 0.181 1.730 0.094 -0.057 0.682
rezago -0.0438 0.181 -0.243 0.810 -0.413 0.326
Omnibus: 56.834 Durbin-Watson: 2.381
Prob(Omnibus): 0.000 Jarque-Bera (JB): 403.695
Skew: 3.767 Prob(JB): 2.18e-88
Kurtosis: 18.685 Cond. No. 1.24


Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Como puede observarse, el problema ha sido resuelto; como tal, ahora se tiene un Número de Condición perfectamente aceptable y, por lo tanto, podemos tener confianza en nuestros resultados.

Repliquemos este proceso para la escala municipal:

In [16]:
# Aislar las variables de interés
vars_municipal =  municipal[['POB1','rezago','total_des']]

# Normalizar las variables
vars_municipal_norm = (vars_municipal - vars_municipal.mean()) / (vars_municipal.std())

# Generar el modelo a través de Mínimos Cuadrados Ordinarios
model = sm.ols("total_des ~ POB1 + rezago", data = vars_municipal_norm)

# Realizar la Regresión Lineal
result = model.fit()

# Visualizar los resultados del proceso
result.summary()
Out[16]:
OLS Regression Results
Dep. Variable: total_des R-squared: 0.361
Model: OLS Adj. R-squared: 0.360
Method: Least Squares F-statistic: 691.8
Date: Tue, 06 Aug 2019 Prob (F-statistic): 6.31e-239
Time: 17:14:13 Log-Likelihood: -2932.4
No. Observations: 2454 AIC: 5871.
Df Residuals: 2451 BIC: 5888.
Df Model: 2
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept -6.494e-17 0.016 -4.02e-15 1.000 -0.032 0.032
POB1 0.5909 0.017 34.986 0.000 0.558 0.624
rezago -0.0310 0.017 -1.837 0.066 -0.064 0.002
Omnibus: 5166.321 Durbin-Watson: 1.824
Prob(Omnibus): 0.000 Jarque-Bera (JB): 22602566.489
Skew: 17.673 Prob(JB): 0.00
Kurtosis: 471.831 Cond. No. 1.35


Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

En estos modelos de Regresión Lineal Multivariada, estamos observando que, mientras a Nivel Estatal el valor de $R^2$ es de 0.031, indicando que se explica menos del 5% del fenómeno, en el caso municpal es de 0.36, ¡un orden de magnitud más grande! Además, la significancia del Índice de Rezago (valor de su coeficiente) es mucho mayor para el caso municipal a comparación del caso estatal.

¿Qué quiere decir esto? ¿Cuál es la verdadera influencia del Índice de Rezago en la cantidad de desaparecidos?


Ejercicio

Uilicen diferentes combinaciones de las variables disponibles para tratar de explicar mejor el Total de Desaparecidos. Repitan sus ejercicios a Nivel Estatal y Municipal; recuerden que es necesario reescalar los datos.


Un Último Refinamiento

Hasta el momento hemos trabajado con los datos del Total de Desaparecidos; cabe preguntarse, ¿el resultado sería muy diferente si se trabajan con tasas en lugar de los valores brutos?.

Repitamos los ejercicios anteriores utilizando ahora, como Variable Dependiente, la Tasa de Desaparecidos por cada 100,000 habitantes. El primer paso es, por supuesto, calcular dicha tasa y agregala como columna a los datos originales:

In [17]:
tasa = estatal['total_des'].divide(estatal['POB1'])*100000
tasa.head()
Out[17]:
0    16.118198
1    23.961434
2     3.453548
3     9.119195
4    44.826227
dtype: float64

Aunque la sintaxis es un poco extraña, lo que estamos haciendo es realmente sencillo: la columna total_des de los datos originales está siendo dividida por la columna POB1, elemento por elemento.

Es importante resaltar que el resultado no es un DataFrame, sino un objeto del tipo Series, que resulta ser más simple en términos de que sólo tiene una columna y un índice. Para poder unir el resultado a los datos originales, necesitamos convertir el objeto Series a un DataFrame, y proceder de la misma forma que se hizo con el Total de Desaparecidos, esto es, utilizar la función .merge():

In [18]:
# Convertir la serie en un DataFrame
tasa_df = pd.DataFrame(tasa,columns=['tasa'])

# Realizar la unión a través de .merge() y utilizando los índices
estatal = pd.merge(estatal,tasa_df,left_index=True, right_index=True)

# Verificar el proceso
estatal.head()
Out[18]:
cvegeo estado POB1 2006 2007 2008 2009 2010 2011 2012 ... pob_alimen pob_capaci pob_patrim analfabeta no_escuela ed_basica rezago geometry total_des tasa
0 01 Aguascalientes 1184996 0 25 7 20 18 30 14 ... 14.9 23.6 51.1 4.15 4.53 41.83 1.14451 POLYGON ((-102.2878651817759 22.41649003941765... 191 16.118198
1 02 Baja California 3155070 0 7 25 11 8 19 128 ... 1.3 2.3 9.2 3.07 4.77 38.94 0.66364 (POLYGON ((-115.2104851485454 28.3722493563768... 756 23.961434
2 03 Baja California Sur 637026 0 0 2 1 3 2 3 ... 4.7 8.0 23.5 3.60 4.03 38.92 0.48199 (POLYGON ((-109.8006324469839 24.1492608586424... 22 3.453548
3 04 Campeche 822441 0 0 5 0 1 0 0 ... 20.0 27.3 51.4 10.17 5.11 49.00 0.32493 POLYGON ((-90.37935699678101 20.84832728853007... 75 9.119195
4 05 Coahuila de Zaragoza 2748391 1 55 102 117 227 258 136 ... 8.6 15.2 41.0 3.28 3.84 38.13 1.25058 POLYGON ((-102.3107926469074 29.87694857356086... 1232 44.826227

5 rows × 22 columns

A diferencia de los casos anteriores, dentro de la función .merge() utilizamos los índices de cada DataFrame como las llaves para la unión, a través de los argumentos left_index y right_index; esto pues ya no existen columnas en común, pero sabemos que los datos se encuentran bien ordenados.

La primera pregunta a realizar es si esta Tasa de Desaparecidos se encuentra correlacionada con la Población Total; para esto, repetimos el proceso realizado anteriormente:

In [19]:
# Generar la Matriz de Colinearidad)
corr = estatal[['POB1', 'total_des', 'tasa']].corr()

# Obtener los Eigenvalores de la matriz
w , v = np.linalg.eig(corr)

# Visualizar el resultado
w
Out[19]:
array([0.02136532, 1.01243548, 1.96619921])

Otra forma de verificar la existencia de Multicolinearidad es observando directamente la Matriz de Correlación generada:

In [20]:
corr
Out[20]:
POB1 total_des tasa
POB1 1.000000 0.303477 -0.020974
total_des 0.303477 1.000000 0.923673
tasa -0.020974 0.923673 1.000000

Puede observarse que la correlación entre la Población Total y la Tasa de Desaparecidos es mucho más cercana a cero que la correlación entre la Población Total y el valor bruto del Total de Desaparecidos. Esto nos permite concluir que, como era de esperarse, la influencia de la Población Total sobre la tasa es menor que sobre el valor bruto. Pregunta - ¿Por qué ocurre esto?

Hecho lo anterior, podemos volver a correr nuestros modelos utilizando el proceso repetido anteriormente:

In [21]:
# Aislar las variables de interés
vars_estatal =  estatal[['POB1','rezago','tasa']]

# Normalizar las variables
vars_estatal_norm = (vars_estatal - vars_estatal.mean()) / (vars_estatal.std())

# Generar el modelo por Mínimos Cuadrados Ordinarios
model = sm.ols("tasa ~ POB1 + rezago", data = vars_estatal_norm)

# Formalizar la Regresión Lineal
result = model.fit()

# Observar el resultado
result.summary()
Out[21]:
OLS Regression Results
Dep. Variable: tasa R-squared: 0.000
Model: OLS Adj. R-squared: -0.068
Method: Least Squares F-statistic: 0.006383
Date: Tue, 06 Aug 2019 Prob (F-statistic): 0.994
Time: 17:14:14 Log-Likelihood: -44.891
No. Observations: 32 AIC: 95.78
Df Residuals: 29 BIC: 100.2
Df Model: 2
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept -1.249e-16 0.183 -6.84e-16 1.000 -0.374 0.374
POB1 -0.0210 0.190 -0.111 0.912 -0.409 0.367
rezago 0.0003 0.190 0.002 0.999 -0.388 0.389
Omnibus: 58.224 Durbin-Watson: 2.358
Prob(Omnibus): 0.000 Jarque-Bera (JB): 439.486
Skew: 3.864 Prob(JB): 3.69e-96
Kurtosis: 19.428 Cond. No. 1.24


Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

¡Ahora no explicamos absolutamente nada de la variable dependiente!

¿Qué es lo que ocurre a Nivel Municipal?

In [22]:
tasa = municipal['total_des'].divide(municipal['POB1'])*100000
tasa_df = pd.DataFrame(tasa,columns=['tasa'])
tasa_df.head()
municipal = pd.merge(municipal,tasa_df,left_index=True, right_index=True)
vars_municipal =  municipal[['POB1','rezago','total_des','tasa']]
vars_municipal_norm = (vars_municipal - vars_municipal.mean()) / vars_municipal.std()
model = sm.ols(formula="tasa ~ POB1 + rezago",data=vars_municipal_norm)
result = model.fit()
result.summary()
Out[22]:
OLS Regression Results
Dep. Variable: tasa R-squared: 0.044
Model: OLS Adj. R-squared: 0.044
Method: Least Squares F-statistic: 56.87
Date: Tue, 06 Aug 2019 Prob (F-statistic): 7.18e-25
Time: 17:14:14 Log-Likelihood: -3425.9
No. Observations: 2454 AIC: 6858.
Df Residuals: 2451 BIC: 6875.
Df Model: 2
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept -4.992e-17 0.020 -2.53e-15 1.000 -0.039 0.039
POB1 0.0062 0.021 0.298 0.766 -0.034 0.047
rezago -0.2087 0.021 -10.107 0.000 -0.249 -0.168
Omnibus: 5157.631 Durbin-Watson: 1.566
Prob(Omnibus): 0.000 Jarque-Bera (JB): 24261762.774
Skew: 17.558 Prob(JB): 0.00
Kurtosis: 488.845 Cond. No. 1.35


Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Aunque el modelo se vuelve un poco más explicativo, aún no podemos asegurar que éste sea válido, ni a Nivel Estatal ni Municipal.


Ejercicio

Planteen un modelo usando las variables disponibles que explique mejor las Tasas de Desaparecidos. Al igual que en el ejercicio anterior, repitan el análisis para ambas escalas.


Parte II - Efecto de Agregación

En la primera parte de la práctica, vimos cómo la Escala de Análisis tiene influencia sobre los resultados del mismo. En esta segunda parte vamos a estudiar cómo diferentes formas de agregar los datos, en una misma escala, también puede tener influencia sobre los resultados del análisis.

En esta segunda parte, se trabajará con los datos de mezcla de Usos de Suelo de la Ciudad de México, y se estimará su efecto en la Generación de Viajes. Los datos de Uso de Suelo están calculados a partir del DENUE, mientras que la información sobre los viajes proviene de la Encuesta Origen-Destino 2007 de INEGI, por lo que la Escala de Análisis serán los Distritos de Tráfico de dicha encuesta.

Como siempre, el primer paso es leer y revisar los datos:

In [23]:
from geopandas import GeoDataFrame
datos = GeoDataFrame.from_file('data/distritos_variables.shp')
datos.head()
Out[23]:
comercio cve_dist entrada gid id loop ocio pob salida servicios viv geometry
0 9614 034 201286 1 1 27627 95 2125687 200217 579 32618 POLYGON ((486947.2187500237 2148842.250066909,...
1 2905 083 209264 2 2 76022 82 3563363 209859 949 42238 POLYGON ((478110.2187500393 2134697.250066519,...
2 2944 113 65701 3 3 10611 72 3128496 66706 459 26003 POLYGON ((501006.0312499983 2148019.750066887,...
3 2425 131 42385 4 4 8468 60 4842162 43106 256 44080 (POLYGON ((507974.718749986 2125105.250066254,...
4 2849 063 128608 5 5 24472 30 4146145 128756 441 35981 POLYGON ((487190.7500000236 2140396.250066676,...

El ShapeFile que leímos tiene columnas para cada uno de los tipos de Uso de Suelo que nos interesan (Vivienda, Comercio, Servicios y Ocio); adicionalmente, tiene tres columnas con información de los viajes:

  • entrada - Viajes que terminan en el Distrito e inician en uno diferente.
  • salida - Viajes que empiezan en el Distrito y terminan en uno diferente.
  • loop - Viajes que inician y terminan dentro del mismo Distrito.

Por otra parte, la mezcla de Usos de Suelos es medida utilizando el Índice de Entropía, el cual se calcula de acuerdo a la siguiente fórmula:

$$ E = \sum\limits_{j}{\frac{p_{j}*ln(p_{j})}{ln(J)}} $$

Donde $p_j$ representa la proporción del $j-ésimo$ Uso de Suelo con respecto al total, y $J$ es el número total de Usos de Suelo. A continuación se presentan los comandos para calcular este índice, los cuales requieren de la librería numpy:

In [24]:
# Importar la biblioteca a utilizar
import numpy as np

# Crear variable 'intensidad', suma de todos los Usos de Suelo
intensidad = datos['comercio'] + datos['viv'] + datos['ocio'] + datos['servicios']

# Calcular las proporciones para cada Uso de Suelo
prop_comercio = datos['comercio'] / intensidad
prop_viv = datos['viv'] / intensidad
prop_ocio = datos['ocio'] / intensidad
prop_servicios = datos['servicios'] / intensidad

# Utilizar la fórmula del Índice de Entropía
entropia = (prop_comercio*np.log(prop_comercio) + prop_viv*np.log(prop_viv) + prop_ocio*np.log(prop_ocio)
           + prop_servicios*np.log(prop_servicios))/np.log(4)

# Visualizar el resultado
entropia.head()
/home/datalab/miniconda3/envs/geoinf/lib/python3.7/site-packages/pandas/core/series.py:853: RuntimeWarning: divide by zero encountered in log
  result = getattr(ufunc, method)(*inputs, **kwargs)
Out[24]:
0   -0.443781
1   -0.250027
2   -0.303138
3   -0.178275
4   -0.235811
dtype: float64

Lo que hicimos para calcular la Entropía es relativamente sencillo; primero, creamos la variable intensidad, que es la suma de todos los Usos de Suelo; luego, fuimos calculando cada una de las proporciones asociadas a cada uso de suelo; finalmente, sumamos estas proporciones y las dividimos por el logaritmo natural del total de Usos de Suelo en el distrito.

Lo que se obtiene es un objeto del tipo Series, que contiene los Índices de Entropía de forma ordenada; ahora, es necesario unir esta serie a los datos originales:

In [25]:
datos['entropia'] = entropia
datos.head()
Out[25]:
comercio cve_dist entrada gid id loop ocio pob salida servicios viv geometry entropia
0 9614 034 201286 1 1 27627 95 2125687 200217 579 32618 POLYGON ((486947.2187500237 2148842.250066909,... -0.443781
1 2905 083 209264 2 2 76022 82 3563363 209859 949 42238 POLYGON ((478110.2187500393 2134697.250066519,... -0.250027
2 2944 113 65701 3 3 10611 72 3128496 66706 459 26003 POLYGON ((501006.0312499983 2148019.750066887,... -0.303138
3 2425 131 42385 4 4 8468 60 4842162 43106 256 44080 (POLYGON ((507974.718749986 2125105.250066254,... -0.178275
4 2849 063 128608 5 5 24472 30 4146145 128756 441 35981 POLYGON ((487190.7500000236 2140396.250066676,... -0.235811

Ahora que ya se tienen todos los datos, probemos un modelo que intente explicar la cantidad de viajes que terminan en cada distrito. Lo pimero que vamos a hacer es explorar la colinearidad de las variables que tenemos:

In [26]:
corr = datos[['ocio','comercio','servicios','viv','entropia']].corr()
w, v = np.linalg.eig(corr)
w
Out[26]:
array([2.75742273, 1.38110304, 0.2203412 , 0.02278048, 0.61835256])

Debido a que se tienen eigenvalores cercanos al cero, parece que existirán problemas de colinearidad; como tal, para seleccionar las variables entre las cuales existe este problema, observamos directamente la Matriz de Correlación:

In [27]:
corr
Out[27]:
ocio comercio servicios viv entropia
ocio 1.000000 0.714724 0.615029 0.586546 -0.348835
comercio 0.714724 1.000000 0.398002 0.455756 -0.592718
servicios 0.615029 0.398002 1.000000 0.389734 -0.353215
viv 0.586546 0.455756 0.389734 1.000000 0.349609
entropia -0.348835 -0.592718 -0.353215 0.349609 1.000000

Como pueden ver, la Entropia guarda bastante correlación con el resto de las variables, sobre todo con las de Comercio y Ocio; como tal, por lo pronto, seleccionemos únicamente Entropía y Vivienda como variables explicativas.

Antes de generar el modelo, normalicemos las variables, pues éstas tienen escalas de variación muy diferentes, y ya sabemos que esto puede traer problemas:

In [28]:
variables = datos[['entrada','viv','entropia']]
vars_norm = (variables - variables.mean())/variables.std()
vars_norm.describe()
Out[28]:
entrada viv entropia
count 1.560000e+02 1.560000e+02 1.550000e+02
mean -5.693451e-18 1.992708e-16 2.326454e-15
std 1.000000e+00 1.000000e+00 1.000000e+00
min -1.609871e+00 -2.281221e+00 -4.975062e+00
25% -6.599170e-01 -6.773889e-01 -4.441884e-01
50% -1.945051e-01 -1.522696e-01 7.601072e-02
75% 2.739547e-01 3.764430e-01 6.324806e-01
max 6.134273e+00 5.270587e+00 1.915211e+00

Con las variables seleccionadas y normalizadas, es posible correr un primer modelo utilizando la biblioteca statsmodels:

In [29]:
import statsmodels.formula.api as sm

model = sm.ols(formula="entrada ~ viv + entropia",data=vars_norm).fit()
model.summary()
Out[29]:
OLS Regression Results
Dep. Variable: entrada R-squared: 0.211
Model: OLS Adj. R-squared: 0.201
Method: Least Squares F-statistic: 20.35
Date: Tue, 06 Aug 2019 Prob (F-statistic): 1.47e-08
Time: 17:14:15 Log-Likelihood: -200.88
No. Observations: 155 AIC: 407.8
Df Residuals: 152 BIC: 416.9
Df Model: 2
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept 0.0041 0.072 0.057 0.955 -0.138 0.146
viv 0.2265 0.078 2.908 0.004 0.073 0.380
entropia -0.4868 0.077 -6.337 0.000 -0.639 -0.335
Omnibus: 39.203 Durbin-Watson: 2.076
Prob(Omnibus): 0.000 Jarque-Bera (JB): 81.687
Skew: 1.116 Prob(JB): 1.83e-18
Kurtosis: 5.768 Cond. No. 1.44


Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Ejercicio

Especifiquen diferentes modelos para tratar de explicar cada una de las tres variables de viajes en los distritos.


Cambiando de Escala

Ahora, vamos a cambiar de escala de análisis, agregando los distritos en unidades más grandes. Para esto, puede utilizarse una biblioteca de Python llamadaclusterpy, la cual ayuda a generar regionalizaciones al azar a partir de los datos; sin embargo, esta librería únicamente es soportada en las versiones de Python 2.X, y descontinuada para las versiones más recientes de Python 3.X.

Como tal, únicamente se importará un ejemplo del resultado de esta librería, la cual ya se encuentra presete en los datos de trabajo:

In [30]:
datos_cp = GeoDataFrame.from_file('data/randmo_1.shp')

Primero, se analizan las columnas presentes en el nuevo GeoDataFrame:

In [31]:
datos_cp.info()
<class 'geopandas.geodataframe.GeoDataFrame'>
RangeIndex: 156 entries, 0 to 155
Data columns (total 12 columns):
ID             156 non-null float64
comercio       156 non-null float64
cve_dist       156 non-null object
entrada        156 non-null float64
gid            156 non-null float64
loop           156 non-null float64
ocio           156 non-null float64
pob            156 non-null float64
salida         156 non-null float64
viv            156 non-null float64
random_2016    156 non-null float64
geometry       156 non-null object
dtypes: float64(10), object(2)
memory usage: 14.8+ KB

La única variable diferente que sobresalta en la llamada random_2016, que es donde la biblioteca clusterpy almacena el identificador de las regiones al azar que ha generado. Como tal, ahora necesitamos colocar este identificador en los datos originales, utilizando la columna cve_dist como llave para realizar la unión a través de la función .merge():

In [32]:
# Aislar las columnas de interés para la unión
tmp = datos_cp[['cve_dist', 'random_2016']]

# Unir el Identificador de Región a los datos originales
datos = pd.merge(datos, tmp, on='cve_dist')

# Renombrar la columna del identificador
datos = datos.rename(columns = {'random_2016':'id_region'})

# Verificar el proceso
datos.head()
Out[32]:
comercio cve_dist entrada gid id loop ocio pob salida servicios viv geometry entropia id_region
0 9614 034 201286 1 1 27627 95 2125687 200217 579 32618 POLYGON ((486947.2187500237 2148842.250066909,... -0.443781 0.0
1 2905 083 209264 2 2 76022 82 3563363 209859 949 42238 POLYGON ((478110.2187500393 2134697.250066519,... -0.250027 46.0
2 2944 113 65701 3 3 10611 72 3128496 66706 459 26003 POLYGON ((501006.0312499983 2148019.750066887,... -0.303138 49.0
3 2425 131 42385 4 4 8468 60 4842162 43106 256 44080 (POLYGON ((507974.718749986 2125105.250066254,... -0.178275 37.0
4 2849 063 128608 5 5 24472 30 4146145 128756 441 35981 POLYGON ((487190.7500000236 2140396.250066676,... -0.235811 27.0

Ahora ya se tiene casi todo lo necesario para correr un nuevo modelo, esta vez sobre las variables agregadas en regiones; lo único que hace falta es, justamente, calcular estas variables agregadas, lo cual puede hacerse a través de la función .groupby() de pandas:

In [33]:
agregados = datos.groupby(by = 'id_region').sum()
agregados.head()
Out[33]:
comercio entrada gid id loop ocio pob salida servicios viv entropia
id_region
0.0 21359 568758 448 448 87736 316 13936965 563448 2447 153150 -1.541034
1.0 3900 435322 127 127 49346 132 2883723 433190 1748 65127 -0.490879
2.0 10502 515836 405 405 93760 240 17983004 516875 1869 143415 -0.944140
3.0 16155 614527 113 113 44117 272 3655128 610838 1245 60287 -0.800240
4.0 19694 379588 233 233 30613 204 6960214 377767 1269 92892 -1.042804

Aquí simplemente agrupamos los datos según la columna id_region, y calculamos la suma de las variables sobre cada grupo; el único problema es que la Entropía permanece calculada como la suma de las entropias individuales, lo cual no es de utilidad. Como tal, es necesario volver a calcular el Índice de Entropia:

In [34]:
# Crear variable 'intensidad', suma de todos los Usos de Suelo
intensidad = agregados['comercio'] + agregados['viv'] + agregados['ocio'] + agregados['servicios']

# Calcular las proporciones para cada Uso de Suelo
prop_comercio = agregados['comercio'] / intensidad
prop_viv = agregados['viv'] / intensidad
prop_ocio = agregados['ocio'] / intensidad
prop_servicios = agregados['servicios'] / intensidad

# Utilizar la fórmula del Índice de Entropía
entropia = (prop_comercio*np.log(prop_comercio) + prop_viv*np.log(prop_viv) + prop_ocio*np.log(prop_ocio)
           + prop_servicios*np.log(prop_servicios))/np.log(4)

# Añadir el nuevo cálculo al DataFrame
agregados['entropia'] = entropia

# Visualizar el resultado
agregados.head()
Out[34]:
comercio entrada gid id loop ocio pob salida servicios viv entropia
id_region
0.0 21359 568758 448 448 87736 316 13936965 563448 2447 153150 -0.325863
1.0 3900 435322 127 127 49346 132 2883723 433190 1748 65127 -0.245699
2.0 10502 515836 405 405 93760 240 17983004 516875 1869 143415 -0.232320
3.0 16155 614527 113 113 44117 272 3655128 610838 1245 60287 -0.440574
4.0 19694 379588 233 233 30613 204 6960214 377767 1269 92892 -0.383624

Ahora es posible correr el mismo modelo que en los ejemplos anteriores, esta vez utilizando los datos agregados. Cabe recordar que, antes que nada, es necesario normalizarlos:

In [35]:
# Aislar las variables a normalizar
variables = agregados[['entrada', 'viv', 'entropia']]

# Normalizar las variables
vars_norm = (variables - variables.mean()) / variables.std()

# Generar la Regresión Lineal por Mínimos Cuadrados Ordinarios
model = sm.ols('entrada ~ viv + entropia' , data = vars_norm).fit()

# Visualizar el resultado de la regresión
model.summary()
Out[35]:
OLS Regression Results
Dep. Variable: entrada R-squared: 0.639
Model: OLS Adj. R-squared: 0.624
Method: Least Squares F-statistic: 41.66
Date: Tue, 06 Aug 2019 Prob (F-statistic): 3.90e-11
Time: 17:14:16 Log-Likelihood: -44.944
No. Observations: 50 AIC: 95.89
Df Residuals: 47 BIC: 101.6
Df Model: 2
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept 1.388e-17 0.087 1.6e-16 1.000 -0.174 0.174
viv 0.7900 0.088 8.954 0.000 0.613 0.968
entropia -0.2504 0.088 -2.838 0.007 -0.428 -0.073
Omnibus: 15.583 Durbin-Watson: 1.675
Prob(Omnibus): 0.000 Jarque-Bera (JB): 21.772
Skew: 1.027 Prob(JB): 1.87e-05
Kurtosis: 5.497 Cond. No. 1.13


Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Ahora, el objetivo es comparar los resultados anteriores con los generados a partir de diferentes regionalizaciones aleatorias de los mismos datos. En la siguiente celda es posible observar este proceso; todos los comandos utilizados anteriormente se repiten, siendo la única diferencia que de las Líneas 1 a 11 se generan números al azar como Identificadores de Región, a través de la función .random() de la biblioteca numpy, los cuales son reasignados a los datos originales para volver a generar la Regresión Lineal:

In [36]:
# Importar biblioteca que se utilizará en el proceso
import math

# Generar una serie con Identificadores de Regiones asignados al azar
rndm = pd.Series()
for x in range(len(datos)):
    rndm.loc[x] = math.floor(np.random.random()*(49+1))

# Colorcar los nuevos identificadores en los datos originales y agrupar
datos['id_region'] = rndm
agregados = datos.groupby(by = 'id_region').sum()

# Recalcular el Índice de Entropía según las nuevas regiones
intensidad = agregados['comercio'] + agregados['viv'] + agregados['ocio'] + agregados['servicios']
prop_comercio = agregados['comercio'] / intensidad
prop_viv = agregados['viv'] / intensidad
prop_ocio = agregados['ocio'] / intensidad
prop_servicios = agregados['servicios'] / intensidad
entropia = (prop_comercio*np.log(prop_comercio) + prop_viv*np.log(prop_viv) + prop_ocio*np.log(prop_ocio)
           + prop_servicios*np.log(prop_servicios))/np.log(4)
agregados['entropia'] = entropia

# Volver a generar la Regresión Lineal según las nuevas regiones
variables = agregados[['entrada', 'viv', 'entropia']]
vars_norm = (variables - variables.mean()) / variables.std()
model = sm.ols('entrada ~ viv + entropia' , data = vars_norm).fit()

# Visualizar el resultado
model.summary()
Out[36]:
OLS Regression Results
Dep. Variable: entrada R-squared: 0.740
Model: OLS Adj. R-squared: 0.729
Method: Least Squares F-statistic: 64.16
Date: Tue, 06 Aug 2019 Prob (F-statistic): 6.65e-14
Time: 17:14:16 Log-Likelihood: -35.240
No. Observations: 48 AIC: 76.48
Df Residuals: 45 BIC: 82.09
Df Model: 2
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept 9.021e-17 0.075 1.2e-15 1.000 -0.151 0.151
viv 0.8352 0.078 10.713 0.000 0.678 0.992
entropia -0.4680 0.078 -6.002 0.000 -0.625 -0.311
Omnibus: 1.382 Durbin-Watson: 1.663
Prob(Omnibus): 0.501 Jarque-Bera (JB): 0.681
Skew: -0.245 Prob(JB): 0.712
Kurtosis: 3.316 Cond. No. 1.26


Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Como pueden ver, cada vez que corran la celda anterior, van a tener un resultado un poco diferente, es decir, cada modelo arroja modelos diferentes en la misma escala. En este caso, podemos pensar que no se trata de un problema muy grave, pues nosotros sabemos que las agregaciones son aleatorias y tenemos alguna certeza de que las desviaciones observadas en los resultados son de igual forma aleatorias.

Sin embargo, cabe preguntar, ¿qué pasa cuando no tenemos control sobre las agregaciones?


Ejercicio

Encuentren un modelo que explique razonablemente bien los viajes entrantes a la escala agregada; pueden utilizar únicamente las regiones presentadas en el ShapeFile de nombre randmo_1.shp, utilizada al principio de esta sección, para no estar sujetos a la aleatoriedad de la celda anterior.

A partir de este modelo, ahora sí, utilicen el proceso de la celda anterior para hacer diferentes experimentos y registrar los parámetros más importantes, como $R^2$, los Coeficientes y las Significancias de las variables. Con los resultados de varias repeticiones traten de demostrar que las diferencias son aleatorias (es decir, siguen una Distribución Normal).


Epílogo - ¿Qué pasa cuando no tenemos control sobre las agregaciones?

Ahora, vamos a trabajar sobre tres agregaciones diferentes de los datos, en la misma escala que las hechas, pero sobre las que no tenemos control. Simplemente, son las unidades que tenemos (de forma similar a las AGEB's, por ejemplo).

Lo primero es leer los nuevos datos:

In [37]:
datos_nuevos = GeoDataFrame.from_file('data/variables_regiones.shp')
datos_nuevos.head()
Out[37]:
ID comercio cve_dist entrada gid id_1 loop ocio pob region_1 region_2 region_3 salida servicios viv entropia geometry
0 1.0 9614.0 034 201286.0 1.0 None 27627.0 95.0 2125687.0 25.0 16.0 20.0 200217.0 579.0 32618.0 -0.443781 POLYGON ((486947.2187500237 2148842.250066909,...
1 2.0 2905.0 083 209264.0 2.0 None 76022.0 82.0 3563363.0 25.0 20.0 4.0 209859.0 949.0 42238.0 -0.250027 POLYGON ((478110.2187500393 2134697.250066519,...
2 3.0 2944.0 113 65701.0 3.0 None 10611.0 72.0 3128496.0 29.0 43.0 17.0 66706.0 459.0 26003.0 -0.303138 POLYGON ((501006.0312499983 2148019.750066887,...
3 4.0 2425.0 131 42385.0 4.0 None 8468.0 60.0 4842162.0 21.0 42.0 23.0 43106.0 256.0 44080.0 -0.178275 (POLYGON ((507974.718749986 2125105.250066254,...
4 5.0 2849.0 063 128608.0 5.0 None 24472.0 30.0 4146145.0 38.0 20.0 2.0 128756.0 441.0 35981.0 -0.235811 POLYGON ((487190.7500000236 2140396.250066676,...

Como pueden ver, son exactamente igual a los originales, salvo por las columnas nuevas region_1, region_2 y region_3. Éstas son, justamente, las nuevas agregaciones de datos sobre las cuales nosotros no hemos tenido control; el resto de las variables son idénticas a las anteriores.

Entonces, realicemos exactamente el mismo proceso, esta vez utilizando las nuevas regiones que nos han sido proporcionadas:

In [38]:
# REGIÓN 1
# Agrupar según la Región 1
r_1 = datos_nuevos.groupby(by = 'region_1').sum()

# Recalcular el Índice de Entropía
intensidad = r_1['comercio'] + r_1['viv'] + r_1['ocio'] + r_1['servicios']
prop_comercio = r_1['comercio'] / intensidad
prop_viv = r_1['viv'] / intensidad
prop_ocio = r_1['ocio'] / intensidad
prop_servicios = r_1['servicios'] / intensidad
entropia = (prop_comercio*np.log(prop_comercio) + prop_viv*np.log(prop_viv) + prop_ocio*np.log(prop_ocio)
           + prop_servicios*np.log(prop_servicios))/np.log(4)
r_1['entropia'] = entropia

# Generar la Regresión Lineal
variables = r_1[['entrada', 'viv', 'entropia']]
vars_norm = (variables - variables.mean()) / variables.std()
model_1 = sm.ols('entrada ~ viv + entropia' , data = vars_norm).fit()

# Visualizar el resultado
model_1.summary()
Out[38]:
OLS Regression Results
Dep. Variable: entrada R-squared: 0.775
Model: OLS Adj. R-squared: 0.765
Method: Least Squares F-statistic: 79.12
Date: Tue, 06 Aug 2019 Prob (F-statistic): 1.29e-15
Time: 17:14:16 Log-Likelihood: -32.822
No. Observations: 49 AIC: 71.64
Df Residuals: 46 BIC: 77.32
Df Model: 2
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept -0.0055 0.070 -0.079 0.937 -0.146 0.135
viv 0.8308 0.072 11.545 0.000 0.686 0.976
entropia -0.1867 0.072 -2.598 0.013 -0.331 -0.042
Omnibus: 22.833 Durbin-Watson: 2.453
Prob(Omnibus): 0.000 Jarque-Bera (JB): 79.007
Skew: 0.998 Prob(JB): 6.98e-18
Kurtosis: 8.892 Cond. No. 1.22


Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
In [39]:
# REGIÓN 2
# Agrupar según la Región 2
r_2 = datos_nuevos.groupby(by = 'region_2').sum()

# Recalcular el Índice de Entropía
intensidad = r_2['comercio'] + r_2['viv'] + r_2['ocio'] + r_2['servicios']
prop_comercio = r_2['comercio'] / intensidad
prop_viv = r_2['viv'] / intensidad
prop_ocio = r_2['ocio'] / intensidad
prop_servicios = r_2['servicios'] / intensidad
entropia = (prop_comercio*np.log(prop_comercio) + prop_viv*np.log(prop_viv) + prop_ocio*np.log(prop_ocio)
           + prop_servicios*np.log(prop_servicios))/np.log(4)
r_2['entropia'] = entropia

# Generar la Regresión Lineal
variables = r_2[['entrada', 'viv', 'entropia']]
vars_norm = (variables - variables.mean()) / variables.std()
model_2 = sm.ols('entrada ~ viv + entropia' , data = vars_norm).fit()

# Visualizar el resultado
model_2.summary()
Out[39]:
OLS Regression Results
Dep. Variable: entrada R-squared: 0.859
Model: OLS Adj. R-squared: 0.853
Method: Least Squares F-statistic: 142.8
Date: Tue, 06 Aug 2019 Prob (F-statistic): 1.06e-20
Time: 17:14:16 Log-Likelihood: -21.517
No. Observations: 50 AIC: 49.03
Df Residuals: 47 BIC: 54.77
Df Model: 2
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
Intercept 3.469e-18 0.054 6.39e-17 1.000 -0.109 0.109
viv 0.9402 0.056 16.889 0.000 0.828 1.052
entropia -0.1991 0.056 -3.576 0.001 -0.311 -0.087
Omnibus: 3.493 Durbin-Watson: 1.909
Prob(Omnibus): 0.174 Jarque-Bera (JB): 2.501
Skew: 0.369 Prob(JB): 0.286
Kurtosis: 3.810 Cond. No. 1.19


Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Ustedes pueden generar ahora el Tercer Modelo.

La pregunta es, ¿qué es lo que está ocurriendo? ¿Por qué se están obteniendo valores tan diferentes a los que se tenían antes?

Ejercicio Final - Tratar de explicar este fenómeno.