Implementación del análisis de correspondencias con C#
El análisis de correspondencias es una técnica estadística que nos permite estudiar relaciones entre datos categóricos mediante el escalado óptimo y proyección ortogonal en dos o tres dimensiones de tablas de contingencia. Su implementación es relativamente sencilla, y en este artículo voy a mostrar un ejemplo utilizando el lenguaje CSharp. Además, el programa de ejemplo permite dibujar gráficas sencillas con los datos resultantes.
En este artículo hablé del análisis de correspondencias con la base de datos PISA, usando el programa R. Igual que allí, os recomiendo el libro La práctica del análisis de correspondencias, de Michael Greenacre si estáis interesados en un texto que explique de forma sencilla y exhaustiva esta técnica de análisis de datos.
El análisis de correspondencias
Como no puedo hacer una exposición exhaustiva de la técnica del análisis de correspondencias en un simple post de un blog, voy a intentar hacer un pequeño resumen sobre el mismo, y os remito a la literatura especializada para un estudio en profundidad.
Partimos de una tabla de contingencia que cruza dos conjuntos de datos categóricos. En este artículo voy a utilizar como ejemplo una tabla con datos de auto percepción de la salud, que cruza una escala de tipo Likert (de muy buena a muy mala), con una serie de grupos de edad (desde de 16 a 24 años, hasta más de 75).
Lo primero que hacemos con esta tabla es calcular las frecuencias marginales de las filas y de las columnas, que no son más que la suma de los datos de cada fila y de cada columna. En el análisis de correspondencias llamamos masa de la fila o columna a estas frecuencias marginales. Consideramos a la fila con las masas de las columnas y la columna con las masas de las filas como un vector n-dimensional que llamamos el perfil medio de las filas o las columnas.
Si dividimos los valores de cada fila o columna por su frecuencia marginal, lo que obtenemos es una serie de vectores n-dimensionales con las frecuencias relativas para cada fila o columna, que llamamos perfil fila y perfil columna. Representa las proporciones de los datos considerando la fila o columna como una entidad separada compuesta por el 100% de los individuos para la categoría correspondiente.
Con este perfil fila o perfil columna y los perfiles medios podemos calcular una distancia que nos indicará como de parecidos son el perfil de la fila o columna y el perfil medio. Utilizamos la distancia ji-cuadrado, que es la raíz cuadrada de la suma de las diferencias al cuadrado entre las componentes del perfil fila/columna y el perfil medio divididas por el perfil medio.
A partir de esta distancia podemos calcular una medida derivada llamada inercia, que es simplemente el cuadrado de esta distancia multiplicado por la masa, La inercia nos da una idea del grado de dispersión de los datos, pues es algo así como una media ponderada de las distancias, y la podemos considerar como un indicador de la varianza de los datos. Si sumamos las inercias de todas las filas o de todas las columnas obtenemos la inercia total de la tabla. Cuanto mayor sea su valor, más separadas estarán las filas y las columnas de su perfil medio o centroide.
Por último, nos interesa la representación gráfica de los datos, para visualizar posibles relaciones entre ellos. Puesto que estamos viendo las filas y las columnas como vectores n-dimensionales, necesitamos reducir la dimensión de los mismos para poder representarlos, normalmente en dos dimensiones. Para esto, utilizamos la técnica de la descomposición en valores singulares o DVS, que es una generalización del cálculo de valores propios de las matrices cuadradas a las matrices rectangulares, y que nos proporciona una serie de valores y vectores con los cuales podemos proyectar una matriz de MxN en otra de Mx2 o de 2xN, conservando la mayor parte posible de la información que contiene sobre los datos. Se trata de descomponer la matriz en otras tres:
S = UDVT
Siendo D una matriz diagonal cuya diagonal está compuesta por los valores singulares en orden descendente y U y V las matrices con los vectores singulares izquierdos y derechos. S se calcula a partir de la tabla de contingencia P de la siguiente manera:
S = Dr-1/2(P - rcT)Dc-1/2
Donde Dr-1/2 es una matriz diagonal cuyos elementos son la inversa de la raíz cuadrada del perfil medio de las filas, Dc-1/2 es lo mismo para las columnas, r es el perfil medio de las filas y c el de las columnas. P es la tabla de contingencia.
En la representación gráfica, para representar las filas reducimos las M columnas a 2 mediante esta proyección, que serán las coordenadas en dos dimensiones de cada una de ellas. Para las columnas hacemos lo mismo con las filas. Las filas y las columnas se representan conjuntamente, utilizando dos tipos diferentes de coordenadas.
Por un lado están las coordenadas estándares, que se calculan dividiendo los dos primeros vectores de la matriz U de la DVS por la raíz cuadrada de la masa de las filas, para obtener las coordenadas en la dimensión 1 y 2 de las filas, o los dos primeros vectores de la matriz V por la masa de las columnas para las coordenadas de columna. Estas coordenadas representan los vértices de las categorías, es decir, los puntos en los que todos los elementos pertenecerían a dicha categoría.
Por otro lado están las coordenadas principales, que se obtienen multiplicando las coordenadas estándares por los dos primeros valores singulares. Estas coordenadas representan la posición de una determinada fila o columna en relación con el perfil medio.
Con estas coordenadas, ya podemos representar el gráfico en dos dimensiones, mostrando las filas en coordenadas principales y las columnas en coordenadas estándar, al revés con las columnas en principales, o mostrando ambas como principales.
El programa CADemo
Para mostrar cómo implementar esta técnica de análisis, he desarrollado un proyecto de ejemplo. En este enlace puedes descargar el código fuente del proyecto CADemo, escrito en csharp con Visual Studio 2013.
Como ejemplo de datos, en este enlace puedes descargar un archivo csv con datos de auto percepción de la salud. Las filas corresponden con grupos de edades de 10 en 10 años, comenzando por el grupo entre 16 y 24 años, entre 25 y 34, etc. Las columnas están numeradas del 1 al 5 y representan la autopercepción del estado de salud, siendo 5 muy buena, 1 muy mala y 3 regular. Las celdas de datos contienen el porcentaje de respuestas cruzadas entre estos dos factores. En el programa siempre habrá que trabajar con este tipo de tablas, con nombres de fila y columna y datos expresados en proporciones entre 0 y 1.
Este es el aspecto del programa:
Como trabajamos con ficheros csv, podemos indicar los caracteres que sirven para separar las columnas y los decimales de los números. También podemos indicar la precisión que queremos utilizar en el redondeo de los cálculos.
Con el icono de la izquierda de la barra de herramientas se puede seleccionar y cargar un archivo csv con datos, cargamos los datos del fichero health.csv de ejemplo:
Aunque los cálculos se pueden realizar todos a la vez, unos a continuación de otros, he preferido dividir el proceso de los datos en tres partes, para que sea más fácil leer y entender el código. La primera parte es la lectura de la tabla desde el archivo csv, en esta fase se calculan las masas de las filas y de las columnas, que recordemos que son simplemente las frecuencias marginales. Este proceso está contenido en el controlador del evento Click del botón.
El siguiente paso consiste en el cálculo de la distancia ji-cuadrado y la inercia, para ello hay que pulsar el botón Chi-Dist. En el controlador del evento Click de este botón se realizan dichos cálculos, y se añaden dos filas y columnas nuevas con estos datos para las filas y las columnas:
En la intersección entre la fila y la columna de las inercias de fila y columna, se encuentra la inercia total de la tabla, que no es más que la suma de las inercias de fila o de columna.
El siguiente paso es calcular la proyección en dos dimensiones. Fijémonos que tenemos 7 filas y 5 columnas, es decir, vectores con 7 o 5 coordenadas, según se trate de las filas o las columnas. Para representarlos necesitaremos reducirlos a vectores de 2 coordenadas. Como dije antes, el procedimiento es usar la descomposición en valores singulares, pero este cálculo es bastante complicado, así que utilizaré una librería matemática que se puede instalar desde el repositorio de paquetes NuGet, se trata de la librería Accord.Math que podéis encontrar buscando por la palabra clave svd.
Pulsando el botón 2D Projection, se realizarán los cálculos para obtener las coordenadas estándar y principales de las filas y columnas, que se añadirán en 4 nuevas filas y columnas, dos para las dos dimensiones de las estándar y dos para las principales:
Podríamos haber proyectado en tres dimensiones en lugar de dos para hacer un mapa tridimensional, solo hay que utilizar uno más de los vectores y valores singulares obtenidos en la SVD. En la fila y columna de la inercia se han añadido dos nuevos valores que corresponden con la inercia proyectada en cada una de las dimensiones. Podéis ver que la suma es menor que el total, esta diferencia nos da una idea de la inercia perdida al reducir la dimensión de los datos.
Por último, podemos ver que ha aparecido una nueva pestaña, Graph, donde podemos ver una muestra de la gráfica del análisis de correspondencias, implementada en el evento Paint del panel que contiene esta pestaña:
Donde la dimensión 1 se encuentra en el eje horizontal y la 2 en el vertical. Con los botones de la barra de herramientas, podemos mostrar los datos usando coordenadas principales para las columnas, para las filas, o para ambas (con el botón Symmetric). También podemos mostrar los puntos con un tamaño proporcional a su masa con el botón Mass. Las filas se representan en color azul, y las columnas en rojo.
Sobre los detalles de la implementación, el código es sencillo de entender, pero si quieres conocer los detalles de la implementación, en este enlace tienes el código comentado (en inglés) en el sitio Code Project.