Ile kosztuje wycieczka do Grecji? Czy last minute jest opłacalne i kiedy cena zaczyna spadać? Jak znaleźć najbardziej atrakcyjne miejsce na wypoczynek (w Grecji)?
Jakiś czas temu analizowałem raporty publikowane przez Polski Związek Organizatorów Turystyki. Z analizy wyszło, że najchętniej jeździmy na wypoczynek do Grecji (tak było w drugiej połowie 2017 roku). Postanowiłem więc sprawdzić ile kosztuje taki wyjazd.
Zebranie danych
Aby cokolwiek analizować trzeba mieć dane. Jeśli nie mamy dostępu do baz oraz nie możemy (albo nie chcemy) ich kupić to można pokusić się o ich zebranie z sieci. Założeniem tego bloga jest to, aby kolejne analizy przygotowywać bez opłat (nie licząc czasu i kosztów typu serwery), zatem dane weźmiemy sobie sami.
Kryteria wyszukiwania
Skoro Grecja najpopularniejsza to niech będzie Grecja. Skoro pomysł pojawił się pod koniec kwietnia to niech będzie trochę czasu na zebranie danych – stąd termin wycieczki. Do tego dodajmy mniej więcej średnią polską rodzinę typu 2+2: dwoje dorosłych oraz dwoje dzieci, w tym jedno potrzebujące własnego łóżka, a drugie mieszczące się w tańszej taryfie (czasem za darmo). Przedszkolak i uczeń podstawówki.
Podsumowując – kryteria są następujące:
- kraj docelowy: Grecja
- wyjazd od 1.09, powrót do 30.09
- samolotem z Warszawy
- dwoje dorosłych, dwoje dzieci (4 i 7 lat)
- standard All inclusive
Źródło danych
Wiemy czego potrzebujemy, skąd to teraz zebrać? Najprościej z jakiegoś serwisu internetowego sprzedającego wycieczki (bo będzie tam w miarę ujednolicona forma prezentacji, a przede wszystkim – będą różni tour-operatorzy). Wybór padł na Wakacje.pl.
Nie, Wakacje.pl nie są sponsorem tego wpisu.
Sponsorem nie jest też żaden z organizatorów wycieczek, żaden rejon Grecji ani greckie miasto.
Nie ma sponsora. Po prostu.
Nasz skrypt zbierający dane będzie wchodził na stronę, zadawał odpowiednie pytanie w wyszukiwarce, co wygeneruje odpowiedni link do strony z wynikami. I z tego gotowego linku korzystamy za każdym razem (zamiast wypełniać formularze). Strona z wynikami jest podzielona na ileś tam ofert za każdym razem (tyle, ile dostępnych jest wycieczek) – wystarczy je wszystkie przeczytać. To już było grane przy szukaniu mieszkań na wynajem czy samochodów, tutaj działa dokładnie tak samo.
Miejsce na dane
Dane zebrane można zapisywać w plikach, ale można też zapisywać do bazy danych. To ostatnie rozwiązanie jest o tyle przyjemne, że baza sobie przyrasta, a raz napisany skrypt po prostu bierze aktualny stan i wyrzuca odpowiedni raport. Skrypt pobierający dane zapisywał je do bazy PostgreSQL.
Pobranie danych – skrypt i cron
Nie będę tutaj przytaczał całego skryptu (jest dość nudny i mało rozwojowy), zainteresowani znajdą go na moim GitHubie. Swoją drogą zebrane dane też tam wpadły (jako plik RDS), być może komuś się przydadzą? W analizie wykorzystam plik statyczny a nie bazę – różnica jest żadna (bo plik jest wyeksportowany z bazy odpytanej przez SELECT * FROM tabela;
).
Dobrze, ale skrypt pobierający dane działa tak, że w momencie uruchomienia przechodzi przez oferty i zapisuje je sobie w bazie. Ale chcemy sprawdzić czy oferty zmieniają się z upływem czasu! Zatem trzeba wszystkie przechodzić na przykład codziennie. No to dodajemy uruchomienie skryptu do cron
a (w systemach linuksowych; crontab -e
i dopisujemy poniższą linijkę na koniec):
1 |
3 2 * * * Rscript /home/lemur/RProjects/wycieczki_grecja/get_data.R |
i tyle. Czekamy aż skończy się wrzesień (bo najpóźniejsza wycieczka według naszych kryteriów może się zakończyć z końcem tegoż miesiąca).
Warto co jakiś czas rzucić okiem czy wszystko działa, czy dane dopisują się do bazy, czy nie ma błędów. Ja czasem nie sprawdziłem i powstały dziury w ciągłości danych – są dni, z których brakuje informacji. Przyczyna była dość prosta: niektóre ogłoszenia miały dodatkowe parametry, które powodowały że tabelki z przeczytanymi ze strony danymi się rozsypywały. Wystarczyło dodać precyzyjne wybieranie kolumn, a nie liczyć na to że zawsze będzie tak samo.
Analiza
Mamy dane, przystępujemy więc do dzieła.
1 2 3 4 5 6 7 |
library(tidyverse) library(lubridate) library(glue) library(ggmap) # wczytanie danych z pliku statycznego wycieczki_db <- readRDS("wycieczki_all.RDS") |
Jakie parametry zebraliśmy?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
glimpse(wycieczki_db) ## Observations: 70,384 ## Variables: 12 ## $ termin_wycieczki <date> 2018-09-18, 2018-09-15, 2018-09-14, 2018-09-1… ## $ wyzywienie <chr> "All Inclusive", "All Inclusive", "All Inclusi… ## $ organizator <chr> "TUI", "Grecos", "TUI", "Grecos", "TUI", "TUI"… ## $ region <chr> "Zakynthos", "Korfu", "Zakynthos", "Kreta", "K… ## $ miejscowosc <chr> "Planos", "Acharavi", "Argassi", "Kato Gouves"… ## $ ocena_wycieczki <dbl> 8.1, 6.9, 8.2, 7.7, 8.7, 9.1, 9.5, 8.1, 9.1, 8… ## $ hotel_nazwa <chr> "Admiral Tsilivi", "Gelina Village Resort & Sp… ## $ hotel_gwiazdki <dbl> 4.0, 4.5, 3.0, 4.0, 5.0, 4.0, 5.0, 4.0, 4.0, 4… ## $ id_wycieczki <chr> "206647", "472888", "374018", "472939", "46648… ## $ liczba_rezerwacji <dbl> 657, 1754, 1804, 890, 406, 313, 531, 414, 823,… ## $ cena_za_osobe <dbl> 2908, 2700, 2604, 2600, 2979, 3607, 3789, 2430… ## $ data_aktualizacji <date> 2018-05-16, 2018-05-16, 2018-05-16, 2018-05-1… |
Mamy termin wycieczki, standard (‘wyzywienie’), organizatora, rejon i miejscowość razem z nazwą hotelu. Liczbę gwiazdek będącą oceną hotelu, ocenę wycieczki (nie wiem skąd ona pochodzi, jest niezmienna w czasie dla poszczególnych wycieczek). ID oferty, liczbę rezerwacji danej oferty w każdym z kolejnych dni, cenę za osobę oraz datę aktualizacji.
Liczba dostępnych wycieczek według daty
W pierwszej kolejności zobaczmy ile wycieczek udało się zebrać w kolejnych dniach:
1 2 3 4 5 6 7 |
wycieczki_db %>% count(data_aktualizacji) %>% ggplot(aes(data_aktualizacji, n)) + geom_col(fill = "lightgreen") + geom_smooth(se = FALSE, color = "darkgreen") + labs(title = "Liczba zebranych ofert", x = "Data pobrania danych", y = "Liczba zebranych ofert") |
Widać wyraźnie dziury, w których skrypt się wywalał. Szkoda, że przypadło to akurat na drugą połowę sierpnia – tuż przed rozpoczęciem okresu jaki nas interesował. Nie chodzi nawet o ilość ofert zebranych w tych dniach, a o parametry (głównie ceny) tych ofert.
Z grubsza widać że im bliżej terminu wyjazdu tym mniej ofert. To dość oczywiste – po prostu wycieczki się wyprzedają.
Gdzie są miejsca docelowe? (mapka)
Zobaczmy dokąd wiozą tour operatorzy. Do narysowania konturów Grecji wykorzystamy dane zawarte w pakiecie ggmap
:
1 |
greece_map <- map_data('world') %>% filter(region == "Greece") |
Ale aby na mapie zaznaczyć punkt potrzebujemy jego współrzędnych. Mamy tylko region i nazwę miejscowości, potrzebujemy przetłumaczyć to na współrzędne geograficzne. Z pomocą przychodzi funkcja mutate_geocode()
z pakietu ggmap
.
1 2 3 4 5 6 7 8 9 10 11 |
# unikalne miejscowości miejsca <- wycieczki_db %>% distinct(region, miejscowosc) # szukamy ich współrzędnych miejsca <- miejsca %>% mutate(location = paste0(.$miejscowosc, ", ", .$region, ", Greece")) %>% mutate_geocode(location, source = "dsk", messaging = FALSE) %>% # ograniczamy wyniki do znalexionych mniej więcej w obszarze Grecji filter(lon >= 15, lon <= 35, lat >= 20) %>% # i tych, które mają jakieś dane w ogóle na.omit() |
Chwilę to trwa, w końcu musimy odpytać API o 231 miejsc. Na koniec możemy to sobie narysować na mapce:
1 2 3 4 5 6 7 |
ggplot() + geom_polygon(data = greece_map, aes(long, lat, group=group), fill = "#e5f5e0", color = "black") + geom_point(data = miejsca, aes(lon, lat, color = region)) + coord_quickmap() + theme(axis.text.x = element_blank(), axis.text.y = element_blank()) + labs(title = "Dokąd wożą tour operatorzy?", x = "", y = "") |
Oceny według regionów
Czy wycieczki do konkretnych miejscowości (miasta) różnią się oceną? Jeśli tak – wskazywałoby to w uproszczeniu, że jedne rejony są bardziej atrakcyjne niż inne. Sprawdźmy na mapie, dodając punktom z mapki powyżej kolor zależny od średniej oceny wycieczki:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
oceny <- left_join(miejsca, wycieczki_db %>% select(region, miejscowosc, ocena_wycieczki), by = c("region"="region", "miejscowosc"="miejscowosc")) %>% group_by(lon, lat) %>% summarise(mocena = mean(ocena_wycieczki, na.rm = TRUE)) %>% ungroup() %>% filter(!is.na(mocena)) %>% mutate(mocena_round = round(mocena)) ggplot() + geom_polygon(data = greece_map, aes(long, lat, group=group), fill = "#e5f5e0", color = "#bdbdbd") + geom_point(data = oceny, aes(lon, lat, color = mocena), size = 3) + scale_color_gradient(low="#fde0dd", high="#de2d26") + coord_quickmap() + facet_wrap(~mocena_round) + labs(title = "Ocena miejsc na podstawie ocen wycieczek", x = "", y = "", color = "Średnia\nocena\nwycieczek\nw mieście") |
Przede wszystkim widać, że najwięcej jest ocen 7 i 8 – na tych panelach mamy najwięcej punktów. To naturalne, wszędzie tak jest (czy to o oceny wycieczek czy np. filmów chodzi). Czy z powyższych map można wyczytać, że jedne rejony są bardziej atrakcyjne niż inne? Według mnie nie…
Możemy to sprawdzić i policzyć ocenę według regionów:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# tabela z finalnymi informacjami o wycieczce wycieczki_uniq <- wycieczki_db %>% # bierzemy tylko najnowszse dane dla każdej wycieczki group_by(id_wycieczki) %>% filter(data_aktualizacji == max(data_aktualizacji )) %>% # wycieczka musi miec ocenę filter(!is.na(ocena_wycieczki)) %>% ungroup() wycieczki_uniq %>% # grupujemy wycieczki po regionach group_by(region) %>% # i liczymy ile wycieczek jest do danego regionu oraz jaka jest średnia ocena summarise(n = n(), sr_ocena = round(mean(ocena_wycieczki, na.rm = TRUE), 2)) %>% ungroup() %>% # całośc układamy od najlepszej śrdeniej oceny arrange(desc(sr_ocena)) |
Region | n | Średnia ocena |
---|---|---|
Zatoka Koryncka | 4 | 8.38 |
Kos | 54 | 8.28 |
Attyka | 4 | 8.22 |
Evia | 3 | 8.20 |
Lesbos | 2 | 8.00 |
Kefalonia | 3 | 7.77 |
Kreta | 316 | 7.72 |
Rodos | 143 | 7.71 |
Skiathos | 1 | 7.70 |
Peloponez | 8 | 7.69 |
Zakynthos | 73 | 7.39 |
Riwiera Olimpijska | 13 | 7.35 |
Thassos | 5 | 7.30 |
Chalkidiki | 35 | 7.27 |
Korfu | 76 | 7.26 |
Skopelos | 3 | 6.73 |
Lefkada | 1 | 6.70 |
Karpathos | 1 | 5.50 |
Najlepiej wypada Zatoka Koryncka i wyspa Kos. Pytanie czy nie powinniśmy waży ocen liczbą (kolumna n) wycieczek do danego regionu? W końcu im więcej ocen tym bardziej stabilny wynik… Na Filmwebie ocena średnia stabilizuje się po zdaje się około 150-200 oddanych głosów (badałem, możecie zaufać).
Oceny według tour operatorów
Dokładnie taką samą tabelę możemy przygotować dla porównania tour operatorów:
1 2 3 4 5 6 7 8 9 |
wycieczki_uniq %>% # grupujemy wycieczki po regionach group_by(organizator) %>% # i liczymy ile wycieczek jest do danego regionu oraz jaka jest średnia ocena summarise(n = n(), sr_ocena = round(mean(ocena_wycieczki, na.rm = TRUE), 2)) %>% ungroup() %>% # całośc układamy od najlepszej śrdeniej oceny arrange(desc(sr_ocena)) |
Organizator | n | Średnia ocena |
---|---|---|
TUI | 118 | 8.29 |
Neckermann | 59 | 7.99 |
Grecos | 89 | 7.91 |
Net Holiday | 22 | 7.70 |
Mouzenidis Travel | 80 | 7.53 |
Itaka | 91 | 7.50 |
Easy Travel | 4 | 7.47 |
Wezyr | 99 | 7.47 |
Ecco Holiday | 6 | 7.35 |
Orex Travel | 26 | 7.35 |
Exim Tours | 34 | 7.28 |
7islands | 25 | 7.24 |
Rainbow | 67 | 7.19 |
Prima Holiday | 7 | 7.13 |
Sun&Fun | 9 | 7.13 |
Onholidays | 7 | 7.06 |
Millennium Travel | 2 | 5.80 |
Najlepiej ocenianymi organizatorami są ci najwięksi gracze: TUI, Neckermann. Będziemy się im jeszcze przyglądać, spokojnie.
Zmiana cen w czasie
Teraz to co najciekawsze chyba – czy im bliżej wycieczki tym niższa jest jej cena?
1 2 3 4 5 6 7 8 9 10 11 |
wycieczki_db %>% group_by(data_aktualizacji, region) %>% summarize(sr_cena = mean(cena_za_osobe, na.rm = TRUE)) %>% ungroup() %>% ggplot(aes(data_aktualizacji, sr_cena)) + geom_point(color = "lightgreen", alpha = 0.8) + geom_smooth(color = "darkgreen") + facet_wrap(~region, scales = "free") + labs(title = "Średnia cena za osobę wycieczki do Grecji, według regionów", subtitle = "Parametry wycieczki: wyjazd 1-30.09, samolotem z Warszawy, 2 dorosłych, 2 dzieci (4 i 7 lat), All inclusive\nDane zebrane w okresie maj-wrzesień 2018 z serwisu Wakacje.pl", x = "Data oferty", y = "Średnia cena/osobę") |
Zwróć uwagę, że skala na osi Y dla każdego z wykresów jest inna – czasem cena spada o 1500 zł (patrz Peloponez), czasem tylko o 200 zł (Lesbos). Bez względu na różnicę w cenie wniosek jest jeden: im bliżej terminu wyjazdu tym niższa cena. Zatem istnieje coś takiego jak last minute i rzeczywiście z grubsza biorąc są to atrakcyjne cenowo oferty.
W każdym razie: na około 50-60 dni przed wycieczką cena zaczyna spadać.
Innym zagadnieniem jest to, czy pozostają jeszcze miejsca na upatrzonej wycieczce, czy też się wyprzedały…
Jak szybko znikają wycieczki?
Możemy to sprawdzić na podstawie liczby rezerwacji. Zobaczmy na totalu – skumulowana liczba rezerwacji dla wszystkich wycieczek w zależności od liczby dni przed wycieczką przedstawia się następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
wycieczki_db %>% mutate(dni_przed_wycieczka = termin_wycieczki - data_aktualizacji) %>% group_by(dni_przed_wycieczka) %>% summarise(rez = sum(liczba_rezerwacji, na.rm = TRUE )) %>% ungroup() %>% arrange(-dni_przed_wycieczka) %>% mutate(rez = cumsum(rez)) %>% ggplot() + geom_smooth(aes(dni_przed_wycieczka, rez)) + scale_y_log10() + scale_x_reverse() + labs(title = "Skumulowana liczba rezerwacji", x = "Liczba dni do wyjazdu", y = "Liczba rezerwacji (skala logarytmiczna)") |
Skala Y jest logarytmiczna dzięki czemu lepiej widać dynamikę. Co tutaj widać? Ano to, że im bliżej wycieczek tym mniej przybywa rezerwacji. Nie wiemy ile jest wszystkich miejsc (gdzie jest górna granica), ale zakładając, że na koniec wszystkie są sprzedane to będzie to około 10 milionów co jest liczbą zaskakującą. Najwięcej było 3047 rezerwacji dla jednej wycieczki, było to w dniu wyjazdu. Skąd więc 10 mln? Tę samą wycieczkę liczymy kilkadziesiąt razy, z różną liczbą rezerwacji dzień po dniu. I to jest pułapka albo i błąd.
Jeśli weźmiemy rozkład liczby rezerwacji w zależności od czasu przed wyjazdem, to wygląda on następująco:
1 2 3 4 5 6 7 8 9 10 11 |
wycieczki_db %>% mutate(dni_przed_wycieczka = termin_wycieczki - data_aktualizacji) %>% group_by(dni_przed_wycieczka) %>% summarise(rez = sum(liczba_rezerwacji, na.rm = TRUE )) %>% ungroup() %>% arrange(-dni_przed_wycieczka) %>% ggplot() + geom_smooth(aes(dni_przed_wycieczka, rez)) + scale_x_reverse() + labs(title = "Liczba rezerwacji", x = "Liczba dni do wyjazdu", y = "Liczba rezerwacji") |
i to jest bardziej prawdopodobne: na około 50 dni przed wycieczką albo dana impreza jest w całości wyprzedana i znika z ogłoszeń, albo pozostają jakieś spady.
Łączna liczba rezerwacji w dostępnych danego dnia wycieczkach wygląda następująco:
1 2 3 4 5 6 7 8 |
wycieczki_db %>% group_by(data_aktualizacji) %>% summarise(rez = sum(liczba_rezerwacji, na.rm = TRUE )) %>% ungroup() %>% ggplot() + geom_smooth(aes(data_aktualizacji, rez)) + labs(title = "Liczba rezerwacji w dostępnych ofertach", x = "Data", y = "Liczba rezerwacji") |
co może potwierdzać powyższą tezę. Ile jest dostępnych miejsc? Stawiałbym na okolice szczytu tego ostatniego wykresu, czyli jakieś 12-13 tysięcy (dla wybranych parametrów wyjazdu oczywiście).
Jak wybrać najlepszą wycieczkę?
Przede wszystkim musimy określić co jest dla nas ważne w wyborze wycieczki? Musimy ograniczyć się do posiadanych danych, a do wyboru mamy właściwie tylko trzy cechy liczbowe (ciągłe) opisujące wycieczki:
- ocena hotelu
- ocena wycieczki
- cena
Dodatkowo możemy na podstawie tych cech wybrać najlepiej ocenianych tour operatorów, regiony, miejscowości czy nawet hotele (kolejne cztery cechy, tym razem kategoryczne). Zbudujmy więc prosty model oceny.
W pierwszej kolejności jednak przyda nam się funkcja normalizująca (dziwi mnie, że nie ma jej w standardowych bibliotekach R) – która cały zbiór liczb sprowadzi do zakresu 0-1.
1 |
normalize <- function(x) { return ((x - min(x))/(max(x)-min(x))) } |
Teraz policzmy średnie z ocen, ceny i liczby gwiazdek hotelu dla organizatorów i miejscowości (regionów co za tym idzie):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# ważność składowych w modelu waga_gwiazdki <- 3 waga_ocena <- 6 waga_cena <- 4 waga_liczba_wycieczek <- 1 # wyliczenie oceny wg modelu df <- wycieczki_uniq %>% group_by(organizator, region, miejscowosc) %>% summarise(sr_gwiazdki = mean(hotel_gwiazdki, na.rm = TRUE), sr_ocena_wycieczki = mean(ocena_wycieczki, na.rm = TRUE), n_wycieczek = n(), sr_cena_za_osobe = mean(cena_za_osobe, na.rm = TRUE)) %>% ungroup() %>% na.omit() %>% mutate(sr_gwiazdki_scale = normalize(sr_gwiazdki), sr_ocena_wycieczki_scale = normalize(sr_ocena_wycieczki), sr_cena_za_osobe = 1 - normalize(sr_cena_za_osobe)) %>% mutate(ocena = normalize(waga_gwiazdki*sr_gwiazdki * waga_ocena*sr_ocena_wycieczki * waga_cena*sr_cena_za_osobe * waga_liczba_wycieczek*log10(n_wycieczek+1))) %>% mutate(ocena_przedzial = cut(ocena, breaks = seq(-0.1, 1.1, 0.1))) |
Wyliczone średnie zostały znormalizowane, a dodatkowo wyższą ocenę dostają wycieczki tańsze (stąd 1 – znormalizowana średnia cena). Ostatnie dwie linijki powyższego kodu to:
- zbudowanie jednej wartości opisującej finalną ocenę – składa się na nią:
- średnia z gwiazdek (z wagą 3)
- średnia z ocen (z wagą 6)
- średnia cena (z wagą 4)
- oraz zlogarytmowana liczba wycieczek (z wagą 1). To ostatnie dlatego, że być może im więcej wycieczek tym bardziej atrakcyjne miejsce? Jakoś warto to uwzględnić
- przypisanie oceny finalnej do przedziału co 0.1 punktu (przyda się za chwilę)
Na wykresie pokażmy zależność oceny finalnej od jakości hotelu (liczba jego gwiazdek) i oceny wycieczki:
1 2 3 4 5 6 7 8 9 |
ggplot(df) + geom_point(aes(sr_ocena_wycieczki, sr_gwiazdki, color = ocena, size = ocena), alpha = 0.8) + coord_equal() + scale_size(range = c(0.5, 5)) + scale_color_gradient(low="#fde0dd", high="#de2d26") + labs(title = "Ocena finalna wycieczki według założonego modelu", x = "Średnia ocena wycieczki", y = "Liczba gwiazdek hotelu") |
Gdyby patrzyć tylko na ocenę wycieczki i hotelu interesowałyby nas te najlepsze – z górnej prawej strony. Ale dodatkowym kryterium jest cena, co odbija się w finalnej ocenie zaprezentowanej kolorem i rozmiarem punktu na wykresie. Najbardziej interesujące są więc najbardziej czerwone punkty, a im bardziej ten czerwony w prawo i w górę tym w sumie jeszcze lepiej.
Wykorzystajmy tak wyliczone oceny do wyboru najlepszych organizatorów:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
df %>% group_by(organizator, ocena_przedzial) %>% summarise(n = n()) %>% ungroup() %>% group_by(organizator) %>% mutate(p = n/sum(n)) %>% ungroup() %>% filter(p != 0) %>% ggplot() + geom_tile(aes(ocena_przedzial, organizator, fill = p), color = "gray80", show.legend = FALSE) + geom_text(aes(ocena_przedzial, organizator, label = sprintf("%.0f%%", 100*p))) + scale_fill_distiller(palette = "GnBu") + labs(title = "Ocena według organizatorów", x = "Przedział ocen", y = "") |
Wykres pokazuje udział procentowy danej oceny (przypisanej do przedziału) dla poszczególnych organizatorów. I tak np. widzimy, że Sun & Fun w 2/3 dostaje oceny (te nasze finalne, według modelu) na poziomie 0.2-0.3 punktu. Interesującym organizatorem jest w moim odczuciu ktoś, kto ma oceny ponad 0.8, a więc:
1 |
wybrane_organizator <- c("Wezyr", "TUI", "Rainbow", "Grecos") |
W analogiczny sposób wybierzmy najlepiej ocenione (wg modelu) regiony:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
df %>% group_by(region, ocena_przedzial) %>% summarise(n = n()) %>% ungroup() %>% group_by(region) %>% mutate(p = n/sum(n)) %>% ungroup() %>% filter(p != 0) %>% ggplot() + geom_tile(aes(ocena_przedzial, region, fill = p), color = "gray80", show.legend = FALSE) + geom_text(aes(ocena_przedzial, region, label = sprintf("%.0f%%", 100*p))) + scale_fill_distiller(palette = "GnBu") + labs(title = "Ocena według regionów", x = "Przedział ocen", y = "") |
Tutaj wybieramy trójkę z najlepszymi ocenami – są to:
1 |
wybrane_region <- c("Kreta", "Rodos", "Kos") |
Zobaczmy jeszcze w tabeli top 10 ocenianych (wg modelu) razem ze składowymi:
1 2 3 4 5 6 |
df %>% top_n(10, ocena) %>% arrange(desc(ocena)) %>% select(organizator, region, miejscowosc, ocena, sr_gwiazdki, sr_ocena_wycieczki, sr_cena_za_osobe) %>% kable() %>% kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive")) |
Organizator | Region | Miejscowość | Ocena | Średnia gwiazdki | Średnia ocena wycieczki | Średnia cena za osobę |
---|---|---|---|---|---|---|
TUI | Kreta | Hersonissos | 1.0000000 | 4.142857 | 8.385714 | 0.6214625 |
TUI | Kos | Kardamena | 0.9483807 | 4.583333 | 8.833333 | 0.7038263 |
Rainbow | Kreta | Chania | 0.8339369 | 3.875000 | 7.175000 | 0.7981358 |
Wezyr | Kreta | Hersonissos | 0.8330704 | 3.727273 | 7.236364 | 0.7267277 |
Grecos | Rodos | Faliraki | 0.8235885 | 4.300000 | 8.020000 | 0.7792905 |
TUI | Rodos | Ixia | 0.8171824 | 4.166667 | 8.433333 | 0.6987468 |
Grecos | Kreta | Hersonissos | 0.7946298 | 4.100000 | 8.560000 | 0.7388209 |
Grecos | Kos | Psalidi | 0.7924064 | 4.500000 | 8.275000 | 0.7730452 |
Orex Travel | Kreta | Hersonissos | 0.7674433 | 3.875000 | 7.525000 | 0.7003341 |
TUI | Rodos | Faliraki | 0.7560857 | 4.166667 | 8.133333 | 0.6703514 |
Widzimy, że regiony i organizatorzy pokrywają się z tym co wybraliśmy na podstawie wykresów. Dorzućmy jeszcze listę wybranych miast:
1 |
wybrane_miejscowosc <- c("Hersonissos", "Chania", "Faliraki", "Ixia", "Kolymbia", "Kiotari", "Georgioupolis") |
Teraz z wybranych regionów, miast i organizatorów wybieramy najlepszą ofertę spośród wszystkich wycieczek:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
wycieczki_db %>% filter(organizator %in% wybrane_organizator, region %in% wybrane_region, miejscowosc %in% wybrane_miejscowosc) %>% group_by(id_wycieczki) %>% filter(data_aktualizacji == max(data_aktualizacji), !is.na(hotel_gwiazdki), !is.na(ocena_wycieczki), !is.na(cena_za_osobe)) %>% ungroup() %>% mutate(gwiazdki_scale = normalize(hotel_gwiazdki), ocena_wycieczki_scale = normalize(ocena_wycieczki), cena_za_osobe_scale = 1- normalize(cena_za_osobe)) %>% mutate(ocena = normalize(3*gwiazdki_scale * 6*ocena_wycieczki_scale * 4*cena_za_osobe_scale)) %>% select(organizator, region, miejscowosc, hotel_nazwa, hotel_gwiazdki, ocena_wycieczki, ocena, cena_za_osobe) %>% top_n(10, ocena) %>% arrange(desc(ocena)) %>% select(organizator, region, miejscowosc, hotel_nazwa, ocena, hotel_gwiazdki, ocena_wycieczki, cena_za_osobe) |
Organizator | Region | Miejscowość | Nazwa hotelu | Ocena | Gwiazdki hotelu | Ocena wycieczki | Cena za osobę |
---|---|---|---|---|---|---|---|
Grecos | Rodos | Kiotari | Princess Andriana | 1.0000000 | 5.5 | 9.3 | 3930 |
Grecos | Rodos | Kiotari | Mitsis Rodos Maris | 0.9126209 | 5.0 | 8.7 | 3100 |
TUI | Kreta | Hersonissos | Aldemar Cretan Village | 0.9108498 | 4.5 | 9.2 | 2739 |
Rainbow | Kreta | Chania | Kiani Beach | 0.9074702 | 5.0 | 8.7 | 3127 |
Grecos | Rodos | Faliraki | Mitsis Alila Resort & Spa | 0.8677997 | 5.5 | 9.4 | 4520 |
TUI | Rodos | Ixia | Atlantica Princess | 0.8234121 | 4.5 | 9.1 | 3136 |
Rainbow | Kreta | Hersonissos | The A Hotels Serita Beach Resort | 0.7812270 | 5.0 | 8.0 | 3014 |
Rainbow | Kreta | Chania | Kalyves Beach | 0.7796192 | 4.5 | 8.4 | 2621 |
Grecos | Rodos | Faliraki | Mitsis Faliraki Beach | 0.7733796 | 5.0 | 8.8 | 3920 |
TUI | Rodos | Ixia | Oceanis Park | 0.7702580 | 4.0 | 9.7 | 2949 |
Wygrywa hotel Princess Andriana odwiedzony z Grecos – na Wakacje.pl była to jedna z ofert.
Czy ten model jest najlepszy? Może wagi nie są dobrane optymalnie? Warto popróbować zmienić wagi, może sposób ich liczenia (nie mnożyć a dodawać ważone składowe). Zaproponowałem jedno rozwiązanie, możecie wypracować inne.
Największy problem modelowania tego typu to brak informacji zwrotnej od uczestników konkretnych wycieczek. Bo nie do końca wiemy co oznacza ocena wycieczki w serwisie Wakacje.pl (może i oni gdzieś to tłumaczą, ale nie szukałem :P). Nie mamy prawdy, nie możemy uczyć i testować modelu, bo nie mamy punktu odniesienia. Ostatnio robię kilka takich projektów i to jest największy w nich problem: czy te liczby, które wychodzą są prawdziwe?
A gdyby tak nie z tour operatorem?
Abstrahując od tego wszystkiego – coś policzyliśmy, coś wybraliśmy. Średnia cena za osobę dla najlepszej wybranej wyżej wycieczki to prawie 4 tysiące złotych (obecnie cena jest niższa, ale jesteśmy po sezonie). Dla rodziny 2+2 to już robi się 16 tysięcy. Zgodnie z informacją w ofercie jest to 7 dni. W tym mamy przelot, zakwaterowanie, jedzenie i jakieś hotelowe atrakcje.
Jakby trochę przyjanuszyć i zorganizować wszystko na własną rękę mamy (wynik 30 minut szukania, można postarać się o wiele bardziej) – dla tygodniowej wizyty w Kiotari:
- samolot z Warszawy w obie strony na Rodos dla gromady 2+2 kupiony teraz na 19-26 stycznia (czyli poza sezonem świątecznym i kupując bilety odpowiednio wcześnie) to około 2100 zł (z jedną przesiadką max 2h w Atenach na obu lotach)
- wynajęcie samochodu typu Compact SUV na ten tydzień razem z dwoma fotelikami to jakieś 400 euro (wg sieci Avis) czyli powiedzmy 1750 zł. Samochód po to, aby dojechać na drugą stronę wyspy (z lotniska do Kiotari) i swobodnie się po niej poruszać
- całe mieszkanie znalezione na AirBnB to niech będzie 300 zł za dobę (można i taniej) – 2100 zł
- porównując ceny za pomocą MacIndex (lokalna cena Big Maca) mamy 3.84 euro w Grecji do 2.69 euro w Polsce, zatem możemy przyjąć że ceny są około 1.4 razy wyższe. Obiad dla rodziny 2+2 w Warszawie w przyzwoitej restauracji to powiedzmy 150-200 zł. Śniadanie i kolacja niech będzie drugie tyle (z rzeczy kupionych w sklepie). Daje to 600 zł (już po przeliczeniu na ceny greckie) na posiłki dziennie, 4200 zł na tydzień
- w sumie mamy około 10 tysięcy zł, jakieś 2/3 ceny wycieczki
To wersja dla wolących wolność, nie lubiących hotelowych basenów zapchanych po brzegi. Osobiście wolę wyprawy na własną rękę niż z biurem podróży.
Szukanie na AirBnB to osobny rozdział. Na ten temat jest masa ciekawych tekstów w idei podobnych do powyższego, z w miarę nowych polecam Exploring & Machine Learning for Airbnb Listings in Toronto z przykładami w Pythonie.
Te wycieczki takie drogie… to ja może już zacznę zbierać na kolejne wakacje ;-). Jak zwykle zapraszam do polubienia fanpage’a Dane i Analizy.
W dodatku wycieczka może się nie udać i urlop zmarnowany. W takim przypadku można spróbować odzyskać pieniądze, pomocna może być na przykład kancelaria Kempa i Wspólnicy, która specjalizuje się w takich sprawach. Śledźcie ich fanpage, tam więcej o wakacjach z biurem podróży, bez stresu.
A czy nie jest tak, że cena wycieczki w okolicach wrzesnia spada ponieważ konczy sie sezon turystyczny w Grecji i ogolnie w Europie?
Last minute to z tego co wiem okreslenie na wycieczke kupowana przez samym terminem wylotu, wiec moim zdaniem wnioski o oplacalnosci lastów moznaby wyciagac w szczycie sezonu , obserwujac po prostu cene wraz ze zblizajacym sie terminem.
Super wpis. Często organizuję wyjazdy na kite, bardzo przydatne informacje. Pozdrawiam.
Bardzo ciekawe zestawienie dla osób, które planują odwiedzić Grecję, można znaleźć tutaj praktycznie porady poparte dokładną analizą. Na pewno wiele osób skorzysta z tego, kiedy będzie zastanawiać się nad wybraniem w te rejony.