Recomendación de items frecuentes en supermercados usando Apriori

  • 30 min | Ultima modificación: Junio 22, 2019

En este ejemplo se desarrolla un sistema de reglas de asociación usando el algoritmo Apriori para realizar recomendaciones a clientes a partir de su compra actual.

Descripción del problema

Una cadena de supermercados desea construir un sistema de recomendación para su página web que sugiera a un comprador otros productos que podrían interesarle dependiendo de la búsqueda o la selección de productos que haya realizado en el momento de realizar la selección.

Se tiene una base de datos de 9835 transacciones recolectadas de forma continua durante 12 días para un supermercado relativamente pequeño. Para hacer este problema manejable no se tuvieron en cuenta las posibles marcas de un mismo producto ni los distintos tipos de presentaciones. De esta forma, se tienen registros de 169 tipos de productos como chicken, frozen meals, margarine, etc. El objetivo en términos de los datos consiste en determinar los grupos de productos que se compran frecuentemente juntos con el fin de que cuando un cliente seleccione uno o más items de un determinado grupo, los restantes elementos le sean sugeridos automáticamente por el sistema.

Preparación

[1]:
##
## Preparación. Se utiliza el paquete rpy2
## para ejecutar código R dentro de Python.
##
%load_ext rpy2.ipython
[2]:
%%sh
PACK=arules
if /usr/bin/test ! -d /usr/local/lib/R/site-library/$PACK;
then
    sudo Rscript -e 'install.packages("'$PACK'")'
fi
[3]:
%%R
##
## Carga de los datos
##
library(arules)
R[write to console]: Loading required package: Matrix

R[write to console]:
Attaching package: ‘arules’


R[write to console]: The following objects are masked from ‘package:base’:

    abbreviate, write


Lectura de datos

[4]:
%%R
groceries <- read.transactions("https://raw.githubusercontent.com/jdvelasq/datalabs/master/datasets/groceries.csv", sep = ",")

El archivo anterior contiene una transacción por fila, y los items comprados por transacción aparecen separados por coma:

citrus fruit,semi-finished bread,margarine,ready soups
tropical fruit,yogurt,coffee
whole milk
pip fruit,yogurt,cream cheese ,meat spreads
other vegetables,whole milk,condensed milk,long life bakery product
whole milk,butter,yogurt,rice,abrasive cleaner
rolls/buns
other vegetables,UHT-milk,rolls/buns,bottled beer,liquor (appetizer)
pot plants
whole milk,cereals

Una de las dificultades para manejar esta información es que cada fila puede contener un número diferente de elementos por lo que no es posible usar directamente un data.frame y se utiliza una matriz esparcida.

Análisis exploratorio

[5]:
%%R
##
## Se obtiene la información más relevante de los datos. Los
## resultados indican lo siguiente:
##
##   * Hay 9.835 transacciones con 169 items (ok!)
##
##   * Los productos más vendidos son:
##
##       producto          transacciones en
##                         las que aparece
##       ------------------------------------
##       whole milk        2.513
##       other vegetables  1.903
##       ...
##
##  * Hay 2.159 transacciones con 1 ítem, 1.643 con 2 ítems, ...,
##    hay una sola transacción con 32 ítems
##
##
summary(groceries)
transactions as itemMatrix in sparse format with
 9835 rows (elements/itemsets/transactions) and
 169 columns (items) and a density of 0.02609146

most frequent items:
      whole milk other vegetables       rolls/buns             soda
            2513             1903             1809             1715
          yogurt          (Other)
            1372            34055

element (itemset/transaction) length distribution:
sizes
   1    2    3    4    5    6    7    8    9   10   11   12   13   14   15   16
2159 1643 1299 1005  855  645  545  438  350  246  182  117   78   77   55   46
  17   18   19   20   21   22   23   24   26   27   28   29   32
  29   14   14    9   11    4    6    1    1    1    1    3    1

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
  1.000   2.000   3.000   4.409   6.000  32.000

includes extended item information - examples:
            labels
1 abrasive cleaner
2 artif. sweetener
3   baby cosmetics

La segunda parte de la tabla anterior indica la cantidad de transacciones que tienen un solo item, luego dos y así sucesivamente.

element (itemset/transaction) length distribution:
sizes
   1    2    3    4    5    6    7    8    9   10   11   12   13   14   15   16
2159 1643 1299 1005  855  645  545  438  350  246  182  117   78   77   55   46

  17   18   19   20   21   22   23   24   26   27   28   29   32
  29   14   14    9   11    4    6    1    1    1    1    3    1

Esta misma tabla indica que hay una transacción en que se compraron 32 items.

[6]:
%%R
##
## Se visualizan los items comprados en las primeras
## cinco transacciones (filas del archivo)
##
inspect(groceries[1:5])
    items
[1] {citrus fruit,
     margarine,
     ready soups,
     semi-finished bread}
[2] {coffee,
     tropical fruit,
     yogurt}
[3] {whole milk}
[4] {cream cheese,
     meat spreads,
     pip fruit,
     yogurt}
[5] {condensed milk,
     long life bakery product,
     other vegetables,
     whole milk}
[7]:
%%R
##
## Se imprime la frequencia con que se compraron los primeros
## tres items (note que esta organizado alfabeticamente)
##
itemFrequency(groceries[, 1:3])
abrasive cleaner artif. sweetener   baby cosmetics
    0.0035587189     0.0032536858     0.0006100661
[8]:
%%R
##
## Se grafica un histograma que muestra la frecuencia
## con que se compraron ciertos items. El parámetro
## `support`  corresponde a la frecuencia mínima que
## deben tener un item para que sea incluído en la gráfica.
## En este caso un item debe aparecer en 0.1 * 9385 = 938.5
## transacciones para ser tenido en cuenta.
##
itemFrequencyPlot(groceries, support = 0.1)
../../../_images/notebooks_sklearn_apriori_02-recomendacion-de-items-frecuentes-en-supermercados_17_0.png
[9]:
%%R
##
## Se obtiene la misma gráfica anterior pero para los
## 20 items más comprados.
##
itemFrequencyPlot(groceries, topN = 20)
../../../_images/notebooks_sklearn_apriori_02-recomendacion-de-items-frecuentes-en-supermercados_18_0.png
[10]:
%%R
##
## Se puede visualizar la matriz de items
## y transacciones para una muestra aleatoria.
## Una linea vertical muestra items que podrían ser
## comprados en cada transacción
##
image(sample(groceries, 100))
../../../_images/notebooks_sklearn_apriori_02-recomendacion-de-items-frecuentes-en-supermercados_19_0.png

Construcción del modelo

[11]:
%%R
##
## La corrida con los parámetros por defecto de la
## función no generan reglas para los datos suministrados.
## El valor por defecto de support es 0.1, es decir,
## un item debe aparecer en un mínimo de 938.5 transacciones
## para ser considerado
##
apriori(groceries)
Apriori

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

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

Absolute minimum support count: 983

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[169 item(s), 9835 transaction(s)] done [0.01s].
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 ... [0 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].
set of 0 rules
[12]:
%%R
##
## El parámetro confianza representa el porcentaje mínimo
## de veces que una regla debe ser correcta para que
## sea considerada; esto permite eliminar reglas sin
## sentido. El parámetro minlen indica que las reglas
## deben contener al menos 2 items.
##
groceryrules <- apriori(groceries,
                        parameter = list(support = 0.006,
                                         confidence = 0.25,
                                         minlen = 2))
Apriori

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

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

Absolute minimum support count: 59

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[169 item(s), 9835 transaction(s)] done [0.01s].
sorting and recoding items ... [109 item(s)] done [0.00s].
creating transaction tree ... done [0.01s].
checking subsets of size 1 2 3 4 done [0.01s].
writing ... [463 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].
[13]:
%%R
##
## Número de reglas generadas
##
groceryrules
set of 463 rules

Evaluación del modelo

[14]:
%%R
##
## Se obtiene la cantidad  de reglas, la cantidad
## de elementos por regla, y el resumen de las
## métricas de calidad
##
summary(groceryrules)
set of 463 rules

rule length distribution (lhs + rhs):sizes
  2   3   4
150 297  16

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
  2.000   2.000   3.000   2.711   3.000   4.000

summary of quality measures:
    support           confidence          lift            count
 Min.   :0.006101   Min.   :0.2500   Min.   :0.9932   Min.   : 60.0
 1st Qu.:0.007117   1st Qu.:0.2971   1st Qu.:1.6229   1st Qu.: 70.0
 Median :0.008744   Median :0.3554   Median :1.9332   Median : 86.0
 Mean   :0.011539   Mean   :0.3786   Mean   :2.0351   Mean   :113.5
 3rd Qu.:0.012303   3rd Qu.:0.4495   3rd Qu.:2.3565   3rd Qu.:121.0
 Max.   :0.074835   Max.   :0.6600   Max.   :3.9565   Max.   :736.0

mining info:
      data ntransactions support confidence
 groceries          9835   0.006       0.25
[15]:
%%R
##
## Se visualizan las primeras 10 reglas.
##
inspect(groceryrules[1:10])
     lhs                      rhs                support     confidence
[1]  {pot plants}          => {whole milk}       0.006914082 0.4000000
[2]  {pasta}               => {whole milk}       0.006100661 0.4054054
[3]  {herbs}               => {root vegetables}  0.007015760 0.4312500
[4]  {herbs}               => {other vegetables} 0.007727504 0.4750000
[5]  {herbs}               => {whole milk}       0.007727504 0.4750000
[6]  {processed cheese}    => {whole milk}       0.007015760 0.4233129
[7]  {semi-finished bread} => {whole milk}       0.007117438 0.4022989
[8]  {beverages}           => {whole milk}       0.006812405 0.2617188
[9]  {detergent}           => {other vegetables} 0.006405694 0.3333333
[10] {detergent}           => {whole milk}       0.008947636 0.4656085
     lift     count
[1]  1.565460 68
[2]  1.586614 60
[3]  3.956477 69
[4]  2.454874 76
[5]  1.858983 76
[6]  1.656698 69
[7]  1.574457 70
[8]  1.024275 67
[9]  1.722719 63
[10] 1.822228 88
[16]:
%%R
##
## Se puede inspeccionar un conjunto de reglas en particular
##
inspect(sort(groceryrules, by = "lift")[1:5])
    lhs                   rhs                      support confidence     lift count
[1] {herbs}            => {root vegetables}    0.007015760  0.4312500 3.956477    69
[2] {berries}          => {whipped/sour cream} 0.009049314  0.2721713 3.796886    89
[3] {other vegetables,
     tropical fruit,
     whole milk}       => {root vegetables}    0.007015760  0.4107143 3.768074    69
[4] {beef,
     other vegetables} => {root vegetables}    0.007930859  0.4020619 3.688692    78
[5] {other vegetables,
     tropical fruit}   => {pip fruit}          0.009456024  0.2634561 3.482649    93
[17]:
%%R
##
## Se puede obtener un subconjunto de las reglas que
## cumplen una condición particular
##
berryrules <- subset(groceryrules, items %in% "berries")
inspect(berryrules)
    lhs          rhs                  support     confidence lift     count
[1] {berries} => {whipped/sour cream} 0.009049314 0.2721713  3.796886  89
[2] {berries} => {yogurt}             0.010574479 0.3180428  2.279848 104
[3] {berries} => {other vegetables}   0.010269446 0.3088685  1.596280 101
[4] {berries} => {whole milk}         0.011794611 0.3547401  1.388328 116
[18]:
%%R
##
## Se pueden almacenar las reglas en el disco
##
write(groceryrules,
      file = "groceryrules.csv",
      sep = ",",
      quote = TRUE,
      row.names = FALSE)
[19]:
!head groceryrules.csv
"rules","support","confidence","lift","count"
"{pot plants} => {whole milk}",0.00691408235892222,0.4,1.56545961002786,68
"{pasta} => {whole milk}",0.00610066090493137,0.405405405405405,1.58661446962283,60
"{herbs} => {root vegetables}",0.00701576004067107,0.43125,3.95647737873134,69
"{herbs} => {other vegetables}",0.00772750381291307,0.475,2.45487388334209,76
"{herbs} => {whole milk}",0.00772750381291307,0.475,1.85898328690808,76
"{processed cheese} => {whole milk}",0.00701576004067107,0.423312883435583,1.65669805355709,69
"{semi-finished bread} => {whole milk}",0.00711743772241993,0.402298850574713,1.57445650433836,70
"{beverages} => {whole milk}",0.00681240467717336,0.26171875,1.02427533077994,67
"{detergent} => {other vegetables}",0.00640569395017794,0.333333333333333,1.72271851462603,63
[20]:
%%R
##
## Se pueden convertir a un data.frame
##
groceryrules_df <- as(groceryrules, "data.frame")
str(groceryrules_df)
'data.frame':   463 obs. of  5 variables:
 $ rules     : Factor w/ 463 levels "{baking powder} => {other vegetables}",..: 340 302 207 206 208 341 402 21 139 140 ...
 $ support   : num  0.00691 0.0061 0.00702 0.00773 0.00773 ...
 $ confidence: num  0.4 0.405 0.431 0.475 0.475 ...
 $ lift      : num  1.57 1.59 3.96 2.45 1.86 ...
 $ count     : num  68 60 69 76 76 69 70 67 63 88 ...

Ejercicio.— ¿Cómo se usan las reglas obtenidas cuándo se sabe que un cliente ha seleccionado ciertos productos?


[21]:
!rm *.csv