Komunikowanie sprzedaży za pomocą wizualizacji

W tej części zbadamy bardzo ważny i przydatny aspekt analizy danych, wizualizacji danych. Pokażemy, jak tworzyć funkcje wykresów, które są funkcjami, które hermetyzują proces tworzenia wykresu i wyświetlają obiekt wykresu, który można zobaczyć lub zapisać na dysku. Praca z wykresami w ten sposób zwiększa wydajność, dodaje elastyczności i zapewnia powtarzalność procesów. Rodzaje wykresów, które utworzymy, obejmują wykresy słupkowe, wykresy pudełkowe, wykresy punktowe wykresy z rozkładami krańcowymi, wykresy radarowe, interaktywne wykresy punktowe 3D, wykresy szeregów czasowych, statyczne i interaktywne mapy oraz fajna wizualizacja globu. Zostaną przedstawione podstawy potrzebne do tworzenia różnorodnych wysokiej jakości wykresów. Niektóre z ważnych tematów omawianych są następujące:

* Efektywna praca z funkcjami wykresów i obiektami wykresów

* Praca z ważnymi pakietami graficznymi, takimi jak ggplot2 i ulotka

* Transformacje danych w celu dostosowania do różnych wizualizacji

* Uogólnianie wykresów poprzez parametryzację zmiennych

* Rosnące wymiary pokazane z kolorami i kształtami

* Rozszerzenie ggplot2 o niestandardowe typy wykresów

* Numeryczna eksploracja danych z interaktywnymi wykresami

* Eksploracja danych geograficznych za pomocą interaktywnych map

Podsumowanie

W tej części ustaliliśmy podstawy przykładu sprzedaży żywności, przedstawiając ogólny scenariusz dla The Food Factory: co robią, co chcą osiągnąć i, co najważniejsze, jak symulować dane, które będą nam potrzebne do końca przykład. Omówiliśmy różne techniki symulacji różnych rodzajów danych, takich jak liczby, kategorie, ciągi i daty. Podejście, które pokazaliśmy, jest wystarczająco elastyczne, aby umożliwić symulowanie wielu różnych rodzajów danych w sposób modułowy i przyrostowy. Pokazaliśmy również, jak zapewnić elastyczność przy różnych założeniach dotyczących symulacji, korzystając z obiektów parametrów. Dowiedzieliśmy się, jak tworzyć funkcje przydatne w różnych scenariuszach oraz jak łączyć nasze symulowane dane z danymi pochodzącymi ze źródeł zewnętrznych. Wreszcie nauczyliśmy się, jak pracować z zewnętrznymi bazami danych MySQL. Jesteśmy gotowi do podjęcia analizy części przykładu. W następnej części, wykorzystamy dane, które właśnie zasymulowaliśmy, do stworzenia wielu wizualizacji, które pozwolą nam uzyskać dobre wyobrażenie o obecnym stanie The Food Factory, a także obszary wymagające poprawy

Praca z relacyjnymi bazami danych

Teraz, gdy mamy już dane potrzebne do pozostałej części przykładu, nauczymy się, jak z nim pracować przy użyciu baz danych. W tej sekcji dowiemy się, jak zapisać nasze dane w relacyjnej bazie danych, a także jak je odczytać. Nie będziemy zagłębiać się w zaawansowane operacje lub przepływy pracy. Przyjrzymy się tylko podstawom i tę sekcję można pominąć, jeśli nie jesteś zainteresowany tym tematem. Znajomość tego nie jest krytyczna, aby odtworzyć resztę RMySQL. Istnieją różne pakiety do pracy z bazami danych i działają one prawie tak samo. Wybraliśmy pakiet RMySQL, ponieważ jest przeznaczony dla bazy danych MySQL, która jest bardzo popularna i łatwa w obsłudze w prawie wszystkich systemach operacyjnych. Aby móc odtworzyć ten kod, będziesz potrzebować poprawnie skonfigurowanej bazy danych MySQL na swoim komputerze i nie będziemy tutaj omawiać szczegółów, jak to zrobić. W Internecie można znaleźć wiele dobrych zasobów. Od tego momentu zakładamy, że masz gotową bazę danych:

install.packages(„RMySQL”)

Pierwszą rzeczą, którą musimy zrobić, aby pracować z bazami danych, jest łączenie się i odłączanie od nich. Aby to zrobić, używamy funkcji dbConnect() i dbDisconnect. Funkcja dbConnect zwraca obiekt, który zawiera połączenie z bazą danych, i które należy zastosować we wszystkich kolejnych działaniach dotyczących bazy danych. Nazwiemy ten obiekt db, aby przypomnieć nam, że reprezentuje bazę danych, z którą pracujemy:

db <- dbConnect(MySQL () , user = <YOUR_USER>, paswword = <YOUR_PASSWORD>, host = „localhost”)

dbDisconnect(db)

#> [1] TRUE

Jeśli używasz bazy danych, która nie działa na tym samym komputerze, z którego korzystasz z języka R, możesz użyć odpowiedniego adresu IP w parametrach host, tak jak w przypadku każdego zdalnego połączenia SQL. Jest piąty parametr, którego musimy użyć, gdy znamy nazwę bazy danych, z którą się łączymy (pojedynczy serwer MySQL może mieć w sobie wiele baz danych). Gdy zobaczysz wartość TRUE po próbie odłączenia się od bazy danych, oznacza to, że wszystko zostało wykonane poprawnie. Aby wysłać zapytanie do serwera bazy danych, używamy funkcji dbSendQuery() po ponownym połączeniu się z nim. Tworzymy nową bazę danych sales (która będzie zawierała nasze tabele sales, and i client_messages) w naszym MySQL serwer poprzez wykonanie:

dbSendQuery(db, „DROP DATABASE IF EXIST sales;”)

dbSendQuery(db, „CREATE DATABASE sales;”)

Ponieważ składnia MySQL wymaga „;” na końcu każdego zapytania, w zależności od konfiguracji, możesz otrzymać błąd, jeśli ich nie wstawisz. Teraz rozłączymy się i ponownie połączymy z serwerem, ale tym razem określimy z jaką konkretną bazą danych chcemy pracować (baza danych sales, którą właśnie stworzyliśmy):

dbDisconnect(db)

db <- dbConnect(

MySQL(),

user = <YOUR_USER>,

password = <YOUR_PASSWORD>,

host = „localhost”,

dbame = „sales”

)

Teraz zapiszemy dane, które zasymulowaliśmy na serwerze MySQL. Aby to zrobić, używamy funkcji dbWriteTable(). Pierwszy argument to obiekt połączenia z bazą danych, drugi argument to nazwa tabeli, w której chcemy przechowywać dane, trzeci argument to ramka danych, która zawiera dane, które chcemy przechowywać, a czwarty argument to nazwa sugeruje, nadpisze (zamiast dołączać) wszelkie dane już obecne w bazie danych. Aby wczytać pełną tabelę z serwera MySQL do R, używamy funkcji dbReadTable(). Należy jednak pamiętać, że kiedy to robimy, wszelkie informacje dotyczące czynników są tracone, a ramka danych wie, że zawiera tylko ciągi znaków, co jest sposobem przechowywania danych na serwerze MySQL. Aby to sprawdzić, możesz przyjrzeć się strukturze danych odczytywanych z serwera MySQL za pomocą funkcji str(). Nie pokażemy tutaj danych wyjściowych, aby zaoszczędzić miejsce, ale przekonasz się, że sales zawiera informacje o czynniku, podczas gdy sales_from_db nie:

sales_from_db <- dbReadTable(db,”sales”)

str(sales)

str(sales_from_db)

Brak rozwiązania problemu z metadanymi dotyczącymi zmiennych czynnikowych będzie miał konsekwencje, gdy utworzymy nasze wizualizacje w następnym rozdziale. Możemy sobie z tym poradzić teraz lub później, ale ponieważ ta część dotyczy pracy z danymi, tutaj pokażemy, jak to zrobić. Najpierw utworzymy funkcję read_table(), która będzie opakowywać funkcję dbReadTable(). Ta funkcja read_table() sprawdzi, która tabela jest odczytywana i zastosuje odpowiednie metadane, wywołując add_sales_metadata(), add_clients_metadata lub add_client_messages_metadata. Zwróć uwagę, że jeśli odczytywana tabela nie jest jedną z tych trzech, nie będziemy na razie wiedzieć, jakie metadane dodać, więc po prostu zwrócimy tabelę bezpośrednio:

read_table <- function(db,table) {

data <- dbReadTable(db, table)

if (table = = „sales”) {

return(add_sales_metadata(data))

} else if (table == „clients’) {

return(add_clients_metadata(data))

} else if (table = = „client_messages”) {

return(add_client_messages_metadata(data))

} else {

return(data)

}

}

Sposób, w jaki dodajemy metadane do każdego przypadku, polega na ponownym zdefiniowaniu zmiennych czynników, tak jak robiliśmy to wcześniej, a także na przekształceniu obiektów daty, które są również odbierane jako ciągi. Nie musimy nic więcej zmieniać w danych:

add_sales_metadata <- function(data) {

status_levels <- c(„PENDING”, „DELIVERED”, RETURNED”, „CANCLLED”)

protein_source_levels <- c(„BEEF”, „FISH”, „CHICKEN”, „VEGETARIAN”)

continent_levels <- c(„AMERICA”, „EUROPE”, „ASIA”)

delivery_levels <- c(„IN STORE” , „TO LOCATION”)

paid_levels <- c(„YES”, „NO”)

data$DATE <- as.Date(data$DATE)

data$PROTEIN_SOURCE <-

factor(data$PROTEIN_SOURCE, levels = protein_source-levels)

data$CONTINENT <- fator(data$CONTINENT, levels = continent_levels)

data$DELIVERY <- fator(data$DELIVERY, levels = delivery_levels)

data$STATUS <- fator(data$STATUS , levels = status_levels)

data$PAID <- fator(data$PAID, levels = paid_levels)

return(data)

}

add_clients_metadata <-fuction(data) {

gender_levels <- c(„FEMALE”, „MALE”)

star_levels <- c(„1”, „2”, „3”, „4”, „5”)

data$BIRTH_DATE <- as.Date(data$BIRTH_DATE)

data$CLIENT_SINCE <- as.Date(data$CLIENT_SINCE)

data$GENDER <- factor(data$GENDER, levels = gender_levels)

data$STARS <- factor(data$STARS, levels = star_levels)

return(data)

}

add_client_messages_metadata <- function(data) {

star_levels <- c(„1”, „2”, „3”, „4”, „5”)

data$DATE <- as.Date(Data$DATE)

data$STARS <- factor(data$STARS, levels = star_levels)

return(data)

}

Teraz widzimy, że zarówno sales, jak i sales_from zawierają te same metadane. Ponownie nie pokazujemy danych wyjściowych, aby zaoszczędzić miejsce, ale zobaczysz, że metadane współczynnika są teraz zachowywane podczas odczytu z serwera MySQL:

sales_from_db <- read_table(db, „sales”)

str(sales)

str(sales_from_db)

Ponieważ mają te same dane i metadane, teraz można bezpiecznie odczytać dane z serwera MySQL, gdy zajdzie taka potrzeba. Pamiętaj tylko, aby używać funkcji read_table zamiast funkcji dbReadTable.

Odczytywanie pełnych tabel z serwera MySQL za pomocą dbReadTable jest praktyczne tylko wtedy, gdy tablice nie są zbyt duże. Jeśli pracujesz z bazą danych w rzeczywistym problemie, prawdopodobnie tak nie jest. Jeśli dane, które próbujesz odczytać, są zbyt duże, użyj kombinacji funkcji dbSendQuery i fetch().

Jeśli chcesz wiedzieć, jaki typ danych będzie używany na serwerze MySQL do przechowywania wysyłanych danych, możesz użyć funkcji dbDataType z argumentem MySQL(), a także typ danych, którego typu serwera chcesz się dowiedzieć:

dbDataType(MySQL (), „a”)

#> [1] “text”

dbDataType(MySQL (), 1.5)

#> [1] “double”

Na koniec możesz użyć funkcji dbListTables()  i dbListFields(), aby znaleźć tabele dostępne w bazie danych i pola dostępne dla odpowiednio konkretną tabelę. Jeśli postępowałeś według tego przykładu do tej pory, powinieneś zobaczyć:

dbListTables(db)

#> [1] “client_messages” “clients” “sales”

dbListFields(db, „sales”)

#> [1] “row_names” “SALE_ID” “CLIENT_ID” “DATE”

#> [5] “QUANTITY” “COST” “PRICE” “DISCOUNT”

#> [9] “PROTEIN” “CARBS” “FAT” “PROTEIN_SOURCE”

#> [13] “STORE” “DELIVERY” “STATUS” “PAID”

dbListFields(db, „clients”)

#> [1] “row_names” “CLIENT_ID” “BIRTH_DATE” “CLIENT_SINCE”

“GENDER”

#> [6] “STARS”

dbListFields(db, „client_messages”)

#> [1] “row_names” “SALE_ID” “DATE” “STARS” “SUMMARY”

“MESSAGE”

Zwróć uwagę, że widzisz pole row.names, ponieważ jest ono niezbędne dla funkcjonalności MySQL, ale kiedy faktycznie odczytujesz dane z bazy danych, nie otrzymasz tego pola. Otrzymasz wszystkie inne pokazane pola (te napisane wielkimi literami).

Symulowanie danych wiadomości klienta

Symulowanie wiadomości tekstowych, które faktycznie mają sens, jest bardzo trudne i nie będziemy próbować tego tutaj. Zamiast tego wykorzystamy zbiór danych opublikowany na temat recenzji żywności na Amazon. Tak jak poprzednio, zaczynamy od zdefiniowania ramki danych client_messages, której będziemy używać ze zmieymi SALE_ID, DATE, STARS, SUMMARY i MESSAGE, jak pokazano w poniższym kodzie:

client_message <- data.frame(

SALE_ID – character(),

SATE = as.Date(character()),

STARS = factor(levels = star_levels),

SUMMARY = character(),

MESSAGE = character(),

LAT = numeric(),

LNG = numeric()

)

Jak już wcześniej robiliśmy, w naszej funkcji  random_cliet_messages_data() najpierw rozpakujemy obiekt parametru i ustawimy ziarno. Następnym krokiem jest pobranie próbki recenzji, którą chcemy, za pomocą funkcji random_reviews(), którą utworzymy w następnej kolejności. Zakładając, że mamy gotowe dane z recenzji, tworzymy ramkę danych client_messages, pobierając losową próbkę z sale_ids ,z danych sprzedażowych, abyśmy może generować połączenie między wiadomościami i zleceniami sprzedaży, a robimy to w taki sposób, że możemy generować różne komunikaty dla jednego zamówienia sprzedaży, ponieważ używamy argumentu replace jako TRUE. Pozostałe części kodu są podobne do tego, co widzieliśmy wcześniej. Spójrzmy na następujący kod:

random_client_messages_data <- function(client_messages, sales, parameters) {

n <- parameters[[„n”]]

date_start <- parameters[[„date_start”]]

date_end <- parameters[[„date_end”]]

reviews_file <- parameters[[„reviews_file”]]

location <- parameters[[„location”]]

set.seed(12345)

reviews <- random_reviews(n, reviews_file)

client_messages <- data.frame (

SALE_ID = sample(uique(sales$SALE_ID, n , TRUE),

DATE = random_dates_in_ranges(n, date_start, date_end),

STARS = factor(review$STARS, levels = levels(client_messages$STARS)),

SUMMARY = reviews$SUMMARY,

MESSAGE = reviews$MESSAGE,

LAT = numeric(n),

LNG = numeric(n),

stringsAsFactor = FALSE

)

client_messages <- add.coordiates(cliet_messages, sales, locations)

retur(client_messages)

}

Funkcja random_reviews() przyjmuje ścieżkę pliku CSV jako argument w reviews_file i używa jej do załadowania danych do obiektu reviews. Następnie pobiera próbkę indeksów wierszy bez zamiany, ponieważ nie chcemy dwukrotnie używać tej samej recenzji, a mamy wystarczająco dużo recenzji, aby upewnić się, że tak się nie stanie (w danych jest ponad 500 000 recenzji) . Po prostu zwracamy ten podzbiór ramki danych z powrotem do użycia w ostatniej 􀁔 ramce danych client_messages:

random_reviews <- fuctio(n , reviews_file) {

reviews <- readRDS(reviews-file)

return(reviews[sample(1:nrow(reviews), n, FALSE), )

}

Na koniec tworzymy obiekt parametrów z niezbędnymi informacjami i przekazujemy go do random_client_messages_data(), aby zaktualizować ramkę danych client_messages z symulowanymi danymi. Upewnij się, że zmieniłeś ścieżkę reviews_file na odpowiednią do konfiguracji (./ oznacza, że ​​znajduje się w tym samym katalogu). Spójrzmy na następujący kod:

parameters <- list)

n = 1000,

date_start = as.Date(„2015-01-01”),

date_end = Sys.Date(),

reviews_file = „./reviews/data/reviews.rds”,

locatios = list (

„AMERICA” = list (

list (LAT = 35.982915, LNG = -119.028006),

list (LAT = 29.023053, LNG = -81.762383),

list (LAT = 41.726658, LNG = -74.731133),

list (LAT = 19.256493, LNG = -99.292577),

list (LAT = -22.472499, LNG = -43.348329),

),

„EUROPE” = list (

list (LAT = 40.436888, LNG = -3.863850),

list (LAT = 48.716026, LNG =  2.350955),

list (LAT = 52.348010, LNG = 13.351161),

list (LAT = 42.025875, LNG = 12.418940),

list (LAT = 51.504122, LNG = -0.364277),

),

„ASIA” = list (

list (LAT = 31.074426, LNG = 121.125328),

list (LAT = 22.535733, LNG = 113.830406),

list (LAT = 37.618251, LNG = 127.135865),

list (LAT = 35.713791, LNG = 139.489820),

list (LAT = 19.134907, LNG = 73.000993),

)

)

)

client_messages <- random_client_messages_data(client_messages, sales, parameters)

Gotowe! Teraz powinniśmy mieć pełną symulację dla danych sprzedażowych, a także danych dla klientów i ich komunikatów dla ich odpowiednich zamówień sprzedaży. Nie każde zamówienie sprzedaży będzie zawierało komunikat, a niektóre z nich mogą mieć więcej niż jeden, co jest zgodne z projektem. Pamiętaj, że recenzje, których użyliśmy w przykładzie, niekoniecznie dotyczą żywności, ale chodziło o pokazanie, jak te techniki można wykorzystać do symulacji nowych danych przy użyciu już istniejących zestawów danych. Spojrzenie na trzy symulowane zbiory danych powinno wywołać uśmiech na naszej twarzy. Zwróć uwagę, że pomijamy dane client_messages, ponieważ były zbyt duże, aby je tutaj pokazać, ale powinieneś zobaczyć je dobrze na swoim komputerze:

head(sales)

#> SALE_ID CLIENT_ID DATE QUANTITY COST PRICE DISCOUNT

PROTEIN

#> 1 OKRLL75596 EAWPJ80001 2015-01-27 3 27.58 50.79 1

0.12422681

#> 2 ZVTFG64065 WQGVB74605 2015-05-26 7 30.78 51.09 3

0.11387543

#> 3 SPRZD12587 XVRAM64230 2017-01-07 8 33.66 54.46 1

0.54351904

#> 4 YGOLB67346 PDVDC58438 2015-01-12 5 34.85 53.06 1

0.49077566

#> 5 CDQRA43926 VJCXI94728 2017-06-21 9 27.35 50.57 0

0.01026306

#>

#> CARBS FAT PROTEIN_SOURCE STORE DELIVERY STATUS PAID

#> 1 0.1548693 0.72090390 CHICKEN STORE 4 IN STORE DELIVERED YES

#> 2 0.1251422 0.76098233 CHICKEN STORE 3 TO LOCATION DELIVERED YES

#> 3 0.2901092 0.16637179 VEGETARIAN STORE 1 TO LOCATION PENDING YES

#> 4 0.1841289 0.32509539 CHICKEN STORE 2 TO LOCATION DELIVERED YES

#> 5 0.2620317 0.72770525 VEGETARIAN STORE 1 TO LOCATION DELIVERED YES

(Truncated output)

head(client)

#> CLIENT_ID BIRTH_DATE CLIENT_SINCE GENDER STARS

#> 1 EAWPJ80001 1974-09-04 2015-05-21 MALE 4

#> 2 WQGVB74605 1987-01-24 2015-12-05 FEMALE 2

#> 3 XVRAM64230 1977-11-18 2017-06-26 FEMALE 2

#> 4 PDVDC58438 1987-11-23 2015-12-20 MALE 2

#> 5 VJCXI94728 1953-07-09 2016-05-03 FEMALE 3

(Truncated output)

Symulacja danych klienta

Teraz, gdy przeszliśmy przez symulację danych sprzedażowych i mamy niezbędne podstawy, reszta symulacji danych będzie znacznie łatwiejsza. Ponadto użyjemy wielu funkcji, które stworzyliśmy wcześniej, do symulacji danych klienta i wiadomości klienta, co jest świetne! Ponowne używanie takich funkcji jest bardzo wydajne i z czasem przyzwyczaisz się do tego. Zbudujesz własną kolekcję kodu wielokrotnego użytku, dzięki czemu będziesz bardziej efektywny podczas programowania. Zaczynamy od zdefiniowania ramki danych, której będziemy używać, tak jak to robiliśmy wcześniej. W tym przypadku będziemy mieć zmienne CLIENT_ID, BIRTH_ID, CLIENT_SINE, GENDER i STARS. STRAS reprezentuje ocenę od 1 (zła) do 5 (doskonała):

geder_levels <- c(„FEMALE”, „MALE”)

star_levels <- c(„1”, „2”, „3”, „4”, „5”)

clients <- data.frame)

CLIENT_ID = character(),

BIRTH_ID = as.Date(character() ),

CLIENT_SINCE = as.Date(character () ),

GENDER = factor(levels – geder_levels),

STARS = factor(levels = star_levels)

)

Pierwszą rzeczą, na którą zwracamy uwagę, jest to, że informacje CLIET__ID nie powinny być ponownie symulowane, ponieważ otrzymamy inne identyfikatory klientów niż te, które już mamy w danych sprzedaży. Chcemy, aby unikalne identyfikatory klientów w danych sprzedażowych odpowiadały zapisowi w danych klienta, co realizujemy przesyłając je jako parametr clinet_ids i przypisując je bezpośrednio zmiennej CLIET_ID w ramce danych clients. W tym przypadku n będzie odpowiadać liczbie unikalnych identyfikatorów klienta, które otrzymamy za pomocą funkcji length(). Pozostałe parametry wyodrębniamy tak, jak zwykle w przypadku obiektów parametrów. W szczególności potrzebujemy zakresu dat, które są ważne dla naszego klienta daty urodzenia (muszą mieć co najmniej 18 lat), a także aktualny zakres dat od kiedy byli klientami (nie mogli być klientami przed rozpoczęciem działalności firmy w styczniu 2015; zobacz parametry danych sprzedażowych symulacja). Reszta kodu jest bardzo podobna do tego, co widzieliśmy w symulacji danych sprzedażowych, więc nie będziemy tego więcej wyjaśniać. Aby to zrozumieć, spójrzmy na następujący kod:

random_cliets_data <- function(clients, client_ids, parameters){

 n <- length(client_ids)

bd_start <- parameters[[„birth_date_start”]]

bd_end<- parameters[[„birth_date_end”]]

cs_start <- parameters[[„client_since_start”]]

cs_end <- parameters[[„client_since_end”]]

stars_pbs <- parameters[[„stars_probabilities”]]

set.seed(12345)

clients <- data.frame (

CLIENT_ID = client_ids,

BIRTH_DATE = random_dates_i_range(n, bd_start, bd_end, TRUE),

CLIENT_SICE = random_dates_in_range(n, cs_start, cs_end, TRUE),

GENDER = factor(

random_levels(n, levels(clients$GENDER)),

levels = levels(clients$GENDER)

),

STARS = factor(

random_levels(n, levels(client$STARS), stars_pbs),

levels = levels(clients$STARS)

),

stringdAsFactor = FALSE

)

return(clients)

}

Aby zasymulować dane klienta, po prostu tworzymy odpowiednie parametry wewnątrz obiektu parametrów i wysyłamy je do funkcji random_clients_data(), aby zaktualizować ramkę danych clients:

parameters <- list (

birth_date_start = as.Date(„1950-01-01”) ,

birth_date_end = as.Date(„1997-01-01”) ,

client_since_start = as.Date(„2015-01-01”) ,

client_since_end = SyS.Date(),

stars_probabilities = c(0,0.5, 0.1, 0.15, 0.2 , 0.5)

)

clients <- random_clients_data (clients, unique(sales$CLIENT_ID),parameters

Czy zauważyłeś, jakie to było łatwe? Dzieje się tak, ponieważ stworzyliśmy nasze podstawy w poprzedniej sekcji i drastycznie uprościły one zastosowanie tych samych koncepcji. W miarę zwiększania umiejętności programowania będzie się to zdarzać częściej

Wszystko razem

Teraz, gdy wykonaliśmy ciężką pracę tworzenia wszystkich naszych funkcji symulacyjnych, możemy po prostu połączyć je w funkcję ogólną, która użyje ich do łatwej symulacji danych dla nas. Pierwszą rzeczą, na którą zwracamy uwagę, jest to, że istnieje wiele parametrów, które musimy kontrolować, i jeśli utworzymy sygnaturę funkcji, która zawiera wszystkie te parametry wyraźnie, będziemy ograniczać się, mając sztywny podpis, z którym trudno jest pracować . Nie chcemy zajmować się tymi parametrami ręcznie, ponieważ utrudni to pracę z kodem. A co by było, gdybyśmy mogli przekazać pojedynczy parametr, który zmieniłby się dla nas zgodnie z naszymi wymaganiami? Cóż, możemy to zrobić! Z tego powodu istnieją obiekty parametrów. Są prostą koncepcją i zapewniają dużą elastyczność. Są to listy, które są pakowane przed wysłaniem do funkcji i rozpakowywane wewnątrz funkcji w celu użycia w razie potrzeby wewnątrz funkcji zagnieżdżonych. To jest forma hermetyzacji. Następnie zauważamy, że ponieważ te symulacje są procesami stochastycznymi, co oznacza, że ​​możemy uzyskać różne wyniki za każdym razem, gdy je wykonujemy, możemy utracić powtarzalność naszych wyników. Aby tego uniknąć, po prostu ustawiamy ziarno na początku symulacji, aby mieć pewność, że za każdym razem otrzymamy te same wyniki. Reszta kodu to po prostu wywołanie funkcji, które już stworzyliśmy z odpowiednimi argumentami, które pochodzą z obiektu parametrów, który rozpakowujemy na początku. Warto zwrócić uwagę na trzy rzeczy. Po pierwsze, nie możemy po prostu użyć funkcji random_composition() bezpośrednio do jednej zmiennej w ramce danych, którą tworzymy, ponieważ wynikowy obiekt zawiera dane dla trzech różnych zmiennych w ramce danych. Dlatego musimy przechowywać obiekt pośredni z wynikami, composition, a następnie użyj go do wyodrębnienia informacji o każdym makroskładniku. Po drugie, używamy argumentu stringsAsFactors funkcji data.frame() jako FALSE, aby upewnić się, że SALE_ID i CLIENT_ID nie są traktowane jako czynniki (ponieważ są ciągami znaków). Kiedy czynniki zaczynają mieć wiele kategorii, przetwarzanie ramek danych staje się wolniejsze i możemy tego uniknąć, traktując je jako proste ciągi, ponieważ będziemy mieć wiele unikalnych identyfikatorów. Po trzecie, ponieważ traktujemy wszystkie ciągi jako nieczynniki i możemy nie uzyskać wszystkich możliwych kategorii w naszej próbie, używając random_levels() zmienną czynnikową można zdefiniować bez niektórych czynników, które wcześniej wymieniliśmy. Aby upewnić się, że tak się nie stanie, wyraźnie definiujemy poziomy wewnątrz funkcji factor() jako poziomy w oryginalnej ramce danych sprzedaży wysłanej do funkcji, która zawiera dane z naszej początkowej definicji:

random_sales_data <- function(sales,parameters) {

n <- parameters[[„n”]]

n_letters <- parameters[[„n_letters”]]

n_digits <- parameters [[„n_digits”]]

redution <- parameters [[„reduction”]]

date_start <- parameters [[„date_start”]]

date_end <- parameters [[„date_end”]]

quality_lambda <- parameters [[„quality_lambda”]]

price_mean <- parameters [[„price_mean”]]

price_variance <- parameters [[„price_variance”]]

cost_mean <- parameters [[„cost_mean”]]

cost_variance <- parameters [[„cost_variace”]]

discount_lambda <- parameters [[„discount_lambda”]]

protein_source_pbs <- parameters [[„protein_source_probabilities”]]

continent_pbs <- parameters [[„continent_probabilities”]]

delivery_pbs <- parameters [[„delivery_probabilities”]]

status_pbs <- parameters [[„status_probabilities”]]

paid_pbs <- parameters [[„paid_probabilities”]]

set.seed(12345)

composition = random_composition(n)

sales <- data.frame(

SALE_ID = random_strigs(n, n_letters, n_digits),

CLIENT_ID = random_strings(n,n_letters, _digits, reduction),

DATE = random_dates_in_range(n, date_start, date_end_,

QUANTITY = random_quantities(n, quantiity_lambda),

COST = random_values(n, cost_,mean, cost_variance)

PRICE = random_values(n, price_mean, price_variance),

DISCOUNT = random_discounts(n, disount_lambda),

PROTEIN = composition$PROTEIN,

CARBS = compistion$CARBS,

FAT = composition$FAT,

PROTEIN_SOURCE = factor(

random_levels(, levels(sales$PROTEIN_SOURCE),

protein_source_pbs),

levels = levels(sales$PROTEIN_SOURCE)

),

CONTINENT = factor (

random_levels(n, levels(sales$COTINENT), continent_pbs),

levels  = levels(sales$CONTINENT)

),

DELIVERY = factor (

random_levels(n, levels(sales$DELIVERY), delivery_pbs),

levels  = levels(sales$DELIVERY)

),

STATUS = factor (

random_levels(n, levels(sales$STATUS), status_pbs),

levels  = levels(sales$STATUS)

),

PAID = factor (

random_levels(n, levels(sales$PAID), paid_pbs),

levels  = levels(sales$PAID)

),

strigsAsFactors = FALSE

)

sales <- skew_sales_data(sales)

retur(sales)

}

Na koniec, aby stworzyć symulację, tworzymy obiekt parameters z niezbędnymi informacjami i aktualizujemy nasz obiekt sales za pomocą funkcji random_sales_data(). W tym przypadku zamierzamy zasymulować 10 000 zamówień sprzedaży między styczniem 2020 r. (data_start) a dzisiejszą datą (date_end, używając funkcji SyS.Date() do generowania daty na dziś). Wymagamy, aby nasze identyfikatory składały się z pięciu liter (n_letters), po których następuje pięć cyfr (n_digits), i chcemy, aby nasz CLIENT_ID używał tylko pierwsze 25% wygenerowanych identyfikatory umożliwiające powtarzających się klientów (reduction). Chcemy średnio pięciu artykułów spożywczych na zamówienie sprzedaży (quantity_lambda), przy średnich kosztach produkcji 30 (cost_mean) i wariancji 10 (cost_variance) i ceny ze średnią 50 (price_mean) i wariancją 10 (price_variace). Chcemy również rabatów w okolicach 1 lub 2 USD (discout_lambda; pamiętaj o transformacji, którą wykonaliśmy wewnątrz odpowiedniej funkcji). Na koniec chcemy, aby prawdopodobieństwa PENDING, DELIVERED, RETURNED i CANELLED jako STATUS były równe odpowiednio na 20%, 60%, 10% i 10%. Podobnie chcemy, aby prawdopodobieństwo opłacenia zamówienia wynosiło 90%:

parameter <- list (

n = 10000,

n_letters = 5,

n_digits = 5

reduction = 0,25,

date_start = as.Date(„2020-01-01”),

date_end = Sys.Date(),

quatity_lambda = 2,

cost_mean = 12,

cost_variance = 1,

price_mean = 15,

price_variance = 2,

discount_lambda = 100,

protein_source_probabilities = c(0.6, 0.2, 0.1 , 0.1),

continent_probabilities = c(0.5, 0.3, 0.2),

delivery_probabilities = c(0.7,0.3),

status_probabilities = c(0.2, 0.6, 0.1, 0.1),

paid_probabilites = c(0.9, 0.1)

)

sales <- random_sales_data(sales, parameters)

Możesz bawić się tymi parametrami i symulować wiele różnych rodzajów scenariuszy. Na przykład, jeśli chcesz zasymulować firmę, która radziła sobie bardzo źle z niskimi marżami lub nawet stratami, możesz połączyć koszty i ceny, a może nawet zwiększyć ich odpowiednie odchylenia, aby upewnić się, że występuje wiele zwrotnic , czyli straty na zlecenie sprzedaży. Gratulacje! Wiesz już, jak tworzyć nietrywialne symulacje danych. Mając tę ​​wiedzę, możesz się dobrze bawić symulując wiele rodzajów danych. Zachęcamy do rozwinięcia tego przykładu i zabawy jego analizą

Symulowanie ciągów znaków dla złożonych identyfikatorów

Czas na najbardziej złożoną część symulacji, czyli identyfikatory. Chcemy wyprodukować n identyfikatorów iw zależności od tego, jakie identyfikatory symulujemy, możemy chcieć, aby były unikalne. Identyfikatory klientów w danych klienta muszą być unikalne, ponieważ nie chcemy dwóch różnych klientów z tym samym identyfikatorem, a dane naszych klientów nie będą miały powtarzalnych rekordów zgodnie z projektem. Z drugiej strony nie chcemy, aby w danych sprzedaży pojawiały się unikalne identyfikatory klientów, ponieważ chcemy, aby pojawiali się tam stali klienci. Moglibyśmy stworzyć dwie odrębne funkcje, które niezależnie zajmują się tymi przypadkami, ale łatwo jest połączyć je w jedną funkcję, używając po prostu parametru reduction który określa procent unikalnych identyfikatorów. Jeśli parametr reduction jest wysyłany jako 0 (wartość domyślna), zakładamy, że żądane są pełne unikalne identyfikatory. Zakładamy, że identyfikatory składają się z grupy liter, po których następuje grupa cyfr, a długość każdej grupy należy określić osobno. Do tego służą n_letters i n_digits. Nasza implementacja będzie działać poprzez osobne tworzenie grup liter i cyfr, a następnie ich łączenie. Najpierw utworzymy kombinacje liter, pobierając próbkę z grupy LETTERS (wewnętrzny obiekt R, który zawiera wszystkie litery ASCII zapisane wielkimi literami) o rozmiarze n z zamiennikami (możemy mieć powtarzające się litery w każdym identyfikator). Następnie powtórzymy tę próbkę dla n_letter, ​​czyli ilości liter, których potrzebujemy w każdym identyfikatorze, i nie będziemy upraszczać struktury, dlatego wysyłamy FALSE parametr. To zwróci listę z n_letters elementami, gdzie każdy element jest wektorem n liter. Teraz chcemy wkleić te obiekty razem. Aby to zrobić, używamy funkcji paste0() (która jest skrótem do funkcji paste(), która zwija wszystko razem, jeśli użyjesz tylko paste(), uzyskać spacje między literami). Nie możemy jednak wysłać naszej konstrukcji do paste0(), ponieważ wyciągniemy trochę śmieci. W tym celu musimy poprawnie użyć funkcji do.call(). Aby zrozumieć, co się dzieje, załóżmy, że n_letters wynosi 5 i zobaczmy, jak zachowuje się kod.

n_letters <- 5

letters <- do.call(paste0, replicate(

n_letters, sample(LETTERS, n , TRUE), FALSE))

letters

#> [1] “KXSVT” “HQASE” “DDEOG” “ERIMD” “CQBOY”

Teraz skupimy się na kombinacjach cyfr. Naszym celem jest uzyskanie liczby od zera do liczby utworzonej z n_digits dziewiątek. Na przykład, jeśli n_digits wynosi 5, potrzebujemy liczb od 0 do 99 999. Będzie to podzielone na dwa etapy. Najpierw utwórz dynamikę skrajnej prawostronnej liczby składająca się tylko z dziewiątek. Następnie upewnij się, że ma dokładnie n_digits cyfr, nawet jeśli naturalny sposób przedstawienia liczby nie. Oznacza to, że jeśli n-digits wynosi 5, a liczba, którą ostatecznie pobieramy, to 123, jako wynik musimy użyć 00123, ponieważ musimy zapewnić n_digits cyfr. Aby wykonać paste() z collapse = „” aby złożyć wszystkie ciągi razem, uzyskując ciąg, taki jak 99999. Następnie konwertujemy ten ciąg na liczbę za pomocą funkcji as.numeric(). Otrzymujemy pożądaną liczbę dziewiątek w obiekcie max_number. Następnie używamy funkcji sprintf(), aby upewnić się, że mamy n_digits podczas używania liczby. Aby to zrobić, określamy format z wstępnym wypełnieniem zer (używając składni „%0”), tak że mamy _digits (używając n_digits a następnie litery d w przypadku cyfr). Umieściliśmy to wewnątrz funkcji paste(), ponieważ ciąg format zostanie utworzony dynamicznie. Zgodnie z powyższym przykładem dla 5 cyfr będzie to „%05d”. Te połączone wiersze dają nam:

max_number <- as.numeric(paste(replicate(n_digits, 9), collapse = „”))

format <- paste(„%0”,n_digits, „d”, sep = „”)

digits <- sprintf(format, sample(max_number, n TRUE))

digits

#> [1] “84150” “88603” “88640” “24548” “06355”

Teraz musimy wkleić razem obiekty letters  i digits, ponownie używając funkcji paste0(). Ponieważ jest to operacja wektoryzowana, otrzymamy pojedynczą tablicę n identyfikatorów.Zauważ, że chociaż nie narzuciliśmy niepowtarzalności, prawdopodobieństwo, że procedury próbkowania dadzą powtarzające się identyfikatory jest tak bardzo niskie, że nie będziemy się tym martwić. Wreszcie, jeśli reduction jest większe od zera, co oznacza, że ​​chcemy użyć tylko reduction procentu identyfikatorów utworzonych do tej pory do wygenerowania łącznej liczby n identyfikatorów, użyje funkcji sample() do pobrania n identyfikatorów z pierwszych reduction identyfikatorów procentu, które są obliczane jako tablica od 1 do dolnej części procenta (musi być liczbą całkowitą ) ids, a zrobimy to z zamianą (stąd parametr TRUE). Jeśli reduction wynosi zero, po prostu wysyłamy ids, które stworzyliśmy do tej pory bez żadnych modyfikacji:

random_strings <- function(n,n_letters, n_digits, reduction = 0) {

letters <- do.call(paste0, replicate(

n_letters, sample(LETTERS, n , TRUE), FALSE))

max_number <- as.numeric(paste(replicate(n_digits, 9), collapse = „”))

format <- paste(„%0”, n_digits, „d” , sep=””)

digits <- sprintf(format, sample(max_number, n, TRUE))

ids <- paste0(letters, digits)

ids <- sample(ids[1:floor(reduction * length(ids))],n , TRUE)

}

return(ids)

}

Symulowanie liczb w ramach wspólnych ograniczeń

Jak być może pamiętasz, The Food Factory tworzy swoją żywność, otrzymując specyfikację makroskładników odżywczych. Klienci mogą określić dowolną kombinację wartości procentowych dla każdego z nich, o ile sumują się do 1. Teraz zamierzamy zasymulować te procentowe wartości makroskładników. Będzie to wymagało trochę więcej pracy niż poprzednie przypadki. Najpierw tworzymy funkcję, która zwraca liczbowe trójek, w których każda liczba mieści się w przedziale od 0 do 1 i razem sumują się do 1. Aby to osiągnąć, użyjemy dwóch liczb losowych i uzależnimy trzecią od pierwszych dwóch. Wykorzystamy następujący fakt matematyczny:

abs(a,b) = max(a,b)  –  min(a,b) =>  1 – max(a,b)  + min(a,b) + abs(a,b)

To każe nam przyjąć jedną liczbę jako 1 – max (a, b), inną jako min (a, b), a ostatnią jako abs (a, b); czyli dokładnie to, co robimy w funkcji random_triple(). Robiąc tak matematycznie gwarantuje, że otrzymamy trzy liczby losowe z zakresu od 0 do 1, które razem dają 1. Zauważ, że random_triple() jest jedną z niewielu funkcji, które stworzyliśmy, która nie wymagamy żadnych argumentów, co ma sens, ponieważ nie potrzebujemy zewnętrznych informacji, aby zasymulować trójkę:

random_tripel <- function() {

a <- runif (1,0,1)

b <- runif(1,0,1)

PROTEIN <- 1 – max(a,b)

CARBS <- abs (a – b)

FAT <- min(a,b)

return *c(PROTEIN, CARBS, FAT))

}

Możemy sprawdzić, czy to działa, używając sum() nad wynikiem:

triple <- random_triple(_

triple

#> [1] 0,05796599 0,76628032 0,17575370

sum(triple)

#> 1

Teraz chcemy wygenerować n tych trójek. Aby to zrobić, używamy funkcji replicate ()do tworzenia n trójek. Argument TRUE odpowiada argumentowi simplify funkcji, który zredukuje listę trójek do postaci macierzowej, z którą łatwiej pracować w tym konkretnym przypadku. Kiedy testujemy kod i patrzymy na wyniki replicate(n,random_triple(), TRUE), znajdziemy że wynikowa struktura jest transpozycją tego, czego chcemy, co oznacza, że ​​ma trzy wiersze i 􀁏 kolumn, gdzie każdy wiersz reprezentuje procent makroskładników, a każda kolumna reprezentuje obserwację. Chcemy przetransponować tę strukturę, aby uzyskać procenty makroskładników odżywczych jako kolumny, a obserwacje jako wiersze; aby to zrobić, używamy po prostu funkcji t(). Następnie po prostu tworzymy ramkę danych z odpowiednimi wartościami dla każdego makroskładnika:

random_composition <- function(n) {

matrix <- t(replicate(n, ranom_triple(), TRUE))

return(data.frame(PROTEIN = matrix[,1],

CARBS = matrix[,2],

FAT = matrix[,3]))

}

Symulowanie dat w określonym zakresie

Funkcja random_dates_i_range używa tej samej funkcji sample(), której używaliśmy wcześniej, ale zamiast otrzymywać listę ciągów jako kategorie ze zmiennych czynnikowych, otrzyma listę dat. Aby wygenerować pełny zestaw prawidłowych dat do symulacji, używamy funkcji seq(). Ta funkcja wygeneruje wszystkie wartości od start do end w określonym przedziale. Jeśli chcemy wygenerować wszystkie liczby nieparzyste z przedziału od 1 do 10, użyjemy seq(1,10,2), co oznacza, że ​​zajmie to 1 i doda  2 olejno, aż zostanie osiągnięte 10. W naszym przypadku chcemy, aby przyrost trwał cały dzień i, dla wygody, funkcja seq() zapewnia tę możliwość podczas wysyłania obiektów daty, wysyłając przyrost jako ciąg „day”:

random_dates_in_range <- function(n, start, end, increasing_prob = FALSE) {

sequence <- seq(start, end, „day”)

if (increasing_prob) {

probabilities <- seq(1, length(sequence)) ^ 2

probabilities <- probabilities / sum(probabilities)

return(sample(sequence, n , TRUE, probabilities))

} else {

return(sample(sequence, n , TRUE))

}

}

Pamiętaj, że zadziała to tylko podczas wysyłania obiektów daty. Jeśli spróbujesz przetestować tę funkcję za pomocą łańcuchów, pojawi się błąd z informacją, że ‘from’ cannot be NA, NaN, or infinite. Zamiast tego powinieneś przekonwertować te ciągi na daty za pomocą funkcji as.Date:

seq(„2020-01-01” , „2020-01-01”, „day”) # Błąd

seq(as.Date(„2020-01-01”) , as.Date(„2020-01-01”), „day”)  # Popawne

Symulowanie wartości jakościowych za pomocą współczynników

Funkcja random_levels() symuluje n wartości kategorialne poprzez próbkowanie levels z zamiennikiem (kontrolowanym przez trzeci parametr, który jest wysyłany jako TRUE). Możesz myśleć o levels jako o tablicy ciągów, z których każdy jest możliwą wartością dla symulacji. Te levels będą pochodzić z kategorii zdefiniowanych dla zmiennych czynnikowych w ramce danych (PROTEIN_SOURCE, STORE, DELIVERY, STATUS i PAID). Próbka z zamiennikiem oznacza, że ​​za każdym razem, gdy wybieramy jedną z wartości z obiektu levels, zwracamy ją, abyśmy mogli ją później wybrać ponownie. Próbkowanie bez wymiany ma sens tylko wtedy, gdy chcesz, aby liczba próbek była mniejsza niż całkowita liczba dostępnych wartości, co nie ma miejsca w tym przypadku, ponieważ chcemy symulować tysiące wierszy i nie będziemy mieć tylu levels. Jest trzeci parametr, o którym nie wspomnieliśmy, parametr probabilities. Jak widać, domyślnie jest ustawione na NULL, ale wysyłamy tam obiekt; musi to być wektor liczb od 0 do 1, tak aby sumowały się do 1 i reprezentowały prawdopodobieństwo wybrania określonej kategorii. Kolejność tego obiektu probabilities musi być taka sama jak w obiekcie levles. Na przykład, jeśli mamy trzy możliwe poziomy i wyślemy obiekt  probabilities jako c90.2, 0.3, 0.5), pierwszy poziom będzie miał prawdopodobieństwo wyboru wynosi 20%, podczas gdy na drugim i trzecim poziomie będą odpowiednio 30% i 50%. Zauważ, że prawdopodobieństwa sumują się do jednego. Spójrzmy na kod:

random_levels <- function(n, levels, probabilities = NULL) {

return(sample(levels, n . TRUE, probabilities))

}

Zauważ, że nie sprawdzamy, czy obiekt probabilities jest wysyłany jako NULL, zanim przekażemy go do funkcji sample(). Można to zrobić, ponieważ odpowiadający mu parametr funkcji sample() używa również wartości domyślnej NULL i interpretuje to jako wykorzystujące równe prawdopodobieństwa dla wszystkich wartości. Możesz to sprawdzić w dokumentacji funkcji. Aby sprawdzić, czy prawdopodobieństwa są poprawnie zaimplementowane, możemy zasymulować 100 wartości, a następnie utworzyć tabelę z wynikami, aby zobaczyć ilość wartości wygenerowanych dla każdej z kategorii. Jak widać, jeśli symulujemy wartości 100 kategorii A,B i C, z prawdopodobieństwami 20%, 30% i 50%, otrzymamy odpowiednio proporcje 18%, 37% i 45%. Wyniki te są wystarczająco zbliżone do naszych specyfikacji, a zatem poprawne. Zwróć uwagę, że za każdym razem, gdy ponownie wykonasz kod, otrzymasz różne wartości i prawie nigdy nie będą one dokładnie tymi, które podałeś, co jest naturalne w symulacjach. Jednak prawie zawsze powinny być zbliżone do specyfikacji:

results <- random_levels(100, c(„A”, „B” , „C”) , c(0.2, 0.3 , 0.5))

table

#> results

#> A B C

#> 18 37 45