Basket Market con Arules

Author

raulito73(c)

La cesta de la compra. Market Basket

En este capítulo buscamos respuestas a las relaciones entre productos, sin tener en cuenta ninguna otra variable más que los diferentes productos que compra el cliente; algo similar al estudio de series temporales, donde se analiza la evolución de la variable en en tiempo sin tener en cuenta nada más. Lo que nos interesa de la cesta para nuestro caso concreto es “que más se adquiere con el seguro de accidentes” (si es que se adquiere algo más), o “que tiene el cliente antes de comprar el seguro de accidentes”.

Es un algoritmo muy usado en banca (por ejemplo para decidir si el cliente tiene crédito o no) analizando sus datos y ratios en comparación con los de otros clientes. De forma muy sencilla, se trata de incluir en una lista todos los articulos (servicios en este caso) que compran los clientes en diferentes fechas Tendremos diferentes listas de la compra para cada cliente como:

  1. “azúcar”

  2. “aceite, pan, sal”

  3. “azúcar, café, fruta”,

  4. “café, pan,”azucar, leche”,

  5. “leche, café, azúcar”, pan, fruta”

Un grupo frecuente seria {azúcar, café}, y también {azúcar, café, leche}

Una regla de asociación será {azúcar -> café} o bien {café -> azúcar}; esto nos puede dar una pista de porque los articulos en un supermercado están colocados como lo están, por caótico que parezca a veces…

Para que el algoritmo sea efectivo se necesitan miles de transacciones, a fin de tener un objeto tipo “lista” para que los algoritmos puedan ejecutar sus cálculos, que son bastante sencillos, ya que están basados en tres medidas :

Supongamos ahora por ejemplo, 900 clientes tienen seguro de accidentes (a), de los cuales 300 tienen seguro de accidentes (a) y también seguro de hogar (b), de un total de 3900 clientes): a y b será el conjunto frecuente.

Soporte (support) a->b: las veces que el conjunto aparece en el total de transacciones. Si el seguro de accidentes y el seguro de hogar aparecen en 300 veces de 3900 transacciones, el soporte de (acd, hogar) es 300/3900 = 0.0769

Confianza (confidence) a->b: mide la exactitud de la predicción: las 300 veces que parece (acd, hogar) dividido entre las 900 veces que aparece el seguro de accidentes: 0.3333

Lift: cuanto aumenta la razón de venta: es la confianza de (a->b) dividido entre el soporte de b: 0.3333/0.0769 = 4.3342. un lift de 1 o por debajo anula la regla (se considera azar). Mientas más alto, más razón de venta.

En la regla a->b la parte izquierda (a) se denomina LHS y la parte derecha de la regla (b) es RHS. El soporte de a->b es igual que el soporte de b->a, pero no la confianza o el lift.

El algoritmo, basándose en las transacciones que le proporcionamos, ira creando subconjuntos de elementos de 1, 2, 3, 4 … pero si un elemento, por ejemplo, el seguro de salud,  no es valorado como un ítem frecuente, ningún subconjunto que contenga la salud será evaluado. Asi que vamos a hacer esto a ver que nos aparece en nuestra base de datos. Creamos un código que extrae de cada registro los servicios contratados por cada cliente (sin ninguna otra información):

library(arules)
Cargando paquete requerido: Matrix

Adjuntando el paquete: 'arules'
The following objects are masked from 'package:base':

    abbreviate, write
library (arulesViz)
library(readr)
BBDD_clientes <- read_csv("BBDD_clientes.csv")
New names:
• `` -> `...1`
Rows: 3900 Columns: 17
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (11): e_civil, nacionalidad, pago, seguro_gratuito, seguro_voluntario, s...
dbl  (6): ...1, cliente, CP, edad, gasto, hijos

ℹ 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.
clientes <- BBDD_clientes
################## market basket ######################
#lista vacia para almacenar los valores
lista <- list()
#miramos cada fila y creamos un vector con cada valor encontrado
for (i in 1:nrow(clientes)) {
  fila <- clientes[i, ]
  tiene_seguro <- unlist (fila[fila == "gratuito" |
                                      fila == "voluntario" |
                                      fila =="accidente" |
                                      fila == "vida" |
                                      fila == "hogar" |
                                      fila == "auto" |
                                      fila == "salud" |
                                      fila == "decesos"])
  lista[[paste0("tr",i)]] <- tiene_seguro 
 }
# y borramos las filas vacias (si es que las hay)
lista <- Filter (function(x) length(x) > 0, lista)
options ("arules.check" = FALSE) 
# Convertimos a objeto tipo transaction
transaciones <- as(lista, "transactions")
# Visualizamos estadásticas básicas sobre nuestro conjunto de transacciones
summary(transaciones)
transactions as itemMatrix in sparse format with
 3850 rows (elements/itemsets/transactions) and
 8 columns (items) and a density of 0.3076299 

most frequent items:
  gratuito voluntario  accidente       auto    decesos    (Other) 
      3822       1755       1363        585        585       1365 

element (itemset/transaction) length distribution:
sizes
   1    2    3    4    5    6    7    8 
1154 1498  374  251  317  179   76    1 

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   1.000   2.000   2.461   3.000   8.000 

includes extended item information - examples:
     labels
1 accidente
2      auto
3   decesos

includes extended transaction information - examples:
  transactionID
1           tr1
2           tr2
3           tr3

Lo primero que vemos es que hemos pasado de 3900 clientes a una lista anónima de 3850 items, lo que significa que 50 clientes no tenian ningún seguro.

Los item más frecuentes aparecen por orden y son el seguro gratuito, el seguro voluntario, seguido del seguro de accidentes, auto y decesos. La categoría (Other) engloba el resto de items de la lista (salud, hogar, vida…)

La distribución nos dice que hay 1154 items con un artículo, 1498 items con 2 artículos, 374 items con 3 artículos…. y así sucesívamente. Un dato importante es que los item de 2 articulos pueden estar englobados en subconjuntos más grandes , los de 3 pueden estar en los de 4 o 5, y asi sucesivamente.

La media de articulo por cliente es de 2,461, y su mediana de 2.

Un listado de las 10 primeras transaciones:

lista [1:10]
$tr1
[1] "gratuito"  "accidente"

$tr2
[1] "gratuito"   "voluntario" "accidente" 

$tr3
[1] "gratuito"   "voluntario"

$tr4
[1] "gratuito"   "voluntario"

$tr5
[1] "gratuito"

$tr6
[1] "gratuito"

$tr7
[1] "gratuito"

$tr8
[1] "gratuito"

$tr9
[1] "gratuito"  "accidente" "vida"      "decesos"   "hogar"     "auto"     

$tr10
[1] "gratuito" "vida"     "decesos" 

Lo que se corresponde con la cesta de seguros de cada cliente (omitidos los que no tienen ningun seguro, ya que no hay “transacción”).

Nuestro objetivo es el estudio del seguro de accidentes, que además hemos visto que es el tercer item más frecuente en la cesta de seguros.

Creamos las reglas de asociación. Vamos a ver ahora el conjunto total de reglas para un soporte de 0.1 y confianza de 0.5; no mostrará ninguna regla de asociación por debajo de esos parámetros. Si ponemos unos valores muy bajos creará demasiadas reglas (que no servirán, serán por azar) y con valores demasiado altos, quizas no encuentre ninguna, por lo que hay que ir probando valores: (y aqui mostramos un ejemplo concreto con esos valores). Otros valores generan otras reglas que habrá que valorar.

#generación de reglas (jugar con soporte y confianza) 
reglas <- apriori(transaciones, parameter = list(supp = 0.1, conf = 0.5, target = "rules")) 
Apriori

Parameter specification:
 confidence minval smax arem  aval originalSupport maxtime support minlen
        0.5    0.1    1 none FALSE            TRUE       5     0.1      1
 maxlen target  ext
     10  rules TRUE

Algorithmic control:
 filter tree heap memopt load sort verbose
    0.1 TRUE TRUE  FALSE TRUE    2    TRUE

Absolute minimum support count: 385 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[8 item(s), 3850 transaction(s)] done [0.00s].
sorting and recoding items ... [7 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 3 4 5 done [0.00s].
writing ... [69 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].

Y viasualizamos un resumen del algoritmo:

summary (reglas)
set of 69 rules

rule length distribution (lhs + rhs):sizes
 1  2  3  4  5 
 1 15 28 20  5 

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   3.000   3.000   3.188   4.000   5.000 

summary of quality measures:
    support         confidence        coverage           lift       
 Min.   :0.1195   Min.   :0.5066   Min.   :0.1195   Min.   :0.9918  
 1st Qu.:0.1226   1st Qu.:0.8281   1st Qu.:0.1242   1st Qu.:1.0073  
 Median :0.1242   Median :0.9746   Median :0.1499   Median :1.8185  
 Mean   :0.1589   Mean   :0.9052   Mean   :0.1817   Mean   :2.9192  
 3rd Qu.:0.1473   3rd Qu.:0.9893   3rd Qu.:0.1519   3rd Qu.:3.5164  
 Max.   :0.9927   Max.   :1.0000   Max.   :1.0000   Max.   :6.4215  
     count       
 Min.   : 460.0  
 1st Qu.: 472.0  
 Median : 478.0  
 Mean   : 611.8  
 3rd Qu.: 567.0  
 Max.   :3822.0  

mining info:
         data ntransactions support confidence
 transaciones          3850     0.1        0.5
                                                                                     call
 apriori(data = transaciones, parameter = list(supp = 0.1, conf = 0.5, target = "rules"))

Nos indica que ha encontrado 69 reglas de asociación en las condiciones indicadas (soporte y confianza), y nos indica también el tamaño total de los conjuntos a la izquierda y a la derecha de la regla (LHS + RHS). Asi mismo, nos dice los estadísticos descriptivos básicos.

Vemos las 5 primeras reglas encontradas.

inspect(reglas[1:5]) 
    lhs          rhs         support   confidence coverage  lift      count
[1] {}        => {gratuito}  0.9927273 0.9927273  1.0000000 1.0000000 3822 
[2] {vida}    => {gratuito}  0.1202597 0.9893162  0.1215584 0.9965640  463 
[3] {decesos} => {accidente} 0.1402597 0.9230769  0.1519481 2.6073706  540 
[4] {decesos} => {gratuito}  0.1498701 0.9863248  0.1519481 0.9935506  577 
[5] {hogar}   => {auto}      0.1472727 0.9692308  0.1519481 6.3786982  567 

Ya encontramos el objetivo de nuestro estudio en una de esas reglas, la número [3] que se leería de esta forma:

  • [3]“con un soporte de 0,14 y una confianza de 0.92 la cobertura de clientes con seguro de decesos que compran el seguro de accidentes es de un 15%”

Es una regla “por la derecha” porque acidentes está en la derecha del grupo, RHS) asi que accidentes sería una consecuencia de tener decesos.

Como buscamos saber la relación del seguro de accidentes con el resto de los servicios y si hay reglas de asociación, no vamos a mirar las 69 reglas encontradas, si no que vamos a filtrar las reglas, buscando en la izquierda (LHS) y en la derecha (RHS) de las reglas de asociación nuestro seguro de accidentes en los subconjuntos:

Como hemos dicho, si el seguro de accidentes está a la derecha, en RHS, la lectura será que el seguro de accidentes lo ha contratado el cliente que ya tiene otro producto:

#RHS accidentes: el seguro de acd como consecuencia de tener otros seguros 
reglas_acd_rhs <- apriori(transaciones,                            parameter = list (supp=0.01, conf = 0.4),                           appearance = list (default = "lhs", rhs = "accidente"),                           target = "rules") 
Apriori

Parameter specification:
 confidence minval smax arem  aval originalSupport maxtime support minlen
        0.4    0.1    1 none FALSE            TRUE       5    0.01      1
 maxlen target  ext
     10  rules TRUE

Algorithmic control:
 filter tree heap memopt load sort verbose
    0.1 TRUE TRUE  FALSE TRUE    2    TRUE

Absolute minimum support count: 38 

set item appearances ...[1 item(s)] done [0.00s].
set transactions ...[8 item(s), 3850 transaction(s)] done [0.00s].
sorting and recoding items ... [8 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 3 4 5 6 7 done [0.00s].
writing ... [62 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].
#mirando 
summary (reglas_acd_rhs) 
set of 62 rules

rule length distribution (lhs + rhs):sizes
 2  3  4  5  6  7 
 5 15 20 15  6  1 

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.000   3.000   4.000   4.081   5.000   7.000 

summary of quality measures:
    support          confidence        coverage            lift      
 Min.   :0.01558   Min.   :0.5066   Min.   :0.01558   Min.   :1.431  
 1st Qu.:0.02539   1st Qu.:0.9882   1st Qu.:0.02662   1st Qu.:2.791  
 Median :0.05143   Median :0.9929   Median :0.05143   Median :2.805  
 Mean   :0.06742   Mean   :0.9561   Mean   :0.07751   Mean   :2.701  
 3rd Qu.:0.11032   3rd Qu.:1.0000   3rd Qu.:0.12026   3rd Qu.:2.825  
 Max.   :0.23091   Max.   :1.0000   Max.   :0.45584   Max.   :2.825  
     count       
 Min.   : 60.00  
 1st Qu.: 97.75  
 Median :198.00  
 Mean   :259.58  
 3rd Qu.:424.75  
 Max.   :889.00  

mining info:
         data ntransactions support confidence
 transaciones          3850    0.01        0.4
                                                                                                                                             call
 apriori(data = transaciones, parameter = list(supp = 0.01, conf = 0.4), appearance = list(default = "lhs", rhs = "accidente"), target = "rules")

El algoritmo encuentra 60 reglas de asociación con un soporte superior a 0.1 y una confianza superior a 0.4. Nos ofrece también el tamaño total de los 62 subconjuntos, agrupados por ítems, y los 5 estadísticos básicos de los subconjuntos. Importante recordar que los 62 subconjuntos son redundantes (algunos de los más pequeños estarán también incluidos en los más grandes).

Podemos elegir, para filtrar las reglas, el ordenarlas por alguna de sus características. O bien por el número de veces que aparecen, o por el mayor soporte o confianza, o una combinación de ambos, lo que más nos interese, o investigando al respecto. Aquí es sencillo establecer reglas, ya que todo son seguros. En un supermercado, una regla que asocia lejía con café no parece una regla que tenga cierta lógica (y puede sucecer por azar, pero para eso esta el “lift”).

Vamos a ordenar por número de veces que aparece la transacción, después por confianza y después por lift:

#ordenando 
reglas_acd_rhs_ord <- sort(reglas_acd_rhs,
                           by = c("count", "lift", "confidence"),
                           decreasing = TRUE) 

#ver las mejores (por count, lift y confidence) 
inspect(reglas_acd_rhs_ord[1:5]) 
    lhs                       rhs         support   confidence coverage 
[1] {voluntario}           => {accidente} 0.2309091 0.5065527  0.4558442
[2] {gratuito, voluntario} => {accidente} 0.2309091 0.5065527  0.4558442
[3] {auto}                 => {accidente} 0.1509091 0.9931624  0.1519481
[4] {hogar}                => {accidente} 0.1498701 0.9863248  0.1519481
[5] {auto, gratuito}       => {accidente} 0.1488312 0.9930676  0.1498701
    lift     count
[1] 1.430835 889  
[2] 1.430835 889  
[3] 2.805338 581  
[4] 2.786024 577  
[5] 2.805070 573  

Y esta información tambien la vemos en forma de gráfico:

# mirando en html 
plot (reglas_acd_rhs_ord[1:5], method = "graph", engine = "html") 

Vemos por ejemplo como partiendo de los clientes que tienen un seguro gratuito, pueden optar por la regla [5] {gratuito, auto} => {acd} o por la regla [2] {gratuito, voluntario} => {acd}, cada uno con sus parámetros. Aunque no vemos los datos aquí, pero visualmente se ven muy bien las relaciones y también lo que estábamos comentando de subconjuntos y reglas redundantes. Ahora bien, podría ser al revés (tener accidentes lleva al auto).

Haríamos lo mismo para el seguro de accidentes, pero ahora incluyéndolo en la parte izquierda de la transacción (es decir que los clientes que han comprado el seguro de accidentes compraron “cierto servicio”):

reglas_acd_lhs <- apriori(transaciones,                           parameter = list (supp = 0.01, conf = 0.4),                          appearance = list (lhs = "accidente", default = "rhs"),                          target = "rules")  
Apriori

Parameter specification:
 confidence minval smax arem  aval originalSupport maxtime support minlen
        0.4    0.1    1 none FALSE            TRUE       5    0.01      1
 maxlen target  ext
     10  rules TRUE

Algorithmic control:
 filter tree heap memopt load sort verbose
    0.1 TRUE TRUE  FALSE TRUE    2    TRUE

Absolute minimum support count: 38 

set item appearances ...[1 item(s)] done [0.00s].
set transactions ...[8 item(s), 3850 transaction(s)] done [0.00s].
sorting and recoding items ... [8 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 done [0.00s].
writing ... [6 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].
inspect (reglas_acd_lhs)
    lhs            rhs          support   confidence coverage lift      count
[1] {}          => {voluntario} 0.4558442 0.4558442  1.000000 1.0000000 1755 
[2] {}          => {gratuito}   0.9927273 0.9927273  1.000000 1.0000000 3822 
[3] {accidente} => {hogar}      0.1498701 0.4233309  0.354026 2.7860238  577 
[4] {accidente} => {auto}       0.1509091 0.4262656  0.354026 2.8053376  581 
[5] {accidente} => {voluntario} 0.2309091 0.6522377  0.354026 1.4308349  889 
[6] {accidente} => {gratuito}   0.3485714 0.9845928  0.354026 0.9918059 1342 
#mirando en html 
plot (reglas_acd_lhs, method = "graph", engine = "html") 

Aparecen menos reglas que en el apartado anterior, pero hay dos de ellas interesantes. De los clientes que tienen en su cesta el seguro de accidentes, algunos adquirieron el seguro del hogar y otros el seguro de auto. (recordatorio: tenemos menos de 4000 transacciones y ademas son “ficticias”). Tambien podiamos haber cambiado lso parámetros anteriormente comentados.