Agrupamiento o Clustering es una tecnica de aprendizaje automatico no supervisado que agrupa datos en funcion de su similitud.
Algunso usos tipicos de esta tecnica son:
#install.packages("cluster") # Analisis de agrupamiento
library(cluster)
#install.packages("ggplot2") # Graficar
library(ggplot2)
#install.packages("data.table") # Manejo de muchos datos
library(data.table)
#install.packages("factoextra") # Grafica de optimizacion del numero de clusters
library(factoextra)
#install.packages("tidyverse")
library(tidyverse)
Agrupa los siguientes 8 puntos.
df1 <- data.frame(x=c(2,2,8,5,7,6,1,4), y=c(10,5,4,8,5,4,2,9))
summary(df1)
## x y
## Min. :1.000 Min. : 2.000
## 1st Qu.:2.000 1st Qu.: 4.000
## Median :4.500 Median : 5.000
## Mean :4.375 Mean : 5.875
## 3rd Qu.:6.250 3rd Qu.: 8.250
## Max. :8.000 Max. :10.000
str(df1)
## 'data.frame': 8 obs. of 2 variables:
## $ x: num 2 2 8 5 7 6 1 4
## $ y: num 10 5 4 8 5 4 2 9
plot(df1$x, df1$y)
## Escalar Datos
# datos_escalados <- scale(datos_originales)
grupos1 <- 3
set.seed(123)
clusters1 <- kmeans(df1, grupos1)
clusters1
## K-means clustering with 3 clusters of sizes 2, 3, 3
##
## Cluster means:
## x y
## 1 1.500000 3.500000
## 2 3.666667 9.000000
## 3 7.000000 4.333333
##
## Clustering vector:
## [1] 2 1 3 2 3 3 1 2
##
## Within cluster sum of squares by cluster:
## [1] 5.000000 6.666667 2.666667
## (between_SS / total_SS = 85.8 %)
##
## Available components:
##
## [1] "cluster" "centers" "totss" "withinss" "tot.withinss"
## [6] "betweenss" "size" "iter" "ifault"
set.seed(123)
optimizacion1 <- clusGap(df1, FUN=kmeans, nstart=1, K.max=7)
# El k.max normalmente es 10, en este ejercicio al ser 8 datos se dejo en 7.
plot(optimizacion1, xlab="Numero de clusters k", main="Optimizacion de Clusters")
# Se selecciona como optimo el primer punto mas alto.
# Si es diferente al original, regresar y ajustar.
fviz_cluster(clusters1, data=df1)
## Agregar grupos a la base de datos
df1_clusters <- cbind(df1, cluster = clusters1$cluster)
head(df1_clusters)
## x y cluster
## 1 2 10 2
## 2 2 5 1
## 3 8 4 3
## 4 5 8 2
## 5 7 5 3
## 6 6 4 3
La tecnica de Clustering permite identificar patrones o grupos naturales en los datos sin necesidad de etiquetas previas.
La base de datos USArrests contiene estadisticas en arrestos por cada 100,000 residentes por agresion, asesinato, violacion en cada uno de los 50 estados de EE.UU en 1973.
df2 <- USArrests
df2 <- df2 %>% select (-UrbanPop)
summary(df2)
## Murder Assault Rape
## Min. : 0.800 Min. : 45.0 Min. : 7.30
## 1st Qu.: 4.075 1st Qu.:109.0 1st Qu.:15.07
## Median : 7.250 Median :159.0 Median :20.10
## Mean : 7.788 Mean :170.8 Mean :21.23
## 3rd Qu.:11.250 3rd Qu.:249.0 3rd Qu.:26.18
## Max. :17.400 Max. :337.0 Max. :46.00
str(df2)
## 'data.frame': 50 obs. of 3 variables:
## $ Murder : num 13.2 10 8.1 8.8 9 7.9 3.3 5.9 15.4 17.4 ...
## $ Assault: int 236 263 294 190 276 204 110 238 335 211 ...
## $ Rape : num 21.2 44.5 31 19.5 40.6 38.7 11.1 15.8 31.9 25.8 ...
df2_escalados <- scale(df2)
summary(df2_escalados)
## Murder Assault Rape
## Min. :-1.6044 Min. :-1.5090 Min. :-1.4874
## 1st Qu.:-0.8525 1st Qu.:-0.7411 1st Qu.:-0.6574
## Median :-0.1235 Median :-0.1411 Median :-0.1209
## Mean : 0.0000 Mean : 0.0000 Mean : 0.0000
## 3rd Qu.: 0.7949 3rd Qu.: 0.9388 3rd Qu.: 0.5277
## Max. : 2.2069 Max. : 1.9948 Max. : 2.6444
grupos2 <- 5
set.seed(123)
clusters2 <- kmeans(df2_escalados, grupos2)
set.seed(123)
optimizacion2 <- clusGap(df2_escalados, FUN=kmeans, nstart=1, K.max=10)
plot(optimizacion2, xlab="NUmero de clusters l", main="Optimizacion de clusters")
fviz_cluster(clusters2, data=df2_escalados)
df2_clusters <- cbind(df2, cluster = clusters2$cluster)
head(df2_clusters)
## Murder Assault Rape cluster
## Alabama 13.2 236 21.2 5
## Alaska 10.0 263 44.5 4
## Arizona 8.1 294 31.0 1
## Arkansas 8.8 190 19.5 3
## California 9.0 276 40.6 4
## Colorado 7.9 204 38.7 4
df2_clusters %>% group_by(cluster) %>% summarise_all(mean) %>% mutate(indicador_inseguridad=Murder+Assault+Rape) %>%
mutate(indicador_inseguridad=Murder+Assault+Rape)
## # A tibble: 5 × 5
## cluster Murder Assault Rape indicador_inseguridad
## <int> <dbl> <dbl> <dbl> <dbl>
## 1 1 11.4 282. 29.7 323.
## 2 2 3.08 80.9 11.8 95.8
## 3 3 6.59 146. 20.1 172.
## 4 4 9.78 249. 42.4 301.
## 5 5 14.4 245 22.2 282.
df2_clusters <- df2_clusters %>%
mutate(cluster== case_when(
cluster == 1 ~ "Inseguridad Muy Alta",
cluster == 4 ~ "Inseguridad Alta",
cluster == 5 ~ "Inseguridad Media",
cluster == 3 ~ "Inseguridad Baja",
cluster == 2 ~ "Inseguridad Muy Baja",
))
head(df2_clusters)
## Murder Assault Rape cluster ==...
## Alabama 13.2 236 21.2 5 FALSE
## Alaska 10.0 263 44.5 4 FALSE
## Arizona 8.1 294 31.0 1 FALSE
## Arkansas 8.8 190 19.5 3 FALSE
## California 9.0 276 40.6 4 FALSE
## Colorado 7.9 204 38.7 4 FALSE
La base de datos ventas tiene los registros entre el 1 de diciembre de 2010 y el 9 de diciembre de 2011 de las ventas de una empresa minorista en linea sin tienda fisica, basada en Reino Unido.
La empresa vende principalmente regalos unicos para toda ocasion, y muchos de sus clientes son mayoristas.
Objetivo: Segmentar clientes, asignarles nombre y caracteristicas de comportamiento, y proponer sugerencias a la empresa para aumentar ventas.
#file.choose()
df_ventas <- read.csv("/Users/eurielgomeztamez/Library/Mobile Documents/com~apple~CloudDocs/Tec/7/M2/ventas.csv",fileEncoding = "latin1",stringsAsFactors = FALSE)
head(df_ventas)
## ï..Ticket Producto Cantidad Fecha Hora
## 1 536365 WHITE HANGING HEART T-LIGHT HOLDER 6 01/12/2010 08:26:00
## 2 536365 WHITE METAL LANTERN 6 01/12/2010 08:26:00
## 3 536365 CREAM CUPID HEARTS COAT HANGER 8 01/12/2010 08:26:00
## 4 536365 KNITTED UNION FLAG HOT WATER BOTTLE 6 01/12/2010 08:26:00
## 5 536365 RED WOOLLY HOTTIE WHITE HEART 6 01/12/2010 08:26:00
## 6 536365 SET 7 BABUSHKA NESTING BOXES 2 01/12/2010 08:26:00
## Precio Cliente PaÃ.s
## 1 2.55 17850 United Kingdom
## 2 3.39 17850 United Kingdom
## 3 2.75 17850 United Kingdom
## 4 3.39 17850 United Kingdom
## 5 3.39 17850 United Kingdom
## 6 7.65 17850 United Kingdom
summary(df_ventas)
## ï..Ticket Producto Cantidad Fecha
## Length :522064 Length :522064 Min. :-9600.00 Length :522064
## N.unique : 21663 N.unique : 4183 1st Qu.: 1.00 N.unique : 305
## N.blank : 0 N.blank : 1455 Median : 3.00 N.blank : 0
## Min.nchar: 6 Min.nchar: 0 Mean : 10.09 Min.nchar: 10
## Max.nchar: 7 Max.nchar: 38 3rd Qu.: 10.00 Max.nchar: 10
## Max. :80995.00
##
## Hora Precio Cliente PaÃ.s
## Length :522064 Min. :-11062.060 Min. :12346 Length :522064
## N.unique : 739 1st Qu.: 1.250 1st Qu.:13950 N.unique : 30
## N.blank : 0 Median : 2.080 Median :15265 N.blank : 0
## Min.nchar: 8 Mean : 3.827 Mean :15317 Min.nchar: 3
## Max.nchar: 8 3rd Qu.: 4.130 3rd Qu.:16837 Max.nchar: 20
## Max. : 13541.330 Max. :18287
## NAs :134041
str(df_ventas)
## 'data.frame': 522064 obs. of 8 variables:
## $ ï..Ticket: chr "536365" "536365" "536365" "536365" ...
## $ Producto : chr "WHITE HANGING HEART T-LIGHT HOLDER" "WHITE METAL LANTERN" "CREAM CUPID HEARTS COAT HANGER" "KNITTED UNION FLAG HOT WATER BOTTLE" ...
## $ Cantidad : int 6 6 8 6 6 2 6 6 6 32 ...
## $ Fecha : chr "01/12/2010" "01/12/2010" "01/12/2010" "01/12/2010" ...
## $ Hora : chr "08:26:00" "08:26:00" "08:26:00" "08:26:00" ...
## $ Precio : num 2.55 3.39 2.75 3.39 3.39 7.65 4.25 1.85 1.85 1.69 ...
## $ Cliente : int 17850 17850 17850 17850 17850 17850 17850 17850 17850 13047 ...
## $ PaÃ.s : chr "United Kingdom" "United Kingdom" "United Kingdom" "United Kingdom" ...
names(df_ventas)
## [1] "ï..Ticket" "Producto" "Cantidad" "Fecha" "Hora" "Precio"
## [7] "Cliente" "PaÃ.s"
names(df_ventas) <- c(
"Ticket",
"Producto",
"Cantidad",
"Fecha",
"Hora",
"Precio",
"Cliente",
"Pais"
)
names(df_ventas)
## [1] "Ticket" "Producto" "Cantidad" "Fecha" "Hora" "Precio" "Cliente"
## [8] "Pais"
df_ventas$Fecha <- as.Date(df_ventas$Fecha,format = "%d/%m/%Y")
df_ventas$Cantidad <- as.numeric(df_ventas$Cantidad)
df_ventas$Precio <- as.numeric(df_ventas$Precio)
df_ventas$Cliente <- as.numeric(df_ventas$Cliente)
df_ventas <- df_ventas %>%
mutate(
Venta = Cantidad * Precio
)
df_ventas_limpia <- df_ventas %>% filter(
!is.na(Cliente),
Cantidad > 0,
Precio > 0
)
fecha_referencia <- max(df_ventas_limpia$Fecha) + 1
df_clientes <- df_ventas_limpia %>% group_by(Cliente) %>% summarise(
recencia = as.numeric(
fecha_referencia - max(Fecha)
),
frecuencia = n_distinct(Ticket),
gasto_total = sum(Venta),
cantidad_productos = sum(Cantidad),
ticket_promedio = gasto_total / frecuencia,
productos_distintos = n_distinct(Producto)
)
summary(df_clientes)
## Cliente recencia frecuencia gasto_total
## Min. :12346 Min. : 1.00 Min. : 1.000 Min. : 3.75
## 1st Qu.:13832 1st Qu.: 18.00 1st Qu.: 1.000 1st Qu.: 306.72
## Median :15322 Median : 51.00 Median : 2.000 Median : 668.85
## Mean :15316 Mean : 93.16 Mean : 4.227 Mean : 1993.61
## 3rd Qu.:16790 3rd Qu.:143.00 3rd Qu.: 5.000 3rd Qu.: 1652.79
## Max. :18287 Max. :374.00 Max. :209.000 Max. :280206.02
## cantidad_productos ticket_promedio productos_distintos
## Min. : 1.0 Min. : 3.45 Min. : 1.00
## 1st Qu.: 160.0 1st Qu.: 178.30 1st Qu.: 16.00
## Median : 376.0 Median : 292.00 Median : 35.50
## Mean : 1161.3 Mean : 415.62 Mean : 61.27
## 3rd Qu.: 983.5 3rd Qu.: 426.63 3rd Qu.: 78.00
## Max. :196915.0 Max. :84236.25 Max. :1774.00
str(df_clientes)
## tibble [4,296 × 7] (S3: tbl_df/tbl/data.frame)
## $ Cliente : num [1:4296] 12346 12347 12349 12350 12352 ...
## $ recencia : num [1:4296] 326 3 19 311 37 205 233 215 23 34 ...
## $ frecuencia : int [1:4296] 1 7 1 1 8 1 1 1 3 1 ...
## $ gasto_total : num [1:4296] 77184 4310 1758 334 2506 ...
## $ cantidad_productos : num [1:4296] 74215 2458 631 197 536 ...
## $ ticket_promedio : num [1:4296] 77184 616 1758 334 313 ...
## $ productos_distintos: int [1:4296] 1 103 73 17 59 4 58 13 53 131 ...
head(df_clientes)
## # A tibble: 6 × 7
## Cliente recencia frecuencia gasto_total cantidad_productos ticket_promedio
## <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 12346 326 1 77184. 74215 77184.
## 2 12347 3 7 4310. 2458 616.
## 3 12349 19 1 1758. 631 1758.
## 4 12350 311 1 334. 197 334.
## 5 12352 37 8 2506. 536 313.
## 6 12353 205 1 89 20 89
## # ℹ 1 more variable: productos_distintos <int>
df_clientes_log <- df_clientes %>%
select(recencia,frecuencia,gasto_total,cantidad_productos,ticket_promedio,productos_distintos) %>% mutate(
across(
everything(),
log1p
)
)
df_clientes_escalados <- scale(df_clientes_log)
summary(df_clientes_escalados)
## recencia frecuencia gasto_total cantidad_productos
## Min. :-2.4185 Min. :-0.9578 Min. :-4.01493 Min. :-3.86824
## 1st Qu.:-0.6976 1st Qu.:-0.9578 1st Qu.:-0.68455 1st Qu.:-0.65755
## Median : 0.0720 Median :-0.3622 Median :-0.06347 Median :-0.03503
## Mean : 0.0000 Mean : 0.0000 Mean : 0.00000 Mean : 0.00000
## 3rd Qu.: 0.8506 3rd Qu.: 0.6561 3rd Qu.: 0.65816 3rd Qu.: 0.66728
## Max. : 1.5822 Max. : 5.8791 Max. : 4.75618 Max. : 4.54388
## ticket_promedio productos_distintos
## Min. :-5.60522 Min. :-2.52890
## 1st Qu.:-0.61486 1st Qu.:-0.63640
## Median : 0.04819 Median : 0.03922
## Mean : 0.00000 Mean : 0.00000
## 3rd Qu.: 0.55869 3rd Qu.: 0.72211
## Max. : 7.69169 Max. : 3.47421
grupos <- 10
set.seed(123)
clusters <- kmeans(
df_clientes_escalados,
grupos,
nstart = 25
)
## Warning: did not converge in 10 iterations
set.seed(123)
optimizacion <- clusGap(
df_clientes_escalados,
FUN = kmeans,
nstart = 25,
K.max = 10
)
plot(
optimizacion,
xlab = "Número de clusters",
main = "Optimización de Clusters"
)
fviz_cluster(
clusters,
data = df_clientes_escalados,
main = "Segmentación de Clientes",
xlab = "Dimensión 1",
ylab = "Dimensión 2",
legend.title = "Segmento"
)
df_clientes_clusters <- cbind(
df_clientes,
cluster = clusters$cluster
)
head(df_clientes_clusters)
## Cliente recencia frecuencia gasto_total cantidad_productos ticket_promedio
## 1 12346 326 1 77183.60 74215 77183.6000
## 2 12347 3 7 4310.00 2458 615.7143
## 3 12349 19 1 1757.55 631 1757.5500
## 4 12350 311 1 334.40 197 334.4000
## 5 12352 37 8 2506.04 536 313.2550
## 6 12353 205 1 89.00 20 89.0000
## productos_distintos cluster
## 1 1 4
## 2 103 5
## 3 73 4
## 4 17 1
## 5 59 6
## 6 4 10
df_clientes_clusters %>%
group_by(cluster) %>%
summarise(
clientes = n(),
recencia = mean(recencia),
frecuencia = mean(frecuencia),
gasto_total = mean(gasto_total),
cantidad_productos = mean(cantidad_productos),
ticket_promedio = mean(ticket_promedio),
productos_distintos = mean(productos_distintos)
)
## # A tibble: 10 × 8
## cluster clientes recencia frecuencia gasto_total cantidad_productos
## <int> <int> <dbl> <dbl> <dbl> <dbl>
## 1 1 624 180. 1.16 426. 240.
## 2 2 520 214. 1.38 200. 103.
## 3 3 116 9.72 28.4 29005. 16186.
## 4 4 310 115. 1.72 2260. 1376.
## 5 5 414 10.7 12.1 3828. 2263.
## 6 6 527 49.2 5.54 2662. 1627.
## 7 7 591 101. 3.08 755. 465.
## 8 8 539 14.2 3.93 1008. 602.
## 9 9 474 27.8 1.71 290. 169.
## 10 10 181 159. 1.28 80.6 43.6
## # ℹ 2 more variables: ticket_promedio <dbl>, productos_distintos <dbl>
df_clientes_clusters <- df_clientes_clusters %>%
mutate(
indicador_valor =
gasto_total +
frecuencia * 100 +
ticket_promedio
)
df_clientes_clusters %>%
group_by(cluster) %>%
summarise(
clientes = n(),
recencia = mean(recencia),
frecuencia = mean(frecuencia),
gasto_total = mean(gasto_total),
ticket_promedio = mean(ticket_promedio),
indicador_valor = mean(indicador_valor)
)
## # A tibble: 10 × 7
## cluster clientes recencia frecuencia gasto_total ticket_promedio
## <int> <int> <dbl> <dbl> <dbl> <dbl>
## 1 1 624 180. 1.16 426. 377.
## 2 2 520 214. 1.38 200. 155.
## 3 3 116 9.72 28.4 29005. 1814.
## 4 4 310 115. 1.72 2260. 1399.
## 5 5 414 10.7 12.1 3828. 346.
## 6 6 527 49.2 5.54 2662. 515.
## 7 7 591 101. 3.08 755. 265.
## 8 8 539 14.2 3.93 1008. 284.
## 9 9 474 27.8 1.71 290. 188.
## 10 10 181 159. 1.28 80.6 68.7
## # ℹ 1 more variable: indicador_valor <dbl>
df_clientes_clusters <- df_clientes_clusters %>%
mutate(
segmento = case_when(
cluster == 1 ~ "Clientes Inactivos",
cluster == 2 ~ "Clientes de Muy Bajo Valor",
cluster == 3 ~ "Clientes VIP",
cluster == 4 ~ "Clientes de Alto Ticket",
cluster == 5 ~ "Clientes Premium Frecuentes",
cluster == 6 ~ "Clientes Frecuentes",
cluster == 7 ~ "Clientes Ocasionales",
cluster == 8 ~ "Clientes Activos",
cluster == 9 ~ "Clientes Ocasionales Recientes",
cluster == 10 ~ "Clientes Inactivos de Bajo Valor"
)
)
head(df_clientes_clusters)
## Cliente recencia frecuencia gasto_total cantidad_productos ticket_promedio
## 1 12346 326 1 77183.60 74215 77183.6000
## 2 12347 3 7 4310.00 2458 615.7143
## 3 12349 19 1 1757.55 631 1757.5500
## 4 12350 311 1 334.40 197 334.4000
## 5 12352 37 8 2506.04 536 313.2550
## 6 12353 205 1 89.00 20 89.0000
## productos_distintos cluster indicador_valor segmento
## 1 1 4 154467.200 Clientes de Alto Ticket
## 2 103 5 5625.714 Clientes Premium Frecuentes
## 3 73 4 3615.100 Clientes de Alto Ticket
## 4 17 1 768.800 Clientes Inactivos
## 5 59 6 3619.295 Clientes Frecuentes
## 6 4 10 278.000 Clientes Inactivos de Bajo Valor
df_clientes_clusters %>%
count(segmento)
## segmento n
## 1 Clientes Activos 539
## 2 Clientes Frecuentes 527
## 3 Clientes Inactivos 624
## 4 Clientes Inactivos de Bajo Valor 181
## 5 Clientes Ocasionales 591
## 6 Clientes Ocasionales Recientes 474
## 7 Clientes Premium Frecuentes 414
## 8 Clientes VIP 116
## 9 Clientes de Alto Ticket 310
## 10 Clientes de Muy Bajo Valor 520
df_clientes_clusters %>%
group_by(segmento) %>%
summarise(
clientes = n(),
recencia_promedio = mean(recencia),
frecuencia_promedio = mean(frecuencia),
gasto_promedio = mean(gasto_total),
ticket_promedio = mean(ticket_promedio),
productos_promedio = mean(productos_distintos)
)
## # A tibble: 10 × 7
## segmento clientes recencia_promedio frecuencia_promedio gasto_promedio
## <chr> <int> <dbl> <dbl> <dbl>
## 1 Clientes Activ… 539 14.2 3.93 1008.
## 2 Clientes Frecu… 527 49.2 5.54 2662.
## 3 Clientes Inact… 624 180. 1.16 426.
## 4 Clientes Inact… 181 159. 1.28 80.6
## 5 Clientes Ocasi… 591 101. 3.08 755.
## 6 Clientes Ocasi… 474 27.8 1.71 290.
## 7 Clientes Premi… 414 10.7 12.1 3828.
## 8 Clientes VIP 116 9.72 28.4 29005.
## 9 Clientes de Al… 310 115. 1.72 2260.
## 10 Clientes de Mu… 520 214. 1.38 200.
## # ℹ 2 more variables: ticket_promedio <dbl>, productos_promedio <dbl>