En base a los datos que publica la Consejería de la Presidencia y Administración Local de la Junta de Andalucía, se desea analizar si la distribución del presupuesto de gasto está relacionado con la población que vive en el municipio.
Es importante tener en cuenta que la configuración de los municipios, en función de su población, viene dada por las distintas normas vigentes:
En nuestro caso, los datos vienen en formato "xls", por lo que podemos proceder a su lectura utilizando una librería que pueda interpretarlos.
# Leemos el fichero obtenido del portal de datos abiertos, como se encuentra en formato xls
# podemos realizar directamente su lectura
import pandas as pd
df = pd.read_excel("RAEL_DATOS_ENTIDADES.xls")
A continuación, se realiza una revisión básica del conjunto de datos para detectar posibles problemas en la carga (por ejemplo de codificación). Estos problemas pueden, posteriormente, afectar al estudio que se va a realizar.
Además, nos permitirá observar mejor las variables que lo conforman y los valores que contienen.
# Revisamos la carga del conjunto de datos extrayendo los primeros registros (5)
print ("Número de variables: %d" % len(df.columns))
df.head().style
En esta primera visión del conjunto de datos podemos observar:
En este caso, nos interesa conservar las siguientes variables para el análisis:
Para analizar el gasto municipal no necesitamos todas las variables del conjunto de datos, por lo que en principio sólo sería de interés manejar las columnas que tienen que ver con la identificación del municipio, provincia, población y gastos.
En la variable CLASE_DE_ENTIDAD se identifican los distintos tipos de entidades (existen varios) que están recogidos en el RAEL. Para nuestro análisis sólo nos interesan los municipios, por lo que únicamente debemos seleccionar estos, y para ello filtraremos por la columna clase de entidad:
df.groupby('CLASE_DE_ENTIDAD')['DENOMINACION'].count()
# Creamos un vector de municipios haciendo una comparación directa entre los valores de CLASE_DE_ENTIDAD
# y la cadena "MUNICIPIO"
filas_de_municipios = df.CLASE_DE_ENTIDAD == "MUNICIPIO"
# Filtramos las columnas que vamos a incluir en el análisis en una nueva variable para evitar variar la fuente original
df_municipios = df[filas_de_municipios] \
[['DENOMINACION','PROVINCIA','POBLACION','REGIMEN_FUNCIONAMIENTO','GASTOS']]
A continuación, se realizan comprobaciones sencillas para validar que el filtro ha funcionado correctamente y la información que tenemos es la correcta. Pueden existir datos que erróneos y que debemos eliminar.
En nuestro caso, un primer paso es revisar la lista de municipios por si existiera algún tipo de problema en la información que hemos tratado anteriormente.
# Comprobamos el número de registros en extraídos del dato original
print (len(df_municipios.DENOMINACION))
# Comprobamos la no existencia de múltiples registros para un mismo municipio
df_municipios.DENOMINACION.value_counts().head()
Se identifican los posibles valores nulos dentro del conjunto de datos. Como el dato que se pretende obtener es general y el volumen de datos es suficientemente grande, vamos a optar por obviar las filas con problemas.
# Obtenemos el listado de municipios con algún nulo
filas_con_alguno_nulo = df_municipios.isnull().any(axis=1)
# Mostramos los datos con nulos con el fin ver claramente su volumen e importancia para el estudio
df_municipios[df_municipios.isnull().any(axis=1)]
# Mostramos el número de registros afectados por algún nulo
ratio_incompletos = sum(filas_con_alguno_nulo) / float(len(df_municipios))
"%d / %d = %f" % (sum(filas_con_alguno_nulo), len(df_municipios), ratio_incompletos)
Una vez comprobado que el volumen es mínimo (<3%) en relación al registro, vamos a proceder a obviarlos del estudio.
# Descartamos del conjunto de datos aquellos que posteriormente puedan dificultar las tareas de análisis
df_municipios_con_datos_incompletos = df_municipios[filas_con_alguno_nulo]
df_municipios = df_municipios[~filas_con_alguno_nulo]
En las anteriores salidas de datos se identificó que estos aparecen en notación científica, lo que dificulta su lectura simple, por lo que vamos a proceder a generar nuevas variables con otra escala, para la económica el Millón (1e6) y para la población Mil (1e3).
# Podemos crear una nueva variable en nuestro conjunto de datos para evitar modificar los datos originales
df_municipios['GASTOS_MM'] = df_municipios.GASTOS / 1000000
df_municipios['POBLACION_mm'] = df_municipios.POBLACION / 1000
Vamos a analizar la distribución de los gastos municipales en relación al tamaño de las poblaciones, para ello seleccionaremos una herramienta de visualización y la configuraremos para que se muestren los datos dentro del presente documento.
from bokeh.charts import Scatter, BoxPlot, Line
from bokeh.io import output_notebook, show
from bokeh.plotting import *
output_notebook()
La información contiene gran cantidad de registros. Para analizar la relación que existe entre la población y el gasto municipal, una buena forma de representar los gráficos es mediante una nube de puntos. En las X se mostrará la población (en miles) y en las Y el presupuesto (en millones). El régimen de funcionamiento es interesante para catalogar los municipios y el gráfico nos puede ayudar a ver los distintos conjuntos de municipios existentes:
# Los tooltips ayudan a identificar elementos puntuales de la gráfica, por lo que incluir bocadillos sobre ellos
# ayuda a que se puedan consultar de forma sencilla cada uno de los puntos que la componen
tooltips_gastos=[
('Municipio', '@DENOMINACION'),
('Población', '@POBLACION_mm'),
('Gastos', '@GASTOS_MM'),
]
scatter_municipios = Scatter(df_municipios,
x='POBLACION_mm', y='GASTOS_MM', color="REGIMEN_FUNCIONAMIENTO",
title="Población (miles) Vs Presupuesto de Gastos (Millones de euros)",
xlabel="Población (en miles)", ylabel="Pto. Gastos (en millones de euros)", plot_width=900,
tooltips=tooltips_gastos)
show(scatter_municipios)
La distribución de los puntos en el gráfico indica que existe una relación bastante clara (Lineal) entre la población y el gasto del municipio.
Para confirmar esta relación vamos a realizar una muestra de los datos mostrados y vamos a calcular la relación lineal entre el gasto y la población con objeto de medir el error en la medida y la bondad del ajuste.
# Basado en http://scikit-learn.org/stable/auto_examples/linear_model/plot_ols.html
import numpy as np
from sklearn import linear_model
# Tomamos los vectores de valores
poblacion_x = df_municipios.POBLACION
gasto_y = df_municipios.GASTOS
# Tomamos una muestra del 80% para entrenar el modelo de regresión lineal, siguiendo el concepto 80/20
msk = np.random.rand(len(df_municipios)) < 0.8
# Dividimos los datos de población entre la muestra de entreamiento y de prueba
poblacion_x_train = poblacion_x[msk].to_frame()
poblacion_x_test = poblacion_x[~msk].to_frame()
# Dividimos los datos de gasto entre la muestra de entreamiento y de prueba
gasto_y_train = gasto_y[msk].to_frame()
gasto_y_test = gasto_y[~msk].to_frame()
# Creamos el modelo de regresión lineal y cargamos los datos de entrenamiento de población y gasto
regr = linear_model.LinearRegression()
regr.fit(poblacion_x_train, gasto_y_train)
# El coeficiente de regresión
print("Función lineal (Muestra): %f x %d" % (regr.coef_, regr.predict(0)))
# El error cuadrático del modelo de regresión lineal
print("Media Errores Cuadráticos: %.2f" % np.mean((regr.predict(poblacion_x_test) - gasto_y_test) ** 2))
# La varianza explicada por el modelo, siendo el máximo 1
print('Varianza explicada / Coeficiente de determinación: %.2f' % regr.score(poblacion_x_test, gasto_y_test))
Revisando la varianza explicada, podemos ver que es muy alta, por lo que se confirma lo indicado en el gráfico, es decir, que existe una relación clara entre el gasto y la población a tenor de los datos analizados.
ATENCIÓN: Al tratarse de una muestra, es posible que los datos cambien respecto a cada ejecución.
Si tomamos el conjunto completo, los resultados son muy similares a la muestra, no obstante, como el conjunto de datos no es completo al haber descartado algunos registros anteriormente, no lo tomaremos como referencia.
regression = np.polyfit(poblacion_x_train.POBLACION.tolist(), gasto_y_train.GASTOS.tolist(), 1)
print("Función lineal : %f x %d" % (regression[0], regression[1]))
Vamos a verlo gráficamente con la representación de la línea de regresión sobre los datos:
# Para representar una línea sólo necesitamos dos números, por ejemplo 10000 y 700000
r_x, r_y = zip(*((i / 1000 , regr.predict(i) / 1000000 ) for i in [10000,700000]))
scatter_municipios.line(r_x,r_y , line_width=2,color="Green")
# show the results
show(scatter_municipios)
Como resultado del análisis podemos comprobar que existe una relación lineal entre la población de cada uno de los municipios y el gasto de la población. Esta relación aproximadamente es: $$ Y = 1227.770674 X -2045541 $$ Donde X es la población del municipio e Y es el gasto estimado para esa población.