starbucksEn 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 ...
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.
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} \]
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 \]
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} \]
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.
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} \]