Analiza Danych CAFÉ PG

2024-01-23

Wprowadzenie

Cafe PG to restauracja przy plaży. Menagerowie próbują ustalić jakie kategorie i produkty przynoszą im największy dochód oraz w jakich okresach czasu sprzedaż osiąga najwyższe wartości.

Wprowadzenie danych do R

cafepgsciezka_do_pliku <- "/Users/Agata/Desktop/uczelnia/nazwa projeku/cafe_pg.xlsx"
Sheet1 <- "Sheet1"
cafe_pg <- read_excel(cafepgsciezka_do_pliku, sheet = Sheet1)
cafe_pg<-data.frame(cafe_pg)
data(cafe_pg)
## Warning in data(cafe_pg): data set 'cafe_pg' not found
head(cafe_pg[1:10],3)
##         Date Bill.Number                Item.Desc                Time Quantity
## 1 2020-01-01    G0470115    MINERAL WATER(1000ML) 1899-12-31 13:15:11        1
## 2 2020-01-01    G0470115 MONSOON MALABAR (AULAIT) 1899-12-31 13:15:11        1
## 3 2020-01-01    G0470116      MASALA CHAI CUTTING 1899-12-31 13:17:35        1
##   Rate   Tax Discount  Total Category
## 1   50 11.88        0  61.88 BEVERAGE
## 2  100 23.75        0 123.75 BEVERAGE
## 3   40  9.50        0  49.50 BEVERAGE
str(cafe_pg)
## 'data.frame':    145830 obs. of  10 variables:
##  $ Date       : POSIXct, format: "2020-01-01" "2020-01-01" ...
##  $ Bill.Number: chr  "G0470115" "G0470115" "G0470116" "G0470117" ...
##  $ Item.Desc  : chr  "MINERAL WATER(1000ML)" "MONSOON MALABAR (AULAIT)" "MASALA CHAI CUTTING" "MINERAL WATER(1000ML)" ...
##  $ Time       : POSIXct, format: "1899-12-31 13:15:11" "1899-12-31 13:15:11" ...
##  $ Quantity   : num  1 1 1 1 1 1 1 1 1 1 ...
##  $ Rate       : num  50 100 40 50 45 50 40 45 60 45 ...
##  $ Tax        : num  11.9 23.8 9.5 11.9 10.7 ...
##  $ Discount   : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ Total      : num  61.9 123.8 49.5 61.9 55.7 ...
##  $ Category   : chr  "BEVERAGE" "BEVERAGE" "BEVERAGE" "BEVERAGE" ...

Czyszczenie danych

Dodawanie kolumny z datą i godziną w odpowiednim formacie oraz kolumny z dniem tygodnia.

cafe_pg$Time <- strptime(cafe_pg$Time, format="%Y-%m-%d %H:%M:%S")
cafe_pg$Time <- format(cafe_pg$Time, format="%H:%M:%S")
pełna_data <- as.POSIXct(paste(cafe_pg$Date, cafe_pg$Time), format="%Y-%m-%d %H:%M:%S")
cafe_pg$pełna_data = pełna_data
cafe_pg$DzienTygodnia <- weekdays(cafe_pg$pełna_data) 

Dodawanie kolumn pomocniczych ID i Total Netto

cafe_pg <- cafe_pg %>%
  mutate(ID = 1:n())
cafe_pg <- cafe_pg %>%
  mutate(Total_netto = Quantity * Rate)

Walidacja danych

Tworzenie zasad walidacyjnych

rules <- validator(
  Total_netto+Tax-Discount == Total
  , Quantity >= 1
  , Rate >= 0
  , Tax >= 0
  , Discount >= 0
  , Total_netto >=0
  , Total >=0
)

cf <- confront(cafe_pg, rules, key="ID")
summary(cf)
##   name  items passes fails nNA error warning
## 1   V1 145830 145557   273   0 FALSE   FALSE
## 2   V2 145830 145830     0   0 FALSE   FALSE
## 3   V3 145830 145830     0   0 FALSE   FALSE
## 4   V4 145830 145830     0   0 FALSE   FALSE
## 5   V5 145830 145830     0   0 FALSE   FALSE
## 6   V6 145830 145830     0   0 FALSE   FALSE
## 7   V7 145830 145830     0   0 FALSE   FALSE
##                                           expression
## 1 abs(Total_netto + Tax - Discount - Total) <= 1e-08
## 2                             Quantity - 1 >= -1e-08
## 3                                 Rate - 0 >= -1e-08
## 4                                  Tax - 0 >= -1e-08
## 5                             Discount - 0 >= -1e-08
## 6                          Total_netto - 0 >= -1e-08
## 7                                Total - 0 >= -1e-08
barplot(cf, main="cafe_pg")
## Warning: The 'barplot' method for confrontation objects is deprecated. Use
## 'plot' instead

as.data.frame(cf) %>% head()
##   ID name value                                         expression
## 1  1   V1  TRUE abs(Total_netto + Tax - Discount - Total) <= 1e-08
## 2  2   V1  TRUE abs(Total_netto + Tax - Discount - Total) <= 1e-08
## 3  3   V1  TRUE abs(Total_netto + Tax - Discount - Total) <= 1e-08
## 4  4   V1  TRUE abs(Total_netto + Tax - Discount - Total) <= 1e-08
## 5  5   V1  TRUE abs(Total_netto + Tax - Discount - Total) <= 1e-08
## 6  6   V1  TRUE abs(Total_netto + Tax - Discount - Total) <= 1e-08

Lokalizacja błędów

error_locations <- locate_errors(cafe_pg, rules)
summary(error_locations)
## Variable:
##             name errors missing
## 14   Total_netto     75       0
## 8       Discount     74       0
## 9          Total     67       0
## 7            Tax     57       0
## 1           Date      0       0
## 2    Bill.Number      0       0
## 3      Item.Desc      0       0
## 4           Time      0       0
## 5       Quantity      0       0
## 6           Rate      0       0
## 10      Category      0       0
## 11    pełna_data      0       0
## 12 DzienTygodnia      0       0
## 13            ID      0       0
## Errors per record:
##   errors records
## 1      0  145557
## 2      1     273

Zmiana błędów na NA

fixable_data <- replace_errors(cafe_pg, rules)
NA1 <- sum(is.na(cafe_pg))
NA2 <- sum(is.na(fixable_data)) 
NA_ <- data.frame(NA1 , NA2)
kable(NA_,format = "html", caption = "Braki danych" ) %>%
  kable_styling("striped", "hover", "condensed", full_width = F, position = "left")
Braki danych
NA1 NA2
0 273

Dedukcyjne czyszczenie danych

lr_imputed <- impute_lr(fixable_data, rules)
cells(start=cafe_pg, fixable=fixable_data, impute_lr=lr_imputed
      , compare='sequential')
## Object of class cellComparison:
## 
##    cells(start = cafe_pg, fixable = fixable_data, impute_lr = lr_imputed, compare = "sequential")
## 
##                   start fixable impute_lr
## cells           2041620 2041620   2041620
## available       2041620 2041347   2041620
## still_available 2041620 2041347   2041347
## unadapted       2041620 2041347   2041347
## adapted               0       0         0
## imputed               0       0       273
## missing               0     273         0
## still_missing         0       0         0
## removed               0     273         0

Konfrontacja danych z zasadami po imputacji

cf_after_imputation <- confront(lr_imputed, rules, key="ID")
summary(cf_after_imputation)
##   name  items passes fails nNA error warning
## 1   V1 145830 145830     0   0 FALSE   FALSE
## 2   V2 145830 145830     0   0 FALSE   FALSE
## 3   V3 145830 145830     0   0 FALSE   FALSE
## 4   V4 145830 145830     0   0 FALSE   FALSE
## 5   V5 145830 145830     0   0 FALSE   FALSE
## 6   V6 145830 145830     0   0 FALSE   FALSE
## 7   V7 145830 145830     0   0 FALSE   FALSE
##                                           expression
## 1 abs(Total_netto + Tax - Discount - Total) <= 1e-08
## 2                             Quantity - 1 >= -1e-08
## 3                                 Rate - 0 >= -1e-08
## 4                                  Tax - 0 >= -1e-08
## 5                             Discount - 0 >= -1e-08
## 6                          Total_netto - 0 >= -1e-08
## 7                                Total - 0 >= -1e-08
barplot(cf_after_imputation, main="cafe_pg after imputation")
## Warning: The 'barplot' method for confrontation objects is deprecated. Use
## 'plot' instead

Brakujące obserwacje

sum(is.na(lr_imputed)) 
## [1] 0

Obserwacje odstające

boxplot(lr_imputed$Total, main="Rozkład Wartości Całkowitych Rachunków")

plot(lr_imputed$Total)

Dodawanie kolumny Party dla, która zwraca “yes” dla zamówień gdzie quantity jest większe niż 5

lr_imputed <- lr_imputed %>%
  mutate(party = ifelse(Quantity >= 5, "yes", "no"))

boxplot(lr_imputed$Total ~ lr_imputed$party, main="Rozkład Wartości Całkowitych Rachunków w Zależności od 'party'", xlab="party", ylab="Total")

Obserwacja odstająca ID:94769 usuwamy w celu “naprawienia” danych - wg opisu wartość powinna być naliczona jako opłata za imprezę w wysokości 1x 500zł, a została naliczona 23x

lr_imputed[94769, "Quantity"] <- 1

500*23
## [1] 11500
2731.25/11500 #podatek wynosi 23,75%
## [1] 0.2375
500*0.2375 
## [1] 118.75
lr_imputed[94769, "Tax"] <- 118.75

cafe_pg_clear <- impute_lr(lr_imputed, rules)

cafe_pg_clear[94769, "Total_netto"] <- 500

cafe_pg_clear[94769, "Total"] <- 618.75

cafe_pg_clear <- cafe_pg_clear %>%
  mutate(party = ifelse(Quantity >= 5, "yes", "no"))

boxplot(cafe_pg_clear$Total ~ cafe_pg_clear$party, main="Rozkład Wartości Całkowitych Rachunków w Zależności od 'party'", xlab="party", ylab="Total")

Aby ustalić podatek dla nowej wartości, wyliczamy podatek z pierwotnych danych. Podatek od 500 zł wynosi 118,75 zł. Podmiana total netto i total dla błędnej obserwacji ID:94769. Widzimy, że nawet w kategorii “party” wartości powyżej 2000 są wartościami odstającymi. Sprawdzamy więc jaki stanowią one udział w całkowitym przychodzie baru.

sum(cafe_pg_clear$Total>=2000)/sum(cafe_pg_clear$Total)*100
## [1] 0.0001463965

Przychody z takich paragonów stanowią marginalny ułamek całkowitych przychodów, a przekłamują i utrudniają analizy. W związku z tym do dalszych analiz będziemy się posługiwać zbiorem z paragonami poniżej 2000$.

cafe_do_2000 <- subset(cafe_pg_clear,Total<=2000)

Wizualizacja danych

Najczęściej wybierana kategoria i pozycja

kategorie <- table(cafe_do_2000$Category)
kategoria <- names(kategorie[which.max(kategorie)])

pozycje <- table(cafe_do_2000$Item.Desc)
pozycja <- names(pozycje[which.max(pozycje)])

popular <- data.frame(kategoria, pozycja)

kable(popular,format = "html", caption = "Najpopularniejsze" ) %>%
  kable_styling("striped", "hover", "condensed", full_width = F, position = "left")
Najpopularniejsze
kategoria pozycja
FOOD NIRVANA HOOKAH SINGLE

Klienci najczęściej kupują w barze Cafe PG jedzenie. Najpopularniejszą pozycją w menu są papierosy “Nirvana Hookah Single”.

Najczęściej wybierane pozycje w danej kategorii

napoje <- subset(cafe_do_2000, Category == "BEVERAGE")
napoje1 <- table(napoje$Item.Desc )
napój <- names(napoje1[which.max(napoje1)])

jedzenie <- subset(cafe_do_2000, Category == "FOOD")
jedzenie1 <- table(jedzenie$Item.Desc)
posiłek <- names(jedzenie1[which.max(jedzenie1)])

alkohol <- subset(cafe_do_2000, Category == "LIQUOR")
alkohol1 <- table(alkohol$Item.Desc)
alkohol <- names(alkohol1[which.max(alkohol1)])

alktyton <- subset(cafe_do_2000, Category == "LIQUOR & TPBACCO")
alktyton1 <- table(alktyton$Item.Desc)
alkohol_tyton <- names(alktyton1[which.max(alktyton1)])

produkty <- subset(cafe_do_2000, Category == "MERCHANDISE")
produkty1 <- table(produkty$Item.Desc)
detaliczne <- names(produkty1[which.max(produkty1)])

inne <- subset(cafe_do_2000, Category == "MISC")
inne1 <- table(inne$Item.Desc)
inny <- names(inne1[which.max(inne1)])

tyton <- subset(cafe_do_2000, Category == "TOBACCO")
tyton1 <- table(tyton$Item.Desc)
papierosy <- names(tyton1[which.max(tyton1)])

wina <- subset(cafe_do_2000, Category == "WINES")
wina1 <- table(wina$Item.Desc)
wino <- names(wina1[which.max(wina1)]) 

najlepsz_pozycje <- data.frame(napój= napój, posiłek= posiłek, alkohol= alkohol, alkohol_tyton= alkohol_tyton, detaliczne= detaliczne, inny= inny, papierosy= papierosy, wino= wino)

kable(najlepsz_pozycje,format = "html", caption = "Najczęściej wybierana pozycja" ) %>%
  kable_styling("striped", "hover", "condensed", full_width = F, position = "left")
Najczęściej wybierana pozycja
napój posiłek alkohol alkohol_tyton detaliczne inny papierosy wino
CAPPUCCINO OCEAN SPECIAL SHAKE CARLSBERG BEER HOOKAH OCEAN SPECIAL T-SHIRTS ADD ON S NIRVANA HOOKAH SINGLE VLN CAB SAUV (GLS)

Przedstawienie najczesciej wybieranych produktów w poszczególnych kategoriach

top_items <- cafe_do_2000 %>%
  group_by(Category, Item.Desc) %>%
  summarize(Count = n(), .groups = 'drop') %>%
  arrange(Category, desc(Count)) %>%
  group_by(Category, .drop = TRUE) %>%
  slice_max(Count, n = 1) %>%
  ungroup()

ggplot(top_items, aes(x = Category, y = Count, fill = Item.Desc)) +
  geom_bar(stat = "identity", position = "dodge") +
  geom_text(aes(label = Count), position = position_dodge(width = 0.9), vjust = -0.5, color = "black", size = 3) +
  labs(title = "Najczęściej wybierane produkty w podziale na kategorie",
       x = "Kategoria",
       y = "Liczba wyborów",
       fill = "Produkt") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Powyższy wykres przedstawia w wizualny sposób informacje z poprzedniej tabeli z najczęściej wybieranymi produktami w podziale na kategorie.

Przedstawienie najczesciej wybieranych produktów ogółem

top_products <- cafe_do_2000 %>%
  group_by(Item.Desc) %>%
  summarize(OrderCount = n()) %>%
  top_n(10, OrderCount)

ggplot(top_products, aes(x = reorder(Item.Desc, -OrderCount), y = OrderCount)) +
  geom_bar(stat = "identity", fill = "orange") +
  geom_text(aes(label = OrderCount), vjust = -0.5, color = "black", size = 3) +
  labs(title = "Najpopularniejsze produkty",
       x = "Produkt",
       y = "Liczba zamówień") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Na poprzednim wykresie ukazano najbardziej popularne produkty dla każdej kategorii. Na powyższym wykresie zauważyć można jakie produkty były wybierane najczęściej spośród wszystkich - bez względu na kategorie. Zdecydowanym zwycięzcą jest “Nirvana hookah single” wybierany 8553 razy, na drugim miejscu znalazł się produkt “Mint flavour single” wybrany 5817 razy. 2 pierwsze miejsca na podium należą do kategorii Tobacco, dopiero na trzecim miejscu znajduje się Cappucino, które było zamawiane prawie 5500 razy.

Przedstawienie najdroższych produktów w każdej kategorii

top_expensive_items <- cafe_do_2000 %>%
  group_by(Category, Item.Desc) %>%
  dplyr::summarize(Max_Rate = max(Rate), .groups = 'drop') %>%
  arrange(Category, desc(Max_Rate)) %>%
  group_by(Category, .drop = TRUE) %>%
  slice_max(Max_Rate, n = 1) %>%
  ungroup()

ggplot(top_expensive_items, aes(x = reorder(Category, -Max_Rate), y = Max_Rate, fill = reorder(Item.Desc, -Max_Rate))) +
  geom_bar(stat = "identity", position = "dodge") +
  geom_text(aes(label = paste0(Max_Rate, " zł")), vjust = -0.5, color = "black", size = 3) +
  labs(title = "Najdroższe produkty w podziale na kategorie",
       x = "Kategoria",
       y = "Maksymalna cena jednostkowa",
       fill = "Produkt") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Ten wykres przedstawia najdroższe produkty w każdej kategorii, gdzie dla każdej kategorii wybierany jest produkt o najwyższej maksymalnej cenie jednostkowej. Jak można zauważyć, najdroższa pozycja znajduje się w kategorii Merchandise i jest to “Flabor 1000 GMS” o cenie 1470, następnie wśród Alkoholi i kategorii inne są pozycje o wartości 1300zł (kolejno “Stella 1ltr 2+1” i “Hoegaarden ltr mugs (2+1)”). Na trzecim miejscu w najdroższych pozycjach znajduje się wino “Sula Brut” o wartości 1200zł.

Analiza popularności kategorii w zależności od liczby osób przy zamówieniu

party_data <- cafe_do_2000 %>%
  filter(party %in% c("yes", "no"))
party_data$party_label <- ifelse(party_data$party == "yes", "Grupa imprezowa", "Grupa nie-imprezowa")

ggplot(party_data, aes(x = Category, y = after_stat(count))) +
  geom_bar(fill = "blue", stat = "count") +
  geom_text(aes(label = after_stat(count)), stat = "count", vjust = -0.3, color = "black", size = 3) +
  labs(title = "Najczęściej wybierane kategorie w zależności od ilości osób w grupie",
       x = "Kategoria",
       y = "Liczba wyborów") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  facet_wrap(~party_label, scales = "free_y", ncol = 1) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.2))) 

Na powyższych wykresach widoczne jest porównanie najczęściej wybieranych kategorii, w zależności od ilości osób, które przyjmuje się, że uczestniczyły w zamówieniu. Przy 5 osobach i więcej zakłada się, że była to Grupa imprezowa, natomiast przy 4 osobach i mniej - Grupa nie-imprezowa. Widoczne są duże różnice w wyborach zamówieniowych. Wśród grup imprezowych zamawiane były przede wszystkim napoje alkoholowe, na drugim miejscu napoje bezalkoholowe, a na miejscu trzecim jedzenie. Jeśli chodzi o grupy nie-imprezowe, najczęściej zamawiano jedzenie, napoje bezalkoholowe i na trzecim miejscu wyroby tytoniowe. Napoje alkoholowe były na miejscu trzecim.

Na podstawie tego zestawienia, możliwą sugestią jest wprowadzenie promocji na napoje alkoholowe, lub godzin “Happy Hour” - promocje typu 3 za 2, 4+1 gratis, mogłoby przyczynić się do wzrostu przychodów z napojów alkoholowych. Kolejną propozycją dla tego obiektu, byłoby wprowadzenie promocji na jedzenie przy zakupie alkoholi - np. Frytki za pół ceny przy wydaniu 100 zł na barze - co mogłoby poprawić sprzedaż jedzenia przy grupach imprezowych.

Wykres prezentujący liczbę zamówień w posczególnych dniach tygodnia według kategorii produktów

orders_by_day <- cafe_do_2000 %>%
  group_by(DzienTygodnia, Category) %>%
  summarize(OrderCount = n()) %>%
  mutate(DzienTygodnia = factor(DzienTygodnia, levels = c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")))
## `summarise()` has grouped output by 'DzienTygodnia'. You can override using the
## `.groups` argument.
ggplot(orders_by_day, aes(x = DzienTygodnia, y = OrderCount, fill = Category)) +
  geom_bar(stat = "identity", position = "dodge") +
  labs(title = "Liczba zamówień w danym dniu tygodnia",
       x = "Dzień tygodnia",
       y = "Liczba zamówień",
       fill = "Kategoria") +
  theme_minimal()

Wykres słupkowy ilustruje liczbę zamówień w Cafe PG dla różnych kategorii produktów w zależności od dnia tygodnia, od poniedziałku do niedzieli. Wykres pokazuje, że niektóre dni tygodnia charakteryzują się większą liczbą zamówień niż inne, z czwartkiem i piątkiem jako najbardziej ruchliwymi dniami, co sugeruje wzmożoną aktywność klientów na początku weekendów. Porównując popularność poszczególnych kategorii można zauważyć proporcjonarne różnice w liczbie zamówień na przestrzeni tygodnia.

Wykres przedstawiający gęstość rozkładu wysokości rachunków pod względem kategorii kupowanych pozycji

ggplot(cafe_do_2000, aes(x= Total, fill=Category)) +
  geom_density(alpha=.25) +
  labs(title = 'Gęstość rozkładu rachunków pod względem kategorii') 

Prezentowany wykres gęstości pokazuje rozkład wysokości rachunków w Cafe PG dla różnych kategorii produktów. Każda kategoria jest oznaczona unikalnym kolorem i reprezentuje gęstość rozkładu całkowitej wartości rachunków związanych z daną kategorią. Obszary, gdzie krzywe są wyższe, wskazują na większą częstość występowania rachunków o określonej wysokości. Na przykład, szerokie bazy krzywych dla kategorii takich jak “Alkohole & Tytoń”, “Wina” mogą wskazywać na większą zmienność w wartościach rachunków, podczas gdy węższe szczyty dla kategorii “Napoje”, “Jedzenie”, “Tytoń” czy “Inne” mogą sugerować, że rachunki dla danej kategorii są bardziej skoncentrowane wokół konkretnej kwoty. Zgodnie z wykresem można powiedzieć, że “Napoje” kumulują się wokół wartości 0-120zł, kategoria “Jedzenie” skupia się głównie wokół wartości 50-250zł, “Alkohole” - są bardziej rozproszone na osi, ale największy sczyt znajduje się w okolicy 200zł, “Alholol&Tytoń” jest jedną z najbardziej rozproszonych kategorii, z największym skupieniem w okolicy wartości 500-900zł. Kategoria “Inne” charakteryzuje się największą gęstością dla wartości między 0-50zł. Dla kategorii “Tytyoń” obserwowane jest kilka wzrostów w częstości występowania rachunków - największy dla wartości w okolicach 300zł, następnie dla ok 370zł i 450 zł. Dla kategorii “Wina” rachunki rozkładają się najczęściej wokół wartości 200-250 zł, 400zł oraz 1000zł.

Rozkład skumulowanych przychodów według dni tygodnia

total_by_day <- cafe_do_2000 %>%
  mutate(DzienTygodnia = factor(DzienTygodnia, levels = c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))) %>%
  group_by(DzienTygodnia) %>%
  dplyr::summarize(TotalSum = sum(Total, na.rm = TRUE))

ggplot(total_by_day, aes(x = DzienTygodnia, y = TotalSum)) +
  geom_bar(stat = "identity", fill = "blue") +
  geom_text(aes(label = paste0(TotalSum, " zł")), vjust = -0.5, color = "black", size = 3) +  # Dodanie etykiet w zł
  labs(title = "Suma przychodów w zależności od dnia tygodnia",
       x = "Dzień tygodnia",
       y = "Suma wartości 'Total'")

Cafe PG najwyższe przychodzy osiągała kolejno w czwartki, piątki, soboty. Najniższe za to we wtorki i poniedziałki.

Sprzedaż w cafe pg według godzin i dni tygodnia

heatmap_data <- cafe_do_2000 %>%
  mutate(Hour = as.numeric(format(strptime(Time, "%H:%M:%S"), "%H")),
         DzienTygodnia = factor(DzienTygodnia, levels = c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))) %>%
  group_by(DzienTygodnia, Hour) %>%
  dplyr::summarize(TotalSales = sum(Total, na.rm = TRUE), .groups = 'drop')

# Tworzenie mapy cieplnej
ggplot(heatmap_data, aes(x = Hour, y = DzienTygodnia, fill = TotalSales)) +
  geom_tile() +
  labs(title = "Analiza sprzedaży w posczególnych godzinach",
       x = "Godzina",
       y = "Dzień tygodnia",
       fill = "Suma sprzedaży") +
  scale_fill_viridis_c() +
  theme_minimal()

Powyższy wykres mapy cieplnej prezentuje przedstawia analizę sprzedaży w poszczególnych godzinach w zależności od dnia tygodnia. Im jaśniejszy kolor tym większa suma sprzedaży występowała w danym dniu tygodnia o danej godzinie. Na wykresie widać, że największe obroty finansowe występowały w godzinach wieczornych i nocnych. Najwyższe wartości sprzedaży kawiarnia osiągała w czwartki w godzinach 23:00-24:00, w środy między godziną 22:00 i 1:00 (noce ze środy na czwartki), a także w piątki między godzinami 17:00-24:00. Najniższa sprzedaż miała miejsce w godzinach porannych (9:00 do 12:00) oraz w godzinach nocnych (2:00-6:00). Patrząc na wykres widać wzmożoną sprzedaż w godzinach nocnych (3:00-5:00) w czwartki, co może świadczyć o stałych imprezach w noc ze środy na czwartki.

Analiza opisowa

Tabela rozkładu przychodów wg kategorii

cafe_do_2000 %>%
  select(Total,Category) %>%
  tbl_summary(
    by=Category,
    type = all_continuous() ~ "continuous2",
    statistic = all_continuous() ~ c(
      "{N_nonmiss}","{mean}","{sd}",
      "{median} ({p25}, {p75})",
      "{min}, {max}"),
    missing = "no",
    label = Total ~ "Przychód") %>%
  modify_header(label ~ "**Kategoria**") %>%
  modify_caption("**Tabela 1. Rozkład przychodu wg kategorii**") %>%
  bold_labels() %>% 
  add_p(pvalue_fun = ~ style_pvalue(.x, digits = 2))
Tabela 1. Rozkład przychodu wg kategorii
Kategoria BEVERAGE, N = 43,572 FOOD, N = 57,018 LIQUOR, N = 6,177 LIQUOR & TPBACCO, N = 54 MERCHANDISE, N = 487 MISC, N = 1,187 TOBACCO, N = 36,490 WINES, N = 797 p-value1
Przychód







<0.001
    N 43,572 57,018 6,177 54 487 1,187 36,490 797
    Mean 126 174 331 788 246 163 397 402
    SD 86 77 248 281 204 219 118 295
    Median (IQR) 105 (74, 142) 167 (136, 198) 295 (158, 394) 660 (660, 660) 225 (100, 295) 111 (19, 161) 330 (323, 462) 221 (221, 441)
    Range 0, 1,485 0, 1,856 92, 1,969 644, 1,980 0, 1,654 19, 1,782 88, 1,980 0, 1,512
1 Kruskal-Wallis rank sum test

Na podstawie wykorzystanego testu Kruskal-Wallis wynik P-value mniejsze niż 0.001 oznacza, że różnice w przychodach między kategoriami są statystycznie znaczące, co oznacza, że prawdopodobnie różnice te nie są przypadkowe.

• Kategoria “LIQUOR & TOBACCO” ma najwyższą średnią przychodów, co sugeruje, że rachunki w tej kategorii są zazwyczaj wyższe niż w innych kategoriach. • Kategoria “WINES” ma największe odchylenie standardowe oraz największy zakres, co wskazuje na dużą różnorodność w przychodach z rachunków - od bardzo niskich do bardzo wysokich. • Kategoria “FOOD” ma najwięcej transakcji (N), ale jej mediana przychodów jest niższa niż w kategorii “LIQUOR & TOBACCO”, co może wskazywać, że jedzenie jest częściej kupowane, ale pojedyncze rachunki są zazwyczaj niższe. • Istotne różnice w medianach, wsparte przez bardzo niską wartość p-value w teście Kruskala-Wallisa, sugerują, że różne kategorie produktów generują różne poziomy przychodów.

Wnioskowanie i testy statystyczne

Sprawdzenie czy w poszczególnych dniach tygodnia udział konkretnych kategorii w sprzedaży istotnie się różni.

H0: Udział kategorii nie różni się istotnie w poszczególnych dniach tygodnia. H1: Udział kategorii istotnie różni się w poszczególnych dniach tygodnia

alfa = 0.05

ggbarstats(
  data             = cafe_do_2000,
  x                = Category,
  y                = DzienTygodnia,
  title            = "Udział kategorii produktów w danym dniu",
  xlab             = "kategoria",
  legend.title     = "Kategoria",
  ggplot.component = list(ggplot2::scale_x_discrete(guide = ggplot2::guide_axis(n.dodge = 2))),
  palette          = "Set2"
)
## Number of labels is greater than default palette color count.
## • Select another color `palette` (and/or `package`).

Interpretacja: Wartość p jest mniejsza od założonego poziomu istotności w każdym przypadku, należy więc odrzucić H0 na korzyść hipotezy alternatywnej. Udział kategorii różni się istotnie w poszczególnych dniach tygodnia.

Czy wysokość rachunków różni się istotnie w grupach “party” i wśród regularnych klientów?

H0: Wysokość rachunków nie różni się istotnie w grupach “party” H1: Wysokość rachunków różni się istotnie w grupach “party”

alfa = 0.05

ggbetweenstats(
  data  = cafe_do_2000,
  x     = party,
  y     = Total,
  title = "Wysokość rachunków z imprez i pojedynczych klientów"
)

Interpretacja: Wartość p jest mniejsza od założonego poziomu istotności, należy więc odrzucić H0 na korzyść hipotezy alternatywnej. Wysokość rachunków różni się istotnie w grupach “party”.

Czy wysokość rachunków ma rozkład normalny?

H0: Wysokość rachunków ma rozkład normalny H1: Wysokość rachunków nie ma rozkładu normalnego

alfa = 0.05

gghistostats(
  data       = cafe_do_2000,
  x          = Total,
  title      = "Wysokość rachunków",
  test.value = 224,
  binwidth   = 50,
  normal.curve      = TRUE,
  normal.curve.args = list(color = "red", size = 1),
)

Interpretacja: Wartość p jest większa od założonego poziomu istotności, nie ma więc podstaw do odrzucenia H0. Wysokość rachunków ma rozkład normalny.

Czy między kategoriami są istotne statystycznie różnice w wysokości rachunków?

H0: Nie ma istotnej statystycznie różnicy w wysokościach rachunków między różnymi kategoriami produktów. H1: Jest istotna statystycznie różnica w wysokościach rachunków między różnymi kategoriami produktów.

alfa = 0.05

ggdotplotstats(
  data       = cafe_do_2000,
  y          = Category,
  x          = Total,
  test.value = 224,
  type       = "robust",
  title      = "Rozkład wysokości rachunków we wszystkich kategoriach",
  xlab       = "Kategorie"
)

Interpretacja: Wartość p jest większa od założonego poziomu istotności, nie ma więc podstaw do odrzucenia H0. Nie ma istotnej statystycznie różnicy w wysokościach rachunków między różnymi kategoriami produktów.

Wnioski Końcowe

Cafe PG to lokal gastronomiczny charakteryzujacy się zróżnicowanym menu. Na podstawie przeprowadzonych wykresów i testów statystycznych można przedstawić temu lokalowi kilka rekomendacji.

  1. W celu ograniczenia kosztów i czasu pracy proponuje się skrócenie menu i skupienie się na sprzedaży produktów najczęściej wybieranych, które są jednocześnie najbardziej dochodowe.
  2. Sprzedaż jest niższa w godzinach porannych. Propozycją jest ograniczenie godzin otwarcia w najmniej dochodowych dniach tygodnia (poniedziałek i wtorek). Lokal mógłby się otwierać po godzinie 15, co pozwoliłoby na minimalizację kosztów eksploatowania lokalu.
  3. W godzinach wieczornych i nocnych, gdy odnotowywana jest wzmożona sprzedaż, można rozważyć wprowadzenie promocji, happy hour czy specjalnych zestawów na wieczór. To może dodatkowo zachęcić klientów do odwiedzania lokalu w tych porach. Dobrym pomysłem byłoby dobranie w zestawy produktów sezonowych, co będzie skutecznym posunięciem marketingowym - promocja czasowa odziałuje psychologicznie i zachęca klientów do skorzystania z oferty.
  4. Weekend oraz dni poprzedzające weekend są najbardziej dochodowe. Aby maksymalizować zyski w inne dni tygodnia (np. poniedziałek lub wtorek) proponuje się wprowadzenie wieczorków tematycznych np.: koncerty z muzyką na żywo, quizy i gry zespołowe, degustacja potraw.
  5. Należy pamiętać o dostosowywaniu liczby personelu do zapotrzebowania. Zaleca się mniej osób na zmianach w godzinach porannych i więcej w godzinach wieczornych.
  6. Dostępność różnorodnych kategorii produktów jest dobra dla lokalu, zainteresowanie bogatą ofertą wśród klientów widać w analizach sprzedaży poszczególnych kategorii. Rekomendujemy zachowanie obecnej struktury, przy zmniejszeniu liczby produktów dostępnych w danej kategorii.