Accediendo al conjunto de datos

En esta demo usaremos una versión modificada del conjunto de datos starbucks disponible mi repositorio en cuya versión original fue extraida de kaggle. El conjunto de datos contiene las respuesta de una encuesta realizada a poco más de 100 clientes de Starbucks. La moneda usada en este conjunto es el Ringit Malayo (RM). Algunas observaciones fueron modificadas, como la columna name cuyos valores fueron generados aleatoriamente. Las columnas que empiezan con itemPurchase fueron también modificadas pues sus valores eran en gran medida nulos o cero

Las variables disponibles en esta versión modificada se muestran a continuación.

Variable Descripción
name Nombre de cliente (ficticio)
gender Género
age Rango de edad en años
status Estado laboral
income Rango de ingreso anual en miles de RM
visit Frecuencia de vista
method Modo de compra
timeSpent Tiempo promedio consumido en local en minutos
location Distancia al Starbucks más cercano en kilómetros
membershipCard Indica si se cuenta con membresia o no
itemPurchasexxx Indica que la compra més frecuente es xxx
spendPurchase Gasto promedio por visita en RM
xxxRate Calificación (1 Pésimo - 5 Excelente) del servicio xxx
loyal Indica si el consumidor volvería a comprar en Starbucks

Leamos el conjunto de datos. Como nos nombres en el conjunto de datos contienen caracteres especiales como acentos, usamos el argumento ecoding = "UTF-8" para que lea los caracteres especiales correctamente.

url = "https://raw.githubusercontent.com/DenisseUrenda/EduResourses/main/starbucks2.csv"
starbucks = read.csv(url, encoding = "UTF-8")

Veamos si lo hemos leido adecuadamente

dim(starbucks)
## [1] 113  25
head(starbucks, n = 15)
str(starbucks)
## 'data.frame':    113 obs. of  25 variables:
##  $ name                  : chr  "Jehan Polla De Accattatis" "Zandaya Oldach" "Straton Heiman" "Yesmin Stoehr" ...
##  $ gender                : chr  "Female" "Female" "Male" "Female" ...
##  $ age                   : chr  "20-29" "20-29" "20-29" "20-29" ...
##  $ status                : chr  "Student" "Student" "Employed" "Student" ...
##  $ income                : chr  "<25" "<25" "<25" "<25" ...
##  $ visit                 : chr  "Monthly" "Monthly" NA "Monthly" ...
##  $ method                : chr  "Dine In" "Take away" "Dine In" "Take away" ...
##  $ timeSpend             : chr  "30-60" "<30" "30-60" "<30" ...
##  $ location              : chr  "<1" "1-3" ">3" ">3" ...
##  $ membershipCard        : int  1 1 1 0 0 0 1 1 1 0 ...
##  $ itemPurchaseCoffee    : int  0 1 0 1 1 0 0 1 0 0 ...
##  $ itemPurchaseCold      : int  1 1 0 0 1 0 0 1 0 0 ...
##  $ itemPurchasePastries  : int  0 0 1 1 1 0 0 0 0 0 ...
##  $ itemPurchaseJuices    : int  1 0 0 0 0 0 0 1 0 0 ...
##  $ itemPurchaseSandwiches: int  0 0 0 1 1 1 0 0 1 0 ...
##  $ itemPurchaseOthers    : int  1 0 1 0 1 0 1 0 0 1 ...
##  $ spendPurchase         : chr  "0-20" "0-20" "0-20" "0-20" ...
##  $ productRate           : int  4 4 4 2 3 4 5 4 5 4 ...
##  $ priceRate             : int  3 3 3 1 3 3 5 2 4 3 ...
##  $ promoRate             : int  5 4 4 4 4 5 5 3 4 3 ...
##  $ ambianceRate          : int  5 4 4 3 2 5 5 3 4 4 ...
##  $ wifiRate              : int  4 4 4 3 2 4 3 3 4 3 ...
##  $ serviceRate           : int  4 5 4 3 3 5 5 3 4 3 ...
##  $ chooseRate            : int  3 2 3 3 3 4 5 3 4 4 ...
##  $ loyal                 : int  1 1 1 0 1 1 1 1 1 1 ...

Explorando el conjunto de datos

Analicemos más cosas del conjunto de datos. Por ejemplo, el número de categorías que cada variable cualitativa tiene

sapply(starbucks, function(x) length(unique(x)))
##                   name                 gender                    age 
##                    113                      2                      4 
##                 status                 income                  visit 
##                      4                      5                      4 
##                 method              timeSpend               location 
##                      4                      5                      3 
##         membershipCard     itemPurchaseCoffee       itemPurchaseCold 
##                      2                      2                      2 
##   itemPurchasePastries     itemPurchaseJuices itemPurchaseSandwiches 
##                      2                      2                      2 
##     itemPurchaseOthers          spendPurchase            productRate 
##                      2                      3                      5 
##              priceRate              promoRate           ambianceRate 
##                      5                      5                      5 
##               wifiRate            serviceRate             chooseRate 
##                      5                      4                      5 
##                  loyal 
##                      2

De hecho, a pesar de que las variables age, income, timeSpend, location y spendPurchase son numéricas, en este conjunto éstas fueron convertidas en categóricas.

sapply(starbucks[ ,c("age","income","timeSpend","location","spendPurchase")], unique)
## $age
## [1] "20-29" "30-39" ">39"   "<20"  
## 
## $income
## [1] "<25"     "50-100"  "25-50"   "100-150" ">150"   
## 
## $timeSpend
## [1] "30-60"   "<30"     ">180"    "60-120"  "120-180"
## 
## $location
## [1] "<1"  "1-3" ">3" 
## 
## $spendPurchase
## [1] "0-20"  "20-40" ">40"

También podemos intuir que las variables con solo dos valores distintos son dicotómicas o binarias, como es el caso de gender, membershipCard, itemPurchasexxx y loyal.

Calculando probabilidades

La función nchar()

Después de una exploración general del conjunto de datos, es hora que nos centremos en una variable en particular. Empecemos con name.

Recordemos que name contiene los nombres (ficticios) de los clientes encuestados. ¿Qué será más probable, tener un cliente cuyo nombre completo tenga ocho caracteres o tenga 28 caracteres? Veamos …

table(nchar(starbucks$name))
## 
##  8 10 11 12 13 14 15 16 17 18 19 20 21 24 25 28 
##  1  2  4  8 23 15 15 10  7  8  4  8  4  2  1  1

La línea anterior creó una tabla de frecuencias (table) para la longitud (nchar) de los nombres de los clientes de Starbucks encuenstados (sartbucks$name).

Recuerda que, en el método empírico, la probabilidad se calcula como la frecuencia relativa, entonces, la probabilidad de que un cliente cuyo nombre completo tenga solo ocho caracteres es la misma a la que tenga 28 caracteres (1/113), pues según la tabla de frecuencias anterior solo uno tiene un nombre completo con ocho caracteres al igual que solo uno tiene un nombre con 28 caracteres. Veamos quienes son estos clientes:

which(nchar(starbucks$name) == 8)
## [1] 82
which(nchar(starbucks$name) == 28)
## [1] 78

Las líneas anteriores nos dicen qué (which) observación tiene una longitud de nombre (nchar(starbucks$name)) de 8 y 28, respectivamente. Veamos sus respuestas

starbucks[c(82,78), ]

Ok, podemos ver que ambos clientes son hombres (gender) de entre 20 y 29 años (age) con empleos propios (status) cuyas visitas suelen ser mensuales (visit), el Starbucks más cercano a ambos se encuentra a más de 3 kilómetros (location), ambos tienen membresia en Starbucks (membershipCard), suelen comprar café y otras cosas (itemPurchaseCoffe, itemPurchaseOthers) y recomendarían Starbucks a otros (loyal).

Entre sus diferencias se encuentra que Dj tiene un ingreso anual mayor que Dimitrius (income), éste suele pasar menor tiempo en el local (timeSpend) pero gasta más en promedio por visita (spendPurchase).

Estas últimas instrucciones pudimos haberlas hecho en una sola línea

starbucks[nchar(starbucks$name) %in% c(8,28), ]

Hagamos ahora un diagrama de barras con frecuencias absolutas.

t = table(nchar(starbucks$name))
par(mar = c(4,1,1,1))
colors = rep("gray",16)
colors[5] = "orange"
b = barplot(t, ylim = c(0,25), col = colors, yaxt = "n", 
            xlab = "Longitud de nombre", border = F)
text(x = b, y = t + 0.7, labels = t, cex = 0.8, col = colors)

Ahora podemos calcular la probabilidad de cualquiera de estos “eventos” como, la probabilidad de que una persona en este Starbucks tenga un nombre completo de (a) 10 caracteres, (b) 18 caracteres, (c) entre 13 y 16 caracteres o (d) al menos 20 caracteres.

\[ \begin{aligned} \Pr(\text{10 caracteres}) &= \frac{2}{113} = 0.018 \\ \Pr(\text{18 caracteres}) &= \frac{8}{113} = 0.071 \\ \Pr(\text{entre 13 y 16 caracteres}) &= \frac{23+15+15+10}{113} = 0.558 \\ \Pr(\text{al menos 20 caracteres}) &= \frac{8+4+2+1+1}{113} = 0.142 \end{aligned} \]

La función union() y la función intercept()

Analicemos otra variable: itemPurchase. Nota que esta variable no se encuentra en el conjunto de datos como una sola columna, de hecho ocupa seis columnas. Para esta variable, el cliente tiene seis opciones a elegir: café, batidos, postres, jugos, sandwiches y otros. Estas categorías no son mutuamente excluyentes. Un cliente puede perfectamente comprar en la misma visita un café y un jugo, o un sandwhich y un batido.

Primero, veamos quiénes compran con mayor frecuencia café, quienes batidos, quiénes postes, quiénes sandwiches y quiénes otros en Starbucks.

starbucks[starbucks$itemPurchaseCoffee == 1, ]
starbucks[starbucks$itemPurchaseCold == 1, ]
starbucks[starbucks$itemPurchasePastries == 1, ]
starbucks[starbucks$itemPurchaseJuices == 1, ]
starbucks[starbucks$itemPurchaseSandwiches == 1, ]
starbucks[starbucks$itemPurchaseOthers == 1, ]

Con la instrucción starbucks[starbucks$itemPurchaseCoffee == 1, ] le estamos pidiendo a R las observaciones (respuesta de los clientes) que compran café con mayor frecuencia. Recuenda que itemPurchaseCoffee es una variable binaria que indica si el cliente a seleccionado café como compra de mayor frecuencia (1) o no (0).

Supongamos entonces que tenemos los eventos coffee, cold, pastries, juices, sandwiches y others, que corresponden a los clientes que han seleccionado estos productos como su compra mas frecuente.

coffee = which(starbucks$itemPurchaseCoffee == 1)
cold = which(starbucks$itemPurchaseCold == 1)
pastries = which(starbucks$itemPurchasePastries == 1)
juices = which(starbucks$itemPurchaseJuices == 1)
sandwiches = which(starbucks$itemPurchaseSandwiches == 1)
others = which(starbucks$itemPurchaseOthers == 1)

Veamos que almacena coffee, por ejemplo

coffee
##  [1]   2   4   5   8  11  13  16  20  21  22  23  24  25  26  31  32  33  34  37
## [20]  50  53  58  59  61  65  67  68  69  71  72  73  78  82  84  87  88  89  92
## [39]  94  97 104 106 107 108 111

coffee, asi como las otras cinco variables, almacena las posiciones de los renglones cuya compra frecuente es cafe. Por otro lado, length(coffee) es el numero de clientes cuya compra mas frecuente es cafe

length(coffee)
## [1] 45

45 de los 113 clientes indicaron como compra frecuente el cafe.

Con esto en mente podemos contestar las siguientes preguntas. Cual es la probabilidad de que un cliente de Starbucks compre (a) cafe, (b) cafe o batido, (c) jugo y sandwich?

La primera practicamente ya la contestamos \[ \Pr(\text{cafe}) = \frac{45}{113} = 0.398 \] Para contestar (b) no podemos simplemente sumar la probabilidad de pedir cafe y la probabilidad de pedir batido porque estos dos eventos no son mutuamente excluyentes pues un cliente puede pedir ambos. Una forma de calcular esta probabilidad es sabiendo que clientes piden cafe o batido, y con R podemos saberlo usando la funcion union()

union(coffee, cold)
##  [1]   2   4   5   8  11  13  16  20  21  22  23  24  25  26  31  32  33  34  37
## [20]  50  53  58  59  61  65  67  68  69  71  72  73  78  82  84  87  88  89  92
## [39]  94  97 104 106 107 108 111   1  17  18  19  38  54  60  62  63  66  70  76
## [58]  77  80  81  85  90  93 101 103 109 110

o, mas bien, cuantos clientes

length(union(coffee, cold))
## [1] 67

Por lo tanto, la probabilidad de que un cliente pida cafe o batido es \[ \Pr(\text{cafe o batido}) = \frac{67}{113} = 0.593 \]

Por ultimo, para contestar (c) podemos hacer uso de la funcion intersect()

intersect(juices, sandwiches)
##  [1]  16  18  38  47  61  68  86  91  97 111

y de la funcion length()

length(intersect(juices, sandwiches))
## [1] 10

La probabilidad de que un cliente pida jugo y sandwich es \[ \Pr(\text{jugo y sandwich}) = \frac{10}{113} = 0.088 \]

La funcion venn.diagram()

Una forma más simple de calcular probabilidades de todo tipo es construyendo diagramas de Venn. En R podemos construirlos usando la funcion venn.diagram() disponible en el paquete VennDiagram

#install.packages("VennDiagram")
library(VennDiagram)
venn = venn.diagram(
    x = list(coffee, cold, pastries, juices, sandwiches),
    category.names = c("Café","Batidos","Postres","Jugos","Sandwiches"),
    filename = NULL,
    fill = hcl.colors(5)
    )
grid.draw(venn)

A la función venn.diagram() le hemos pasado una lista de vectores que indican las observaciones que compran frecuentemente cada producto. Esta función calcula internamente las intersecciones y muestra el número de observaciones que coinciden con cada intersección.

Por alguna razón, esta función no permite crear más de cinco intersecciones, por lo que solo hemos dibujado el diagrama de venn con cinco de los eventos.

Si queremos incluir los seis, deberiamos unir others con algún otro evento, por ejemplo, con sandwiches

venn = venn.diagram(
    x = list(coffee, cold, pastries, juices, union(sandwiches, others)),
    category.names = c("Café","Batidos","Postres","Jugos","Otros"),
    main = "Productos comprados con mayor frecuencia",
    main.cex = 1.5, main.fontface = 2,
    filename = NULL,
    fill = 2:6,
    col = "white",
    cat.pos = c(0,0,180,180,0)
    )
grid.draw(venn)

Con esto podemos calcular probabilidades de cualquier tipo. Por ejemplo, ¿cuál es la probabilidad de que algún cliente (a) solo pida café, (b) postre y otro o (c) café, batido, postre, jugo y otro?

\[ \begin{aligned} \Pr(\text{solo cafe}) = \frac{3}{113} = 0.027 \\ \Pr(\text{postre y otro}) = \frac{11}{113} = 0.097 \\ \Pr(\text{cafe, batido, postre, jugo y otro}) = \frac{4}{113} = 0.035 \end{aligned} \]

La función draw.pairwise.venn()

Si lo que quieres es dibujar un diagrama de Venn con dos conjuntos conociendo sus intersecciones puedes usar la función draw.pairwise.venn()

venn2 = draw.pairwise.venn(
  area1 = 50, area2 = 35, cross.area = 15,
  fill = c(2,3), category = c("A","B")
  )
grid.draw(venn2)

Similarmente, puedes usar al función draw.triple.venn() para dibujar un diagrama de Venn con tres conjuntos

venn3 = draw.triple.venn(
  area1 = 50, area2 = 35, area3 = 45,
  n12 = 15, n13 = 10, n23 = 10, n123 = 5,
  fill = c(2,3,4), category = c("A","B","C")
)
grid.draw(venn3)

Hay otras opciones similares en el paquete VennDiagram para cuatro y cinco conjuntos pero recuerda que para estos debes conocer las intersecciones entre los conjuntos.

La función table()

En los ejemplo anteriores hablamos de eventos provenientes de una sola variable (name o itemPurshase). Cuando se quiere analizar la relación entre dos variables solemos usar las tablas de contingencia. Una tabla de contingencia es una tabla de frecuencias que incluye las frecuencias de las intersecciones. Para hacer una tabla de este tipo, podemos usar la función table() de la siguiente manera

table(starbucks$status, starbucks$timeSpend)
##                
##                 <30 >180 120-180 30-60 60-120
##   Employed       32    2       0    19      5
##   Housewife       2    0       0     0      0
##   Self-Employed   8    0       1     4      3
##   Student        22    0       0    11      4

Nota que las categorías la variable timeSpend no están ordenadas. Eso es porque R las ordena de forma alfabética. Si queremos un orden en específico, debemos convertirlo en factor ordenado

starbucks$timeSpend = factor(starbucks$timeSpend,
                             levels = c("<30","30-60","60-120","120-180",">180"),
                             ordered = TRUE)
table(starbucks$status, starbucks$timeSpend)
##                
##                 <30 30-60 60-120 120-180 >180
##   Employed       32    19      5       0    2
##   Housewife       2     0      0       0    0
##   Self-Employed   8     4      3       1    0
##   Student        22    11      4       0    0

Ahora sí. Si queremos los totales, debemos hacerlo de forma “manual”

t = table(starbucks$status, starbucks$timeSpend)
cbind(t, rowSums(t))
##               <30 30-60 60-120 120-180 >180   
## Employed       32    19      5       0    2 58
## Housewife       2     0      0       0    0  2
## Self-Employed   8     4      3       1    0 16
## Student        22    11      4       0    0 37
rbind(t, colSums(t))
##               <30 30-60 60-120 120-180 >180
## Employed       32    19      5       0    2
## Housewife       2     0      0       0    0
## Self-Employed   8     4      3       1    0
## Student        22    11      4       0    0
##                64    34     12       1    2

En la chunk anterior, t amancena la tabla de contingencia sin los totales. En la siguiente línea rowSums(t) calcula la suma de renglones, cbind() pega por columna t y el resultado obtenido en rowSum. De manera similar, rbind(t, colSums(t)) pega por renglón a t y al resultado de colSums(t) que es la suma por columas.

Podemos hacer todo junto

t = table(starbucks$status, starbucks$timeSpend)
t = cbind(t, rowSums(t)) # Pegando por columna la suma de renglones
t = rbind(t, colSums(t)) # Pegando por renglon la suma de columnas
t
##               <30 30-60 60-120 120-180 >180    
## Employed       32    19      5       0    2  58
## Housewife       2     0      0       0    0   2
## Self-Employed   8     4      3       1    0  16
## Student        22    11      4       0    0  37
##                64    34     12       1    2 113

Ahora podemos calcular cualquier probabilidad que deseemos. Por ejemplo, la probabilidad de que un cliente elegido al azar (a) sea estudiante, (b) pase entre 60 y 120 minutos en el local, (c) tenga empleo y pase menos de 30 minutos en el local o (d) pase entre 30 y 60 minutos dado que tiene empleo.

\[ \begin{aligned} \Pr(\text{estudiante}) &= \frac{37}{113} = 0.328 \\ \Pr(\text{pasar de 60 a 120 minutos}) &= \frac{12}{113} = 0.106\\ \Pr(\text{empleado y pasar menos de 30 minutos}) &= \frac{32}{113} = 0.283 \\ \Pr(\text{pasar entre 30 y 60 minutos dado que es empleado}) &= \frac{32}{58} = 0.552 \end{aligned} \]