Cartografía temática II: Cálculo de intervalos de clase con PL/R
Como ya vimos en la primera parte de esta mini-serie dedicada a la cartografía temática, hasta ahora sólo tenemos resuelta una parte, la asignación de paletas de colores para nuestros mapas o semiología cartográfica, pero queda otra parte por resolver, la lógica estadística, es decir, calcular cuales serán los valores de cada uno de los intervalos de clase que intervienen en una distribución numérica.
Como ya es habitual en www.gisandchips.org tenemos una especial predilección por almacenar nuestra fuente de datos en tablas espaciales contenidas en una base de datos PostgreSQL-PostGIS, y esta vez no va a ser menos.
Generalmente, el cálculo de los puntos de ruptura de una distribución ha sido tratado habitualmente desde el punto de vista del cliente (gvSIG, Quantum GIS, ArcGIS, etc), descargándose a la aplicación los datos oportunos (shapefiles, PostGIS, etc) para escoger posteriormente algún método de agrupamiento o clustering. Esta solución resulta apropiada cuando los datos a representar proceden de diferentes fuentes, cambios de geometría constantes realizamos muchos temáticos distintos, o cualquier otra variable a considerar. Pero sin embargo, cuando necesitamos representar la misma realidad espacial (municipios, provincias, comunidades autónomas, continentes, etc) desde diferentes aspectos (demografía, economía, agriculturas, etc) no resulta tán cómodo delegar esta función a la aplicación cliente.
Pongamos un ejemplo para clarificar el tema: Imaginemos que disponemos de datos de población agregados a nivel de municipios, donde existen tantas columnas como censos de población se hayan hecho, y deseamos generar en un portal de Internet mapas temáticos a requerimiento del usuario (año del censo, nº de clases, método de agrupamiento o clustering y paleta de colores). Crear un mapa coroplético de la población lleva implícito un conocimiento de la distribución de los datos (valores mínimos y máximos de la serie, media), y sobre todo las medidas de dispersión o de variabilidad: desviación típica, varianza, co-varianza, coeficiente de correlación de Pearson. Con estos parámetros estamos en condiciones de definir cual será el método de cálculo de intervalo de clase más apropiado para nuestra distribución, para posteriormente asignar la paleta de colores acorde al fenómeno a representar (este último aspecto ya lo tenemos solucionado con nuestra clase PHPcolorBrewer). Con una aplicación de GIS de escritorio sólo podemos realizar un mapa para visualizarlo en pantalla o imprimirlo, pero en un ambiente web(GIS) este forma de actuar carece de lógica, pues estamos limitando al usuario la posibilidad de elegir estos parámetros (método de clustering, número de clases y paleta de colores), ya que es muy complicado obtener datos de un GIS y realizar los cálculos de los intervalos de clases en la própia página web (JavaScript, PHP).
En este artículo vamos a solucionar esta cuestión proponiendo un método que nos permita desde el servidor, es decir, desde el gestor de base de datos, calcular los intervalos de clase. Para ello no nos queda más remedio que recurrir a la creación de funciones o procedimientos almacenados que contemplen dicha lógica. Realizarlo con PLPg/SQL es una solución, pero programar estas funciones requiere de mucho esfuerzo al no formar parte de las funciones nativas del servidor (por ejemplo, no existe una función que calcule la desviación típica). ¿Por qué no utilizamos un lenguaje especializado en el manejo estadístico? En este sentido, PL/R reúne todas las condiciones para convertirse en el lenguaje de programación elegido. Una prueba de la potencia de este lenguaje, en este caso R lo podemos ver con este ejemplo.
Implementación en R
Tenemos la población activa (valores en miles) de todas las provincias españolas, obtenidas del INE, en el año 1996, disponibles en un objeto vector:
| c(126.4,137.9,570.9,192.1,409.7,60.2,251.3,325.9,2109.1,145.8,159.4,425.5,207.8,194.3, 172.5,301.5,443,69,252.4,300,55.2,296.4,158.2,84.5,244.3,200.3,141.7,174.2,2180.7, 475.8,438,224.6,161.4,71.2,320.8,390,104.7,144.3,328.3,56.5,646.5,36.3,242,52.8,196.5, 919.6,211.6,473.9,70.2,356.7,50) |
En una sesión de R se crearía de esta manera
| poblacion.1996 <- c(126.4,137.9,570.9,192.1,409.7,60.2,251.3,325.9,2109.1,145.8,159.4, 425.5,207.8,194.3,172.5,301.5,443,69,252.4,300,55.2,296.4,158.2,84.5,244.3,200.3,141.7, 174.2,2180.7,475.8,438,224.6,161.4,71.2,320.8,390,104.7,144.3,328.3,56.5,646.5,36.3, 242,52.8,196.5,919.6,211.6,473.9,70.2,356.7,50) |
Y se podría ver simplemente escribiendo:
poblacion.1996
Resultado
| [1] 126.4 137.9 570.9 192.1 409.7 60.2 251.3 325.9 2109.1 145.8 [11] 159.4 425.5 207.8 194.3 172.5 301.5 443.0 69.0 252.4 300.0 [21] 55.2 296.4 158.2 84.5 244.3 200.3 141.7 174.2 2180.7 475.8 [31] 438.0 224.6 161.4 71.2 320.8 390.0 104.7 144.3 328.3 56.5 [41] 646.5 36.3 242.0 52.8 196.5 919.6 211.6 473.9 70.2 356.7 [51] 50.0 |
Para ver un sumario estadístico básico de dicho objeto escribimos:
summary(poblacion.1996)
Con este resultado:
| Min. 1st Qu. Median Mean 3rd Qu. Max. 36.3 139.8 207.8 320.8 342.5 2181.0 |
Entrando ya en materia, vamos a calcular 5 clases distribuidas por el método de intervalos iguales, es decir, (valor máximo- valor mínimo) / nº intervalos:
seq(min(poblacion.1996),max(poblacion.1996),length.out=(5+1))
Resultado
| [1] 36.30 465.18 894.06 1322.94 1751.82 2180.70 |
Es decir, las clases quedarían agrupadas de esta forma:
- clase 1: 36.30 – 465.18
- clase 2: 465.18 – 894.06
- clase 3: 894.06 – 1322.94
- clase 4: 1322.94 – 1751.82
- clase 5: 1751.82 – 2180.70
Este método, aunque sencillo de implementar (incluso desde la parte cliente) ofrece una clasificación muy distorsionada cuando los valores son muy dispares, por lo que necesitamos recurrir a otros métodos más eficientes, como son los cuantiles. En R este cálculo queda reducido a la mínima expressión:
quantile(poblacion.1996)
Resultado:
| 0% 25% 50% 75% 100% 36.3 139.8 207.8 342.5 2180.7 |
Esta sería nuestra clasificación
- 1º quantil: 36.3 – 139.8
- 2º quantil: 139.8 – 207.8
- 3º quantil: 207.8 – 342.5
- 4º quantil: 342.5 – 2180.7
¡Más sencillo imposible!
El problema de trabajar directamente en R estriba en la dificultad para obtener datos desde otras fuentes, aunque siempre podemos recurrir a paquetes externos para traer los datos de shapefiles o tablas de PostgreSQL a R
Implementación en PostgreSQL
Como ya hemos visto en anteriores artículos sobre PL/R, esta lenguaje procedural es muy sencillo de utilizar. Sólo hay que copiar el código de R y realizar pequeños cambios para adaptarlo a la sintaxis de PL/R.
Un aspecto que debemos tener muy claro es que casi todos los cálculos en R de una distribución deben de estar dispuestos en forma de un vector. Como es lógico, PostgreSQL no soporta este tipo de dato, pero el lenguaje PL/R si que nos permite una conversión hacia el tipo de datos “array”, ofreciéndonos además una función que realiza todo el trabajo (array_accum)
Por ejemplo, si queremos obtener un array con los datos de la poblacion activa en 1996 (columna t1996t1) de la tabla ine.poblacion_activa, esta es la forma de conseguirlo con SQL:
SELECT array_accum(t1996t1) FROM ine.poblacion_activa;
Y este el resultado:
| {126.4,137.9,570.9,192.1,409.7,60.2,251.3,325.9,2109.1,145.8, 159.4,425.5,207.8,194.3,172.5,301.5,443,69,252.4,300,55.2,296.4, 158.2,84.5,244.3,200.3,141.7,174.2,2180.7,475.8,438,224.6,161.4, 71.2,320.8,390,104.7,144.3,328.3,56.5,646.5,36.3,242,52.8,196.5, 919.6,211.6,473.9,70.2,356.7,50} |
Con esta pequeña aclaración ya podemos utilizar cualquier columna con datos numéricos para realizar funciones de clustering. Las siguientes funciones de PL/R reproducen los cálculos realizados anteriormente con R:
Función para el método de intervalos iguales:
CREATE OR REPLACE FUNCTION r_equal(double precision[], integer) RETURNS double precision[] AS $BODY$ sort(seq(min(arg1),max(arg1),length.out=(arg2+1))) $BODY$ LANGUAGE 'plr' VOLATILE STRICT COST 100;
Vamos a analizar esta función.
Tiene dos parámetros de entrada:
- double precision[]: El array con los datos de entrada que queremos utilizar para calcular los intervalos, utilizando la función array_accum
- integer: el nº de intervalos de clase que deseamos obtener
El resultado (RETURNS) será siempre una estructura de tipo array.
El cuerpo de la función se reduciría a una sola línea que recibe los dos argumentos de la función (arg1 y arg2):
sort(seq(min(arg1),max(arg1),length.out=(arg2+1)))
El uso de esta función es muy sencillo:
SELECT r_equal( array_accum(t1996t1), 5 ) FROM ine.poblacion_activa;
Nos devolverá el siguiente array:
| {36.3,465.18,894.06,1322.94,1751.82,2180.7} |
Para aquellos más versados en PLPg/SQL habrán advertido que realizar la función en este lenguaje requiere de algunas líneas más de código, que pueden ser bastantes más si queremos utilizar otros métodos, como el de cuantiles, que reproducimos a continuación.
CREATE OR REPLACE FUNCTION r_quantile(double precision[], integer) RETURNS double precision[] AS $BODY$ quantile(arg1,probs = seq(0, 1, 1/arg2)) $BODY$ LANGUAGE 'plr' VOLATILE STRICT COST 100;
Ejemplo de uso para 5 intervalos de clase:
SELECT r_quantile(array_accum(t1996t1),5) FROM ine.poblacion_activa;
Devuelve:
| {36.3,104.7,174.2,251.3,409.7,2180.7} |
Ahora que tenemos implementado un método para calcular los intervalos de clase ya estamos en condiciones de desarrollar un auténtico motor de cartografía temática (corológica) en un ambiente web que combine la capacidad de cálculo de PL/R con la semiología cartográfica implementada en PHPcolorBrewer que sea capaz de “pintar” geometrias de una tabla espacial de PostGIS. El resultado de esta coctelera explosiva lo podeis ver en las siguientes demostraciones on-line:
Test 1: Distribución de la población activa por provincias (valores en miles de personas)
Este sencillo ejemplo nos permite ver las posibilidades de integración de varias piezas de software (PostGIS, PL/R. PHP MapScripts y PHPcolorBrewer). Los cálculos de los intervalos de clase utilizan el método “Kmeans”, y el usuario tiene la posibilidad de cambiar la paleta de colores y el nº de clases, así como el año que desea consultar.
Test 2: Distribución de la tasa de ocupación/desempleo por Comunidades Autónomas(valores en miles de personas)
Similar al anterior, pero aplicado a CC.AA. Ofrece la posibilidad de múltiples consultas (número de ocupados/desempleados diferenciando hombre, mujeres y total)
Test 3: Distribución de la población activa por provincias (valores en miles de personas) según diferentes métodos de clustering
Este ejemplo es similar al primero, pero hemos añadido la posibilidad de que el usuario selecciones el método de cálculo de intervalos de clase desea. Es sin duda, un buen ejemplo de la potencia que tiene PL/R al permitirnos elegir hasta 8 métodos diferentes:
Test 4: Varios mapas temáticos en una sóla página con todos los métodos anteriores
El objeto de este ejemplo es demostrar de una sóla vez como se representa un mismo fenómeno desde diferentes métodos de clustering, para ver cual es el que mejor se adapta a nuestras necesidades.
Test 5: Mapa temático municipal sensible con HTML dinámico
Se trata del ejemplo más completo de todos, que nos permite seleccionar una provincia y ver la distribución de la población (total, hombres o mujeres) por municipio. También permite seleccionar todos los métodos indicados, así como la paleta de colores y nº de intervalos.
Test 6: Todas las paletas PHPcolorBrewer aplicadas a un mapa temático
El objetivo de este ejemplo es demostrar en una sóla página como un mismo fenómeno es representado con todas las paleta de PHPcolorBrewer, para que el usuario seleccione la que más le convenga.




Excelentes posts, éste y el anterior, el método de creación de intervalos es determinante para lo que un mapa temático finalmente transmite, y a menudo nos quedamos con lo que nuestro soft desktop nos propone sin pensar en lo que significa.
Como siempre, proponéis una solución completa,testada y con ejemplos. ¡Me encanta!
Hola Guillermo,
El que en nuestro blog haya gente como tú que le guste nuestros mini-artículos nos llena de orgullo. Estoy seguro que en el camino encontraremos algún eslabón que permita conectar todos estos conocimientos a las técnicas de geomarketing, de las que soy un ignorante ávido de conocimientos.
Muchas gracias.
Gracias, está muy bueno el post. Es realmente potente el manejo de datos que realizan con PostgreSQL, hay quienes no conocemos sus límites, siempre nos sorprende algo nuevo.
Saludos.
Muchas gracias Tuxman. Agradezco mucho tu comentario porque soy un seguidor de tu portal (http://geotux.tuxfamily.org/). Es más, estoy registrado y he encontrado aplicaciones y utilidades de mucha calidad que están en sintonía con lo que deseamos hacer en Gis&Chips.
Enhorabuena
Gracias, esta muy bueno el post, estoy siguiendo tus articulos, ya realice el ejercicio del mapa sensible y funciona bien, ahora quiero aplicar este ejercicio de calculo de intervalos PL/R, como el ejemplo de test5.php que mencionas.
Estoy usando postgres 8.4.2 y windows seven, ya instale PL/R y aplicando la función en el sql de postgres me da los intervalos de mi tabla municipios con mi campo pob_tot, pero ahora como lo aplico a mi mapa sensible, tendrás el código para aplicarlo.
Hola Alex,
En el fondo es más sencillo de lo que parece. En PHP sólo tienes que recoger el array que te proporciona PostgreSQL:
Ëjemplo: {36.3,104.7,174.2,251.3,409.7,2180.7}
y convertirlo en un array de PHP
Ejemplo
array(36.3,104.7,174.2,251.3,409.7,2180.7)
Una vez que tienes el array ya lo puedes recorrer sin problemas, contar los elementos, etc.
De esta forma el intervalo inicial estará formado por el 1er y 2ª elemento del array, así sucesivamente añadiendole uno en el contador