CafePG.
Analiza danych sprzedażowych z roku 2020.

2024-01-26

Cel projektu

Nasza agencja “C jak Cudowni” została poproszona o przeanalizowanie danych sprzedażowych kawiarni CafePG z roku 2020. Celem było przeanalizowanie przyzwyczajeń zakupowych klientów kawiarni, zrozumienie trendów sprzedażowych, ustalenie produktów i kategorii produktów, które przynoszą największy i najmniejszy przychód. Na podstawie tych danych poczyniono rekomendacje co do stworzenia kombinacji produktów, które kupują klienci, w celu zaproponowania nowych zestawów do menu i maksymalizacji zysków. W podsumowaniu przekazano również zalecenia względem utrzymania lub rezygnacji ze sprzedaży pewnej kategorii produktów ze względu na płynące z nich przychody.

Czyszczenie danych

Poniższe kroki zostały wykonane w celu weryfikacji poprawności oraz poprawienia jakości analizowanych danych. Poprawiono błędy językowe, błędne zaszeregowanie do kategorii, wprowadzono dodatkowe zmienne w celu dokładniejszej analizy, zmieniono niejasną nazwę kategorii MSC na poprawną, zweryfikowano braki danych oraz dokonano walidacji danych, zmieniono format danych według potrzeb.

Weryfikacja poprawności i kompletności danych

Identyfikacja wyników odstających

#weryfikacja outlierów
cafe_pg%>%
  ggplot(aes(x = category, y = total)) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 25, hjust = 0.7)) +
  geom_boxplot()

W pierwszym kroku wykryto wartość bardzo odstającą. Zastosowano filtr na zmiennej total<7500 w celu lepszej wizualizacji reszty danych.

#weryfikacja outlierów
cafe_pg%>%
  filter(total<7500)%>%
  ggplot(aes(x = category, y = total)) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 25, hjust = 0.7)) +
  geom_boxplot()

Wykres obrazuje, że główna koncentracja wartości zmiennej total zawiera się poniżej 1000. W związku z tym przyjmujemy założenie, że total>1000 związany jest z nietypowymi sytuacjami, najprawdopodobniej są to zakupy grupowe.


W celu lepszej analizy różnic w zakupach indywidualnych względem grupowych wprowadzono kategorię zakupów grupowych, gdzie total jest większy niż 1000.

cafe_pg <- cafe_pg %>%
  mutate(groups = ifelse(total> 1000, 1, 0))

Weryfikacja, czy występują brakujące obserwacje - nie występują.

sum(is.na.data.frame(cafe_pg))
## [1] 0

Walidacja nazw kategorii

Występuje 54 obserwacje w których nazwa kategorii nie zgadza się z zadanymi kategoriami.

summary((cafe_pg$category %in% c('BEVERAGE', 'FOOD', 'LIQUOR', 'LIQUOR & TOBACCO', 'MERCHANDISE', 'MISC', 'TOBACCO','WINES')))
##    Mode   FALSE    TRUE 
## logical      54  145776
cafe_pg %>% 
  group_by(category) %>%
  summarise(count = n()) %>%
  arrange(-count)
## # A tibble: 8 × 2
##   category         count
##   <chr>            <int>
## 1 FOOD             57023
## 2 BEVERAGE         43573
## 3 TOBACCO          36496
## 4 LIQUOR            6201
## 5 MISC              1187
## 6 WINES              809
## 7 MERCHANDISE        487
## 8 LIQUOR & TPBACCO    54

Zidentyfikowano, ze wynika to z literówki w nazwie kategorii ‘LIQUOR & TOBACCO’ i poprawiono te nazwy.

Skorygowanie błędnej nazwy.

cafe_pg <- cafe_pg %>%
  mutate(category = replace (category,category=='LIQUOR & TPBACCO','LIQUOR & TOBACCO'))

Po poprawieniu literówki wszystkie kategorie są poprawne.

summary((cafe_pg$category %in% c('BEVERAGE', 'FOOD', 'LIQUOR', 'LIQUOR & TOBACCO', 'MERCHANDISE', 'MISC', 'TOBACCO','WINES')))
##    Mode    TRUE 
## logical  145830

Zmiany językowe

Skorygowanie błędnego zapisu polskich znaków w nazwach produktów.

cafe_pg$`item desc` <- gsub("á", "", cafe_pg$`item desc`)

Zmiany kategorii

Dodanie oznaczeń liczbowych dla produktu w danej kategorii, co pozwoli na sprawniejszą analizę produktów oferowanych przez CafePG i znalezienie błędów typu kieliszek wina skategoryzowany jako TOBACCO.

cafe_pg <- cafe_pg %>%
  group_by(category) %>%
  mutate(position_in_category = dense_rank(tolower(`item desc`))) %>%
  ungroup()

options(max.print = 100)
position_counts <- cafe_pg %>%
  filter(!is.na(position_in_category)) %>%
  group_by(category, position_in_category, `item desc`) %>%
  summarize(count = n())
print(position_counts %>% filter(category == "MISC"))
## # A tibble: 48 × 4
## # Groups:   category, position_in_category [48]
##    category position_in_category `item desc`                count
##    <chr>                   <int> <chr>                      <int>
##  1 MISC                        1 1 AXE TWIST                    6
##  2 MISC                        2 ADD CHICKEN BACON              4
##  3 MISC                        3 ADD GROUND MEAT                3
##  4 MISC                        4 ADD HERB ROAST CHICKEN       158
##  5 MISC                        5 ADD ON S                     379
##  6 MISC                        6 ADD POTATO WEDGES             34
##  7 MISC                        7 ADD TRADITIONAL MEAT FEAST     8
##  8 MISC                        8 DARK RUM (SM)                  2
##  9 MISC                        9 ENG BREAKFAST TEA              4
## 10 MISC                       10 FISH FINGER                    2
## # ℹ 38 more rows

Uporządkowanie kategorii MISC do właściwych kategorii.

Zostanie tylko jedna pozycja w kategorii MISC “PARTY CHARGES @ 500/-” co jest racjonalne ze względu na to, że opłaty administracyjnej nie da się przyporządkować do konkretnej kategorii.

cafe_pg$category[cafe_pg$category == "MISC" & cafe_pg$position_in_category %in% c(1,9,15,24,34,42,43,45)] <- "BEVERAGE"
cafe_pg$category[cafe_pg$category == "MISC" & cafe_pg$position_in_category %in% c(2, 3,4,5,6,7,10,12,14,18,20,21,23,26,27,29,31,35,36,37,38,39,40,41,46,47)] <- "FOOD"
cafe_pg$category[cafe_pg$category == "MISC" & cafe_pg$position_in_category %in% c(8,11,17,16)] <- "LIQUOR"
cafe_pg$category[cafe_pg$category == "MISC" & cafe_pg$position_in_category %in% c(22,30)] <- "MERCHANDISE"
cafe_pg$category[cafe_pg$category == "MISC" & cafe_pg$position_in_category %in% c(13,19,25,27,28,32,44,48)] <- "TOBACCO"

Uporządkowanie towarów błędnie skategoryzowanych

Dodatkowo, wykryto kilka błędnie przypisanych tytoni i również je przeniesiono do właściwej kategorii.

cafe_pg$category[cafe_pg$`item desc` == "SWEET MELON FLAVOUR SINGLE"] <- "TOBACCO"
cafe_pg$category[cafe_pg$`item desc` == "MIXED FLAVOUR SINGLE"] <- "TOBACCO"

Zmiana kategorii dla LATE HARVEST SULA CHENIN z TOBACCO na WINES.

cafe_pg$category[cafe_pg$`item desc` == "LATE HARVEST SULA CHENIN (GLS)"] <- "WINES"
cafe_pg$category[cafe_pg$`item desc` == "LATE HARVEST SULA CHENIN (BTL)"] <- "WINES"

Zmiana kategorii J.PCHENET SPARKLING ROSE (BTL) z FOOD na WINES.

cafe_pg$category[cafe_pg$`item desc` == "J.PCHENET SPARKLING ROSE (BTL)"] <- "WINES"

Ponowne przypisanie i sprawdzenie numerów pozycji w kategoriach - przede wszystkim MISC.

cafe_pg <- cafe_pg %>%
  group_by(category) %>%
  mutate(position_in_category = dense_rank(tolower(`item desc`))) %>%
  ungroup()

options(max.print = 10)
position_counts <- cafe_pg %>%
  filter(!is.na(position_in_category)) %>%
  group_by(category, position_in_category, `item desc`) %>%
  summarize(count = n())
print(position_counts %>% filter(category == "MISC"))
## # A tibble: 1 × 4
## # Groups:   category, position_in_category [1]
##   category position_in_category `item desc`           count
##   <chr>                   <int> <chr>                 <int>
## 1 MISC                        1 PARTY CHARGES @ 500/-     1

W związku z tym, że w kategorii MISC została jedna pozycja “Party charges” zamieniono nazwę kategorii MISC na PARTY CHARGES.

cafe_pg <- cafe_pg %>%
  mutate(category = replace (category,category=='MISC','PARTY CHARGES'))

Zmiana formatu zmiennych i dodanie zmiennych potrzebnych do analizy zmian w czasie

Wyciągnięcie z pełnej daty z godziną samej godziny w celu analizy zakupów godzinowych. Utworzenie zmiennych dla dni i miesięcy.

cafe_pg$date <- as.Date(cafe_pg$date, format="%Y-%m-%d")
cafe_pg$time <- as_hms(cafe_pg$time)

cafe_pg <- cafe_pg %>%
  mutate(dzientyg = wday(cafe_pg$date, label=TRUE, abbr=FALSE))
cafe_pg <- cafe_pg %>%
  mutate(miesiac = month(cafe_pg$date, label=TRUE, abbr=FALSE))
cafe_pg <- cafe_pg %>%
  mutate(hour_minute_second = format(time, format = "%H:%M:%S"),
         godzina = hour(time))

Analiza opisowa

Analiza Statystyczna Kategorii Produktów:

Sporządzono tabele opisującą wartości “total” według poszczególnych kategorii. W kategorii MISC obecnie znajduje się tylko jedna obserwacja jak opisano wyżej. Największą średnią wielkością sprzedaży charakteryzuje się kategoria LIQUOR & TOBACCO, gdzie minimalna wartość transakcji wyniosła 644. Najniższe średnie wartości sprzedaży odnotowano w kategorii BEVERAGE, gdzie mediana wynosi 105.19, czyli połowa wartości w kategorii BEVERAGE jest niższa od 105.19, a druga połowa wyższa. Przejdźmy do pojęcia skośności, która wskazuje, czy większość zmian zmiennej “total” znajduje się poniżej średniej czy powyżej. W przypadku, gdy więcej obserwacji jest poniżej średniej, wtedy rozkład zmiennej jest prawostronny. Wszystkie kategorie charakteryzują się dodatnim współczynnikiem skośności, a więc rozkładem prawostronnym.
category średnia odchylenie wariancja minimum maximum Q1 mediana Q3 Skośność Kurtoza
BEVERAGE 126.67 87.46 0.69 30 2735 74.25 105.19 142.31 3.55 33.30
FOOD 172.69 79.25 0.46 0 3267 136.13 167.06 191.81 4.27 73.29
LIQUOR 341.17 294.04 0.86 122 5250 157.50 295.31 393.75 4.31 35.33
LIQUOR & TOBACCO 788.18 281.17 0.36 644 1980 660.00 660.00 660.00 2.22 4.68
MERCHANDISE 256.20 211.34 0.82 27 1654 100.13 224.70 300.00 2.54 9.45
PARTY CHARGES 14231.25 NA NA 14231 14231 14231.25 14231.25 14231.25 NA NA
TOBACCO 398.08 121.43 0.31 88 2970 323.40 330.00 462.00 3.49 32.79
WINES 441.95 421.88 0.95 1 4536 220.50 252.00 441.00 3.81 23.29

Analiza szczegółowa:

  1. BEVERAGE: Średnia wartość produktów w kategorii BEVERAGE wynosi 126.67, co stanowi miarę centralną reprezentującą wartość oczekiwaną. Jednakże, istotnym aspektem jest odchylenie standardowe, które wynosi 87.46. Oznacza to, że ceny napojów w tej kategorii wahają się średnio o tę wartość wokół średniej. Wartość skośności (3.55) wskazuje na asymetrię w prawo, co sugeruje, że istnieją pewne produkty w tej kategorii o wyższych cenach, co potwierdza także wysoka kurtoza (33.30), świadcząca o obecności ekstremalnych cen.

  2. FOOD: W kategorii FOOD średnia wartość produktów wynosi 172.69, a odchylenie standardowe 79.25. Średnia ta jest centralną miarą reprezentującą cenę oczekiwaną, natomiast odchylenie standardowe wskazuje na średnią zmienność cen wokół tej wartości. Wysoka skośność (4.27) sugeruje obecność wyższych cen, co potwierdza także kurtoza (73.29), wskazująca na występowanie ekstremalnych wartości cenowych.

  3. LIQUOR: Kategoria LIQUOR charakteryzuje się średnią wartością produktów na poziomie 341.17, jednak odchylenie standardowe (294.04) wskazuje na dużą zmienność cen w tej kategorii. Skośność (4.31) i kurtoza (35.33) potwierdzają występowanie skrajnych wartości cenowych, co oznacza, że niektóre produkty w tej kategorii są znacznie droższe niż inne.

  4. LIQUOR & TOBACCO: W przypadku kategorii LIQUOR & TOBACCO, średnia wartość wynosi 788.18, a niskie odchylenie standardowe (281.17) wskazuje na stosunkowo niską zmienność cen w porównaniu z wartością średnią. Skośność (2.22) wskazuje na pewną asymetrię w prawo, co może oznaczać obecność produktów o wyższych cenach. Wysoka kurtoza (4.68) sugeruje obecność ekstremalnych cen.

  5. MERCHANDISE: Kategoria MERCHANDISE prezentuje średnią wartość produktów na poziomie 256.20, a odchylenie standardowe 211.34 wskazuje na znaczną zmienność cen w tej kategorii. Skośność (2.54) i kurtoza (9.45) potwierdzają obecność wyższych wartości cenowych, co może być istotne dla analizy rynku tego rodzaju produktów.

  6. TOBACCO: Produkty tytoniowe charakteryzują się średnią wartością wynoszącą 398.08, a odchylenie standardowe 121.43 wskazuje na umiarkowaną zmienność cen. Skośność (3.49) i kurtoza (32.79) sygnalizują obecność produktów o wyższych cenach, a także możliwość występowania ekstremalnych wartości cenowych.

  7. WINES: W kategorii WINES średnia wartość produktów wynosi 441.95, a wysokie odchylenie standardowe (421.88) wskazuje na znaczną zmienność cen. Skośność (3.81) i kurtoza (23.29) potwierdzają obecność wyższych wartości cenowych oraz możliwość występowania ekstremalnych wartości w tej kategorii.

Podsumowując, odchylenie standardowe stanowi kluczową miarę w analizie zmienności cen produktów w poszczególnych kategoriach, a jego wartość informuje o rozproszeniu cen wokół średniej. Wysokie odchylenie standardowe sugeruje większe wahania cenowe, co może być istotne w kontekście strategii cenowej oraz zrozumienia dynamiki rynku dla poszczególnych kategorii produktów.

Analiza najpopularniejszych produktów i kategorii

Celem poniższej analizy jest zbadanie, który produkt oraz jaka kategoria są najczęściej wybierane przez klientów restauracji CafePG. Wzięto pod uwagę zarówno częstość wyboru (count) jak i ogólnie zamówioną ilość (quantity). Wyniki przedstawiono poniżej.

5 najpopularniejszych produktów ogółem

par(mfrow=c(3,1))

ggplot(cafe_pg %>%
  group_by(`item desc`)%>%
  summarise(count = n(), total_quantity = sum(quantity))%>%
  arrange(desc(count))%>%
  head(5), 
  aes(x = reorder(`item desc`, -total_quantity), y = total_quantity)) +
  geom_bar(stat = "identity", fill = "skyblue") +
  geom_text(aes(label = total_quantity), vjust = -0.5, color = "black", size = 3) +
  labs(title = "Najpopularniejsze Produkty Ogółem", x = "Produkt", y = "Liczba") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 25, hjust = 0.7))

Popularnosc każdej z kategorii

ggplot(cafe_pg %>% 
  group_by(category) %>%
  summarise(count = n(), total_quantity = sum(quantity)) %>%
  arrange(desc(count)), 
  aes(x = reorder(category, -total_quantity), y = total_quantity)) +
  geom_bar(stat = "identity", fill = "skyblue") +
  geom_text(aes(label = total_quantity), vjust = -0.5, color = "black", size = 3) +
  labs(title = "Popularność poszczególnych kategorii", x = "Kategoria", y = "Liczba") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 25, hjust = 0.7))

Najpopularniejszy produkt wg kategorii (z uwzglednieniem quantity)

gt(cafe_pg %>%
  group_by(category, `item desc`) %>%
  summarise(count = n(), total_quantity = sum(quantity), .groups = "drop_last") %>%
  group_by(category) %>%
  slice_max(order_by = count, n = 1)) %>%
  tab_spanner(
    label = "Najpopularniejsze produkty w danej kategorii",
    columns = c(count, total_quantity)
  ) %>%
  fmt_number(columns = c(count, total_quantity), decimals = 0) %>%
  tab_header(
    title = "Popularność wg kategorii",
    subtitle = "Z uwzględnieniem wartości count, czyli ile razy zamówiono dany produkt na rachunku oraz total_quantity z uwzględnieniem ilości zamówionych produktów na jednym rachunku.",
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_body()
  )
Popularność wg kategorii
Z uwzględnieniem wartości count, czyli ile razy zamówiono dany produkt na rachunku oraz total_quantity z uwzględnieniem ilości zamówionych produktów na jednym rachunku.
item desc Najpopularniejsze produkty w danej kategorii
count total_quantity
BEVERAGE
CAPPUCCINO 5,495 7,144
FOOD
OCEAN SPECIAL SHAKE 4,895 5,914
LIQUOR
CARLSBERG 1,716 3,380
LIQUOR & TOBACCO
BEER HOOKAH 40 49
MERCHANDISE
OCEAN SPECIAL T-SHIRTS 34 37
PARTY CHARGES
PARTY CHARGES @ 500/- 1 23
TOBACCO
NIRVANA HOOKAH SINGLE 8,553 8,686
WINES
VLN CAB SAUV (GLS) 146 216

Najbardziej dochodowe produkty

ggplot(cafe_pg %>%
         group_by(`item desc`) %>%
         summarise(total1 = sum(total)) %>%
         arrange(desc(total1)) %>%
         head(5), aes(x = reorder(`item desc`, -total1), y = total1)) +
  geom_bar(stat = "identity", fill = "skyblue") +
  geom_text(aes(label = sprintf("%.2f", round(total1, 2))), vjust = -0.5, color = "black", size = 3) +
  labs(title = "Najbardziej dochodowe produkty", x = "Produkt", y = "Przychód (tys.)") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 25, hjust = 0.7)) +
  scale_y_continuous(labels = scales::number_format(scale = 1e-3))

Najpopularniejsza kategoria to FOOD. Najpopularniejszy produkt ogółem to NIRVANA HOOKAH SINGLE.

Analiza produktów pod względem cen

Znalezienie najdroższego przedmiotu ogółem i w każdej kategorii.

TOP10 najdroższych produktów oferowanych przez CafePG.

highest_price_product <- cafe_pg %>%
  arrange(desc(rate)) %>%
  distinct(`item desc`, .keep_all = TRUE) %>%
  slice_head(n = 10) %>%
  select(`item desc`, rate)
print(highest_price_product)
## # A tibble: 10 × 2
##    `item desc`                     rate
##    <chr>                          <dbl>
##  1 GOSSIPS CHARD AUS (BTL)         2100
##  2 MATEUS ROSE PORTUGAL(BTL)       2000
##  3 2 OCEAN PINOTAGE (BTL)          1900
##  4 J.PCHENET SPARKLING ROSE (BTL)  1700
##  5 MAISON PIERRE SAUV MARSAN       1700
##  6 FLAVOR 1000 GMS                 1470
##  7 HOEGAARDEN LTR MUGS (2+1)       1300
##  8 STELLA 1LTR 2+1                 1300
##  9 SULA BRUT (BTL)                 1200
## 10 SCHNEIDER BUCKET - 6            1200

Najdroższy przedmiot ogółem to butelka australijskiego wina Gossip Chard.

Weryfikacja najdroższego produktu w pozostałych kategoriach

Pominięto kategorię PARTY CHARGES, ponieważ tam występuje tylko opłata administracyjna za zorganizowanie przyjęcia.

highest_products_by_category <- cafe_pg %>%
  filter(category %in% c("BEVERAGE", "FOOD", "LIQUOR", "LIQUOR & TOBACCO", "MERCHANDISE", "TOBACCO","WINES")) %>%
  group_by(category) %>%
  arrange(desc(rate)) %>%
  slice_head(n = 1) %>%
  ungroup() %>%
  select(category, `item desc`, rate)
print(highest_products_by_category)
## # A tibble: 7 × 3
##   category         `item desc`                  rate
##   <chr>            <chr>                       <dbl>
## 1 BEVERAGE         "RED SANGRIA (CARAFE) "       800
## 2 FOOD             "SCHNEIDER 2+1"               600
## 3 LIQUOR           "HOEGAARDEN LTR MUGS (2+1)"  1300
## 4 LIQUOR & TOBACCO "4 DOM BEER + 1SPL SHEESHA"   750
## 5 MERCHANDISE      "FLAVOR 1000 GMS"            1470
## 6 TOBACCO          "VALENTINE SPECIAL SHEESHA"   500
## 7 WINES            "GOSSIPS CHARD AUS (BTL)"    2100

Najdroższy przedmiot w każdej kategorii to: 1. BEVERAGE - karafka czerwonej Sangrii za 800 2. FOOD - Schneider w zestawie 2+1 za 600 3. LIQUOR - litrowy HOEGAARDEN w zestawie 2+1 4. LIQUOR & TOBACCO - zestaw dla grupki znajomych 4 DOM BEER + 1SPL SHEESHA za 750 5. MERCHANDISE - kilogramowy uszlachetniacz do tytoniu FLAVOR 1000 GMS za 1470 6. TOBACCO - zdecydowanie króluje walentynkowa mieszanka dla zakochanych <3 za 500 7. WINES - butelka australijskiego wina Gossip Chard za 2100

Badanie udziału kategorii w przychodach

Poniżej zaprezentowano wykres ukazujący udział kategorii w przychodach. CafePG ma największe przychody ze sprzedaży produktów tytoniowych - ponad 40% wszystkich przychodów. Najmniejsze przychody (poniżej 2%) generuje sprzedaż wina, gadżetów, kombinacji wyrobów tytoniowych z alkoholami oraz opłaty związane z organizacją imprez.

# Summarizing total sales per category
category_sales <- cafe_pg %>%
  group_by(category) %>%
  summarise(total_sales = sum(total))

# Calculating the total sales across all categories
whole_sales <- sum(category_sales$total_sales)

# Calculating the percentage of total sales for each category
category_sales <- category_sales %>%
  mutate(percentage_of_total = ((total_sales / whole_sales) * 100),
  percentage_label = sprintf("%.2f%%", percentage_of_total))

# Reordering the categories based on percentage_of_total
category_sales <- category_sales %>%
  mutate(category = reorder(category, -percentage_of_total))

# Plotting the data with formatted labels
ggplot(category_sales, aes(x = category, y = percentage_of_total, fill = category)) +
  geom_bar(stat = "identity", fill = "skyblue") +
  theme_minimal() +
  geom_text(aes(label = percentage_label), vjust = -0.5, color = "black", size = 3) +
  labs(title = "Procentowy udział kategorii w przychodach", x = "Kategoria", y = "Przychód") +
  scale_y_continuous(labels = function(x) sprintf("%.2f%%", x)) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 25, hjust = 0.7))

Największe dochody przynosi sprzedaż w kategorii TOBACCO.

Badanie ruchu klientów: dzienny, godzinowy, miesięczny

Badając ruch klientów w poszczególnych miesiącach widać, że najbardziej dochodowym miesiącem był październik, natomiast najmniej dochodowym luty. W podziale na dni tygodnia, średnio największe zarobki osiągano w czwartki, a najniższe w poniedziałki i wtorki. Analizując liczbę otwartych rachunków ogółem w wybranych okresach, można wywnioskować że wrzesień jest miesiącem, w którym w 2020 roku było najwięcej gości. Najniższą aktywność restauracja odnotowała w lutym, gdzie liczba otwartych rachunków była najniższa. Najwięcej rachunków odnotowano w czwartki, a najmniej we wtorki. Analizując godzinową aktywność, najwięcej zamówień odnotowano około godziny 20, a między 10-17 zaobserwować można stopniowy wzrost aktywności, co widać po wzrastającej liczbie zamówień.

# Analiza miesięczna 
monthly_summary <- cafe_pg %>%
  group_by(miesiac) %>%
  summarise(avg_value = sum(total),avg_bills = n_distinct(`bill number`))

# Analiza dzienna
daily_summary <- cafe_pg %>%
  group_by(dzientyg) %>%
  mutate(total1 = sum(total))%>%
  summarise(avg_value = mean(`total1`),avg_bills = n_distinct(`bill number`))

# Analiza godzinowa
hourly_summary <- cafe_pg %>%
  group_by(godzina) %>%
  summarise(avg_bills = n_distinct(`bill number`))

Ruch klientów: miesięczny

ggplot(monthly_summary, aes(x = factor(miesiac), y = avg_value)) +
  geom_bar(stat = "identity", fill = "skyblue") +
   geom_text(aes(label = round(avg_value, 0)), vjust = -0.5, color = "black", size = 3)+
  labs(title = "Wartość przychodu w danych miesiącach", x = "Miesiąc", y = "Przychód (tys.)")+
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 25, hjust = 0.7))+
  scale_y_continuous(labels = scales::number_format(scale = 1e-3))

ggplot(monthly_summary, aes(x = factor(miesiac), y = avg_bills)) +
  geom_bar(stat = "identity", fill = "black") +
     geom_text(aes(label = avg_bills), vjust = -0.5, color = "black", size = 3)+
  geom_text(aes(label = avg_bills), vjust = -0.5, color = "black", size = 3)+
  labs(title = "Liczba rachunków w danych miesiącach", x = "Miesiąc", y = "liczba rachunków")+
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 25, hjust = 0.7))

Ruch klientów: dzienny

ggplot(daily_summary, aes(x = factor(dzientyg), y = avg_value)) +
  geom_bar(stat = "identity", fill = "skyblue") +
  labs(title = "Średnia wartość zarobków na dzień", x = "Dzień", y = "Średnia wartość (tys.)") +
scale_y_continuous(labels = scales::number_format(scale = 1e-3))

ggplot(daily_summary, aes(x = factor(dzientyg), y = avg_bills)) +
  geom_bar(stat = "identity", fill = "black") +
  geom_text(aes(label = avg_bills), vjust = -0.5, color = "black", size = 3)+
  labs(title = "Liczba rachunków na dzień", x = "Dzień", y = "liczba rachunków")

Ruch klientów: godzinowy

ggplot(hourly_summary, aes(x = godzina, y = avg_bills)) +
  geom_line() +
  labs(title = "Liczba rachunków godzinowo", x = "Godzina", y = "liczba rachunków")+
  theme_minimal()

Dzienne trendy sprzedaży

Analiza dziennych trendów sprzedaży w CafePG rzuca światło na ciekawe zjawiska. Co ciekawe, średnia sprzedaż jedzenia, napojów i tytoniu utrzymuje się na stabilnym poziomie każdego dnia, choć warto zauważyć pewne subtelne różnice. Szczególnie interesujące są maksymalne średnie sprzedaże w poszczególne dni tygodnia.

daily_summary_TREND <- cafe_pg %>%
  filter(category != "PARTY CHARGES") %>%
  group_by(dzientyg, category) %>%
  summarise(avg_total = mean(total), num_bills = n_distinct(`bill number`))

# Find the rows with maximum values for each category
max_values <- daily_summary_TREND %>%
  group_by(category) %>%
  filter(avg_total == max(avg_total))

ggplot(daily_summary_TREND, aes(x = dzientyg, y = avg_total, color = category, group = category)) +
  geom_line(size = 1) +
  geom_point(data = max_values, aes(x=dzientyg,y=avg_total), pch = 19, size = 3, color = "black") +
  geom_text(data = max_values, aes(x = dzientyg, y = avg_total, label = sprintf("%.2f", avg_total)), vjust = -0.8, hjust = 0.5, size = 3) +
  labs(title = "Dzienne trendy sprzedaży",
       x = "Dni tygodnia",
       y = "Średnia z total",
       color = "Category") +
  scale_color_manual(values = c("BEVERAGE" = "#696969", "FOOD" = "orange", "LIQUOR" = "darkgreen", "LIQUOR & TOBACCO" = "black", "MERCHANDISE" = "navy", "TOBACCO" = "red", "WINES" = "darkorchid")) +
  scale_y_continuous(limits = c(0, max(daily_summary_TREND$avg_total) + 100)) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 25, hjust = 1))

Nasze dania cieszą się największym zainteresowaniem w piątki osiągając średnią sprzedaż na poziomie 176,22. Jest to prawdopodobnie związane z wyraźnym odprężeniem, jakiego ludzie szukają po intensywnym tygodniu pracy, decydując się na relaksujące spotkania w naszej kawiarni. Piątkowe popołudnie to nie tylko jedzenie. Średnia sprzedaż napojów w tym dniu osiąga 129,38, co sugeruje, że nasi goście pragną wykwintnych smaków i doświadczeń kulinarnych z ulubionymi napojami.

Największe zainteresowanie tytoniem przypada w czwartki, gdzie średnia sprzedaż wynosi imponujące 404,76. To może być związane z charakterystycznym dla tego dnia rytuałem relaksu po cięższym początku tygodnia.

W piątki notujemy najwyższą średnią sprzedaż alkoholi na poziomie 358,51, co sugeruje, że nasza kawiarnia staje się miejscem wybieranym na spotkania towarzyskie podczas wieczoru przedweekendowego. Wino zaś najczęściej sprzedajmy w sobotę i osiąga największą średnią sprzedaż na poziomie 493,66, co można wiązać z tradycją lampki wina do sobotniego obiadu.

Najbardziej imponującą średnią sprzedażą charakteryzuje się kategoria “alkohole i tytonie” w środę, osiągając 820,78. To może być związane z potrzebą środowego odstresowania, gdzie klienci decydują się na połączenie napojów i tytoniu.

Gadżety również najczęściej sprzedają się w piątek ze średnią sprzedażą 292,56

Podsumowując, nasza kawiarnia oferuje różnorodne doświadczenia dostosowane do preferencji naszych gości w zależności od dnia tygodnia. Dzięki tej analizie możemy lepiej zrozumieć, co przyciąga naszych klientów i jak możemy dalej dostosować naszą ofertę, aby spełniać ich oczekiwania.

Trendy dla klientów indywidualnych

Analiza sprzedaży dla klientów indywidualnych ujawnia interesujące trendy. Średnia sprzedaż jedzenia, napojów i tytoniu utrzymuje się na zbliżonym poziomie każdego dnia, z pewnymi wyjątkami.

daily_summary_TREND_groups0 <- cafe_pg %>%
  filter(category != "PARTY CHARGES", groups == 0) %>%
  group_by(dzientyg, category) %>%
  summarise(avg_total = mean(total), num_bills = n_distinct(`bill number`))

# Find the rows with maximum values for each category
max_values2 <- daily_summary_TREND_groups0 %>%
  group_by(category) %>%
  filter(avg_total == max(avg_total))

ggplot(daily_summary_TREND_groups0, aes(x = dzientyg, y = avg_total, color = category, group = category)) +
  geom_line(size = 1) +
  geom_point(data = max_values2, aes(x = dzientyg, y = avg_total), pch = 19, size = 3, color = "black") +
  geom_text(data = max_values2, aes(x = dzientyg, y = avg_total, label = sprintf("%.2f", avg_total)), vjust = -0.8, hjust = 0.5, size = 3) +
  labs(title = "Dzienne trendy sprzedaży dla klientów indywidualnych",
       x = "Dni tygodnia",
       y = "Średnia z total",
       color = "Category") +
  scale_color_manual(values = c("BEVERAGE" = "#696969", "FOOD" = "orange", "LIQUOR" = "darkgreen", "LIQUOR & TOBACCO" = "black", "MERCHANDISE" = "navy", "TOBACCO" = "red", "WINES" = "darkorchid")) +
  scale_y_continuous(limits = c(0, max(daily_summary_TREND_groups0$avg_total) + 100)) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 25, hjust = 1))

W piątki napoje osiągają najwyższą średnią sprzedaż 128,84, co może wynikać z chęci relaksu klientów po tygodniu pracy. Tego samego dnia sprzedaż jedzenia wynosi 175,41, co sugeruje, że piątkowe spotkania przyciągają gości zainteresowanych zarówno napojami, jak i kulinariami.

W czwartki tytoń cieszy się zwiększonym zainteresowaniem, osiągając średnią sprzedaż na poziomie 400,66. To może być rezultat tradycji relaksu w środku tygodnia.

W kategorii alkoholi, wina są popularne w niedziele 327,48, prawdopodobnie ze względu na ich towarzyski charakter podczas niedzielnego obiadu. Alkohole notują największą średnią sprzedaż w środy 310,43, co może wynikać z tendencji do środowych spotkań towarzyskich.

Największą średnią sprzedażą w kategorii “alkohole i tytonie” charakteryzuje się sobota (766,88), co prawdopodobnie jest efektem weekendowej atmosfery, gdzie klienci korzystają z chwili relaksu.

Podsumowując, analiza trendów sprzedaży pomaga dostosować ofertę kawiarni do preferencji klientów w zależności od dnia tygodnia, tworząc unikalne doświadczenia dla każdej grupy produktowej.

Trendy dla klientów grupowych

CafePG odwiedzają również grupy tworząc interesujące trendy. Przede wszystkim widać jak chaotyczne trendy tworzą grupy.

daily_summary_TREND_groups1 <- cafe_pg %>%
  filter(category != "PARTY CHARGES", groups == 1) %>%
  group_by(dzientyg, category) %>%
  summarise(avg_total = mean(total), num_bills = n_distinct(`bill number`))

# Find the rows with maximum values for each category
max_values3 <- daily_summary_TREND_groups1 %>%
  group_by(category) %>%
  filter(avg_total == max(avg_total))

ggplot(daily_summary_TREND_groups1, aes(x = dzientyg, y = avg_total, color = category, group = category)) +
  geom_line(size = 1) +
  geom_point(data = max_values3, aes(x = dzientyg, y = avg_total), pch = 19, size = 3, color = "black") +
  geom_text(data = max_values3, aes(x = dzientyg, y = avg_total, label = sprintf("%.2f", avg_total)), vjust = -0.8, hjust = 0.5, size = 3) +
  labs(title = "Dzienne trendy sprzedaży dla grup",
       x = "Dni tygodnia",
       y = "Średnia z total",
       color = "Category") +
  scale_color_manual(values = c("BEVERAGE" = "#696969", "FOOD" = "orange", "LIQUOR" = "darkgreen", "LIQUOR & TOBACCO" = "black", "MERCHANDISE" = "navy", "TOBACCO" = "red", "WINES" = "darkorchid")) +
  scale_y_continuous(limits = c(0, max(daily_summary_TREND_groups1$avg_total) + 100)) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 25, hjust = 1))

W piątki najpopularniejszymi produktami są napoje, alkohole i wina. Najwyższą średnią sprzedaż osiągają napoje na poziomie 1998,57, następnie alkohole 1691,66 i wina 1586,45 co może wynikać z chęci relaksu klientów po udręce tygodniu pracy. Tego samego dnia sprzedaż jedzenia jest najniższa wśród swojej kategorii. Z tego można ewidentnie wywnioskować, że piątki są dniem odpoczynku i zabawy. Jedzenie jest najbardziej popularne w niedziele ze średnią 1980. W niedzielę tytoń cieszy się zwiększonym zainteresowaniem, osiągając średnią sprzedaż na poziomie 1599,40. Gadżety wśród grup najlepiej sprzedają się w środę ze średnią wysokością 1653,75.

Przeprowadzenie analizy menu w celu opracowania kombinacji, które klienci polubią najbardziej

Zestawy trzyelementowe:

Analizując produkty kupowane wspólnie dochodzimy do wniosku, że bardo popularne jest kupowanie B.M.T Panini w połączeniu z drugim produktem z kategorii FOOD np. MAGGI NDL ARRABIATA, OCEAN SPECIAL SHAKE lub POUTINE WITH FRIES i Sambucą. Można zatem zaproponować menu składające się z dwóch potraw i Sambuki. Drugą popularną kombinacją jest B.M.T Panini w połączeniu z Red Bullem i Sambucą.

# Transforming the data to a transaction format
data_trans <- as(split(cafe_pg$`item desc`, cafe_pg$`bill number`), "transactions")

# Apply the Apriori algorithm to find frequent itemsets with a suitable support threshold
# The support threshold is set to capture repeating combinations
min_support_for_repeats = 0.0001  # Adjust this based on your dataset
frequent_itemsets <- apriori(data_trans, parameter = list(target = "frequent itemsets", supp = min_support_for_repeats))
## Apriori
## 
## Parameter specification:
##      confidence minval smax arem aval originalSupport maxtime support minlen
##      maxlen target ext
##  [ reached 'max' / getOption("max.print") -- omitted 1 rows ]
## 
## Algorithmic control:
##  filter tree heap memopt load sort verbose
##     0.1 TRUE TRUE  FALSE TRUE    2    TRUE
## 
## Absolute minimum support count: 6 
## 
## set item appearances ...[0 item(s)] done [0.00s].
## set transactions ...[579 item(s), 69982 transaction(s)] done [0.01s].
## sorting and recoding items ... [430 item(s)] done [0.00s].
## creating transaction tree ... done [0.01s].
## checking subsets of size 1 2 3 4 5 done [0.00s].
## sorting transactions ... done [0.01s].
## writing ... [6202 set(s)] done [0.01s].
## creating S4 object  ... done [0.00s].
# Filter itemsets to include only those with three or more items
itemsets_three_or_more <- subset(frequent_itemsets, subset = size(frequent_itemsets) >= 3)

# Sort these itemsets by support
sorted_itemsets <- sort(itemsets_three_or_more, by = "support", decreasing = TRUE)

# Get the top combinations (adjust the number as needed, here using top 50)
top_combinations <- head(sorted_itemsets, 10)

# Display the top combinations
inspect(top_combinations)
##     items                                                 support      count
## [1] {MINERAL WATER(1000ML), OCEAN SPECIAL SHAKE, SAMBUCA} 0.0009145209 64   
## [2] {B.M.T. PANINI, MAGGI NDL ARRABIATA, SAMBUCA}         0.0009002315 63   
## [3] {B.M.T. PANINI, MINERAL WATER(1000ML), SAMBUCA}       0.0008430739 59   
##  [ reached 'max' / getOption("max.print") -- omitted 7 rows ]

Zestawy dwuelementowe:

Najczęściej kupowanymi kombinacjami są NIRVANA HOOKAH SINGLE i POUTINE WITH FRIES. Można zatem założyć, że dobrą propozycją byłoby stworzenie zestawów składających się z dania z kategorii FOOD (POUTINE WITH FRIES) oraz produktu z kategorii ‘TOBBACO’(NIRVANA HOOKAH SINGLE). Podobne kombinacje widzimy w przypadku NIRVANA HOOKAH SINGLE i OCEAN SPECIAL SHAKE oraz B.M.T. PANINI i NIRVANA HOOKAH SINGLE

# Transforming the data to a transaction format
data_trans <- as(split(cafe_pg$`item desc`, cafe_pg$`bill number`), "transactions")

# Apply the Apriori algorithm to find frequent itemsets with an even lower support threshold
very_low_supp = 0.0001  # A much lower support threshold
frequent_itemsets <- apriori(data_trans, parameter = list(target = "frequent itemsets", supp = very_low_supp))
## Apriori
## 
## Parameter specification:
##      confidence minval smax arem aval originalSupport maxtime support minlen
##      maxlen target ext
##  [ reached 'max' / getOption("max.print") -- omitted 1 rows ]
## 
## Algorithmic control:
##  filter tree heap memopt load sort verbose
##     0.1 TRUE TRUE  FALSE TRUE    2    TRUE
## 
## Absolute minimum support count: 6 
## 
## set item appearances ...[0 item(s)] done [0.00s].
## set transactions ...[579 item(s), 69982 transaction(s)] done [0.01s].
## sorting and recoding items ... [430 item(s)] done [0.00s].
## creating transaction tree ... done [0.01s].
## checking subsets of size 1 2 3 4 5 done [0.00s].
## sorting transactions ... done [0.01s].
## writing ... [6202 set(s)] done [0.01s].
## creating S4 object  ... done [0.00s].
# Filter itemsets to include only those with more than one item
itemsets_with_combinations <- subset(frequent_itemsets, subset = size(frequent_itemsets) > 1)

# Sort these itemsets by support
sorted_combinations <- sort(itemsets_with_combinations, by = "support", decreasing = TRUE)

# You might want to increase the number of combinations you view
# For example, here we take the top 100 combinations
top_combinations <- head(sorted_combinations, 10)

# Display the top combinations
inspect(top_combinations)
##     items                                       support     count
## [1] {NIRVANA HOOKAH SINGLE, POUTINE WITH FRIES} 0.006330199 443  
## [2] {CAPPUCCINO, MINT FLAVOUR SINGLE}           0.005987254 419  
## [3] {CAPPUCCINO, OCEAN SPECIAL SHAKE}           0.005544283 388  
##  [ reached 'max' / getOption("max.print") -- omitted 7 rows ]

Wnioskowanie

Czy total sie różni w zależnosci od kategorii dla zakupów indywidualnych?

Przyjęliśmy założenie, że duża grupa to ‘total’ większy niż 1000 - na podstawie tego, że do 3 kwantyla średni zakupy są na poziomie około 300.

Obserwujemy zdecydowanie wyższą średnią sumę zakupów w kategorii LIQUOR & TOBACCO. Wszystkie zależności poza zależnością pomiędzy kategoriami LIQUOR & TOBBACCO a WINE są statystycznie istotne.

dane <- cafe_pg %>% filter(groups==0)

ggbetweenstats(
  data = dane,
  x = category,
  y = total,
  title = "Total per kategorie",
  pairwise.display = "non-significant",
  subtitle= "Suma rachunku dla zakupów indywidualnych"
)

Czy total sie różni w zalznosci od kategorii dla zakupów grupowych?

Średnia suma zakupów grupowych dla kategorii produktów jest dużo bardziej zbliżona, niż w przypadku zakupów indywidualnych.

dane <- cafe_pg %>% filter(total<10000 & groups==1)

ggbetweenstats(
  data = dane,
  x = category,
  y = total,
  title = "Total per kategorie",
  pairwise.display = "significant",
  subtitle= "Suma rachunku dla zakupów grupowych"
)

Czy rozkład kategorii różni się w zależności od godziny?

Zakupy z kategorii FOOD i BEVERAGE zaczynają się najwceśniej. Zakupy z kategorii LIQUOR i TOBACCO oraz WINE zaczynają się najpóźniej. Najpóźniej sprzedaje się artykuł z kategorii TOBACCO. Widać wyraźny wzrost zakupów Wina i Alkoholi w godzinach popołudniowych.

dane <- cafe_pg %>% filter(total<14000)

ggbetweenstats(
  data = dane,
  y = godzina, # musimy utworzyc zmienna godzina
  x = category,
  title = "Kategorie względem czasu",
  pairwise.display = "significant",
  theme(axis.text.x = element_text(angle = 25, hjust = 0.7))
)

Czy total się różni w zależnosci od miesiąca?

Średnie wartości zakupów w różnych miesiącach są bardzo porównywalne.

dane <- cafe_pg %>% filter(total<2000)

ggbetweenstats(
  data = dane,
  y = total,
  x = miesiac,
  pairwise.display = "none",
  title = "Total względem miesięcy"
)

Podsumowanie i rekomendacje

Dogłębna analiza danych sprzedażowych CafePG wykazała, że kawiarnia bardzo dobrze prosperuje. W 2020 roku dochód całkowity wyniósł niespełna 33 mln.

Kawiarnia osiąga największy przychód ze sprzedaży wyrobów tytoniowych (około 45% ogółu przychodów), na drugim miejscu uplasowało się jedzenie, a na trzecim napoje. W celu zwiększenia sprzedaży alkoholi, na których marża może być bardzo wysoka, warto byłoby zaproponować promocję na wyrób tytoniowy + alkohol lub wino.

Najmniejszy przychód przynosi sprzedaż win oraz gadżetów. Warto przeanalizować koszty produkcji oraz magazynowania gadżetów, być może ich sprzedaż nie jest rentowna i należałoby zrezygnować z tej kategorii produktów. Warto również przeprowadzić ankietę wśród klientów w celu zrozumienia, dlaczego wina nie cieszą się dużą popularnością. Być może oferowane wina są zbyt drogie, lub typowi klienci CafePG nie lubują się w tego typu trunkach i można ograniczyć ich dostępność w celu minimalizowania kosztów.

3 najpopularniejsze produkty to Nirvana Hookah Single, Cappuccino oraz Mint Flavour Single. Największy przychód generuje sprzedaż Nirvana Hookah Single, Sambuki oraz Mint Flavour Single.

Największe zarobki osiągano w czwartki. W związku z czym, warto skorzystać z faktu, że w czwartki wizyty w CafePG są bardzo popularne. W czwartki liczba pracowników na zmianie powinna zostać zwiększona W celu maksymalizacji możliwej liczby obsłużonych klientów, co korzystnie wpłynie na przychód.

Najniższe przychody odnotowano w poniedziałki i wtorki. W celu zachęcenia klientów do odwiedzania CafePG w te dni można zorganizować w tych dniach dodatkowe atrakcje, na przykład koncerty na żywo lub pub quizy.

Około 21 godziny zaobserwowano spowolnienie ruchu klientów, to dobry moment aby wprowadzić promocje typu “happy hour” i utrzymać wyższy ruch klientów.

W sobotę i w niedzielę wzrasta sprzedaż wina, a spada sprzedaż innych alkoholi - aby podbić sprzedaż innych rodzajów alkoholi sugerujemy wprowadzenie promocji na pozostałe rodzaje alkoholi.

W sobotę spada średnia sprzedaż gadżetów, można ją wzmocnić poprzez wprowadzenie promocji wino + gadżet za 50%.

Analiza kombinacji, które klienci wybierają najczęściej wykazała, że bardzo popularne jest zamawianie BMT Panini z innym produktem kategorii FOOD oraz Sambucą. Można zatem zaproponować menu składające się z dwóch potraw i Sambuki.

Jeśli chodzi o kombinacje dwuelementowe, to najczęściej kupowanymi kombinacjami są NIRVANA HOOKAH SINGLE i POUTINE WITH FRIES. Można zatem założyć, że dobrą propozycją byłoby stworzenie zestawów składających się z dania z kategorii FOOD oraz produktu z kategorii TOBBACO.