Symulacja danych sprzedażowych

Wystarczająco dużo pojęć; zacznijmy programować. Aby uzyskać jasny obraz tego, dokąd zmierzamy, zaczynamy od zainicjowania ramki danych sales, której będziemy używać, z zerową liczbą obserwacji. Robimy to, definiując dostępne kategorie dla każdej zmiennej czynnikowej i definiując puste wartości z typem danych, którego potrzebujemy dla każdej zmiennej. Jak widać, posiada identyfikatory SALE_ID i CLIET_ID, które pozwolą nam powiązać te dane z tymi z clients i client_messages. Aby to zrozumieć, spójrzmy na następujący kod:

status_levels <- c(”PENDING” , DELIVERED” , „RETURED”, „CANCELLED”)

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

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

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

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

sales <- data.frame(

SALE_ID = character(),

CLIENT_ID = character(),

DATE = as.Date(character() ),

QUANTITY = integer(),

COSI = numeric(),

PRICE = numeric(),

DISCOUNT = numeric(),

PROTEIN = numeric(),

CARBS = numeric(),

FAT = numeric (),

PROTEIN_SOURCE = factor(levels = protein_source_levels),

CONTINENT = factor(levels – cotinent_levels),

DELIVERY = factor(levels – delivery_levels),

STATUS = factor(levels = status_levels),

PAID = factor(levels = paid_levels)

)

Ten sposób inicjalizacji pustej ramki danych, w przeciwieństwie do wielu innych metod, które możesz znaleźć gdzie indziej, jest bezpieczniejszy, ponieważ od początku będziesz mieć poprawne typy kolumn. Jeśli twój kod opiera się na sprawdzaniu typów kolumn (tak jak to zrobimy), będzie działał nawet z ramkami danych z zerowymi wierszami (tak jak w tym przypadku)

Problem zbyt wielu powtarzających się danych

Jaką zmienną mamy jeszcze w ramce danych sprzedaży? Cóż, aby sformułować to w taki sposób, aby problem był bardzo oczywisty, nadal mamy zmienne sprzedaży i zmienne klienta. Więc jaki może być problem? Cóż, za każdym razem, gdy klient dokonuje nowego zakupu, zapisujemy jej BIRTH_DATE, CLIENT_SINCE, GENDER i STARS informacje ponownie. Co się stanie, jeśli częsty klient dokona 100 różnych zakupów w The Food Factory? Cóż, jej informacje zostaną powtórzone 100 razy! Musimy to naprawić. Jak możemy to zrobić? Robimy to samo, co wcześniej, oddzielamy różne rzeczy. Zgadza się. Tworzymy osobną ramkę danych dla danych klienta i już wiemy, jak powiązać klientów ze sprzedażą, ponieważ używaliśmy tej samej techniki w poprzednim problemie, tworzymy identyfikatory w obu ramkach danych. Jest to relacja wiele do jednego (z punktu widzenia danych sprzedażowych w stosunku do danych klientów). Jestem pewien, że możesz dowiedzieć się, które zmienne należą do których ramek danych.

Eliminując powtarzające się dane, eliminujemy również możliwość przypadkowej zmiany niektórych z tych powtarzających się wartości, a następnie nie wiemy, które z nich są prawidłowe. Podsumowując, dokonaliśmy rozbicia ogromnej tabeli początkowej, która zawierała wszystkie informacje w jednym miejscu, na trzy różne tabele, które są połączone identyfikatorami, w taki sposób, że reprezentujemy różne rzeczy w każdej tabeli (sprzedaż, klienci, i wiadomości klientów), eliminując jednocześnie dużo marnowanej przestrzeni i powtarzających się wartości. Aby uzyskać więcej intuicji na temat organizacji po tych dostosowaniach, możemy przyjrzeć się poniższemu obrazowi, który pokazuje, jakie atrybuty danych należą do poszczególnych encji i jak są ze sobą powiązane:

Techniki te, wraz z wieloma innymi, nazywane są normalizacją bazy danych, co może być przydatne w niektórych scenariuszach. Czasami jednak nie chcemy, aby nasze dane były w pełni znormalizowane z powodu problemów z wydajnością, ale są to zaawansowane przypadki, których nie omówimy tutaj.

Problem zbyt dużej pustej przestrzeni

Powiedzmy, że mamy rekord sprzedaży; co się stanie, jeśli otrzymamy wiadomość dotyczącą tego zamówienia od naszego klienta? Po prostu dodajemy dane do odpowiednich kolumn DATE, STARS i MESSAGE. Co się stanie, jeśli otrzymamy kolejną wiadomość dotyczącą tego samego zamówienia? Cóż, możliwym rozwiązaniem byłoby dodanie nowej kombinacji DATE, STARS i MESSAGE do nowej wiadomości, ale nazwy zwinęłyby się. Jak byśmy się między nimi rozróżnili? Cóż, możemy dołączyć liczbę wskazującą aktualny numer wiadomości. Wtedy mielibyśmy DATE_1, STARS_1 i MESSAGE_1 dla pierwszej wiadomości oraz DATE_2, STARS_2i MESSAGE_2 dla drugiej wiadomości. To by to rozwiązało, prawda? Co się stanie, jeśli otrzymamy trzecią lub więcej wiadomości związanych z zamówieniem? Cóż, skończylibyśmy z wieloma zmiennymi w naszej ramce danych. W szczególności mielibyśmy tyle kombinacji, ile wynosi maksymalna liczba wiadomości, które zostały wysłane do jednego zamówienia. Jaka byłaby zawartość komórek dla zamówień, które nie miały tak dużej liczby komunikatów? Byłyby puste. To byłoby dużo zmarnowanej przestrzeni! Ponadto ogólna struktura danych wydawałaby się niezręczna. Musi być lepszy sposób. Jeśli się nad tym zastanowić, wydaje się, że komunikaty i sprzedaż to dwie różne rzeczy i że powinny być oddzielone, prawda? Jeśli myślisz o tym w ten sposób, masz rację. Wyobraźmy sobie więc, zachowajmy jedną ramkę danych dla zamówień sprzedaży, a drugą dla wiadomości. Jest jeszcze jeden problem. Możesz to zobaczyć? Jak ustalimy, które wiadomości należą do jakich zamówień sprzedaży? Identyfikatory na ratunek! Możemy dodać SALE_ID do ramki danych sprzedaży, gdzie powinna być unikalna, a możemy dodać to samo SALE_ID do ramki danych wiadomości, gdzie nie będzie unikalna, ponieważ być wieloma wiadomościami dotyczącymi tego samego zamówienia sprzedaży. Oznacza to, że mamy relację jeden do wielu. Mając to na uwadze, ramka danych sprzedaży zawierałaby wszystkie wspomniane wcześniej zmienne, pomniejszone o zmienne DATE, STARS i MESSAGE dla komunikatów (nie należy mylić sprzedaży kolejność DATE z DATE dla każdej wiadomości), a te trzy zmienne będą zgodne z oddzielną ramką danych wiadomości. Obie ramki danych miałyby zmienną SALE_ID. Świetny; to już minęło.

Potencjalne pułapki

Teraz, gdy rozumiemy ogólną strukturę danych, musimy znaleźć potencjalne pułapki, których należy unikać. Możemy myśleć o tej strukturze danych jako o standardowej strukturze tabeli (ramce danych lub arkuszu kalkulacyjnym), w której każda kolumna reprezentuje zmienną, a każdy wiersz reprezentuje obserwację (w naszym przypadku rekord sprzedaży).

Upraszczające założenia

Możemy skomplikować przykład tak bardzo, jak chcemy, ale aby symulacja była prosta (choć nie trywialna), przyjmiemy z góry kilka rzeczy. Najpierw zakładamy, że każdy rekord sprzedaży zawiera jeden rodzaj żywności. Jeśli dana osoba kupuje dwa lub więcej różnych rodzajów żywności, sprzedaż każdego z nich będzie inny rekord. Jednak każda sprzedaż może zawierać dowolną liczbę porcji żywności, o ile są one tego samego rodzaju (połączenie makroskładników i źródeł białka). Jest to najważniejsze uproszczenie, ponieważ zamówienia firmowe zwykle obejmują różne pozycje na sprzedaż, ale pozwoli nam to skupić się na stronie programowania. Po drugie, nie będziemy się martwić, że relacja między rodzajami żywności a kosztami (lub cenami) będzie ciągła w sensie matematycznym. Oznacza to, że możemy znaleźć rodzaj żywności z pewną kombinacją makroskładników odżywczych i źródeł białka, która jest bardzo podobna do kombinacji innej żywności, ale ich koszty produkcji, a także ceny są bardzo różne. Podobnie zakładamy, że każdy rodzaj żywności ma swój unikalny koszt i cenę i może się różnić dla różnych sprzedaży (ten sam rodzaj żywności może mieć różne koszty i ceny dla różnych sprzedaży). Nie jest to realistyczne założenie, ponieważ większość firm ma standardowe produkty (w tym koszty i ceny), ale możemy myśleć o Fabryce Żywności jako o sklepie rzemieślniczym, w którym każda żywność jest wyjątkowa i może generować różnice w kosztach i cenach . Jeśli już, to dodanie złożoności (zabawy) do analizy. Po trzecie, nie będziemy się martwić o związek między datami sprzedaży a statusami sprzedaży lub między datami sprzedaży i tym, czy sprzedaż została opłacona. Oznacza to, że możemy faktycznie znaleźć sprzedaż, która została dostarczona i jest stara, ale nie została opłacona. Dzieje się tak w niektórych przypadkach z życia wziętych, więc nie ma problemu z przyjęciem tego. Po czwarte, fakt, że wiadomości klientów dotyczące konkretnej sprzedaży są oceniane wysoko lub nisko, nie wpływa na ogólną ocenę, jaką przyznali oni firmie The Food Factory. Istnieją dwie kolumny z GWIAZDKAMI, jedna dla ogólnej oceny Fabryki żywności i jedna, która zostanie wysłana z każdą sesją związaną z zamówieniem. Oznacza to, że klient, który ogólnie lubi The Food Factory, może mieć złe doświadczenia i nie wpłynie to na to, jak bardzo będzie lubił ten sklep. I odwrotnie, klient, który generalnie nie lubi The Food Factory, nie polubi go, ponieważ pewnego dnia miał z nim dobre doświadczenia. To założenie jest prawdziwe dla osób o ustalonych preferencjach, ale ogólnie nie sprawdza się. Gdybyśmy chcieli, moglibyśmy uwzględnić w symulacji mechanizmy uwzględniające tę dynamikę. Zresztą zachęcam do samodzielnego wdrożenia niektórych z tych mechanizmów. To będzie dobra praktyka. Po piąte, nie będziemy się martwić, że makroskładniki odżywcze mają sens, w tym połączenie ze źródłami białka. Zwykła dieta zawiera około 50% białka, 35% węglowodanów i 15% tłuszczu, ale nie będziemy się martwić, że nasze liczby mają sens żywieniowy. Oznacza to, że proszę nie myśleć, że żadne z tych symulowanych zamówień na jedzenie jest realistyczne lub w rzeczywistości zdrowe.

Projektowanie naszych tabel danych

Przed rozpoczęciem programowania zawsze dobrze jest projektować za pomocą papieru i ołówka. Jeśli to zrobisz, przekonasz się, że twój kod jest znacznie lepszy, ponieważ będziesz rozważać scenariusze, których możesz nie zobaczyć, jeśli zaczniesz programować od razu, i zamiast hakować to, co już zaprogramowałeś, będziesz w stanie wcześniej zaprojektować rozwiązania. To łatwa inwestycja, która bardzo często się opłaca, więc to właśnie zrobimy w tej sekcji, zaprojektujemy nasze dane.

Podstawowe zmienne

Zacznijmy od najprostszego scenariusza, jaki możemy sobie wyobrazić, i spróbujmy znaleźć wszelkie potencjalne problemy, jakie możemy napotkać. Dla każdej sprzedaży chcielibyśmy mieć następujące zmienne: sprzedaż DATE, COST dla produkcji tego rodzaju żywności, QUANTITY kupiona, PRICE dla rodzaju żywności, niezależnie od tego, czy lub nie zastosowaliśmy a DISCOUNT, procentowe wartości makroskładników pokarmowych dla CARBS (węglowodany), PROTEIN i FAT, PROETIN_SOURCE jedzenia (albo FISH, CHICKEN, BEEF lub VEGETARIAN, jeśli dana osoba tego nie robi jeść mięso), STORE gdzie zostało sprzedane, DELIVERT metoda (wyślij TO LOCATION lub dostarcz IN STOR), STATUS sprzedaży, którym może być PENDING, DELIVERED, RETUNED lub CANELLED (sprzedaż nie może mieć dwóch statusów jednocześnie), niezależnie od tego, czy była PAID, klient BIRTH_DATE i GENDER, ile STARS przyznali firmie datę CUSTOMER_SINCE i ile przesłali nam wiadomości związanych z ich zamówieniem, a także DATE, STARS i rzeczywista MSSAGE dla każdego z nich.

Symulowanie danych sprzedaży i praca z bazami danych

Przykład Food Factory dotyczy fikcyjnej firmy o nazwie The Food Factory. Sprzedają niestandardowe posiłki dla osób poszukujących zdrowej żywności. Pozwalają swoim klientom wybrać kombinacje makroskładników, które chcą, a także źródła białka. Makroskładniki odżywcze są podstawą każdej diety i składają się z węglowodanów, białek i tłuszczów. Klienci mogą wybrać procent każdego makroskładnika, a także źródło białka (ryba, kurczak, wołowina lub wegetariańskie); następnie Fabryka Żywności zaproponuje smaczny posiłek, który spełnia wymagania dietetyczne. W ten sposób znaleźli świetne kombinacje i jeśli nadal będą robić tak dobrze, jak mają, dodadzą więcej opcji posiłków, a także ustalone przepisy, zgodnie z tym, co lubią ich klienci. Fabryka żywności wykonała dotychczas dobrą robotę i ma system, który pozwala im gromadzić dużą ilość danych z pięciu lokalizacji sklepów, a także śledzić wiadomości klientów. Naszym zadaniem w tym przykładzie będzie analiza danych w celu zdiagnozowania obecnego stanu biznesu i zaproponowania sposobów jego poprawy. Brzmi dobrze, prawda? Jednak zanim będziemy mogli to wszystko zrobić, musimy zdobyć dane, których jeszcze nie mamy. Zamierzamy to zasymulować! Tutaj pokaże Ci, jak zaprojektować nietrywialną symulację danych, aby wygenerować dane dla przykładu. Co więcej, The Food Factory, podobnie jak wiele organizacji, nie zawsze ułatwia nam życie, udostępniając pliki CSV, a często mają bazy danych, z którymi musimy pracować. Tutaj również pokaże Ci, jak pracować z takimi bazami danych. Niektóre z ważnych tematów to:

* Projektowanie i wdrażanie nietrywialnych symulacji

* Symulowanie liczb, kategorii, ciągów i dat

* Sygnatury funkcji z obiektami parametrów

* Ponowne wykorzystanie funkcji w różnych kontekstach

* Mieszanie danych wewnętrznych i zewnętrznych

* Praca z relacyjnymi bazami danych

Wymagane pakiety

Jedyny pakiet wymagany w tym rozdziale to RMySQL. Jednak aby móc w pełni zreplikować kod przedstawiony pod koniec rozdziału, będziesz potrzebować działającej instalacji bazy danych MySQL.

Pakiet: Zastosowanie

RMySQL: Interfejs do bazy danych MySQL

Podsumowanie

Tu pokazano, jak korzystać z modeli wielokrotnej regresji liniowej, jednej z najczęściej używanych rodzin modeli, do przewidywania danych liczbowych i jakościowych. Skupiliśmy się na pokazaniu technik programowania, które pozwalają analitykom być bardziej wydajnymi w projektach przy zachowaniu wysokiej jakości kodu. Zrobiliśmy to, pokazując, jak programowo tworzyć różne kombinacje modeli, mierząc dokładność predykcyjną i wybierając najlepszą. Zastosowane techniki można z łatwością stosować z innymi, bardziej zaawansowanymi typami modeli, dlatego zachęcamy do poprawy dokładności predykcyjnej przy użyciu innych rodzin modeli.  W następnej sekcji zaczniemy pracować z innym i nieco mniej technicznym przykładem, który wykorzystuje dane produktu z hipotetycznej firmy, aby pokazać, jak pracować z danymi manipulacyjnymi na różne sposoby i używać ich z wieloma rodzajami wizualizacji, w tym 3D, wykresy interaktywne i geoprzestrzenne

Przewidywanie głosów z okręgów o nieznanych danych

Teraz, gdy wiemy, jak wytrenować nasze modele i znaleźć najlepszy z możliwych, przedstawimy prognozy dla tych okręgów, dla których nie mamy danych głosowania, używając najlepszych modeli, które znaleźliśmy przy użyciu miary Vote. Aby to zrobić, po prostu wykonujemy następujący wiersz:

predictions <- predit(best_lm_fit_by_votes, data_incomplete)

predictions

#> 804 805 806 807 808 809 810 811 812 813

#> 0,6845 0,6238 0,5286 0,4092 0,5236 0,6727 0,6322 0,6723 0,6891 0,6004

#> 814815816817818819820821822823

#> 0,6426 0,5854 0,6966 0,6073 0,4869 0,5974 0,5611 0,4784 0,5534 0,6151

(Obcięte wyjście)

Spowoduje to wybranie najlepszego modelu, jaki znaleźliśmy wcześniej przy użyciu miary Vote, i wykorzystamy go do wygenerowania prognoz dla zmiennej Proprotion w data_incomplete dane, które zawierają te obserwacje, dla których nie mamy żadnych danych do głosowania. Są to najlepsze prognozy, jakie możemy dostarczyć dzięki temu, co zrobiliśmy do tej pory i możemy oczekiwać, że będą miały 91% dokładność, gdy zostaną użyte do kategoryzowania zmiennej Proprotion do zmiennej Vote.

Generowanie kombinacji modeli

Pierwszą rzeczą, którą musimy zrobić, jest opracowanie sposobu uzyskiwania kombinacji regresorów, które chcemy przetestować. Ponieważ jest to problem kombinatoryczny, liczba kombinacji jest wykładnicza wraz z liczbą dostępnych opcji. W naszym przypadku, przy 19 dostępnych zmiennych, liczba możliwych modeli jest sumą liczby modeli, które możemy utworzyć za pomocą jednego regresora oraz liczby modeli, które możemy utworzyć za pomocą dwóch regresorów itd., Aż zsumujemy liczbę modeli, które możemy stworzyć ze wszystkimi 19 regresorami. Oto jaka jest suma:

Oczywiście obliczanie tak wielu modeli, choć łatwych dla komputera, może trochę potrwać, dlatego chcemy ograniczyć minimalną i maksymalną liczbę regresorów dozwolonych w kombinacjach. W tym celu określamy minimalny i maksymalny procent regresorów, które zostaną uwzględnione w parametrach min_percentage i max_percentage, odpowiednio. W naszym przypadku, jeśli podamy min_percentage = 0.9 i max_percentage = 1  prosimy o podanie wszystkich kombinacji zawierających od 17 do 19 regresorów, co daje w sumie 191 modeli. Wyobraź sobie, ile czasu zajęłoby Ci ręczne wygenerowanie 191 specyfikacji modelu! Miejmy nadzieję, że myślenie o tym uświadomi ci siłę tej techniki.

Na początek tworzymy funkcję generate_combinations_unvectorized(), która wyświetli listę z wszystkimi możliwymi kombinacjami dla parametrów variable oraz min_percentage i max_percentage wspomnianych wcześniej. Pierwszą rzeczą, jaką robimy, jest usunięcie zmiennej Proportion, określając ją jako FALSE w wektorze varaibles (W tym miejscu obiekt variables odpowiada obiektowi numerial_variables, ale dostosowaliśmy jego nazwę w ramach tej funkcji, aby była bardziej czytelna). Inne niechciane zmienne (NVotes, Leave, Vote i RegionName) zostały usunięte w funkcji get_numerical_variable_ames()na początku części. Następnie otrzymujemy rzeczywiste nazwy zmiennych z wartościami TRUE, dzięki czemu możemy pracować z ciągiem znaków, a nie z wartością logiczną. Następnie obliczamy całkowitą liczbę zmiennych jako n, a rzeczywistą liczbę zmiennych uwzględnimy w kombinacjach, biorąc parametry procentowe, mnożąc je przez liczbę zmiennych i uzyskując dolną lub górną granicę dla tej liczby do upewnij się, że uwzględniamy skrajności. Następnie inicjalizujemy obiekt all_combination, który będzie zawierał listę żądanych kombinacji. Następna część to obiekt paska postępu, którego nie będziemy wyjaśniać, ponieważ używaliśmy go wcześniej. Właściwa praca jest wykonywana wewnątrz pętli for. Zauważ, że przechodzi od minimalnej do maksymalnej liczby zmiennych, które chcemy w naszych kombinacjach. W każdej iteracji obliczamy liczbę kombinacji, które są nam zwracane jako macierz, w której każda kolumna reprezentuje inną kombinację, a każdy wiersz zawiera indeks zmiennych dla tej konkretnej kombinacji. Oznacza to, że musimy dodać każdą z tych kolumn do naszej całkowitej listy kombinacji (all_combination), co robimy wewnątrz zagnieżdżonej pętli for. Wreszcie, ponieważ mamy zagnieżdżone listy, chcemy użyć funkcji unlist(), aby przenieść je na ten sam poziom, ale nie chcemy tego robić rekurencyjnie, ponieważ zakończylibyśmy po prostu jedną długą listą i nie bylibyśmy w stanie odróżnić jednej kombinacji od drugiej. Zachęcam do zmiany instrukcji return, aby uniknąć używania parametru recursive = FALSE, a także w ogóle unikać używania funkcji unlist(). Dzięki temu szybko dowiesz się, jaki wpływ mają one na wyjście funkcji i dlaczego ich potrzebujemy.

library(progress)

generate_combinations_uvectorized <- function(variables, min_percentage, max_percentage){

variables[[„Proportion”]] <- FALSE

variables <- names(variables[variables == TRUE])

n <- length(varibles)

n_min <-  floor(n * min_percentage)

n_max <- ceiling(n * max_percentage)

all_combinations <- NULL

progres_bar <- progres_bar$new (

format = „Progress [:bar] :percent ETA: :eta”,

total = length(n_min:n_max)

)

for (k in n_min:n_max) {

progres_bar$tick()

combinations <- combn(variables,k)

for (column i 1:ncol(combinations)) {

new_llist <- list(combiations[, column])

all_combinatinos <- c(all_ombinations , list(new_list_)

}

}

return(unlist(all_ombinations, recursive = FALSE))

Przykładowe dane wyjściowe obiektu, dla którego funkcja generate_combinations_unvectorized() nie jest pokazana obok Jak widać, jest to lista, na której każdy element jest wektorem lub typem character. Pierwsza utworzona kombinacja zawiera tylko 17 zmiennych, co stanowi minimalną liczbę użytych zmiennych, gdy całkowita liczba zmiennych wynosi 19, a minimalny wymagany procent to 90%. Ostatnia kombinacja (numer kombinacji 191) zawiera wszystkie 19 zmiennych i odpowiada modelowi, który zbudowaliśmy ręcznie wcześniej:

combinations <- generate_combinations_unvectorized(

numerical_variables, 0.9, 1.0

)

combiations

[[1]]

[1] „Residents”  „Households” „White” „Owned”

[5] „OwnedOutright”  „SocialRent”  „PrivateRent” „Students”

[9] „Unemp” „UnempRate_EA” „HigherOccup” „Density”

[13] „Deprived” „MultiDepriv” „Age_18to44”  „Age_45plus”

[17] „NonWhite”

[[191]]

[1] ] „Residents”  „Households” „White”

[4] „Owned”  „OwnedOutright”  „SocialRent”

[7] „PrivateRent” „Students” „Unemp”

[10] „UnempRate_EA” „HigherOccup” „Density”

[13] „Deprived” „MultiDepriv” „Age_18to44”

[16] „Age_45plus” „NonWhite” „HighEducationalLevel”

[19] „LowEducationLevel”

Uzyskanie tylko tych kombinacji, które zawierają od 90% do 100% zmiennych, może wydawać się nieco restrykcyjne. A co jeśli chcemy wygenerować wszystkie możliwe kombinacje? W takim przypadku zmienilibyśmy pierwszy parametr na 0, ale może to nie zakończyć się w praktycznym czasie. Powodem jest to, że nasza funkcja generate_combiations_unvectorized(), jak sama nazwa wskazuje, nie jest wektoryzowany, a co gorsza, ma zagnieżdżone pętle for. W tym konkretnym przypadku jest to ogromne wąskie gardło i jest to coś, na co chcesz zwrócić uwagę we własnym kodzie. Jednym z możliwych rozwiązań jest wykonanie zwektoryzowanej wersji funkcji.  W tych przypadkach, w których wektoryzacja i inne podejścia zależne tylko od samego R nie są wystarczająco dobre, możesz spróbować delegować zadanie do szybszego (skompilowanego) języka. Wracając do naszego przykładu, następną rzeczą do zrobienia jest utworzenie funkcji find_best_fit(), która przejdzie przez każdą z wygenerowanych kombinacji, używając danek data_train, aby wytrenować model z odpowiednią kombinacją, przetestuj jego dokładność za pomocą selekcji measure (albo Proportion (numeryczna) albo Vote (kategoryczna) ) i zapisze odpowiedni wynik w wektorze scores. Następnie znajdzie indeks optymalnego wyniku, znajdując minimalny lub maksymalny wynik, w zależności od wyboru measure, którego używamy (Proportion wymaga od nas zminimalizowania podczas gdy Vote wymaga od nas maksymalizacji), a na koniec odtworzy optymalny model, wydrukuje informacje i zwróci model użytkownikowi. Funkcje computer_model_and_fit(), computer_score() i print_best_model)info() zostaną opracowane w następnej kolejności, zgodnie z podejściem odgórnym:

find_best_fit <- fuctio(measure, data_trai, data_test, ombiations) {

n_cases <- length(combinations)

progress_bar <- progress_bar$new(

format = „Progress [:bar] :percent ETA:  :eta”,

total = _cases

)

scores <-lapply*1:n_cases, function(i) {

progress_bar$tick()

results <- computer_model_and_fit(combinatios [[i]] , data_train)

score <- computer_sore(measure, results[[„fit”]] , data_test)

return(score)

})

i <- ifelse(measure == „Proprotion” , which.min(scores),

which.max(scores))

best_results <- computer_model_and_fit(combinations [[i]], data_train)

best_score <- computer_score(measure, best_results[[„fit”]], data_test)

print_best_model_info(i, best_results[[„mode”]], best_score, measure)

return(best_results[[„fit”]])

}

Następnie tworzymy funkcję computer_model_and_fit, która po prostu generuje formułę dla wybranej kombinacji i wykorzystuje ją w funkcji lm() Jak widać w obiekcie combiations, poprzednio wróciliśmy z funkcji generate_combiations_unvectorized() , to lista z wektorami znaków, to nie jest formuła my może przejść do funkcji lm(); dlatego potrzebujemy funkcji generate_model(), która przejmie te wektory i połączy jej elementy w jeden ciąg ze znakiem plus (+) między nimi poprzez użycie funkcji paste() z argumentem collapes + „ + „ i doda do niej łańcuch Proportion . To daje nam z powrotem obiekt formuły określony przez łańcuch, taki jak Proprotion ~ Resident + … + NonWhite, który zawiera zamiast kropek wszystkie zmienne w pierwszej kombinacji pokazanej w poprzednim kodzie. Ten ciąg jest następnie używany wewnątrz funkcji lm()  do wykonania naszej regresji, a zarówno model, jak ifit są zwracane w ramach listy do użycia w następujących krokach:

cmpute_model_and_fit <- function(cmobination,data_train) {

model <- generate_model(combination)

return (list(model = model , fit = lm(model, data_train)))

}

geerate_model <- function(combination) {

sum <- paste(combinatio, collapse = „ + „ )

return(formula(paste(„Proportion”, „~” , sum)))

}

Jak widać po linii score<- computer_sore(measure, results[[„fit”]], data_test, funkcja computer_score() otrzymuje obiekt measure, obiekt fit (który pochodzi z listy results) oraz dane użyte do testów. Oblicza wynik przy użyciu wspomnianego wcześniej wzorca strategii dla wykresów użytych do sprawdzenia założenia o normalności. Zasadniczo, w zależności od wartości ciągu measure (wybranej strategii), wybierze jedną z dwóch funkcji, które mają ten sam podpis, i ta funkcja zostanie użyta do obliczenia ostatecznych prognoz. Wysyłamy parametr se.fit = TRUE do funkcji predict(), którą widzieliśmy wcześniej, ponieważ chcemy, aby standardowe błędy były również wysyłane w przypadku, gdy używamy wyniku liczbowego co ich wymaga. Funkcje score_proprotions() i score_votes() zostały zdefiniowane wcześniej :

compute_score <- function(measure, fit , data_test) {

if (measure = = „Proprotion”) {

score <- score_proprotions

}else {

score <- score_votes

}

predictions <- predict(fit, data_test, se,fit = TRUE_

return(score(data_test, prediction))

}

Na koniec tworzymy małą wygodną funkcję o nazwie print_best_model_info(), która wypisze wyniki dotyczące najlepszego znalezionego modelu. Po prostu pobiera indeks najlepszego modelu, formułę modelu, jego wynik oraz typ miary i drukuje to wszystko dla użytkownika. Jak widać, ponieważ obiekt model nie jest prostym ciągiem, ale obiektem formuły, musimy trochę popracować z nim, aby uzyskać pożądane wyniki, konwertując go na ciąg i dzieląc za pomocą znaku plus (+) wiemy, że jest uwzględniony; w przeciwnym razie byłby to bardzo długi ciąg:

print_best_model_ifo <- function(i, model, best_score, measure) {

print („ **************************************************”)

print(paste(„Best model number: „ , i))

print(paste(„Best score:    „, best_score))

print(paste(„Score measure :    „ , measure))

print(„Best model: „)

print(strsplit(toString(model), „\\+:))\

print („ **************************************************”)

Możemy znaleźć najlepszy model, zgodnie z miarą Proportion, wywołując:

best_lm_fit_by_proprotions <- find_best_fit(

measure = „Proprotion”,

data_train = data_train,

data_test = data_test,

combinatios = combinations

)

#> [1] “*************************************”

#> [1] “Best model number: 3”

#> [1] “Best score: 10.2362983528259”

#> [1] “Score measure: Proportion”

#> [1] “Best model:”

#> [[1]]

#> [1] “~, Proportion, Residents ” ” Households “

#> [3] ” White ” ” Owned “

#> [5] ” OwnedOutright ” ” SocialRent “

#> [7] ” PrivateRent ” ” Students “

#> [9] ” Unemp ” ” UnempRate_EA “

#> [11] ” HigherOccup ” ” Density “

#> [13] ” Deprived ” ” MultiDepriv “

#> [15] ” Age_18to44 ” ” Age_45plus “

#> [17] ” LowEducationLevel”

#> [1] “*************************************”

Jak widać, najlepszym modelem był trzeci ze 191 modeli z notą 10,23. Możemy również zobaczyć regresory użyte w modelu. Jak widać, NoWhite i HighEducationLevel zostały pominięte w metodzie optymalizacji, prawdopodobnie ze względu na ich odpowiedniki zawierające wszystkie niezbędne informacje dla odpowiednich grup. To nie przypadek, że są to jedne z najbardziej reprezentatywnych zmiennych w danych. Aby znaleźć najlepszy model według miary Vote, używamy następującego kodu. Zauważ, że biorąc pod uwagę dobre techniki, których użyliśmy do stworzenia tej funkcji, wszystko, co musimy zrobić, to zmienić wartość parametru measure, aby zoptymalizować nasze wyszukiwanie przy użyciu innego podejścia:

best_lm_fit_by_votes <- find_best_fit (

measure = „Vote”

data_train = data_train,

data_test = data_test

ombinations = combinations

)

#> [1] “*************************************”

#> [1] “Best model number: 7”

#> [1] “Best score: 220”

#> [1] “Score measure: Vote”

#> [1] “Best model:”

#> [[1]]

#> [1] “~, Proportion, Residents ” ” Households “

#> [3] ” White ” ” Owned “

#> [5] ” OwnedOutright ” ” SocialRent “

#> [7] ” PrivateRent ” ” Students “

#> [9] ” Unemp ” ” UnempRate_EA “

#> [11] ” HigherOccup ” ” Density “

#> [13] ” Deprived ” ” MultiDepriv “

#> [15] ” Age_45plus ” ” NonWhite “104

#> [17] ” HighEducationLevel”

#> [1] “*************************************”

W tym przypadku najlepszym modelem był siódmy ze 191 modeli, z 220 z 241 poprawnych prognoz, co daje nam dokładność 91%, co jest poprawą, biorąc pod uwagę dokładność obliczoną wcześniej w tym rozdziale. W tym przypadku LowEducationLevel i Age_18to44 zostały pominięte. Ponownie, nie jest przypadkiem, że są to jedne z najważniejszych zmiennych w danych.