1 ¿Qué es Clustering?

Clustering o Análisis de Conglomerados, en español, comprende un gran número de metódos que tienen como objetivo descubir un número limitado de grupos significativos (clusters) para un conjunto de datos.

Un grupo es significativo si sus unidades son similares entre si y diferentes a las unidades de otros grupos.

El concepto de similitud o disimilitud entre unidades es facil de entender y bien aceptado: Todos estamos en la capacidad de evaluar si dos unidades son similares o diferentes.

Se debe tener el supuesto implícito de que existe algún tipo de homogeneidad y separación en el conjunto de datos.

2 Clustering vs Clasificación

El Clustering es un Método No Supervisado cuyo objetivo es descubrir grupos o clusters sugeridos de manera bastante natural por los datos sin tener un conocimiento previo de grupos ya formados (Unlabeled Data).

En contraste, los metodos que se usan en problemas de clasificación son Métodos Supervisados donde los grupos o clases se conocen a priori y el problema de investigación es predecir la pertenencia de nuevas unidades a una de estas clases basandonos en las variables de las unidades pasadas (Labeled Data).

Existe otro tipo llamado Aprendizaje Semi-Supervisado el cual combina tecnicas de ambos tipos de aprendizaje para conjuntos de datos que contienen datos etiquetados como no etiquetados.

3 Tipos de Clustering

Decimos que un metodo es hard clustering (agrupamiento duro) si una unidad solo puede pertenecer a un cluster.

Los metodos soft clustering (agrupamiento suave) relajan esa asignación dura, de modo que cada unidad tiene grados de pertenencia a cada cluster, estos grados son valores en el intervalo \([0,1]\) donde valores cercanos a \(1\) indican una fuerte pertenencia a un cluster en especifico. En esta categoria caen los enfoques Difusos (Fuzzy Aproach) y basados en modelos (Model-based Aproach).

Comparasion de Tipos de Clustering
Caracteristica Estandar Difuso Basado en Modelos
Hard clustering No No
Soft clustering No
Probabilistic assumptions No No

4 Enfoque Estandar

4.1 Hierarchical clustering (Agrupamiento Jerarquico)

Todos los metodos de agrupamiento jerarquico requieren de:

  • Una matriz de distancias o disimilitud
  • Un metodo/función de enlace que indique como se debe medir la distancia entre 2 cluster.

Una vez se tienen estos 2 insumos, todos los metodos jerarquicos tradicionales se dividen en 2 tipos:

  • Aglomerativos (Agglomerative Nesting): Funcionan de “de abajo hacia arriba”. Es decir, cada unidad se considera inicialmente como un cluster de un solo elemento. En cada paso del algoritmo, los dos clusters que son más similares se combinan en un nuevo cluster más grande. El algoritmo termina cuando todas las unidades pertenecen a un solo cluster.

  • Divisivos (DIvisive ANAlysis Clustering): Funcionan de “de arriba hacia abajo”. Es decir, en el inicio del algoritmo todas las unidades pertenecen al mismo grupo. Luego, los clusters más heterogéneos se dividen sucesivamente hasta que todas las unidades terminan en su propio cluster.

Los resultados de los metodos jerárquica se representan mediante un arbol que indica todas las particiones obtenidas durante el proceso, esta representación se le conoce como dendograma.

Observación: Note que no fue necesario especificar un número de clusters a priori.

4.1.1 Medidas de Distancia o Disimilitud

Aunque hemos hablado de medidas de similitud y disimilitud, los metodos (en su mayoria por no decir que todos) funcionan con medidas de disimilitud. Además se suele emplear el termino Matriz de Distancias en lugar de Matriz de Disimilitudes, aunque muchas medidas no sean propiamente medidas de distancia en el estricto sentido.

A continuación se enlistan algunas medidas de habitual uso.

Medida de distancia Fórmula Tipo de variable
Máxima (Chebyshev) \(d(x,y) = \max_i |x_i - y_i|\) Continuas
Euclidiana \(d(x,y) = \sqrt{\sum_{i=1}^p (x_i - y_i)^2}\) Continuas
Manhattan \(d(x,y) = \sum_{i=1}^p |x_i - y_i|\) Continuas o Discretas
Minkowski \(d(x,y) = \left( \sum_{i=1}^p |x_i - y_i|^q \right)^{1/q}\) Numéricas \(q \geq 1\)
Canberra \(d(x,y) = \sum_{i=1}^p \frac{|x_i - y_i|}{|x_i| + |y_i|}\) Numéricas (sensible a valores pequeños)
Binaria (Jaccard) \(d(x,y) = 1 - \frac{a}{a + b + c}\)
Más detalles aquí
Binarias
Mahalanobis \(d(x,y) = \sqrt{(x - y)^T S^{-1} (x - y)}\) Continuas
Gower \(d_{ij} = \frac{\sum_{k=1}^p w_k \delta_{ij}^{(k)} d_{ij}^{(k)}}{\sum_{k=1}^p w_k \delta_{ij}^{(k)}}\)
Más detalles aquí
Datos Mixtos (Numericas, Ordinales, Nominales, Binarias)
Correlación Pearson \(d(x,y) = 1 - r_{xy}\) Numéricas
Correlación Spearman \(d(x,y) = 1 - \rho_{xy}\) Numéricas, Ordinales
Correlación Kendall \(d(x,y) = 1 - \tau_{xy}\) Numéricas, Ordinales

Observación: La Matriz de Distancias NO necesariamente tiene que provenir de los datos.

Por ejemplo, supongamos que se invita a un consumidor a expresar sus niveles subjetivos de disimilitud entre pares de artículos. En este caso, las distancias no se calculan según las variables de los artículos, sino a segun relaciones percibidas entre ellos por el consumidor.

4.1.2 Metodos Aglomerativos

Los métodos de clustering jerárquico aglomerativo producen una serie de particiones donde las dos unidades o clusters más similares se fusionan sucesivamente. En detalle, dada una matriz de distancias \(D_n\) de orden \((n \times n)\), consisten en los siguientes pasos. Nótese que, en el primer paso, la matriz de distancias proporciona las medidas de distancia entre \(n\) clusters singleton.

  1. De acuerdo con \(D_n\), fusionar las dos unidades/clusters con la distancia mínima en un nuevo cluster. Esto conduce a una nueva partición con \(n - 1\) clusters: el nuevo cluster de tamaño 2 y los restantes \(n - 2\) clusters singleton.

  2. Calcular la nueva matriz de distancias \(D_{n-1}\), de orden \((n - 1) \times (n - 1)\). Las medidas de distancia entre los clusters singleton se heredan de la matriz original \(D_n\). Existen varias alternativas para calcular las distancias entre el nuevo cluster y los restantes. Esta elección distingue los diferentes métodos aglomerativos y se discutirá más adelante.

  3. Fusionar los clusters con la distancia mínima usando \(D_{n-1}\), obteniendo una partición con \(n - 2\) clusters.

  4. Repetir los pasos 2 y 3 hasta que permanezca un único cluster. En el último paso, \(D_2\) tiene orden \((2 \times 2)\) y contiene las medidas de distancia entre los dos clusters que se fusionan para obtener la partición final trivial con un único cluster de \(n\) unidades.

4.1.3 Funcion de Enlace o Vinculación

El punto crucial de un procedimiento de clustering aglomerativo radica en el método para calcular distancias entre un cluster formado por la fusión de dos clusters y los otros. En general, diferentes métodos producen soluciones distintas.

A continuación se muestran algunas de las funciones de enlace más usadas:

Método Fórmula
Enlace Simple o Mínimo \(\min d(i_1, i_2) \ \ , \ \ i_1 \in C_1,\, i_2 \in C_2\)
Enlace Completo o Máximo \(\max d(i_1, i_2) \ \ , \ \ i_1 \in C_1,\, i_2 \in C_2\)
Enlace Promedio \(\dfrac{\sum_{i_1 \in C_1} \sum_{i_2 \in C_2} d(i_1, i_2)}{|C_1|\,|C_2|}\)
Método de Ward \(d(\bar{x}_{C_1},\, \bar{x}_{C_2})\), donde \(\bar{x}_{C_1} = \dfrac{\sum_{i_1 \in C_1} x_i}{|C_1|}\) y \(\bar{x}_{C_2} = \dfrac{\sum_{i_2 \in C_2} x_i}{|C_2|}\)

Estos métodos pueden definirse según la Fórmula de Lance–Williams. Sean \(C_1\) y \(C_2\) dos clusters que se fusionan para formar el cluster \(C_{1,2}\). La distancia entre este nuevo cluster y el cluster \(C_3\) puede expresarse como:

\[ d(C_{1,2}, C_3) = \alpha_1 d(C_1, C_3) + \alpha_2 d(C_2, C_3) + \beta d(C_1, C_2) + \gamma |d(C_1, C_3) - d(C_2, C_3)| \]

Método \(\alpha_1\) \(\alpha_2\) \(\beta\) \(\gamma\)
Enlace Simple 0.5 0.5 0 −0.5
Enlace Completo 0.5 0.5 0 0.5
Enlace Promedio \(\dfrac{|C_1|}{|C_1| + |C_2|}\) \(\dfrac{|C_2|}{|C_1| + |C_2|}\) 0 0
Enlace de Ward \(\dfrac{|C_1| + |C_3|}{|C_1| + |C_2| + |C_3|}\) \(\dfrac{|C_2| + |C_3|}{|C_1| + |C_2| + |C_3|}\) \(-\,\dfrac{|C_3|}{|C_1| + |C_2| + |C_3|}\) 0

4.1.4 Metodos Divisivos

El agrupamiento divisivo es menos común que el agrupamiento aglomerativo, y los métodos relacionados se han ignorado con frecuencia, especialmente en el pasado. La razón es que son computacionalmente más costosos, por ejemplo, en el primer paso, existen \(2^{(n−1)} − 1\) posibles maneras de dividir el clúster inicial en dos.

4.1.4.1 Algoritmo DIANA (DIvisive ANAlysis clustering)

Es el algoritmo más famoso de clustering jerárquico divisivo y uno de los procedimientos más optimos para realizar la divison de clusters. Las ideas principales del algoritmo son las siguientes:

  1. Se inicia de un cluster que contiene todas las unidades del conjunto de datos.

  2. Dentro del cluster actual, se identifica la observación con la mayor disimilitud promedio respecto a las demás. Esta observación se convierte en el “núcleo” de un nuevo cluster inicial para una partición.

  3. Se evalúa para cada observación restante si es más similar al nuevo cluster que al cluster original. Si es así, la observación se transfiere al nuevo cluster. A este cluster se le suele llamar splinter group (grupo disidente).

  4. Una vez que ninguna observación adicional deba ser movida, se obtiene una partición en dos clusters.

  5. Se selecciona el cluster con mayor diámetro (mayor disimilitud interna) y se repite el procedimiento de división hasta obtener tantas particiones como se desee o hasta llegar a clusters individuales.

4.2 Non-Hierarchical clustering (Agrupamiento No Jerarquico)

Tambien conocido como agrupamiento particionado (Partitioning Clustering). Si bien los metodos jerarquicos nos dan una solución intuitiva al problema, existen al menos 2 propiedas valiosas que justifican la existencia de los metodos no jerarquicos:

  • No requieren el calculo de la Matrix de Distancias. Esto es particularmente relevante para conjunto de datos moderadamente grandes para los cuales el calculo de la matriz de distancias consume mucha memoria y tiempo.

  • En los metodos jerarquicos aglomerativos, una vez 2 unidades se unen en un cluster, estas no se separarn durante el resto del algoritmo. Similarmente, en los metodos divisivos, si 2 unidades pertenecen a clusters diferentes, ellas no pueden volver a estar juntas. Por lo tanto, para ciertas estructuras los metodos jerarquicos no detectan bien los clusters implicitos.

Observación: A diferencia de los metodos jerarquicos, aquí se debe especificar el número de cluster que se desea obtener.

4.2.1 K-means

El algoritmo K-Means es uno de los métodos de clustering no jerárquicos más utilizados. Busca minimizar la variabilidad interna dentro de cada cluster. Su objetivo es asignar cada observación al cluster cuyo centro (o centroide) sea el más cercano.

Es adecuado cuando todas las variables son cuantitativas y busca encontrar la mejor partición de \(n\) unidades en \(k\) clusters.

Para evaluar qué tan buena es una partición, se descompone la suma total de cuadrados (\(T\)) en dos componentes:

  • La suma de cuadrados dentro de los clusters \(W\),
  • La suma de cuadrados entre los clusters \(B\).

La relación fundamental es:

\[ T = W + B. \]

donde:

  • Suma total de cuadrados:

\[ T = \sum_{i=1}^n \sum_{j=1}^p (x_{ij}-\bar{x}_j)^2. \]

  • Suma de cuadrados dentro de los clusters:

\[ W = \sum_{g=1}^k W_g, \qquad W_g = \sum_{i=1}^{n_g} \sum_{j=1}^p (x_{ij}-\bar{x}_{gj})^2. \]

  • Suma de cuadrados entre clusters:

\[ B = \sum_{g=1}^k n_g (\bar{x}_{gj}-\bar{x}_j)^2. \]

Aquí:

  • \(n_g\) es el número de unidades del cluster \(g\),
  • La media global de la variable \(j\) es: \[ \bar{x}_j = \frac{1}{n} \sum_{i=1}^n x_{ij} \]
  • La media de la variable \(j\) dentro del cluster \(g\) es: \[ \bar{x}_{gj} = \frac{1}{n_g} \sum_{i=1}^{n_g} x_{ij} \] .

Cuando las observaciones de un cluster coinciden exactamente con su centroide (\(x_{ij}=\bar{x}_{gj}\)), se obtiene \(W_g = 0\).

Por tanto, la mejor partición de \(n\) unidades en \(k\) clusters se obtiene minimizando la suma de cuadrados dentro de los clusters:

\[ \min W. \]

El procedimiento estándar de K-means consiste en los siguientes pasos:

  1. Inicialización
    Se eligen \(K\) centroides iniciales, ya sea al azar o mediante un método específico como k-means++.

  2. Asignación de observaciones a clusters
    Cada observación se asigna al cluster cuyo centroide esté más cerca según la distancia Euclidiana:

    \[ \text{Asignar } x_i \rightarrow C_j \quad \text{si} \quad d(x_i, \mu_j) = \min_{k} d(x_i, \mu_k) \]

  3. Recomputación de centroides
    Para cada cluster, se actualiza su centroide calculando el promedio de las observaciones asignadas:

    \[ \mu_j = \frac{1}{|C_j|}\sum_{x_i \in C_j} x_i \]

  4. Iteración
    Se repiten los pasos de asignación y actualización hasta que las asignaciones no cambien o la disminución de la suma de cuadrados dentro de los clusters sea mínima.

  5. Criterio de optimización
    K-means minimiza la suma de cuadrados dentro de los clusters:

    \[ \sum_{j=1}^K \sum_{x_i \in C_j} d(x_i, \mu_j)^2 \]

Observaciones:

  • K-Means funciona con variables numéricas continuas.
  • La forma de los clusters suele ser aproximadamente esférica.
  • El resultado puede depender fuertemente de los centroides iniciales.
  • Es común ejecutar K-means varias veces para encontrar la partición con menor variabilidad interna.

4.2.2 K-Meloids

El algoritmo k-medoids es un enfoque de clustering similar a k-means, cuyo objetivo es particionar un conjunto de datos en \(k\) grupos. A diferencia de k-means, donde cada cluster es representado por un centroide, en k-medoids cada cluster está representado por uno de los puntos reales del conjunto de datos, llamado medoide.

Un medoide es la observación dentro del cluster cuya disimilitud promedio respecto a las demás observaciones del cluster es mínima. En otras palabras, es el punto más central y representativo del cluster. Esto proporciona una interpretación más intuitiva que los centroides, ya que los medoides corresponden a observaciones reales.

El método k-medoids es más robusto al ruido y a los valores atípicos que k-means, ya que no utiliza medias sino observaciones reales como prototipos de cluster.

El algoritmo k-medoids más utilizado es PAM (Partitioning Around Medoids).

El procedimiento estándar de K-medoids consiste en los siguientes pasos:

  1. Seleccionar \(k\) objetos como medoides iniciales
    Si el usuario proporciona los medoides, utilizarlos directamente.

  2. Calcular la matriz de disimilitud
    Este cálculo se realiza únicamente si no se ha proporcionado previamente.

  3. Asignación de objetos a clusters
    Cada objeto se asigna al medoide más cercano según la medida de disimilitud elegida.

  4. Actualización de medoides
    Para cada cluster:

    • Evaluar si algún objeto del cluster disminuye el coeficiente promedio de disimilitud.
    • Si existe un objeto que reduzca este valor, seleccionarlo como el nuevo medoide del cluster.
  5. Criterio de parada

    • Si al menos un medoide cambia, regresar al paso (3).
    • Si no hay cambios, el algoritmo finaliza.

5 Problemas y Desafios que el usuario debe afrontar

  1. No existe conocimiento a priori de la existencia ni del número de grupos.

  2. Como medir el grado de similitud o disimilitud entre unidades. El como tiene una gran influencia sobre los resultados que se obtengan. Aquí hay almenos 3 puntos a tener en cuenta:

    • La eleción de variables que deben considerarse para los propositos del clustering.
    • El preprocesamiento de los datos, en particular la estandarización/noramlización de las variables para que estan puedan desempeñar el mismo rol, es decir, erradicar el sesgo por la escala de medición.
    • La medida de similitud (disimilitud) entre las unidades, ya que existe una cantidad impresionante de alternativas.
  3. El Metodo de Clustering a usar.

No existen reglas de oro para estas elecciones porque dependen del problema de investigación específico de los datos subyacentes.

6 Caso de Estudio: Insurance Claims

Ver dataset Insurance Claims.

El dataset Insurance Claims contiene información detallada sobre reclamaciones de seguros por parte de clientes. Incluye variables demográficas, características del conductor y del vehículo, información de la póliza, historial de incidentes y una etiqueta de fraude utilizada originalmente para el contexto supervisado. Para este análisis de clustering solo se utilizarán las variables relevantes no identificadoras.

El archivo está compuesto por aproximadamente 1.000 observaciones y más de 30 variables, de tipo numérico y categórico. A continuación, se presenta una descripción general de las principales variables:

6.1 Columnas del conjunto de datos

6.1.1 Variables del Cliente y Demográficas

  • age: Edad del asegurado.
  • insured_sex: Sexo del asegurado.
  • insured_education_level: Nivel educativo del asegurado.
  • insured_occupation: Profesión del asegurado.
  • insured_hobbies: Lista de hobbies del asegurado.
  • insured_relationship: Relación del asegurado con el titular de la póliza.

6.1.2 Variables del Vehículo

  • auto_make: Marca del vehículo.
  • auto_model: Modelo del vehículo.
  • auto_year: Año del vehículo.

6.1.3 Variables del Historial y Reclamo

  • incident_date: Fecha del incidente.
  • incident_type: Tipo de incidente (por ejemplo, colisión, vandalismo, etc.).
  • collision_type: Tipo de colisión (si aplica).
  • incident_severity: Severidad del incidente.
  • authorities_contacted: Autoridad contactada después del incidente.
  • incident_state: Estado donde ocurrió el incidente.
  • incident_city: Ciudad donde ocurrió el incidente.
  • police_report_available: Indica si se generó un reporte policial.

6.1.4 Variables de la Póliza

  • policy_number: Número identificador de la póliza.
  • policy_bind_date: Fecha de inicio de la póliza.
  • policy_state: Estado donde se emitió la póliza.
  • policy_csl: Límite combinado de responsabilidad.
  • policy_deductable: Deducible asociado a la póliza.
  • policy_annual_premium: Prima anual de la póliza.
  • umbrella_limit: Límite adicional de cobertura.

6.1.5 Variables Económicas y de Riesgo

  • capital-gains: Ganancias de capital del asegurado.
  • capital-loss: Pérdidas de capital del asegurado.
  • total_claim_amount: Monto total reclamado.

6.1.6 Variables del Fraude (Supervisadas, no usadas en clustering)

  • fraud_reported: Indica si el reclamo fue reportado como fraude (“Y”/“N”).
  • **_c39**: Variable sin nombre (descartable).

6.1.7 Observaciones Generales

  • Existen múltiples variables categóricas con alto número de categorías (por ejemplo: ocupación, ciudad, hobbies).
  • Algunas columnas contienen valores faltantes o categorías como "?" que deben ser tratadas en el preprocesamiento.
  • La base está diseñada originalmente para detección de fraude, pero puede utilizarse en análisis no supervisado para identificar patrones de clientes o tipos de reclamaciones.

6.2 Paso 1: Preprocesamiento

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ forcats   1.0.0     ✔ readr     2.1.5
## ✔ ggplot2   4.0.1     ✔ stringr   1.5.1
## ✔ lubridate 1.9.4     ✔ tibble    3.3.0
## ✔ purrr     1.0.4     ✔ tidyr     1.3.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter()          masks stats::filter()
## ✖ kableExtra::group_rows() masks dplyr::group_rows()
## ✖ dplyr::lag()             masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(cluster)         # daisy(), pam()
library(factoextra)      # visualización y validación
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
library(NbClust)         # número óptimo de clusters
library(janitor)         # limpieza de columnas
## 
## Adjuntando el paquete: 'janitor'
## 
## The following objects are masked from 'package:stats':
## 
##     chisq.test, fisher.test
# Cargar archivo y estructura de la base
df_raw <- read_csv("insurance_claims.csv") %>% 
  clean_names()
## Rows: 1000 Columns: 40
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (19): policy_state, policy_csl, insured_sex, insured_education_level, i...
## dbl  (18): months_as_customer, age, policy_number, policy_deductable, policy...
## lgl   (1): _c39
## date  (2): policy_bind_date, incident_date
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
glimpse(df_raw)
## Rows: 1,000
## Columns: 40
## $ months_as_customer          <dbl> 328, 228, 134, 256, 228, 256, 137, 165, 27…
## $ age                         <dbl> 48, 42, 29, 41, 44, 39, 34, 37, 33, 42, 42…
## $ policy_number               <dbl> 521585, 342868, 687698, 227811, 367455, 10…
## $ policy_bind_date            <date> 2014-10-17, 2006-06-27, 2000-09-06, 1990-…
## $ policy_state                <chr> "OH", "IN", "OH", "IL", "IL", "OH", "IN", …
## $ policy_csl                  <chr> "250/500", "250/500", "100/300", "250/500"…
## $ policy_deductable           <dbl> 1000, 2000, 2000, 2000, 1000, 1000, 1000, …
## $ policy_annual_premium       <dbl> 1406.91, 1197.22, 1413.14, 1415.74, 1583.9…
## $ umbrella_limit              <dbl> 0e+00, 5e+06, 5e+06, 6e+06, 6e+06, 0e+00, …
## $ insured_zip                 <dbl> 466132, 468176, 430632, 608117, 610706, 47…
## $ insured_sex                 <chr> "MALE", "MALE", "FEMALE", "FEMALE", "MALE"…
## $ insured_education_level     <chr> "MD", "MD", "PhD", "PhD", "Associate", "Ph…
## $ insured_occupation          <chr> "craft-repair", "machine-op-inspct", "sale…
## $ insured_hobbies             <chr> "sleeping", "reading", "board-games", "boa…
## $ insured_relationship        <chr> "husband", "other-relative", "own-child", …
## $ capital_gains               <dbl> 53300, 0, 35100, 48900, 66000, 0, 0, 0, 0,…
## $ capital_loss                <dbl> 0, 0, 0, -62400, -46000, 0, -77000, 0, 0, …
## $ incident_date               <date> 2015-01-25, 2015-01-21, 2015-02-22, 2015-…
## $ incident_type               <chr> "Single Vehicle Collision", "Vehicle Theft…
## $ collision_type              <chr> "Side Collision", "?", "Rear Collision", "…
## $ incident_severity           <chr> "Major Damage", "Minor Damage", "Minor Dam…
## $ authorities_contacted       <chr> "Police", "Police", "Police", "Police", "N…
## $ incident_state              <chr> "SC", "VA", "NY", "OH", "NY", "SC", "NY", …
## $ incident_city               <chr> "Columbus", "Riverwood", "Columbus", "Arli…
## $ incident_location           <chr> "9935 4th Drive", "6608 MLK Hwy", "7121 Fr…
## $ incident_hour_of_the_day    <dbl> 5, 8, 7, 5, 20, 19, 0, 23, 21, 14, 22, 21,…
## $ number_of_vehicles_involved <dbl> 1, 1, 3, 1, 1, 3, 3, 3, 1, 1, 1, 3, 1, 1, …
## $ property_damage             <chr> "YES", "?", "NO", "?", "NO", "NO", "?", "?…
## $ bodily_injuries             <dbl> 1, 0, 2, 1, 0, 0, 0, 2, 1, 2, 2, 1, 1, 1, …
## $ witnesses                   <dbl> 2, 0, 3, 2, 1, 2, 0, 2, 1, 1, 2, 2, 0, 1, …
## $ police_report_available     <chr> "YES", "?", "NO", "NO", "NO", "NO", "?", "…
## $ total_claim_amount          <dbl> 71610, 5070, 34650, 63400, 6500, 64100, 78…
## $ injury_claim                <dbl> 6510, 780, 7700, 6340, 1300, 6410, 21450, …
## $ property_claim              <dbl> 13020, 780, 3850, 6340, 650, 6410, 7150, 9…
## $ vehicle_claim               <dbl> 52080, 3510, 23100, 50720, 4550, 51280, 50…
## $ auto_make                   <chr> "Saab", "Mercedes", "Dodge", "Chevrolet", …
## $ auto_model                  <chr> "92x", "E400", "RAM", "Tahoe", "RSX", "95"…
## $ auto_year                   <dbl> 2004, 2007, 2007, 2014, 2009, 2003, 2012, …
## $ fraud_reported              <chr> "Y", "Y", "N", "Y", "N", "Y", "N", "N", "N…
## $ c39                         <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
# Numero de Na por columna
df_raw %>% sapply(function(x) sum(is.na(x)))
##          months_as_customer                         age 
##                           0                           0 
##               policy_number            policy_bind_date 
##                           0                           0 
##                policy_state                  policy_csl 
##                           0                           0 
##           policy_deductable       policy_annual_premium 
##                           0                           0 
##              umbrella_limit                 insured_zip 
##                           0                           0 
##                 insured_sex     insured_education_level 
##                           0                           0 
##          insured_occupation             insured_hobbies 
##                           0                           0 
##        insured_relationship               capital_gains 
##                           0                           0 
##                capital_loss               incident_date 
##                           0                           0 
##               incident_type              collision_type 
##                           0                           0 
##           incident_severity       authorities_contacted 
##                           0                           0 
##              incident_state               incident_city 
##                           0                           0 
##           incident_location    incident_hour_of_the_day 
##                           0                           0 
## number_of_vehicles_involved             property_damage 
##                           0                           0 
##             bodily_injuries                   witnesses 
##                           0                           0 
##     police_report_available          total_claim_amount 
##                           0                           0 
##                injury_claim              property_claim 
##                           0                           0 
##               vehicle_claim                   auto_make 
##                           0                           0 
##                  auto_model                   auto_year 
##                           0                           0 
##              fraud_reported                         c39 
##                           0                        1000
# Limpieza de los signos "?"
df <- df_raw %>% 
  mutate(
    across(
      .cols = where(is.character),
      .fns  = ~ na_if(., "?")
    )
  )

# Variables que se omiten del analisis
cols_drop <- c(
  "policy_number", 
  "policy_bind_date",
  "incident_date",
  "incident_location", 
  "insured_zip", 
  "insured_hobbies",
  "fraud_reported", 
  "c39"
)

df <- df %>% select(-all_of(cols_drop))

# Variables numericas y categoricas
numeric_vars   <- df %>% select(where(is.numeric)) %>% names()
categorical_vars <- df %>% select(where(is.character)) %>% names()

numeric_vars
##  [1] "months_as_customer"          "age"                        
##  [3] "policy_deductable"           "policy_annual_premium"      
##  [5] "umbrella_limit"              "capital_gains"              
##  [7] "capital_loss"                "incident_hour_of_the_day"   
##  [9] "number_of_vehicles_involved" "bodily_injuries"            
## [11] "witnesses"                   "total_claim_amount"         
## [13] "injury_claim"                "property_claim"             
## [15] "vehicle_claim"               "auto_year"
categorical_vars
##  [1] "policy_state"            "policy_csl"             
##  [3] "insured_sex"             "insured_education_level"
##  [5] "insured_occupation"      "insured_relationship"   
##  [7] "incident_type"           "collision_type"         
##  [9] "incident_severity"       "authorities_contacted"  
## [11] "incident_state"          "incident_city"          
## [13] "property_damage"         "police_report_available"
## [15] "auto_make"               "auto_model"
# Tratamiento de NA
df <- df %>% drop_na()

6.2.1 Escalamiento de los datos

  • Usaremos distancia de Gower (función daisy()) para métodos basados en distancia (jerárquico aglomerativo, PAM/k-medoids).

  • Para k-means será necesario usar solo variables numéricas escaladas.

# Volver variables caracteres en factor
df <- df %>% 
  mutate(
    across(
      .cols = where(is.character),
      .fns  = as.factor
    )
  )

str(df)
## tibble [340 × 32] (S3: tbl_df/tbl/data.frame)
##  $ months_as_customer         : num [1:340] 328 134 256 27 447 60 180 473 140 160 ...
##  $ age                        : num [1:340] 48 29 39 33 61 23 38 58 31 37 ...
##  $ policy_state               : Factor w/ 3 levels "IL","IN","OH": 3 3 3 1 3 3 3 2 2 3 ...
##  $ policy_csl                 : Factor w/ 3 levels "100/300","250/500",..: 2 1 2 1 1 3 2 1 3 3 ...
##  $ policy_deductable          : num [1:340] 1000 2000 1000 500 2000 500 2000 2000 500 500 ...
##  $ policy_annual_premium      : num [1:340] 1407 1413 1351 1443 1137 ...
##  $ umbrella_limit             : num [1:340] 0e+00 5e+06 0e+00 0e+00 0e+00 3e+06 0e+00 0e+00 6e+06 0e+00 ...
##  $ insured_sex                : Factor w/ 2 levels "FEMALE","MALE": 2 1 1 1 1 2 1 1 2 1 ...
##  $ insured_education_level    : Factor w/ 7 levels "Associate","College",..: 6 7 7 7 3 6 2 6 3 6 ...
##  $ insured_occupation         : Factor w/ 14 levels "adm-clerical",..: 3 12 13 8 4 11 7 14 7 3 ...
##  $ insured_relationship       : Factor w/ 6 levels "husband","not-in-family",..: 1 4 5 4 3 6 2 3 5 3 ...
##  $ capital_gains              : num [1:340] 53300 35100 0 0 0 0 41300 55700 53500 45500 ...
##  $ capital_loss               : num [1:340] 0 0 0 0 -51000 0 -55500 0 0 -37800 ...
##  $ incident_type              : Factor w/ 2 levels "Multi-vehicle Collision",..: 2 1 1 2 1 2 2 1 2 2 ...
##  $ collision_type             : Factor w/ 3 levels "Front Collision",..: 3 2 2 1 1 2 2 3 3 3 ...
##  $ incident_severity          : Factor w/ 3 levels "Major Damage",..: 1 2 1 3 1 3 3 1 3 3 ...
##  $ authorities_contacted      : Factor w/ 4 levels "Ambulance","Fire",..: 4 4 2 4 2 1 4 3 4 3 ...
##  $ incident_state             : Factor w/ 7 levels "NC","NY","OH",..: 5 2 5 7 5 5 5 7 7 2 ...
##  $ incident_city              : Factor w/ 7 levels "Arlington","Columbus",..: 2 2 1 1 7 4 7 3 4 5 ...
##  $ incident_hour_of_the_day   : num [1:340] 5 7 19 21 21 9 12 12 9 19 ...
##  $ number_of_vehicles_involved: num [1:340] 1 3 3 1 3 1 1 4 1 1 ...
##  $ property_damage            : Factor w/ 2 levels "NO","YES": 2 1 1 1 2 2 1 2 1 2 ...
##  $ bodily_injuries            : num [1:340] 1 2 0 1 1 1 0 0 0 1 ...
##  $ witnesses                  : num [1:340] 2 3 2 1 2 0 2 0 2 0 ...
##  $ police_report_available    : Factor w/ 2 levels "NO","YES": 2 1 1 2 2 1 2 1 2 1 ...
##  $ total_claim_amount         : num [1:340] 71610 34650 64100 27700 114920 ...
##  $ injury_claim               : num [1:340] 6510 7700 6410 2770 17680 ...
##  $ property_claim             : num [1:340] 13020 3850 6410 2770 17680 ...
##  $ vehicle_claim              : num [1:340] 52080 23100 51280 22160 79560 ...
##  $ auto_make                  : Factor w/ 14 levels "Accura","Audi",..: 11 5 11 13 2 11 5 1 12 1 ...
##  $ auto_model                 : Factor w/ 39 levels "3 Series","92x",..: 2 31 4 9 5 4 28 26 22 35 ...
##  $ auto_year                  : num [1:340] 2004 2007 2003 2012 2006 ...
# Matriz de distancia usando la medida de Gower
gower_dist <- daisy(df, metric = "gower")
gower_mat  <- as.matrix(gower_dist)
# Escalamiento de datos
df_num_scaled <- df %>% 
  select(all_of(numeric_vars)) %>% 
  scale() %>% 
  as.data.frame()

6.3 Paso 2: Tendencia al Clustering

fviz_dist(gower_dist, 
          gradient = list(low = "white", mid = "blue", high = "red"))
## Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
## ℹ Please use tidy evaluation idioms with `aes()`.
## ℹ See also `vignette("ggplot2-in-packages")` for more information.
## ℹ The deprecated feature was likely used in the factoextra package.
##   Please report the issue at <https://github.com/kassambara/factoextra/issues>.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

library(hopkins)

set.seed(123)
hopkins_value <- hopkins::hopkins(df_num_scaled, m = nrow(df_num_scaled) - 1)

hopkins_value
## [1] 0.9946715
# Valores mayores a 0.5 indican tendencia al clustering
pca_res <- prcomp(df_num_scaled, scale. = FALSE)

fviz_pca_ind(pca_res,
             geom = "point",
             repel = TRUE)

## Paso 3 — Clustering Jerárquico Aglomerativo (AGNES)

6.3.1 3.1 Construcción del modelo AGNES

library(cluster)
library(factoextra)

hc_agnes <- agnes(gower_dist, method = "ward")
hc_agnes
## Call:     agnes(x = gower_dist, method = "ward") 
## Agglomerative coefficient:  0.8690064 
## Order of objects:
##   [1]   1 285 301 334   7 282  79 323  56 276  50  73 188 101 214 171 272  95
##  [19] 195 241 103 153 117 191 197 235 179 319 156 185 169   4 206 267 320  17
##  [37] 208 138 280 242 306  30  62  41  83 115 308 231 295 270  61  76 135 238
##  [55] 119 204 274 139 335 218 271   9 281  64  78 102 300 173 202  14 182  89
##  [73] 311  46 317  84 269 251 291  15  45 161 329  71 122 162  26 108 121 336
##  [91] 114 257  60 287 113 127   6 189  63  80 110  22 196 112 177 338  12 210
## [109]  47 232 128 283 146 296 164 123 198 298 330 181 288 154 193 200 254  10
## [127] 327 315 339  18 107 325  32  33 133  65  88 305 314  20 245 233 332  34
## [145] 304  68  74 131 145 261   2 222  24 152 149 302  37 192  94 303 175 220
## [163] 259 318  31 228 278  90 104 249 299 125 203 186 223 207 266   3  86  21
## [181] 219  13 310  28 132 297  69  85 194  72 109 155 201 248   5 237  27 313
## [199]  38 293  40  59 216 292 331  55 286 140 157  44 229 290 316 187 212 289
## [217] 333  52 263 273 326 275  57  98  81 250  35 183 143  91 148 100 170 167
## [235] 209  11 324 166 199 215  42 165  96 144 262 150 163 264  77 244 258 268
## [253]  16  99  66  75  25 307  87 240 252  19 309 111 147  36 178  93  43 158
## [271]  39 260 236 213 225  49 234  70 126 247 340  58 246 168 151 227  53 190
## [289] 180 106 328 284 312   8  48 322 118 136 116 176 256 294  29 221 120 321
## [307] 211 255  51 184  97 230  23  92 337 217 243 277 129 279 134 141  54 137
## [325]  67 226 142 172 105 265 224 253  82 159 239 124 174 160 130 205
## Height (summary):
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.2383  0.3254  0.3921  0.4624  0.5207  2.5165 
## 
## Available components:
## [1] "order"  "height" "ac"     "merge"  "diss"   "call"   "method"
# Metodo jerarquico
fviz_dend(hc_agnes,
          k = NULL,
          cex = 0.6,
          main = "Dendrograma - AGNES (Ward)",
          rect = FALSE)
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## ℹ The deprecated feature was likely used in the factoextra package.
##   Please report the issue at <https://github.com/kassambara/factoextra/issues>.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: The `<scale>` argument of `guides()` cannot be `FALSE`. Use "none" instead as
## of ggplot2 3.3.4.
## ℹ The deprecated feature was likely used in the factoextra package.
##   Please report the issue at <https://github.com/kassambara/factoextra/issues>.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

### Determinacion del numero optimo de clusters

# Usando Silhouette
fviz_nbclust(df, FUN = hcut, method = "silhouette",
             diss = gower_dist) +
  ggtitle("Número óptimo de clusters — Silhouette (AGNES)")
## Warning in stats::dist(x, method = method, ...): NAs introducidos por coerción
## Warning in stats::dist(x, method = method, ...): NAs introducidos por coerción
## Warning in stats::dist(x, method = method, ...): NAs introducidos por coerción
## Warning in stats::dist(x, method = method, ...): NAs introducidos por coerción
## Warning in stats::dist(x, method = method, ...): NAs introducidos por coerción
## Warning in stats::dist(x, method = method, ...): NAs introducidos por coerción
## Warning in stats::dist(x, method = method, ...): NAs introducidos por coerción
## Warning in stats::dist(x, method = method, ...): NAs introducidos por coerción
## Warning in stats::dist(x, method = method, ...): NAs introducidos por coerción

k_opt <- 2

hc_final <- hcut(gower_dist, k = k_opt, hc_method = "ward.D2")
# Visualizacion del Clustering
df_vis <- df %>% 
  select(where(is.numeric))  # o escoger un subconjunto manualmente

fviz_cluster(hc_final,
             data = df_vis,
             geom = "point",
             pointsize = 2,
             ellipse.type = "convex",
             main = paste("Clusters AGNES con k =", k_opt))

6.4 Paso 4: K-means

# Eleccion del numero de Clusters
fviz_nbclust(df_num_scaled, kmeans, method = "wss") +
  ggtitle("Elbow Method — K-means")

fviz_nbclust(df_num_scaled, kmeans, method = "silhouette") +
  ggtitle("Silhouette — K-means")

# Aplicar el k-means con 2 clusters
k_opt_km <- 2

set.seed(123)
km_res <- kmeans(df_num_scaled, centers = k_opt_km, nstart = 25)
km_res
## K-means clustering with 2 clusters of sizes 146, 194
## 
## Cluster means:
##   months_as_customer        age policy_deductable policy_annual_premium
## 1          0.2687356  0.2437651         0.1776577           0.010941947
## 2         -0.2022443 -0.1834521        -0.1337012          -0.008234661
##   umbrella_limit capital_gains capital_loss incident_hour_of_the_day
## 1    -0.12202821    0.02857083  -0.12208120              -0.04360346
## 2     0.09183566   -0.02150176   0.09187554               0.03281498
##   number_of_vehicles_involved bodily_injuries   witnesses total_claim_amount
## 1                 -0.05945138      0.03728160  0.11891325          0.8902618
## 2                  0.04474176     -0.02805729 -0.08949141         -0.6699909
##   injury_claim property_claim vehicle_claim   auto_year
## 1    0.6929119      0.6476988     0.7934455  0.09357760
## 2   -0.5214697     -0.4874434    -0.5971291 -0.07042438
## 
## Clustering vector:
##   [1] 1 2 2 2 1 2 2 1 1 1 1 1 1 1 1 2 2 2 2 2 2 1 2 2 2 2 1 2 1 1 2 1 2 2 2 2 2
##  [38] 1 2 1 1 2 2 2 2 1 1 1 2 1 1 1 2 2 1 1 1 2 1 1 1 1 1 1 2 2 2 2 1 2 2 2 1 2
##  [75] 2 1 2 2 2 2 1 1 1 2 1 2 2 2 2 2 1 1 2 2 2 1 1 2 2 1 1 1 1 2 2 1 2 2 2 1 1
## [112] 1 1 2 1 1 2 1 2 2 2 1 2 2 2 2 1 1 1 2 1 1 2 2 1 2 2 2 2 1 2 1 2 1 1 1 1 2
## [149] 2 2 1 2 2 1 1 1 1 1 1 2 2 1 1 1 2 2 2 1 1 1 2 2 1 2 2 1 1 2 1 2 1 1 2 1 2
## [186] 2 2 1 2 2 1 1 1 1 2 2 2 2 1 1 1 1 2 2 2 2 2 2 2 1 1 1 2 1 1 2 2 2 1 2 2 2
## [223] 2 2 2 2 1 2 2 2 1 1 2 2 2 1 1 1 1 2 1 2 1 2 2 1 1 1 2 1 2 2 2 1 1 2 1 2 2
## [260] 2 2 2 2 2 2 2 2 2 2 1 2 2 2 2 1 1 2 2 1 2 1 2 1 1 2 2 1 1 2 1 2 2 1 2 2 2
## [297] 2 2 2 2 1 2 2 2 2 2 2 1 1 2 2 2 1 1 2 2 1 2 1 2 2 1 1 1 2 1 2 1 1 2 2 2 2
## [334] 1 2 1 2 2 2 2
## 
## Within cluster sum of squares by cluster:
## [1] 2199.276 2573.411
##  (between_SS / total_SS =  12.0 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"
# Visualizacion
fviz_cluster(km_res,
             data = df_num_scaled,
             geom = "point",
             ellipse.type = "norm",
             main = paste("K-means con k =", k_opt_km))