Correspondance Analysis

Análisis de Correspondencias
Análisis de Correspondencias

Introducción

El análisis de correspondencias es una técnica estadística que permite explorar la relación entre dos variables categóricas utilizando una representación gráfica. Parte de una tabla de contingencia (frecuencias) y transforma esa información en un mapa bidimensional donde se observan asociaciones entre categorías. Las categorías que aparecen cerca en el gráfico tienen una relación más fuerte, mientras que las que están lejos tienen poca o ninguna relación.

Por ejemplo, si analizamos bebidas (café, té, refresco) y grupos de edad (jóvenes, adultos, mayores), el análisis puede mostrar qué grupo prefiere cada bebida.

También se usa en marketing para estudiar preferencias de consumidores por marcas, en análisis de encuestas, en ciencias sociales para estudiar opiniones, y en lingüística o NLP para analizar relaciones entre palabras y documentos.

En resumen, el análisis de correspondencias convierte tablas de frecuencias en mapas visuales que facilitan interpretar asociaciones entre categorías.

Perfil de filas y columnas

Una tabla de contingencia con \(a\) filas y \(b\) columnas contiene las frecuencias \(n_{ij}\) para cada combinación de la fila \(i\) y la columna \(j\).

Los totales marginales se definen como

\[ n_{i\cdot} = \sum_{j=1}^{b} n_{ij} \]

que representa el total de la fila \(i\), y

\[ n_{\cdot j} = \sum_{i=1}^{a} n_{ij} \]

que representa el total de la columna \(j\).

La frecuencia total de la tabla se denota por

\[ n = \sum_{i=1}^{a}\sum_{j=1}^{b} n_{ij}. \]

Las frecuencias de la tabla de contingencia pueden convertirse en frecuencias relativas dividiendo entre el total \(n\):

\[ p_{ij} = \frac{n_{ij}}{n}. \]

La matriz formada por estas frecuencias relativas se denomina matriz de correspondencias y se define como

\[ P = (p_{ij}) = \left(\frac{n_{ij}}{n}\right). \]

En la siguiente imagen podemos observar una matriz de correspondencias.

‘Tabla de frecuencias’
‘Tabla de frecuencias’

La última columna de la Tabla anterior contiene las sumas de las filas:

\[ p_{i\cdot} = \sum_{j=1}^{b} p_{ij}, \quad i = 1, 2, \dots, a \]

Este vector columna se denota por \(\mathbf{r}\) y puede obtenerse como:

\[ \mathbf{r} = P \mathbf{j} = (p_{1\cdot}, p_{2\cdot}, \dots, p_{a\cdot})^{'} = \left(\frac{n_{1\cdot}}{n}, \frac{n_{2\cdot}}{n}, \dots, \frac{n_{a\cdot}}{n}\right)^{'} \]

donde \(\mathbf{j}\) es un vector columna de unos de tamaño \(a \times 1\).

De manera similar, la última fila contiene las sumas de las columnas:

\[ p_{\cdot j} = \sum_{i=1}^{a} p_{ij}, \quad j = 1, 2, \dots, b \]

Este vector fila se denota por \(\mathbf{c}\) y puede obtenerse como:

\[ \mathbf{c}^{'} = \mathbf{j}^{'} P = (p_{\cdot 1}, p_{\cdot 2}, \dots, p_{\cdot b}) = \left(\frac{n_{\cdot 1}}{n}, \frac{n_{\cdot 2}}{n}, \dots, \frac{n_{\cdot b}}{n}\right) \]

donde \(\mathbf{j}^{'}\) es un vector fila de unos de tamaño \(1 \times b\).

Los elementos de los vectores \(\mathbf{r}\) y \(\mathbf{c}\) a veces se denominan masas de fila y columna.

La matriz de correspondencias junto con los totales marginales puede representarse como:

\[ \begin{bmatrix} P & \mathbf{r} \\ \mathbf{c}^{'} & 1 \end{bmatrix} = \begin{bmatrix} p_{11} & p_{12} & \dots & p_{1b} & p_{1\cdot} \\ p_{21} & p_{22} & \dots & p_{2b} & p_{2\cdot} \\ \vdots & \vdots & \ddots & \vdots & \vdots \\ p_{a1} & p_{a2} & \dots & p_{ab} & p_{a\cdot} \\ p_{\cdot 1} & p_{\cdot 2} & \dots & p_{\cdot b} & 1 \end{bmatrix} \]

A continuación, cada fila y columna de \(P\) se puede convertir en un perfil.

El perfil de la fila \(i\) (\(i = 1,2,\dots,a\)) se define dividiendo cada elemento de la fila por su total marginal:

\[ \mathbf{r}_i = \frac{1}{p_{i\cdot}} (p_{i1}, p_{i2}, \dots, p_{ib}) = \left(\frac{n_{i1}}{n_{i\cdot}}, \frac{n_{i2}}{n_{i\cdot}}, \dots, \frac{n_{ib}}{n_{i\cdot}}\right) \]

Los elementos de cada perfil son frecuencias relativas dentro de la fila, por lo que suman 1:

\[ \mathbf{r}_i^{'} \mathbf{j}= \sum_{j=1}^{b} \frac{n_{ij}}{n_{i\cdot}} = 1. \]

Definiendo la matriz diagonal de las masas de fila:

\[ D_r = \mathrm{diag}(\mathbf{r}) = \begin{bmatrix} p_{1\cdot} & 0 & \dots & 0 \\ 0 & p_{2\cdot} & \dots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \dots & p_{a\cdot} \end{bmatrix}, \]

entonces, la matriz R de perfiles de fila se puede expresar como:

\[ R = D_r^{-1} P = \begin{bmatrix} \frac{p_{11}}{p_{1\cdot}} & \frac{p_{12}}{p_{1\cdot}} & \dots & \frac{p_{1b}}{p_{1\cdot}} \\ \frac{p_{21}}{p_{2\cdot}} & \frac{p_{22}}{p_{2\cdot}} & \dots & \frac{p_{2b}}{p_{2\cdot}} \\ \vdots & \vdots & \ddots & \vdots \\ \frac{p_{a1}}{p_{a\cdot}} & \frac{p_{a2}}{p_{a\cdot}} & \dots & \frac{p_{ab}}{p_{a\cdot}} \end{bmatrix}. \]

De manera similar, el perfil de la columna \(C_j\), \(j=1,2,\dots,b\), se define dividiendo cada elemento de la columna por su total marginal:

\[ \mathbf{c}_j = \frac{1}{p_{\cdot j}} (p_{1j}, p_{2j}, \dots, p_{aj})' = \left( \frac{n_{1j}}{n_{\cdot j}}, \frac{n_{2j}}{n_{\cdot j}}, \dots, \frac{n_{aj}}{n_{\cdot j}} \right)'. \]

Los elementos de cada perfil de columna son frecuencias relativas, por lo que suman 1:

\[ \sum_{i=1}^{a} \frac{n_{ij}}{n_{\cdot j}} = 1. \]

Definiendo la matriz diagonal de las masas de columna:

\[ D_c = \mathrm{diag}(\mathbf{c}) = \begin{bmatrix} p_{\cdot 1} & 0 & \dots & 0 \\ 0 & p_{\cdot 2} & \dots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \dots & p_{\cdot b} \end{bmatrix}, \]

y, la matriz \(C\) de perfiles de columna se puede expresar como:

\[ C = P D_c^{-1} = (\mathbf{c}_1, \mathbf{c}_2, \dots, \mathbf{c}_b), \]

donde cada \(\mathbf{c}_j\) es el perfil de la columna \(j\).

Finalmente, el vector \(\mathbf{r}\) definido anteriormente como vector columna de sumas de fila también puede expresarse como promedio ponderado de los perfiles de columna:

\[ \mathbf{r} = \sum_{j=1}^{b} p_{\cdot j} \mathbf{c}_j. \]

De manera similar, \(\mathbf{c}^{'}\) definido anteriormente como vector fila de sumas de columna también puede expresarse como un promedio ponderado de los perfiles de fila:

\[ \mathbf{c}^{'} = \sum_{i=1}^{a} p_{i\cdot} \mathbf{r}_i^{'}, \]

donde \(\mathbf{r}_i^{'}\) es el perfil de la fila \(i\) transpuesto (vector fila).

Nótese que:

\[ \sum_{i=1}^{a} p_{i\cdot} = \sum_{j=1}^{b} p_{\cdot j} = 1, \]

por lo que los totales marginales \(p_{i\cdot}\) y \(p_{\cdot j}\) sirven como pesos apropiados en los promedios ponderados.

Matrices diagonales de masas

Definimos las matrices diagonales de masas de filas y columnas:

\[ D_r = \mathrm{diag}(\mathbf{r}) = \begin{bmatrix} p_{1\cdot} & 0 & \dots & 0 \\ 0 & p_{2\cdot} & \dots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \dots & p_{a\cdot} \end{bmatrix}, \quad D_c = \mathrm{diag}(\mathbf{c}) = \begin{bmatrix} p_{\cdot 1} & 0 & \dots & 0 \\ 0 & p_{\cdot 2} & \dots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \dots & p_{\cdot b} \end{bmatrix}. \]

Estas matrices permiten ponderar filas y columnas en los pasos posteriores.

Matriz de desviaciones estandarizadas

Se define la matriz de desviaciones estandarizadas:

\[ S = D_r^{-1/2} (P - \mathbf{r} \mathbf{c}') D_c^{-1/2}, \]

cuyos elementos se definen como \[ s_{ij} = \frac{p_{ij} - p_{i\cdot} p_{\cdot j}}{\sqrt{p_{i\cdot} p_{\cdot j}}}, \] donde \(P - \mathbf{r}\mathbf{c}'\) es la matriz de residuos respecto a la independencia entre filas y columnas. Esta matriz centra y normaliza la información para el análisis gráfico.

Descomposición en valores singulares (SVD) Realizamos la descomposición en valores singulares de \(S\):

\[ S = U \Sigma V^{'} \]

donde:

  • \(U\) son los vectores singulares de filas \((a \times k)\)
  • \(\Sigma\) es la matriz diagonal de valores singulares \((k \times k)\)
  • \(V\) son los vectores singulares de columnas \((b \times k)\)
  • \(k = \min(a-1, b-1)\) es el número máximo de dimensiones significativas

Coordenadas principales Las coordenadas principales de filas se calculan como:

\[ R = D_r^{-1/2} U \Sigma \]

y las coordenadas principales de columnas como:

\[ C = D_c^{-1/2} V \Sigma \]

  • Cada fila de \(R\) representa la posición de una fila de la tabla en el espacio de correspondencias.
  • Cada fila de \(C\) representa la posición de una columna de la tabla en el mismo espacio.

Estas coordenadas son las que se usan para construir el biplot de análisis de correspondencias.

Relaciones con promedios ponderados

Finalmente, los vectores de masas \(\mathbf{r}\) y \(\mathbf{c}\) pueden expresarse como promedios ponderados de los perfiles:

\[ \mathbf{r} = \sum_{j=1}^{b} p_{\cdot j} \mathbf{c}_j, \quad \mathbf{c}' = \sum_{i=1}^{a} p_{i\cdot} \mathbf{r}_i' \]

donde los totales marginales \(p_{i\cdot}\) y \(p_{\cdot j}\) funcionan como pesos en los promedios.

Interpretación

La interpretación gráfica del Análisis de Correspondencias se realiza mediante un biplot que muestra las coordenadas principales de filas y columnas. Los puntos representan categorías de filas y columnas en un espacio de dimensiones reducidas (generalmente las dos primeras dimensiones).

Proximidad de filas y columnas: - Filas cercanas → perfiles de columna similares
- Columnas cercanas → perfiles de fila similares
- Filas próximas a columnas → asociaciones fuertes (frecuencias relativas altas)

Distancias chi-cuadrado

La distancia entre filas \(i\) y \(i'\) se define como:

\[ d^2_{\chi^2}(i,i') = \sum_{j=1}^{b} \frac{(r_{ij} - r_{i'j})^2}{p_{\cdot j}} \]

La distancia entre columnas \(j\) y \(j'\) se define como:

\[ d^2_{\chi^2}(j,j') = \sum_{i=1}^{a} \frac{(r_{ij} - r_{ij'})^2}{p_{i\cdot}} \]

Interpretación de ángulos y orientación

  • Ángulo pequeño entre vectores fila y columna → perfiles similares
  • Ángulo cercano a 90° → independencia aproximada
  • Los vectores que apuntan en direcciones opuestas → asociaciones negativas

Posición relativa al origen - Puntos cercanos al origen → perfiles promedio o poco distintivos
- Puntos alejados del origen → perfiles extremos o altamente diferenciados

Masa y tamaño de los puntos - Opcionalmente, el tamaño de cada punto puede representar la masa de la fila o columna \(p_{i\cdot}\) o \(p_{\cdot j}\), reflejando la importancia relativa de cada categoría.

Descripción de los datos

Los datos corresponden a la incidencia delictiva registrados de forma mensual en México desde enero de 2015 a junio de 2023 (Incidencia delictiva)

Las variables que contiene el conjunto de datos son:

  • Año : Año de registro de las averiguaciones previas y/o carpetas de investigación.
  • Clave_Ent : Clave de la entidad, según el Marco Geoestadístico Nacional (MGN) del Instituto Nacional de Geografía y Estadística (INEGI).
  • Entidad : Entidad federativa de registro de las averiguaciones previas y/o carpetas de investigación.
  • Bien jurídico afectado : Primera clasificación de los delitos en las averiguaciones previas y/o carpetas de investigación.
  • Tipo de delito : Segunda clasificación de los delitos.
  • Subtipo de delito: Tercera clasificación de los delitos.
  • Modalidad : Cuarta clasificación de los delitos.
  • Enero - diciembre : Mes de registro de las averiguaciones previas y/o carpetas de investigación.

Aplicación Análisis de correspondencias

import pandas as pd
import numpy as np
from altair_saver import save
import altair as alt
from prince import MCA
from prince import CA
import matplotlib.pyplot as plt
import seaborn as sns
from numpy.linalg import norm
from dbconnection import MySQLDatabase
from scipy.spatial import ConvexHull
sns.set_theme()
alt.renderers.enable('mimetype')
## RendererRegistry.enable('mimetype')
db = MySQLDatabase("delitos")

Seleccionamos el año

anio = 2024
query = f"""   
    SELECT 
        a.id,
        a.Anio,
        a.Mes,
        a.CVE_ENT,
        b.NOM_ENT,
        a.CVE_MUN,
        b.NOM_MUN,
        b.POB_TOTAL,
        a.Tipo_delito,
        a.Total
    FROM Total_delitos as a
    LEFT JOIN geo_mun as b
    ON a.CVE_ENT = b.CVE_ENT AND a.CVE_MUN = b.CVE_MUN
    WHERE a.Anio = {anio};
"""

df = db.execute_query(query)
## ✅ Conexión exitosa
print(f"Shape: {df.shape}")
## Shape: (2923536, 10)
df.head()
##          id  Anio Mes  ... POB_TOTAL             Tipo_delito Total
## 0  24908857  2024  04  ...    948990                  Aborto     0
## 1  24908858  2024  04  ...    948990      Abuso de confianza    57
## 2  24908859  2024  04  ...    948990            Abuso sexual     0
## 3  24908860  2024  04  ...    948990            Acoso sexual     0
## 4  24908861  2024  04  ...    948990  Allanamiento de morada    34
## 
## [5 rows x 10 columns]
# Convertir POB_TOTAL a numérico
df['POB_TOTAL'] = pd.to_numeric(df['POB_TOTAL'], errors='coerce')

# Agrupar por entidad y tipo de delito, sumar los delitos
df_ent = df.groupby(['CVE_ENT', 'NOM_ENT', 'Tipo_delito'], as_index=False).agg({'Total':'sum'})

# Pivotear: tipos de delito a columnas
df_pivot = df_ent.pivot_table(
    index=['CVE_ENT', 'NOM_ENT'],  # Solo estos índices, NO POB_TOTAL
    columns='Tipo_delito',
    values='Total',
    aggfunc='sum',
    fill_value=0
).reset_index()

# estados únicos
print(df_pivot['CVE_ENT'].nunique())
## 32
data = df_pivot.drop(columns=['CVE_ENT'])
data = data.set_index('NOM_ENT')
data.head()
## Tipo_delito           Aborto  ...  Violencia familiar
## NOM_ENT                       ...                    
## Aguascalientes             7  ...                3358
## Baja California           41  ...               14729
## Baja California Sur       10  ...                3344
## Campeche                   0  ...                1186
## Coahuila de Zaragoza       0  ...               13022
## 
## [5 rows x 40 columns]

Ajustamos un análisis de correspondencias con 2 componentes

mca = CA(n_components=2)
mca.fit(data)
## <prince.ca.CA object at 0x0000029989358080>

Función para obtener la distancia más cercana a un perfil dado,

# función 
def get_min(col_coords, row_coords):
    col_labels = col_coords.index.to_list()
    d = []
    d_min = []
    for j in range(row_coords.shape[0]):
      v1 = np.array(row_coords.iloc[j,:])
      dmin = []
      for i in range(col_coords.shape[0]):
        v2 = np.array(col_coords.iloc[i,:])
        euclidean_distance = norm(v1 - v2)
        d.append(euclidean_distance)
        dmin.append(euclidean_distance)
        #print(dmin)
      d_min.append(col_labels[np.argmin(np.array(dmin))])
    return d_min

Obtenemos las coordenadas de filas y columnas

# extraemos coordenadas por fila
row_coords = mca.row_coordinates(data)
# extraemos coordenadas por columna
col_coords = mca.column_coordinates(data)
# varianza acumulada
cum_var = mca.cumulative_percentage_of_variance_
# obtenemos el mínimo
d_min = get_min(row_coords, col_coords)
min = pd.DataFrame({'min':d_min})
# nombre de coordenadas de fila 
col_coords.columns = ['x', 'y']
col_coords = pd.concat([col_coords.reset_index(), min], axis=1)
# nombre de coordenadas de columna
row_coords.columns = ['x', 'y']
row_coords = row_coords.reset_index()

Gráficamos

# gráfico 
alt.renderers.enable('mimetype')
## RendererRegistry.enable('mimetype')
alt.data_transformers.disable_max_rows()
## DataTransformerRegistry.enable('default')

# activa zoom y pan en ambos ejes
zoom = alt.selection_interval(bind='scales')  


# Capas del gráfico:
#  Líneas de referencia (ejes)
ref_lines = (
    alt.Chart(pd.DataFrame({'a': [0]})).mark_rule(color='gray').encode(x='a:Q') +
    alt.Chart(pd.DataFrame({'a': [0]})).mark_rule(color='gray').encode(y='a:Q')
)

# Puntos de delitos (esta capa tiene la selección interactiva)
points_crime = alt.Chart(col_coords).mark_point(
    shape='diamond', filled=True, size=100
).encode(
    x=alt.X('x:Q', scale=alt.Scale(zero=False)),
    y=alt.Y('y:Q', scale=alt.Scale(zero=False)),
    color=alt.Color('min:N', legend=alt.Legend(title="Nearest State")),
    tooltip=['Tipo_delito', 'min']
).add_params(
    zoom  
)

# Etiquetas de delitos
labels_crime = alt.Chart(col_coords).mark_text(
    align='left',
    baseline='middle',
    dx=7,
    fontSize=10
).encode(
    x='x:Q',
    y='y:Q',
    text='Tipo_delito',
    color='min:N'
)

# Puntos de estados
points_states = alt.Chart(row_coords).mark_point(
    filled=True, color='black', size=80
).encode(
    x='x:Q',
    y='y:Q',
    tooltip=['NOM_ENT']
)

# Etiquetas de estados
labels_states = alt.Chart(row_coords).mark_text(
    align='right',
    baseline='middle',
    dx=-7,
    fontSize=10
).encode(
    x='x:Q',
    y='y:Q',
    text='NOM_ENT'
)


# Combina todas las capas
chart = alt.layer(
    ref_lines,
    points_states,
    labels_states,
    points_crime,
    labels_crime
).properties(
    width=950,
    height=600,
    title="Correspondence Analysis – Mexican States and Type of Crime"
).configure_view(
    stroke=None
).configure_title(
    fontSize=18,
    anchor='start'
).configure_axis(
    grid=False
)

chart.save('grafico.html')

Resultados

Ejes y proximidad

  • El eje horizontal (Dim 1) y el eje vertical (Dim 2) representan combinaciones lineales de los tipos de delito que maximizan la varianza explicada.

  • Estados y delitos cercanos en el gráfico están más relacionados entre sí.

  • Estados en la misma región del gráfico comparten patrones similares de incidencia delictiva.

Agrupamientos de delitos

  • En la parte superior derecha:

    • Delitos como Electorales y Otros delitos que atentan contra la libertad y la seguridad sexual está cerca de Ciudad de México, sugiriendo que estos delitos son relativamente más frecuentes allí.
  • En la parte superior izquierda:

    • Delitos como Violación equiparada y Falsedad se asocian con Puebla y Ciudad de México.
  • En la parte inferior derecha:

    • Delitos como Extorsión, Otros delitos y Hostigamiento sexual están más vinculados a Hidalgo, Guanajuato y Tabasco.
  • En la parte inferior izquierda:

    • Delitos como Narcomenudeo se relacionan más con Guanajuato, Sonora y Coahuila de Zaragoza, lo que podría indicar que estos estados tienen más incidencia de delitos relacionados con drogas.

Estados con patrones específicos

  • Ciudad de México: cercana a delitos electorales y de libertad sexual → alta frecuencia relativa de esos delitos.

  • Tlaxcala: aislada en la parte superior derecha → delitos particulares como electorales dominan.

  • Guanajuato y Hidalgo: cerca de Extorsión y Otros delitos → mayor incidencia relativa de esos tipos de delito.

  • Coahuila y Sonora: asociados con delitos de narcotráfico (Narcomenudeo).

Interpretación de proximidad entre delitos

  • Delitos que aparecen juntos (como Abuso sexual y Despojo) sugieren que tienden a ocurrir en los mismos estados o contextos.

  • Delitos alejados del centro (como Narcomenudeo y Electorales) son más característicos o diferenciadores de ciertos estados, es decir, no ocurren uniformemente en todo México.

Estados cercanos entre sí

  • Estados agrupados en la parte central, como Sinaloa, Veracruz, Chihuahua, indican patrones de delitos más comunes y compartidos, sin un tipo de delito extremadamente dominante.

Conclusiones

El análisis de correspondencias realizado sobre los estados de México y los tipos de delito permite identificar patrones estructurales en la distribución relativa de los delitos entre las distintas entidades federativas. El gráfico evidencia:

  • Heterogeneidad regional: Los estados muestran perfiles delictivos diferenciados. Estados como Ciudad de México y Tlaxcala, se asocian con delitos de índole electoral y contra la libertad y seguridad sexual, respectivamente, mientras que otros, como Hidalgo y Guanajuato, presentan mayor incidencia relativa de extorsión y hostigamiento sexual.

  • Delitos característicos: Los delitos situados en los extremos del gráfico (por ejemplo, Narcomenudeo, Electorales, Extorsión) representan conductas con alta capacidad discriminante entre estados, indicando que su ocurrencia es más específica y no uniforme en todo el país.

  • Co-ocurrencia de delitos: La proximidad entre ciertos tipos de delito, como Abuso sexual y Despojo, sugiere que estos crímenes tienden a presentarse en conjunto dentro de los mismos estados, reflejando patrones delictivos relacionados.

  • Patrones centrales: Los estados ubicados próximos al centro del gráfico, como Aguascalientes, Campeche y Michoacán, presentan perfiles más equilibrados y sin predominancia de un tipo de delito específico, indicando una distribución más homogénea en cuanto a la ocurrencia relativa de los distintos crímenes.

En términos generales, el análisis de correspondencias ha permitido reducir la complejidad de los datos originales, proyectando los datos en un espacio de baja dimensión que facilita la visualización de asociaciones y diferencias entre estados y tipos de delito. Esto proporciona una herramienta útil para priorizar políticas públicas y estrategias de seguridad basadas en los perfiles delictivos específicos de cada región.

Referencias

  • C. Rencher , Alvin and F. Christensen, William (2012). Methods of Multivariate Analysis. Third edition. John Wiley & Sons, Inc
  • Johnson, Richard A. and Wichern, Dean W. (2007). Applied Multivariate Statistical Analysis. Sixth edition. Pearson Prentice Hall.
  • Greenacre, Michael J. (2017). Correspondence Analysis in Practice. Third edition. Chapman & Hall/CRC.