Análisis de la Distribución de los presupuestos municipales en función de la población.

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:

Cargar los datos

En nuestro caso, los datos vienen en formato "xls", por lo que podemos proceder a su lectura utilizando una librería que pueda interpretarlos.

In [1]:
# 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")

Exploración de los datos

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.

In [2]:
# Revisamos la carga del conjunto de datos extrayendo los primeros registros (5)
print ("Número de variables: %d" % len(df.columns))
Número de variables: 50
In [3]:
df.head().style
Out[3]:
COD_RAEELL CLASE_DE_ENTIDAD TIPO_DE_ENTIDAD DENOMINACION TIPO_DE_VIA NOMBRE_DE_VIA NUMERO LETRA ESCALERA PISO PUERTA MUNICIPIO PROVINCIA CODIGO_POSTAL TELEFONO_FIJO FAX DIRECCION_WEB CORREO_ELECTRONICO NOMBRE_PRESIDENTE PRIMER_APELLIDO_PRESIDENTE SEGUNDO_APELLIDO_PRESIDENTE CARGO_PRESIDENTE FECHA_NOMBRAMIENTO_PRESIDENTE GRUPO_POLITICO_PRESIDENTE NUMERO_BOJA FECH_BOJA PROVINCIA_PERTENECE EXTENSION_SUPERFICIE NUM_BOJA_CAPITALIDAD NUM_FECHA_BOJA_CAPITALIDAD CAPITALIDAD_MUNICIPIO CAPITALIDAD_PROVINCIA POBLACION REGIMEN_FUNCIONAMIENTO CLASIFICACION_SECRETARIA CLASIFICACION_INTERVENCION EJERCICIO GASTOS INGRESOS BOJA_COMPETENCIA FECHA_BOJA_COMPETENCIA CONFIANZA ANOTACION_JUDI_ORGANO_JUDICIAL ANOTACION_JUDI_DATOS_AFECTADOS ANOTACION_JUDICIAL_RESULTADO ANOTACION_JUDI_FECHA_SENTENCIA ANOTACION_ORGANO ANOTACION_DATO ANOTACION_RESULTADO NUMERO_CONCEJALES
0 JA01040010 MUNICIPIO nan ABLA PLAZA MAYOR 6 nan nan nan nan ABLA Almeria 4510 9.50351e+08 9.50351e+08 WWW.ABLA.ES REGISTRO@ABLA.ES ANTONIO MANUEL ORTIZ OLIVA ALCALDE/SA 2015-06-13 00:00:00 PARTIDO SOCIALISTA OBRERO ESPA?OL DE ANDALUCIA nan nan PROVINCIA ALMERIA 45.24 0 nan ABLA PROVINCIA ALMERIA 1342 COMUN TERCERA NO POSEE INTERVENCION 2016 2.2246e+06 2.2246e+06 nan nan nan nan nan nan nan nan nan nan 9
1 JA01040025 MUNICIPIO nan ABRUCENA PLAZA DE ANDALUCIA 1 nan nan nan nan ABRUCENA Almeria 4520 9.5035e+08 9.5035e+08 WWW.ABRUCENA.ES REGISTRO@ABRUCENA.ES ANTONIO TORRES RUIZ ALCALDE/SA 2015-06-13 00:00:00 PARTIDO POPULAR nan nan PROVINCIA ALMERIA 83.68 0 nan ABRUCENA PROVINCIA ALMERIA 1279 COMUN TERCERA NO POSEE INTERVENCION 2016 1.60273e+06 1.60273e+06 nan nan nan nan nan nan nan nan nan nan 9
2 JA01140018 MUNICIPIO nan ADAMUZ CALLE FUENTE 1 nan nan nan nan ADAMUZ Cordoba 14430 9.57166e+08 9.57166e+08 WWW.ADAMUZ.ES AYUNTAMIENTO@ADAMUZ.ES MANUELA BOLLERO CALVILLO ALCALDE/SA 2015-06-13 00:00:00 DEMOCRACIA CIUDADANA DE ADAMUZ nan nan PROVINCIA CORDOBA 334.84 0 nan ADAMUZ PROVINCIA CORDOBA 4317 COMUN TERCERA NO POSEE INTERVENCION 2016 4.22745e+06 4.22745e+06 nan nan nan nan nan nan nan nan nan nan 11
3 JA01040031 MUNICIPIO nan ADRA CALLE PUERTA DEL MAR- 3 nan nan nan nan ADRA Almeria 4770 9.504e+08 9.50403e+08 WWW.ADRA.ES rperez@adra.es;presidencia@adra.es MANUEL CORTES PEREZ ALCALDE/SA 2015-06-13 00:00:00 PARTIDO POPULAR nan nan PROVINCIA ALMERIA 90.04 0 nan ADRA PROVINCIA ALMERIA 24670 COMUN PRIMERA PRIMERA 2015 1.91282e+07 1.91282e+07 nan nan nan nan nan nan nan nan nan nan 21
4 JA01180016 MUNICIPIO nan AGRON CALLE SAN JOSE 5 nan nan nan nan AGRON Granada 18132 9.58557e+08 9.58557e+08 WWW.AGRON.ES AGRON@DIPGRA.ES MARIA DEL PILAR LOPEZ ROMERO ALCALDE/SA 2015-06-13 00:00:00 PARTIDO SOCIALISTA OBRERO ESPA?OL DE ANDALUCIA nan nan PROVINCIA GRANADA 27 0 nan AGRON PROVINCIA GRANADA 310 COMUN TERCERA NO POSEE INTERVENCION 2016 392000 392000 nan nan nan nan nan nan nan nan nan nan 7

En esta primera visión del conjunto de datos podemos observar:

  • que existen 50 variables diferentes en el conjunto;
  • que las provincias de la variable "PROVINCIA PERTENECE" vienen precedidas del texto "PROVINCIA";
  • que existen caracteres especiales que se han omitido como "Ñ" o acentos;
  • que algunas variables no se han interpretado correctamente, como por ejemplo los teléfonos que son considerados como números y son mostrados con notación científica;
  • que determinadas columnas muestran valores extraños (NaN);
  • que algunos campos incluyen información múltiple, como por ejemplo sucede en algunos casos con los correos electrónicos;
  • etc.

En este caso, nos interesa conservar las siguientes variables para el análisis:

  • Municipios,
  • Provincias,
  • Gastos y
  • Población.

Selección y Transformación de los datos

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:

In [4]:
df.groupby('CLASE_DE_ENTIDAD')['DENOMINACION'].count()
Out[4]:
CLASE_DE_ENTIDAD
CONSORCIO                        114
ENTIDAD LOCAL DESCENTRALIZADA     42
MANCOMUNIDAD                      69
MUNICIPIO                        778
PROVINCIA                          8
Name: DENOMINACION, dtype: int64
In [5]:
# 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.

In [6]:
# Comprobamos el número de registros en extraídos del dato original
print (len(df_municipios.DENOMINACION))
778
In [7]:
# Comprobamos la no existencia de múltiples registros para un mismo municipio
df_municipios.DENOMINACION.value_counts().head()
Out[7]:
BENIZALON    1
GELVES       1
MIJAS        1
LANTEIRA     1
LEPE         1
Name: DENOMINACION, dtype: int64

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.

In [8]:
# 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)]
Out[8]:
DENOMINACION PROVINCIA POBLACION REGIMEN_FUNCIONAMIENTO GASTOS
96 AROCHE Huelva 3183.0 COMÚN NaN
164 BRENES Sevilla 12697.0 COMÚN NaN
256 COGOLLOS DE GUADIX Granada 718.0 COMÚN NaN
405 DEHESAS VIEJAS Granada 783.0 NaN 693906.10
410 DOMINGO PEREZ DE GRANADA Granada 857.0 NaN 854088.06
472 GERENA Sevilla 7404.0 COMÚN NaN
499 GUIJO, EL Córdoba 372.0 COMÚN NaN
525 IGUALEJA Málaga 816.0 COMÚN NaN
536 IZNALLOZ Granada 5159.0 COMÚN NaN
543 JATAR Granada 624.0 NaN 599070.00
574 LOPERA Jaen 3779.0 PROPIO NaN
693 MONTECORTO Málaga 636.0 NaN NaN
716 NERVA Huelva 5514.0 COMÚN NaN
734 ORCE Granada 1226.0 COMÚN NaN
736 ORGIVA Granada 5483.0 COMÚN NaN
827 RONQUILLO, EL Sevilla 1398.0 COMÚN NaN
861 SANTA FE Granada 15067.0 COMÚN NaN
917 TREBUJENA Cadiz 7072.0 COMÚN NaN
978 VILLANUEVA DEL ARISCAL Sevilla 6395.0 COMÚN NaN
986 VILLARALTO Córdoba 1238.0 COMÚN NaN
In [9]:
# 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)
Out[9]:
'20 / 778 = 0.025707'

Una vez comprobado que el volumen es mínimo (<3%) en relación al registro, vamos a proceder a obviarlos del estudio.

In [10]:
# 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).

In [11]:
# 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

Análisis de la información

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.

In [12]:
from bokeh.charts import Scatter, BoxPlot, Line
from bokeh.io import output_notebook, show
from bokeh.plotting import *
output_notebook()
Loading BokehJS ...

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:

In [13]:
# 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.

In [14]:
# 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))
Función lineal (Muestra): 1227.770674 x -2045541
Media Errores Cuadráticos: 65126063634219.26
Varianza explicada / Coeficiente de determinación: 0.98

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.

In [15]:
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]))
Función lineal : 1227.770674 x -2045541

Vamos a verlo gráficamente con la representación de la línea de regresión sobre los datos:

In [16]:
# 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)

Conclusión

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.

In [ ]: