Porównanie algorytmów

Ponieważ wiemy, jak korzystać z SVM, regresji logistycznej i kNN, porównajmy ich wydajność w zbiorze danych SpamAssassin, nad którym pracowaliśmy we wcześniejszych częściach. Eksperymentowanie z wieloma algorytmami jest dobrym nawykiem do rozwijania podczas pracy z danymi ze świata rzeczywistego ponieważ często nie będziesz w stanie z góry wiedzieć, który algorytm będzie działał najlepiej z Twoim zestawem danych. Jedną z głównych umiejętności, która odróżnia najbardziej doświadczonych ludzi w uczeniu maszynowym od tych, którzy dopiero zaczynają go używać, jest umiejętność poznania struktury problemu, gdy określony algorytm nie działa dobrze. Najlepszym sposobem na zbudowanie tej intuicji jest zastosowanie wszystkich standardowych algorytmów do każdego napotkanego problemu, dopóki nie zorientujesz się, kiedy się nie powiedzie. Pierwszym krokiem jest po prostu załadowanie naszych danych i odpowiednie ich wstępne przetworzenie. Ponieważ było to wcześniej zrobione szczegółowo, pomińmy kilka kroków i po prostu załadujemy macierz dokumentów z dysku za pomocą funkcji ładowania w R, która czyta w formacie binarnym, który może być używany do zapisywania obiektów R na dysku w celu przechowywania długoterminowego. Następnie podzielimy zestaw treningowy / zestaw testowy i odrzucimy nieprzetworzony zestaw danych za pomocą funkcji rm, która pozwala nam usunąć obiekt z pamięci:

load(‘data/dtm.RData’)

set.seed(1)

training.indices <- sort(sample(1:nrow(dtm), round(0.5 * nrow(dtm))))

test.indices <- which(! 1:nrow(dtm) %in% training.indices)

train.x <- dtm[training.indices, 3:ncol(dtm)]

train.y <- dtm[training.indices, 1]

test.x <- dtm[test.indices, 3:ncol(dtm)]

test.y <- dtm[test.indices, 1]

rm(dtm)

Teraz, gdy mamy zestaw danych w pamięci, możemy od razu przejść do przodu i dopasować regaryzowaną regresję logistyczną za pomocą glmnet:

library(‘glmnet’)

regularized.logit.fit <- glmnet(train.x, train.y, family = c(‘binomial’))

Oczywiście pozostawia to nam dużą elastyczność, dlatego chcielibyśmy porównać różne ustawienia hiperparametru lambda, aby zobaczyć, która daje nam najlepszą wydajność. Aby szybko przejść przez ten przykład, trochę oszukujemy i przetestujemy ustawienia hiperparametrów w zestawie testowym, zamiast powtarzać podziały danych treningowych. Jeśli rygorystycznie testujesz modele, nie powinieneś robić tego rodzaju uproszczenia, ale pozostawimy czyste tuningowanie lambda jako ćwiczenie, które możesz wykonać samodzielnie. Na razie wypróbujemy wszystkie wartości zaproponowane przez glmnet i zobaczymy, które wyniki są najlepsze w zestawie testowym:

lambdas <- regularized.logit.fit$lambda

performance <- data.frame()

for (lambda in lambdas)

{

predictions <- predict(regularized.logit.fit, test.x, s = lambda)

predictions <- as.numeric(predictions > 0)

mse <- mean(predictions != test.y)

performance <- rbind(performance, data.frame(Lambda = lambda, MSE = mse))

}

ggplot(performance, aes(x = Lambda, y = MSE)) +

geom_point() +

scale_x_log10()

Patrząc na Rysunek , widzimy całkiem wyraźny obszar wartości dla lambda, który daje najniższy możliwy poziom błędu.

Aby znaleźć najlepszą wartość dla naszej ostatecznej analizy, możemy następnie zastosować proste indeksowanie i funkcję min:

best.lambda <- with(performance, max(Lambda[which(MSE == min(MSE))]))

W tym przypadku istnieją dwie różne wartości lambda, które dają identyczną wydajność, więc wyodrębniamy większą z nich za pomocą funkcji max. Wybieramy większą wartość, ponieważ jest to ta, dla której zastosowano większą regularyzację. Następnie możemy obliczyć MSE dla naszego modelu logistycznego, używając tej najlepszej wartości dla lambda:

mse <- with(subset(performance, Lambda == best.lambda), MSE)

mse

#[1] 0.06830769

Widzimy, że stosowanie regularnej regresji logistycznej, która wymaga tylko niewielkiej ilości dostrajania hiperparametrów, błędnie klasyfikuje tylko 6% wszystkich wiadomości e-mail w naszym zestawie testowym. Chcielibyśmy jednak zobaczyć, jak inne metody radzą sobie z podobną ilością pracy, abyśmy mogli zdecydować, czy powinniśmy stosować regresję logistyczną, SVM czy kNN. Z tego powodu zacznijmy od dopasowania liniowego SVM jądra, aby zobaczyć, jak to się ma do regresji logistycznej:

library(‘e1071’)

linear.svm.fit <- svm(train.x, train.y, kernel = ‘linear’)

Montaż dużego jądra SVM jest nieco powolny przy tym dużym zestawie danych. Z tego powodu będziemy nieco niesprawiedliwi wobec SVM i po prostu użyjemy domyślnych ustawień hiperparametrów. Podobnie jak w przypadku wybierania idealnego hiperparametru do regresji logistycznej poprzez ocenę wydajności naszego zestawu testowego, to użycie domyślnych wartości hiperparametrów nie jest idealne, ale jest również regularnym zjawiskiem w literaturze dotyczącej uczenia maszynowego. Porównując modele, należy pamiętać, że jedną z rzeczy, które widzisz w wynikach, jest po prostu miara tego, jak ciężko pracowałeś, aby dopasować każdy model do danych. Jeśli poświęcasz więcej czasu na dostrajanie jednego modelu od drugiego, różnice w wydajności mogą częściowo wynikać z ich odmiennej struktury, ale z różnych poziomów wysiłku, który w nie zainwestowałeś. Wiedząc o tym, ślepo posuwamy się naprzód i oceniamy wydajność SVM liniowego jądra na podstawie naszych danych testowych:

predictions <- predict(linear.svm.fit, test.x)

predictions <- as.numeric(predictions > 0)

mse <- mean(predictions != test.y)

mse

#0.128

Widzimy, że otrzymujemy 12% poziom błędu, który jest dwukrotnie wyższy niż w przypadku modelu regresji logistycznej. Aby lepiej zrozumieć rzeczywiste granice SVM jądra liniowego, powinieneś eksperymentować z manipulacją hiperparametrem kosztów w celu znalezienia jego idealnej wartości przed oszacowaniem poziomu błędu dla SVM jądra liniowego. Ale na razie będziemy trzymać się wyników, które już mamy dla liniowego SVM jądra i przejść do radialnego SVM jądra, aby zobaczyć, jak bardzo jądro zmienia wyniki więc mamy do czynienia z tym praktycznym problemem, którego nie możemy wizualizować w ten sam sposób, w jaki moglibyśmy wizualizować problem zabawki na początku tego rozdziału:

radial.svm.fit <- svm(train.x, train.y, kernel = ‘radial’)

predictions <- predict(radial.svm.fit, test.x)

predictions <- as.numeric(predictions > 0)

mse <- mean(predictions != test.y)

mse

#[1] 0.1421538

Nieco zaskakujące jest to, że jądro promieniowe działa trochę gorzej na tym zestawie danych niż jądro liniowe, co jest przeciwieństwem tego, co widzieliśmy na przykładzie danych nieliniowych. A to przykład szerszej lekcji, którą przyswoisz sobie z większym doświadczeniem w pracy z danymi: idealny model twojego problemu zależy od struktury twoich danych. W tym przypadku gorsza wydajność radialnego jądra SVM sugeruje, że idealna granica decyzji dla tego problemu może być naprawdę liniowa. Jest to również wspierane przez fakt, że widzieliśmy już, że regresja logistyczna bije zarówno SVM jądra liniowego, jak i promieniowego. Tego rodzaju obserwacje są najciekawsze, jakie możemy przeprowadzić porównując algorytmy na ustalonym zbiorze danych, ponieważ dowiadujemy się czegoś o prawdziwej strukturze danych na podstawie niewłaściwego dopasowania naszych modeli. Zanim jednak przestaniemy dopasowywać modele i zdecydujemy się pozostać przy regresji logistycznej, spróbujmy metody, która najlepiej działa z danymi nieliniowymi: kNN. Tutaj dopasowujemy kNN przy użyciu 50 sąsiadów dla każdej prognozy:

library(‘class’)

knn.fit <- knn(train.x, test.x, train.y, k = 50)

predictions <- as.numeric(as.character(knn.fit))

mse <- mean(predictions != test.y)

mse

#[1] 0.1396923

Jak widzimy, otrzymujemy 14% błąd z kNN, co jest kolejnym dowodem, że modele liniowe są lepsze do klasyfikowania spamu niż modele nieliniowe. A ponieważ kNN nie zajmuje tak dużo czasu, aby zmieścić się w tym zestawie danych, spróbujemy również kilka wartości dla k, aby zobaczyć, który działa najlepiej:

performance <- data.frame()

for (k in seq(5, 50, by = 5))

{

knn.fit <- knn(train.x, test.x, train.y, k = k)

predictions <- as.numeric(as.character(knn.fit))

mse <- mean(predictions != test.y)

performance <- rbind(performance, data.frame(K = k, MSE = mse))

}

best.k <- with(performance, K[which(MSE == min(MSE))])

best.mse <- with(subset(performance, K == best.k), MSE)

best.mse

#[1] 0.09169231

Dzięki tuningowi widzimy, że możemy uzyskać współczynnik błędu 9% z kNN. Jest to połowa drogi między wydajnością, jaką zaobserwowaliśmy dla maszyn SVM, a regresją logistyczną, co można potwierdzić, przeglądając tabelę 12-1, która zestawia wskaźniki błędów, które uzyskaliśmy z każdym z czterech algorytmów zastosowanych w tym zestawie danych spamu .

Ostatecznie wydaje się, że najlepszym rozwiązaniem tego problemu jest regresja logistyczna z dostrojonym hiperparametrem do regularyzacji. I to właściwie rozsądny wniosek, ponieważ wszystkie filtry antyspamowe o dużej mocy przeszły na regresję logistyczną i porzuciłem podejście Naive Bayes, które opisaliśmy wcześniej. Z przyczyn, które nie są dla nas całkowicie jasne, regresja logistyczna po prostu działa lepiej w przypadku tego rodzaju problemu. Jakie szersze lekcje powinieneś wyciągnąć z tego przykładu? Mamy nadzieję, że odejdziesz z myślą o kilku lekcjach: (1) zawsze powinieneś wypróbować wiele algorytmów na dowolnym praktycznym zbiorze danych, szczególnie dlatego, że tak łatwo jest eksperymentować z R; (2) rodzaje algorytmów, które działają najlepiej, są specyficzne dla problemu; oraz (3) na jakość wyników uzyskanych z modelu ma wpływ struktura danych, a także ilość pracy, jaką chcesz poświęcić na ustawianie hiperparametrów, więc nie unikaj kroku dostrajania hiperparametrów, jeśli chcę uzyskać dobre wyniki. Aby udoskonalić te lekcje w domu, zachęcamy do powrotu do czterech modeli, które pasowaliśmy w tym rozdziale i systematycznego ustawiania hiperparametrów za pomocą powtarzanych podziałów danych treningowych. Następnie zachęcamy do wypróbowania wielomianu i sigmoidu jądra, które zaniedbaliśmy podczas pracy z danymi spamu. Jeśli zrobisz obie te rzeczy, zyskasz duże doświadczenie w zakresie dopasowywania skomplikowanych modeli do rzeczywistych danych i nauczysz się doceniać, jak odmiennie modele pokazaliśmy ci w tym tekście może działać na stałym zestawie danych. W związku z tym doszliśmy do końca ostatniego rozdziału i książki jako całości. Mamy nadzieję, że odkryłeś piękno uczenia maszynowego i doceniłeś szerokie pomysły, które pojawiają się wielokrotnie, gdy próbujesz budować predykcyjne modele danych. Najlepszymi praktykami uczenia maszynowego są ci posiadający zarówno doświadczenie praktyczne, jak i teoretyczne, dlatego zachęcamy do wyjścia i rozwoju obu. Po drodze baw się dobrze podczas hakowania danych. Masz wiele zaawansowanych narzędzi, więc zastosuj je do interesujących Cię pytań!

(II) : Tryb raw

Tryb raw służy do analizy historycznej. Liczby w surowym obiekcie są w formacie szesnastkowym, przy czym każdy element składa się z dwóch cyfr, z których każda może przyjąć dowolną wartość od zera do dziewięciu lub od a do f. Elementy raw nie mogą mieć dziesiętnego odpowiednika większego niż 255 (to znaczy być liczbą szesnastkową z więcej niż dwiema cyframi) ani być ujemne. Funkcja raw() zwraca wektor o długości 00 określonej przez argument. Jeśli nie podano żadnego argumentu lub argumentu zero, raw() zwraca raw(0), surowy pusty zbiór o długości zero. Jeśli jako argument zostanie wprowadzona pojedyncza liczba, raw() zwraca wektor o długości równej liczbie zaokrąglonej w dół do liczby całkowitej. Jeśli jako argument podany zostanie jakikolwiek inny obiekt, raw() podaje błąd. Funkcja as.raw() próbuje przekonać argument funkcji do raw. Jeśli nie podano żadnego argumentu, as.raw() zwraca błąd. Jeśli argument ma wartość NULL, as.raw() zwraca wartość raw(0), surowy pusty zestaw. Jeśli argument jest równy zero, funkcja zwraca 00, zero szesnastkowe. Obiekty dowolnego z trybów atomowych mogą być użyte jako argumenty dla as.raw(). W przypadku obiektów w trybie logicznym FAŁSZ jest ustawiony na 00, a PRAWDA na 01. W przypadku obiektów w trybie numerycznym, dla wartości większych lub równych zero i mniejszych niż 256, liczby są zaokrąglane w dół do liczby całkowitej i konwertowane na szesnastkową. Liczby poza dopuszczalnym zakresem są konwertowane na 00. W przypadku obiektów o zespole modów część rzeczywista jest traktowana w taki sam sposób jak obiekty numeryczne, a część urojona jest odrzucana. W przypadku obiektów o charakterze trybu wszystkie elementy są konwertowane na 00. Każdy element równy NA zostanie również ustawiony na 00. Użycie obiektów argumentów innych niż tryby atomowe jako argumentu powoduje błąd. Funkcja is.raw() sprawdza, czy obiekt jest w trybie raw. Funkcja zwraca PRAWDA, jeśli obiekt jest w trybie surowym, a FALSE w przeciwnym razie. Dowolny obiekt może być użyty jako argument funkcji is.raw(). Więcej informacji na temat trybu raw można znaleźć, wprowadzając? Raw po znaku zachęty R.

Porównanie modeli

SVM: maszyna wektora wsparcia

Wcześniej przedstawiliśmy ideę granic decyzji i zauważyliśmy, że problemy, w których granica decyzji nie jest liniowa, stanowią problem dla prostych algorytmów klasyfikacji. Pokazaliśmy, jak wykonać regresję logistyczną, algorytm klasyfikacji, który działa poprzez konstruowanie liniowej granicy decyzji. I w obu rozdziałach obiecaliśmy opisać technikę zwaną sztuczką jądra, która może być użyta do rozwiązania problemów z nieliniowymi granicami decyzyjnymi. Złóżmy tę obietnicę, wprowadzając nowy algorytm klasyfikacji zwany maszyną wektorów wsparcia (SVM skrótowo), który pozwala używać wielu różnych jąder do znajdowania nieliniowych granic decyzyjnych. Użyjemy SVM do klasyfikacji punktów z zestawu danych z nieliniową granicą decyzji. W szczególności będziemy pracować z zestawem danych pokazanym na rysunku poniżej. Patrząc na ten zestaw danych, powinno być jasne, że punkty z klasy 0 znajdują się na obrzeżach, podczas gdy punkty z klasy 1 znajdują się w centrum wykresu. Tego rodzaju nieliniowej granicy decyzji nie można odkryć za pomocą prostego algorytmu klasyfikacji, takiego jak algorytm regresji logistycznej, który opisaliśmy wcześniej. Pokażmy to, próbując zastosować regresję logistyczną za pomocą funkcji glm. Następnie przeanalizujemy przyczynę niepowodzenia regresji logistycznej.

df <- read.csv(‘data/df.csv’)

logit.fit <- glm(Label ~ X + Y,

family = binomial(link = ‘logit’),

data = df)

logit.predictions <- ifelse(predict(logit.fit) > 0, 1, 0)

mean(with(df, logit.predictions == Label))

#[1] 0.5156

Jak widać, poprawnie przewidzieliśmy klasę tylko 52% danych. Ale moglibyśmy zrobić dokładnie to dobrze, przewidując, że każdy punkt danych należy do klasy 0:

mean(with(df, 0 == Label))

#[1] 0.5156

Krótko mówiąc, model regresji logistycznej (i znaleziona liniowa granica decyzji) jest całkowicie bezużyteczny. Dokonuje takich samych przewidywań jak model bez żadnych informacji poza faktem, że klasa 0 występuje częściej niż klasa 1, a zatem powinna być stosowana jako prognoza w przypadku braku wszystkich innych informacji. Jak więc możemy zrobić lepiej? Jak zobaczysz za chwilę, SVM zapewnia trywialny sposób i przewyższa regresję logistyczną. Zanim opiszemy, jak to robi, pokażemy, że działa jak algorytm czarnej skrzynki, który daje użyteczne odpowiedzi. Aby to zrobić, użyjemy pakietu e1071, który udostępnia funkcję svm, która jest równie łatwa w użyciu jak glm:

library(‘e1071’)

svm.fit <- svm(Label ~ X + Y, data = df)

svm.predictions <- ifelse(predict(svm.fit) > 0, 1, 0)

mean(with(df, svm.predictions == Label))

#[1] 0.7204

Tutaj wyraźnie osiągnęliśmy lepsze wyniki niż regresja logistyczna, przechodząc na SVM. Jak robi to SVM? Pierwszym sposobem na uzyskanie wglądu w doskonałą wydajność maszyny SVM jest wykreślenie jej prognoz w porównaniu z regresją logistyczną:

df <- cbind(df,

data.frame(Logit = ifelse(predict(logit.fit) > 0, 1, 0),

SVM = ifelse(predict(svm.fit) > 0, 1, 0)))

predictions <- melt(df, id.vars = c(‘X’, ‘Y’))

ggplot(predictions, aes(x = X, y = Y, color = factor(value))) +

geom_point() +

facet_grid(variable ~ .)

Tutaj dodaliśmy prognozy regresji logistycznej i prognozy SVM do surowego zestawu danych. Następnie używamy funkcji stopu, aby zbudować zestaw danych, z którym łatwiej jest pracować do celów drukowania, i przechowujemy ten nowy zestaw danych w ramce danych zwanej prognozami. Następnie wykreślamy podstawowe etykiety prawdy wraz z przewidywaniami logit i SVM na fasetowanym wykresie pokazanym tu.

Gdy to zrobimy, staje się oczywiste, że regresja logistyczna jest bezużyteczna, ponieważ ustawia granicę decyzji poza zbiorem danych, podczas gdy dane dotyczące prawdziwości gruntu w górnym rzędzie wyraźnie zawierają zakres wpisów dla klasy 1 pośrodku. SVM jest w stanie uchwycić tę strukturę pasma, nawet jeśli dokonuje nieparzystych prognoz w pobliżu najdalszych granic zbioru danych. Teraz widzieliśmy, że SVM tworzy, zgodnie z obietnicą, nieliniową granicę decyzji. Ale jak to robi? Odpowiedź jest taka, że ​​SVM używa sztuczki jądra. Za pomocą transformacji matematycznej przenosi oryginalny zestaw danych do nowej przestrzeni matematycznej, w której granice decyzji są łatwe do opisania. Ponieważ ta transformacja zależy tylko od prostego obliczenia obejmującego „jądra”, technika ta nazywa się sztuczką jądra. Opis matematyki sztuczki jądra nie jest prosty, ale łatwo jest uzyskać intuicję, testując różne jądra. Jest to łatwe, ponieważ funkcja SVM ma parametr o nazwie jądro, który można ustawić na jedną z czterech wartości: liniową, wielomianową, radialną i sigmoidalną. Aby dowiedzieć się, jak działają te jądra, spróbujmy użyć ich wszystkich do wygenerowania prognoz, a następnie wykreślić prognozy:

df <- df[, c(‘X’, ‘Y’, ‘Label’)]

linear.svm.fit <- svm(Label ~ X + Y, data = df, kernel = ‘linear’)

with(df, mean(Label == ifelse(predict(linear.svm.fit) > 0, 1, 0)))

polynomial.svm.fit <- svm(Label ~ X + Y, data = df, kernel = ‘polynomial’)

with(df, mean(Label == ifelse(predict(polynomial.svm.fit) > 0, 1, 0)))

radial.svm.fit <- svm(Label ~ X + Y, data = df, kernel = ‘radial’)

with(df, mean(Label == ifelse(predict(radial.svm.fit) > 0, 1, 0)))

sigmoid.svm.fit <- svm(Label ~ X + Y, data = df, kernel = ‘sigmoid’)

with(df, mean(Label == ifelse(predict(sigmoid.svm.fit) > 0, 1, 0)))

df <- cbind(df,

data.frame(LinearSVM = ifelse(predict(linear.svm.fit) > 0, 1, 0),

PolynomialSVM = ifelse(predict(polynomial.svm.fit) > 0, 1, 0),

RadialSVM = ifelse(predict(radial.svm.fit) > 0, 1, 0),

SigmoidSVM = ifelse(predict(sigmoid.svm.fit) > 0, 1, 0)))

predictions <- melt(df, id.vars = c(‘X’, ‘Y’))

ggplot(predictions, aes(x = X, y = Y, color = factor(value))) +

geom_point() +

facet_grid(variable ~ .)

Jak widać na rysunku , jądra liniowe i wielomianowe przypominają mniej więcej regresję logistyczną.

Natomiast jądro promieniowe daje nam granicę decyzyjną podobną do granicy prawdy naziemnej. Jądro sigmoidów daje nam bardzo złożoną i dziwną granicę decyzji. Powinieneś wygenerować własne zestawy danych i bawić się tymi czterema jądrami, aby zbudować intuicję dotyczącą ich działania. Po wykonaniu tej czynności możesz podejrzewać, że SVM może przewidywać znacznie lepsze przewidywania, niż wydaje się to po wyjęciu z pudełka. To prawda. SVM jest dostarczany z zestawem hiperparametrów, które domyślnie nie są ustawione na przydatne wartości, a uzyskanie najlepszych przewidywań z modelu wymaga dostrajania ich hiperparametry. Opiszmy główne hiperparametry i zobaczmy, jak ich tuning poprawia wydajność naszego modelu. Pierwszym hiperparametrem, z którym możesz pracować, jest stopień wielomianu używanego przez jądro wielomianu. Możesz to zmienić, ustawiając wartość stopnia podczas wywoływania svm. Zobaczmy, jak ten hiperparametr działa na czterech prostych przykładach:

polynomial.degree3.svm.fit <- svm(Label ~ X + Y,

data = df,

kernel = ‘polynomial’,

degree = 3)

with(df, mean(Label != ifelse(predict(polynomial.degree3.svm.fit) > 0, 1, 0)))

#[1] 0.5156

polynomial.degree5.svm.fit <- svm(Label ~ X + Y,

data = df,

kernel = ‘polynomial’,

degree = 5)

with(df, mean(Label != ifelse(predict(polynomial.degree5.svm.fit) > 0, 1, 0)))

#[1] 0.5156

polynomial.degree10.svm.fit <- svm(Label ~ X + Y,

data = df,

kernel = ‘polynomial’,

degree = 10)

with(df, mean(Label != ifelse(predict(polynomial.degree10.svm.fit) > 0, 1, 0)))

#[1] 0.4388

polynomial.degree12.svm.fit <- svm(Label ~ X + Y,

data = df,

kernel = ‘polynomial’,

degree = 12)

with(df, mean(Label != ifelse(predict(polynomial.degree12.svm.fit) > 0, 1, 0)))

#[1] 0.4464

Tutaj widzimy, że ustawienie stopnia na 3 lub 5 nie ma żadnego wpływu na jakość prognoz modelu. (Warto zauważyć, że domyślna wartość stopnia to 3.) Ale ustawienie stopnia na 10 lub 12 ma wpływ. Aby zobaczyć, co się dzieje, ponownie ustalmy granice decyzji:

df <- df[, c(‘X’, ‘Y’, ‘Label’)]

df <- cbind(df,

data.frame(Degree3SVM = ifelse(predict(polynomial.degree3.svm.fit) > 0,

1,

0),

Degree5SVM = ifelse(predict(polynomial.degree5.svm.fit) > 0,

1,

0),

Degree10SVM = ifelse(predict(polynomial.degree10.svm.fit) > 0,

1,

0),

Degree12SVM = ifelse(predict(polynomial.degree12.svm.fit) > 0,

1,

0)))

predictions <- melt(df, id.vars = c(‘X’, ‘Y’))

ggplot(predictions, aes(x = X, y = Y, color = factor(value))) +

geom_point() +

facet_grid(variable ~ .)

Patrząc na prognozy pokazane poniżej, widać wyraźnie, że użycie większego stopnia poprawia jakość prognoz, choć robi to w hacking sposób, który tak naprawdę nie naśladuje struktury danych.

 I, jak zauważysz, krok dopasowania modelu staje się coraz wolniejszy wraz ze wzrostem stopnia. I w końcu pojawią się te same problemy z nadmiernym dopasowaniem, które widzieliśmy w rozdziale 6 z regresją wielomianową. Z tego powodu należy zawsze używać weryfikacji krzyżowej w celu eksperymentowania z ustawianiem hiperparametru stopnia w aplikacjach korzystających z SVM z jądrem wielomianowym. Chociaż nie ma wątpliwości, że maszyny SVM z jądrem wielomianowym są cennym narzędziem w zestawie narzędzi, nie można zagwarantować, że będą działać dobrze bez wysiłku i myślenia z Twojej strony. Po zabawie z hiperparametrem stopnia dla jądra wielomianowego wypróbujmy hiperparametr kosztu, który jest stosowany we wszystkich możliwych jądrach SVM. Aby zobaczyć efekt zmiany kosztu, użyjemy jądra radialnego i wypróbujemy cztery różne ustawienia kosztów. Aby to zmienić, przestaniemy również zliczać błędy i zaczniemy sprawdzać, ile punktów danych przewidujemy poprawnie, ponieważ ostatecznie jesteśmy zainteresowani zobaczeniem, jak dobry jest najlepszy model, a nie, jak zły jest najgorszy model. Poniżej przedstawiono kod tego badania parametru kosztu:

radial.cost1.svm.fit <- svm(Label ~ X + Y,

data = df,

kernel = ‘radial’,

cost = 1)

with(df, mean(Label == ifelse(predict(radial.cost1.svm.fit) > 0, 1, 0)))

#[1] 0.7204

radial.cost2.svm.fit <- svm(Label ~ X + Y,

data = df,

kernel = ‘radial’,

cost = 2)

with(df, mean(Label == ifelse(predict(radial.cost2.svm.fit) > 0, 1, 0)))

#[1] 0.7052

radial.cost3.svm.fit <- svm(Label ~ X + Y,

data = df,

kernel = ‘radial’,

cost = 3)

with(df, mean(Label == ifelse(predict(radial.cost3.svm.fit) > 0, 1, 0)))

#[1] 0.6996

radial.cost4.svm.fit <- svm(Label ~ X + Y,

data = df,

kernel = ‘radial’,

cost = 4)

with(df, mean(Label == ifelse(predict(radial.cost4.svm.fit) > 0, 1, 0)))

#[1] 0.694

Jak widać, zwiększenie parametru kosztu powoduje, że model stopniowo dopasowuje się coraz bardziej i gorzej. Jest tak, ponieważ koszt jest hiperparametrem regularyzacji, takim jak parametr lambda, który opisaliśmy wcześniej, a jego zwiększenie zawsze sprawi, że model będzie mniej pasował do danych treningowych. Oczywiście ten wzrost regularyzacji może poprawić wydajność twojego modelu na danych testowych, więc zawsze powinieneś zobaczyć, jaka wartość kosztu najbardziej poprawia wydajność testu przy użyciu weryfikacji krzyżowej. Aby uzyskać wgląd w to, co się dzieje pod względem dopasowanego modelu, spójrzmy na przewidywania graficznie:

df <- df[, c(‘X’, ‘Y’, ‘Label’)]

df <- cbind(df,

data.frame(Cost1SVM = ifelse(predict(radial.cost1.svm.fit) > 0, 1, 0),

Cost2SVM = ifelse(predict(radial.cost2.svm.fit) > 0, 1, 0),

Cost3SVM = ifelse(predict(radial.cost3.svm.fit) > 0, 1, 0),

Cost4SVM = ifelse(predict(radial.cost4.svm.fit) > 0, 1, 0)))

predictions <- melt(df, id.vars = c(‘X’, ‘Y’))

ggplot(predictions, aes(x = X, y = Y, color = factor(value))) +

geom_point() +

facet_grid(variable ~ .)

Zmiany wywołane parametrem kosztu są dość subtelne, ale można je zobaczyć na obrzeżach zestawu danych pokazanego poniżej. W miarę wzrostu kosztów granice tworzone przez jądro radialne stają się coraz bardziej liniowe

Po przeanalizowaniu parametru kosztu zakończymy nasze eksperymenty z hiperparametrami SVM, grając z hiperparametrem gamma. Do celów testowych obserwujemy jego wpływ na jądro sigmoidalne, testując cztery różne wartości gamma:

sigmoid.gamma1.svm.fit <- svm(Label ~ X + Y,

data = df,

kernel = ‘sigmoid’,

gamma = 1)

with(df, mean(Label == ifelse(predict(sigmoid.gamma1.svm.fit) > 0, 1, 0)))

#[1] 0.478

sigmoid.gamma2.svm.fit <- svm(Label ~ X + Y,

data = df,

kernel = ‘sigmoid’,

gamma = 2)

with(df, mean(Label == ifelse(predict(sigmoid.gamma2.svm.fit) > 0, 1, 0)))

#[1] 0.4824

sigmoid.gamma3.svm.fit <- svm(Label ~ X + Y,

data = df,

kernel = ‘sigmoid’,

gamma = 3)

with(df, mean(Label == ifelse(predict(sigmoid.gamma3.svm.fit) > 0, 1, 0)))

#[1] 0.4816

sigmoid.gamma4.svm.fit <- svm(Label ~ X + Y,

data = df,

kernel = ‘sigmoid’,

gamma = 4)

with(df, mean(Label == ifelse(predict(sigmoid.gamma4.svm.fit) > 0, 1, 0)))

#[1] 0.4824

Za każdym razem, gdy zwiększamy gamma, model robi się trochę lepiej. Aby znaleźć źródło tego ulepszenia, przejdźmy do graficznej diagnostyki prognoz:

df <- df[, c(‘X’, ‘Y’, ‘Label’)]

df <- cbind(df,

data.frame(Gamma1SVM = ifelse(predict(sigmoid.gamma1.svm.fit) > 0, 1, 0),

Gamma2SVM = ifelse(predict(sigmoid.gamma2.svm.fit) > 0, 1, 0),

Gamma3SVM = ifelse(predict(sigmoid.gamma3.svm.fit) > 0, 1, 0),

Gamma4SVM = ifelse(predict(sigmoid.gamma4.svm.fit) > 0, 1, 0)))

predictions <- melt(df, id.vars = c(‘X’, ‘Y’))

ggplot(predictions, aes(x = X, y = Y, color = factor(value))) +

geom_point() +

facet_grid(variable ~ .)

Jak widać, dość skomplikowana granica decyzyjna wybrana przez jądro sigmoidalne wypacza się, gdy zmieniamy wartość gamma.

Aby naprawdę uzyskać lepszą intuicję w tym, co się dzieje, zalecamy eksperymentowanie z dużo większą liczbą wartości gamma niż cztery, które przed chwilą pokazaliśmy. To kończy nasze wprowadzenie do SVM. Uważamy, że jest to cenny algorytm w twoim zestawi narzędzi, ale nadszedł czas, aby przestać budować zestaw narzędzi i zamiast tego zacząć skupiać się na krytycznym zastanowieniu się, które narzędzie jest najlepsze dla danego zadania. W tym celu zbadamy wiele modeli jednocześnie na jednym zestawie danych.

Tryb complex

Tryb complex to tryb liczb zespolonych. Liczby zespolone można tworzyć za pomocą metody complex() lub po prostu wpisując liczby po znaku zachęty R. Na przykład:

> a = complex(real=1:5, imaginary=6:10)

> a

[1] 1+ 6i 2+ 7i 3+ 8i 4+ 9i 5+10i

> a = 1:5 + 1i*6:10

> a

[1] 1+ 6i 2+ 7i 3+ 8i 4+ 9i 5+10i

Zauważ, że dla liczb zespolonych zawsze przed i znajduje się liczba bez operatora, co informuje R, że i jest urojonym pierwiastkiem minus jeden. Dla funkcji complex() argument o wartości zero lub brak argumentu zwraca wartość complex(0), pusty zestaw trybów złożonych i długość zero. Jeśli argument jest pojedynczą liczbą dodatnią, funkcja complex() zwraca wektor złożonych zer o długości liczby zaokrąglonej w dół do liczby całkowitej. Jeśli argument składa się z obiektu numerycznego z więcej niż jednym elementem lub jeśli argument jest logiczny z jednym elementem lub więcej niż jednym elementem, używany jest tylko pierwszy element argumentu, gdzie dla obiektów logicznych wartość FALSE jest zerowana, a TRUE to jeden. Funkcja complex() przyjmuje również argumenty rzeczywiste i urojone lub moduł i argument. Argumenty rzeczywiste i urojone lub moduł i argument można ustawić równe dowolnemu obiektowi numerycznemu lub logicznemu. Obiekty nie muszą być tej samej długości i będą się cyklicznie poruszać. Argumenty rzeczywiste i urojone to rzeczywiste i urojone części liczb, podczas gdy moduł argumentów i argument są biegunowymi współrzędnymi liczb, z modułem równym długościom liczb i argumentem równym kątom powyżej osi x liczb w radianach. Liczby trybów raw mogą być użyte do prawdziwych i wymyślonych argumentów i woli zostać zmienione na podwójną precyzję, ale nie można ich użyć do modułów argumentu argumentów. W przypadku pary rzeczywistej i urojonej można pominąć jedną z nich, a pominięty argument zostanie ustawiony na zero. W przypadku modułu i pary argumentów, jeżeli moduł zostanie pominięty, wartość modułu zostanie ustawiona na jeden, a jeśli argument zostanie pominięty, wartość argumentu zostanie ustawiona na zero. Niektóre przykłady funkcji complex() obejmują:

> complex(real=c(T,F), imaginary=1:5+0.5)

[1] 1+1.5i 0+2.5i 1+3.5i 0+4.5i 1+5.5i

> complex(modulus=c(1,2), argument=pi/4)

[1] 0.7071068+0.7071068i 1.4142136+1.4142136i

> as.raw(27:30)

[1] 1b 1c 1d 1e

> complex(real=as.raw(27:30))

[1] 27+0i 28+0i 29+0i 30+0i

> complex(ima=as.raw(27:30))

[1] 0+27i 0+28i 0+29i 0+30i

> complex(mod=as.raw(27:30))

Error in rep_len(modulus, n) * exp((0+1i) * rep_len(argument, n)) :

non-numeric argument to binary operator

> complex(mod=3:5)

[1] 3+0i 4+0i 5+0i

> complex(arg=3:5*pi/180)

[1] 0.9986295+0.0523360i 0.9975641+0.0697565i 0.9961947+0.0871557i

Funkcja as.complex () spróbuje zmusić obiekt do przejścia w tryb złożony. Jeśli obiekt może być wymuszony na wartości numeryczne (tryby atomowe), ale nie jest złożony, to wynikiem jest złożony obiekt z wymuszonym argumentem jako częścią rzeczywistą i zerami dla części urojonej, z wyjątkiem NA, które są zwracane po prostu jako NA. W trybach nieatomowych as.complex() zwraca błąd. Funkcja is.complex() sprawdza, czy argumentem funkcji jest tryb złożony. Funkcja zwraca TRUE, jeśli argumentem jest tryb złożony, w przeciwnym razie FALSE. Więcej informacji na temat trybu złożonego można znaleźć, wprowadzając ?complex po znaku zachęty R.

Budowanie własnego silnika „Who to Follow”

Istnieje wiele sposobów, w jakie moglibyśmy pomyśleć o zbudowaniu własnego silnika rekomendacji znajomych na Twitterze. Twitter ma wiele wymiarów danych, więc możemy pomyśleć o polecaniu ludziom na podstawie tego, o czym „tweetują”. Byłoby to ćwiczenie w eksploracji tekstu i wymagałoby dopasowywania ludzi na podstawie jakiegoś wspólnego zestawu słów lub tematów w ramach ich tweetów. Podobnie wiele tweetów zawiera dane geolokalizacyjne, dlatego możemy polecić użytkowników, którzy są aktywni i znajdują się w pobliżu. Lub możemy połączyć te dwa podejścia, przechodząc przez punkt przecięcia 100 najlepszych rekomendacji z każdego. To jednak część o sieciach, więc skupimy się na budowaniu silnika opartego tylko na relacjach międzyludzkich. Dobrym miejscem na początek jest prosta teoria o ewolucji użytecznych relacji w dużej sieci społecznościowej. W swojej przełomowej pracy z 1958 r. Fritz Heider przedstawił ideę „teorii równowagi społecznej”.

przyjaciel mojego przyjaciela jest moim przyjacielem

wróg mojego przyjaciela jest moim wrogiem

przyjaciel mojego wroga jest moim wrogiem

wróg mojego wroga jest mój przyjaciel

—Fritz Heider, Psychologia relacji interpersonalnych

Pomysł jest dość prosty i można go opisać w kategoriach zamykania i dzielenia trójkątów na wykresie społecznościowym. Teoria Heidera wymaga tylko obecności podpisanych relacji, tj. Mojego przyjaciela (pozytywny) lub mojego wroga (negatywny). Wiedząc, że nie mamy tych informacji, w jaki sposób możemy wykorzystać jego teorię, aby zbudować silnik rekomendacji dla relacji na Twitterze? Po pierwsze, skuteczna rekomendacja silnik Twittera może zadziałać, aby zamknąć otwarte trójkąty, to znaczy znaleźć przyjaciół moich przyjaciół i uczynić ich przyjaciółmi. Chociaż nie mamy w pełni podpisanych relacji, znamy wszystkie pozytywne relacje, jeśli założymy, że wrogowie nie podążają za sobą na Twitterze. Kiedy wykonaliśmy nasze początkowe pobieranie danych, przeprowadziliśmy dwuetapowe wyszukiwanie śnieżki. Te dane zawierają naszych przyjaciół i przyjaciół naszych przyjaciół. Możemy więc wykorzystać te dane do zidentyfikowania trójkątów, które wymagają zamknięcia. Pytanie brzmi zatem: który z wielu potencjalnych trójkątów powinienem polecić jako pierwszy? Ponownie możemy przyjrzeć się teorii równowagi społecznej. Szukając tych węzłów w naszym początkowym wyszukiwaniu śnieżkami, których nie śledzi nasienie, ale które śledzi wielu ich przyjaciół, możemy mieć dobrych kandydatów na rekomendacje. To rozszerza teorię Heidera na następujące: przyjaciel wielu moich przyjaciół może być dla mnie dobrym przyjacielem. Zasadniczo chcemy zamknąć najbardziej oczywisty trójkąt w zbiorze relacji z nasionami na Twitterze. Z technicznego punktu widzenia to rozwiązanie jest również znacznie łatwiejsze niż próba eksploracji tekstu lub analizy geoprzestrzennej w celu polecania znajomym. Tutaj musimy po prostu policzyć, którzy z przyjaciół naszych przyjaciół śledzą większość naszych przyjaciół. Aby to zrobić, zaczynamy od wczytania pełnych danych sieciowych, które zebraliśmy wcześniej. Tak jak poprzednio, użyjemy danych Drew w tym przykładzie, ale zachęcamy do śledzenia własnych danych, jeśli je masz.

userk <- „drewconway”

user.graph <- read.graph (wklej („dane /”, user, „/”, userk, „_net.graphml”, sep = „”),

format = „graphml”)

Naszym pierwszym krokiem jest zdobycie na Twitterze nazwisk wszystkich przyjaciół seedów. Możemy użyć funkcji neighbors, aby uzyskać wskaźniki sąsiadów, ale pamiętajmy, że z powodu różnych domyślnych wartości indeksowania igraph w stosunku do R musimy dodać jedną do wszystkich tych wartości. Następnie przekazujemy te wartości do specjalnej funkcji V, która zwróci węzeł atrybutu wykresu, którym w tym przypadku jest nazwa. Następnie wygenerujemy pełną listę krawędzi wykresu jako dużą macierz N-na-2 z funkcją get.edgelist.

friends <- V(user.graph)$name[neighbors(user.graph, user, mode=”out”)+1]

user.el <- get.edgelist(user.graph)

Mamy teraz wszystkie dane, których potrzebujemy, aby policzyć liczbę moich znajomych, którzy śledzą wszystkich użytkowników, którzy nie są aktualnie śledzeni przez ziarno. Najpierw musimy zidentyfikować wiersze w pliku user.el, które zawierają linki od znajomych nasion do użytkowników, których nasiona obecnie nie śledzą. Tak jak w poprzednich rozdziałach, użyjemy wektoryzowanej funkcji sapply do uruchomienia funkcji, która zawiera dość złożony test logiczny w każdym rzędzie macierzy. Chcemy wygenerować wektor wartości PRAWDA i FAŁSZ, aby określić, które wiersze zawierają znajomych znajomych nasion, których nasiona nie śledzą. Używamy funkcji ifelse do konfigurowania testu, który sam jest wektoryzowany. Wstępny test pyta, czy którykolwiek element wiersza jest użytkownikiem i czy pierwszy element (źródło) nie jest jednym z przyjaciół nasion. Korzystamy z dowolnej funkcji, aby sprawdzić, czy którakolwiek z tych instrukcji jest prawdziwa. Jeśli tak, będziemy chcieli zignorować ten wiersz. Drugi test sprawdza, czy drugi element rzędu (cel) nie jest jednym z przyjaciół nasion. Dbamy o to, kim są przyjaciele naszych przyjaciół, a nie kto podąża za nimi, dlatego też je ignorujemy.

Ten proces może potrwać minutę lub dwie, w zależności od liczby wierszy, ale po zakończeniu wyodrębniamy odpowiednie wiersze do pliku non.friends.el i tworzymy liczbę nazw za pomocą funkcji tabel.

non.friends <- sapply(1:nrow(user.el), function(i) ifelse(any(user.el[i,]==user |

!user.el[i,1] %in% friends) | user.el[i,2] %in% friends, FALSE, TRUE))

non.friends.el <- user.el[which(non.friends==TRUE),]

friends.count <- table(non.friends.el[,2])

Następnie chcemy zgłosić wyniki. Chcemy znaleźć najbardziej „oczywisty” trójkąt do zamknięcia, dlatego chcemy znaleźć użytkowników w tych danych, które pokazują się najczęściej. Tworzymy ramkę danych z wektora utworzonego przez funkcję tabeli. Dodamy również znormalizowaną miarę najlepszych użytkowników, których należy polecić, obliczając procent znajomych nasienia, którzy stosują się do każdej potencjalnej rekomendacji. W ostatnim kroku możemy posortować ramkę danych w kolejności malejącej według najwyższego odsetka znajomych obserwujących każdego użytkownika.

friends.followers <- data.frame(list(Twitter.Users=names(friends.count),

Friends.Following=as.numeric(friends.count)), stringsAsFactors=FALSE)

friends.followers$Friends.Norm <- friends.followers$Friends.Following/length(friends)

friends.followers <- friends.followers[with(friends.followers, order(-Friends.Norm)),]

Aby zgłosić wyniki, możemy sprawdzić pierwsze 10 wierszy lub nasze 10 najlepszych rekomendacji, dla których należy postępować, uruchamiając Friends.followers [1:10,]. W przypadku Drew wyniki są w Tabeli 

Jeśli znasz Drew, te imiona będą miały sens. Najlepszą rekomendacją Drew jest pójście za Clayem Shirky (cshirky), profesorem na NYU, który studiuje i pisze na temat roli technologii i Internetu w społeczeństwie. Biorąc pod uwagę to, czego już dowiedzieliśmy się o rozwidlonym mózgu Drew, wydaje się, że to dobre dopasowanie. Mając to na uwadze, pozostałe zalecenia pasują do jednego lub obu ogólnych zainteresowań Drew. Jest pokój niebezpieczeństw (pokój niebezpieczeństw); Blog Wired National Security; Big Data (bigdata); oraz 538 (pięćdziesiąt trzydzieści), blog prognozowania wyborów New York Timesa autorstwa Nate Silver. I oczywiście cholera. Chociaż te rekomendacje są dobre – i od czasu napisania pierwszego szkicu tej książki Drew cieszył się z nazwisk przedstawionych przez ten silnik – być może istnieje lepszy sposób na polecanie ludzi. Ponieważ wiemy już, że sieć danego użytkownika początkowego ma wiele wschodzących struktur, warto skorzystać z tej struktury, aby polecić użytkownikom pasującym do tych grup. Zamiast polecać najlepszych przyjaciół przyjaciół, możemy polecić przyjaciół przyjaciół, którzy są jak ziarno w danym wymiarze. W przypadku Drew możemy polecić zamknięcie trójkątów w jego społeczności bezpieczeństwa narodowego i politycznego lub w społeczności danych lub R.

user.ego <- read.graph(paste(“data/”, user, “/”, user, “_ego.graphml”, sep=””),

format=”graphml”)

friends.partitions <- cbind(V(user.ego)$HC8, V(user.ego)$name)

Pierwszą rzeczą, którą musimy zrobić, to załadować z powrotem do naszej sieci ego, która zawiera dane partycji. Ponieważ już zbadaliśmy partycję HC8, pozostaniemy przy tej ostatniej poprawce silnika rekomendacji. Po załadowaniu sieci utworzymy macierz friends.partitions, która ma teraz numer partycji w pierwszej kolumnie i nazwę użytkownika w drugiej. W przypadku danych Drew wygląda to tak:

> head(friends.partitions)

[,1] [,2]

[1,] “0” “drewconway”

[2,] “2” “311nyc”

[3,] “2” “aaronkoblin”

[4,] “3” “abumuqawama”

[5,] “2” “acroll”

[6,] “2” “adamlaiacano”

Teraz wszystko, co musimy zrobić, to obliczyć najbardziej oczywiste trójkąty, które należy zamknąć w każdej społeczności podrzędnej. Tworzymy więc funkcję partition.follows, która pobiera numer partycji i znajduje tych użytkowników. Wszystkie dane zostały już obliczone, więc funkcja po prostu wyszukuje użytkowników na każdej partycji, a następnie zwraca tę, która ma najwięcej obserwujących wśród znajomych nasion. Jedynym fragmentem sprawdzania błędów w tej funkcji, który może wystawać, jest instrukcja if, która sprawdza, czy liczba wierszy w danym podzbiorze jest mniejsza niż dwa. Robimy to, ponieważ wiemy, że jedna partycja będzie miała tylko jednego użytkownika, seed, i nie chcemy tworzyć rekomendacji z tej ręcznie kodowanej partycji.

partition.follows <- function(i) {

friends.in <- friends.partitions[which(friends.partitions[,1]==i),2]

partition.non.follow <- non.friends.el[which(!is.na(match(non.friends.el[,1],

friends.in))),]

if(nrow(partition.non.follow) < 2) {

return(c(i, NA))

}

else {

partition.favorite <- table(partition.non.follow[,2])

partition.favorite <- partition.favorite[order(-partition.favorite)]

return(c(i,names(partition.favorite)[1]))

}

}

partition.recs <- t(sapply(unique(friends.partitions[,1]), partition.follows))

partition.recs <- partition.recs[!is.na(partition.recs[,2]) &

!duplicated(partition.recs[,2]),]

Możemy teraz spojrzeć na te rekomendacje według partycji. Jak wspomnieliśmy, partycja „0” dla zarodka nie ma żadnych zaleceń, ale pozostałe tak. Co ciekawe, w przypadku niektórych partycji widzimy niektóre z tych samych nazw z poprzedniego kroku, ale w przypadku wielu nie.

> partition.recs

[,1] [,2]

0 “0” NA

2 “2” “cshirky”

3 “3” “jeremyscahill”

4 “4” “nealrichter”

5 “5” “jasonmorton”

6 “6” “dangerroom”

7 “7” “brendan642”

8 “8” “adrianholovaty”

Oczywiście o wiele bardziej satysfakcjonujące jest zobaczenie tych zaleceń w sieci. Ułatwi to sprawdzenie, kto jest polecany dla jakiej podspołeczności. Kod zawarty w tym rozdziale doda te zalecenia do nowego pliku wykresu zawierającego te węzły i numery partycji. Wykluczyliśmy kod tutaj, ponieważ jest to przede wszystkim ćwiczenie w zakresie sprzątania, ale zachęcamy do przejrzenia go w kodzie dostępnym w tej książce przez O’Reilly. Ostatnim krokiem będzie wizualizacja tych danych pod kątem rekomendacji Drew. Wyniki pokazano na ryc. 11-9.

Te wyniki są całkiem dobre! Przypomnij sobie, że niebieskie węzły to ci użytkownicy Twittera w sieci Drew, którzy interesują się technologią i bezpieczeństwem narodowym. Silnik polecił blog Danger Room, który dokładnie opisuje obie te rzeczy. Zielone węzły to ludzie, którzy tweetują o polityce bezpieczeństwa narodowego; wśród nich nasz silnik polecił Jeremy Scahill jeremyscahill). Jeremy jest krajowym reporterem ds. Ekologii dla magazynu The Nation, który doskonale pasuje do tej grupy i być może informuje nas trochę o własnych perspektywach politycznych Drew. Z drugiej strony czerwone węzły to te w społeczności R. Polecający sugeruje Brendan O’Connor (brendan642), doktorantkę w dziedzinie uczenia maszynowego w Carnegie Mellon. Jest także kimś, kto tweetuje i bloguje o R. Wreszcie, grupa fioletowa zawiera innych ze społeczności danych. Tutaj sugestią jest Jason Morton (jasonmorton), asystent profesora matematyki i statystyki w stanie Pensylwania. Wszystkie te rekomendacje pasują do zainteresowań Drew, ale być może są bardziej przydatne, ponieważ teraz wiemy dokładnie, jak pasują do jego zainteresowań. Istnieje wiele innych sposobów na zhakowanie silnika rekomendacji i mamy nadzieję, że będziesz bawić się kodem i poprawiać go, aby uzyskać lepsze rekomendacje dla własnych danych.

(II) :Tryb numeryczny

W przypadku trybu numerycznego sprawy stają się nieco skomplikowane. Pierwotnie w S obiekty numeryczne mogły być liczbami całkowitymi, rzeczywistymi lub podwójnymi (dla podwójnej precyzji). Prawdziwa opcja jest przestarzała i nie należy jej używać. W S3 opcje liczb całkowitych i podwójnych znajdują się w trybie numerycznym. W S4 każdy ma osobny typ. Omówiono tutaj funkcje numeric (), is.numeric () i as.numeric (). Funkcje integer (), as.integer (), is.integer (), double (), as.double () i is.double () zachowują się podobnie, ale nie są tutaj omówione, ponieważ znajdują się na poziomie S4. Funkcja numeric () przyjmuje jako argument obiekt numeryczny lub NULL. Jeśli argument jest równy zero lub NULL lub nie ma argumentu, funkcja numeryczna () zwraca wartość liczbową (0), pusty obiekt w trybie numerycznym i długości zero. Jeśli argumentem jest obiekt numeryczny o długości większej niż jeden lub obiekt logiczny, oceniany jest tylko pierwszy element. W przypadku argumentu logicznego PRAWDA jest wymuszana na jeden, a FAŁSZ jest wymuszany na zero, natomiast w przypadku argumentu numerycznego pierwszy element jest zaokrąglany w dół do liczby całkowitej. Następnie funkcja zwraca wektor na zerach długości równych wartości pierwszego elementu. Dla argumentów trybów innych niż numeryczne lub logiczne R zwraca błąd. Funkcja as.numeric () próbuje zmusić obiekt do podwójnej precyzji. Argumentem może być dowolny obiekt w trybie atomowym. Jeśli argument ma wartość NULL lub nie podano żadnego argumentu, zwracana jest wartość numeryczna (0), gdzie liczba (0) jest pustym obiektem typu numerycznego i długości zero. Jeśli obiekt jest logiczny, PRAWDA są ustawiane na jeden, a FAŁSZ jest ustawiany na zero w obiekcie. Jeśli obiekt jest liczbowy, wartości elementów są zwracane jako liczby o podwójnej precyzji. Jeśli obiekt jest złożony, zwracane są tylko części rzeczywiste – jako liczby o podwójnej precyzji. Jeśli obiekt jest w trybie raw, as.numeric () konwertuje wartości szesnastkowe na podwójną precyzję. Jeśli obiekt ma charakter trybowy, funkcja zwraca NA dla elementów obiektu. Jeśli argument nie jest atomowy, R podaje błąd. Elementy o wartości NA zwracane są jako NA. Funkcja is.numeric () testuje obiekt, aby sprawdzić, czy obiekt jest obiektem numerycznym i czy działa z obiektami dowolnego trybu. Wartość PRAWDA jest zwracana, jeśli obiekt jest liczbowy, w przeciwnym wypadku FALSE. Więcej informacji na temat obiektów numerycznych w trybie można znaleźć, wprowadzając ?numeric po znaku zachęty R.

Wizualizacja klastrowanej sieci Twitter za pomocą Gephi

Jak wspomniano wcześniej, będziemy używać programu Gephi do wizualnego przeglądania naszych danych sieciowych. Jeśli już pobrałeś i zainstalowałeś Gephi, pierwszą rzeczą do zrobienia jest otwarcie aplikacji i załadowanie danych z Twittera. W tym przykładzie użyjemy sieci ego Drew na Twitterze, która znajduje się w katalogu code / data / drewconway / w tym rozdziale. Jeśli jednak wygenerowałeś własne dane na Twitterze, możesz ich użyć. Nie jest to bynajmniej pełne lub wyczerpujące wprowadzenie do wizualizacji sieci w Gephi. W tej sekcji wyjaśniono, jak wizualnie eksplorować struktury lokalnej społeczności danych ego-sieci na Twitterze. Gephi to solidny program do wizualizacji sieci, który zawiera wiele opcji analizy danych. W tej części wykorzystamy bardzo niewiele z tych możliwości, ale gorąco zachęcamy do zabawy z programem i zapoznania się z jego wieloma opcjami. Jednym świetnym miejscem do rozpoczęcia jest samouczek Gephi Szybki start, który jest dostępny online tutaj: http://gephi.org/

2010 / tutorial szybkiego startu /. Gdy Gephi jest otwarte, ładujesz sieć ego na pasku menu za pomocą Plik → Otwórz. Przejdź do katalogu Drawconway i otwórz plik drewconway_ego.graphml, as

pokazane w górnym panelu na rysunku 

Po załadowaniu wykresu Gephi przekaże kilka podstawowych informacji o właśnie załadowanym pliku sieciowym. Dolny panel na ryc. 11-4 pokazuje ten raport, który zawiera liczbę węzłów (263) i krawędzi (6945). Jeśli klikniesz kartę Raport w tym oknie, zobaczysz również wszystkie dane atrybutów, które dodaliśmy do tej sieci. Szczególnie interesujące są atrybuty węzła HC *, które są hierarchicznymi etykietami partycji klastrowych dla pierwszych 10 nietrywialnych partycji. Pierwszą rzeczą, którą zauważysz, jest to, że Gephi ładuje sieć jako duży bałagan losowo rozmieszczonych węzłów, przerażające „sieciowe kłębowisko”. Wiele informacji o strukturze społeczności w sieci można wyrazić przez bardziej świadome rozmieszczenie tych węzłów. Metody i algorytmy umieszczania węzłów w dużych, złożonych sieciach są czymś w rodzaju chałupy; jako taki istnieje ogromna liczba sposobów, w jakie możemy zmienić rozmieszczenie węzłów. Do naszych celów chcemy, aby węzły z większą liczbą współużytkowanych połączeń były umieszczone bliżej siebie. Przypomnijmy, że nasza metoda klastrowania polegała na umieszczaniu węzłów w grupach na podstawie ich odległości od siebie. Węzły o krótszych odległościach zostaną zgrupowane razem i chcemy, aby nasza technika wizualizacji odzwierciedlała to. Jedna grupa popularnych metod umieszczania węzłów składa się z algorytmów „wymuszonych”.

Jak sama nazwa wskazuje, algorytmy te próbują symulować sposób rozmieszczenia węzłów, gdyby siła przyciągania i odpychania została umieszczona w sieci. Wyobraź sobie, że zniekształcony bałagan krawędzi między węzłami, które obecnie wyświetla Gephi, są w rzeczywistości elastycznymi pasmami, a węzły są łożyskami kulkowymi, które mogą utrzymywać ładunek magnetyczny. Algorytm kierowany siłą próbuje obliczyć, w jaki sposób węzły łożysk kulkowych odpychają się od siebie w wyniku ładunku, ale następnie są przyciągane przez elastyczne krawędzie. Rezultatem jest wizualizacja, która starannie łączy węzły razem w zależności od struktury lokalnej społeczności. Gephi zawiera wiele przykładów układów wymuszonych. W panelu Układ menu rozwijane zawiera wiele różnych opcji, z których niektóre są wymuszone. Do naszych celów wybieramy algorytm proporcjonalny Yifan Hu i używamy ustawień domyślnych. Po wybraniu tego algorytmu kliknij przycisk Uruchom, a zobaczysz, że Gephi przestawia węzły w sposób wymuszony. W zależności od rozmiaru sieci i używanego sprzętu może to zająć trochę czasu. Gdy węzły przestaną się poruszać, algorytm zoptymalizował ich rozmieszczenie i jesteśmy gotowi do przejścia. Aby łatwiej zidentyfikować społeczności lokalne w sieci i ich członków, zmienimy rozmiar i pokolorujemy węzły. Ponieważ sieć jest ukierunkowaną siecią ego, ustawimy rozmiar węzła jako funkcję stopni węzłów. To sprawi, że węzeł początkowy będzie największy, ponieważ prawie każdy członek sieci podąża za nim, a także zwiększy rozmiar innych wybitnych użytkowników w sieci ego. W panelu Rankingi kliknij kartę Węzły i wybierz InDegree z rozwijanego menu. Kliknij ikonę czerwonego diamentu, aby ustawić rozmiar; możesz ustawić minimalne i maksymalne rozmiary na dowolne. Jak widać w dolnej połowie rysunku 11-5, wybraliśmy odpowiednio 2 i 16 dla sieci Drew, ale inne ustawienia mogą działać lepiej dla Ciebie.

Po ustawieniu wartości kliknij przycisk Zastosuj, aby zmienić rozmiar węzłów. Ostatnim krokiem jest pokolorowanie węzłów według ich partycji społeczności. W panelu partycji, znajdującym się nad panelem rankingów, zobaczysz ikonę z dwiema przeciwnymi strzałkami. Kliknij to, aby odświeżyć listę partycji dla tego wykresu.

Po wykonaniu tej czynności menu rozwijane będzie zawierać dane atrybutów węzła, które uwzględniliśmy dla tych partycji. Jak pokazano w górnej połowie Ryc. 11-5, wybraliśmy HC8 lub ósmą partycję, która obejmuje partycję dla Drew (Drawconway) i siedem innych węzłów w jego sieci ego. Ponownie kliknij przycisk Zastosuj, a węzły zostaną pokolorowane według partycji. Natychmiast zobaczysz podstawową strukturę! Doskonałym sposobem na zobaczenie, jak dana sieć zaczyna pękać na mniejsze społeczności, jest przejście przez partycje hierarchicznego klastra. Jako ćwiczenie sugerujemy zrobienie tego w Gephi poprzez iteracyjne ponowne kolorowanie węzłów przez coraz bardziej ziarniste partycje. Rozpocznij od HC2 i przejdź do HC10, za każdym razem ponownie kolorując węzły, aby zobaczyć, jak większe grupy zaczynają się dzielić. Dzięki temu dowiesz się wiele o podstawowej strukturze sieci. Rycina 11-6 pokazuje sieć ego Drew pokolorowaną HC8, która pięknie podkreśla strukturę społeczności lokalnej w jego sieci Twitter.

Drew wydaje się mieć zasadniczo cztery podstawowe podspołeczności. Z samym Drew w kolorze turkusowym pośrodku widzimy dwie ściśle powiązane grupy w kolorze czerwonym i fioletowym po jego lewej stronie; a dwie inne mniej ściśle powiązane podgrupy po jego prawej stronie są w kolorze niebieskim i zielonym. Istnieją oczywiście inne grupy w kolorze pomarańczowym, różowym i jasnozielonym, ale skupimy się na czterech podstawowych grupach. Na ryc. 11-7 skupiliśmy się na lewej stronie sieci i usunęliśmy krawędzie

aby ułatwić przeglądanie etykiet węzłów.

Szybko przeglądając nazwy Twittera w tym klastrze, jasne jest, że ta strona sieci Drew zawiera dane, które Ner śledzi na Twitterze. Po pierwsze, widzimy bardzo znanych frajerów danych, takich jak Tim O’Reilly (timoreilly) i Nathan Yau (Flowdata) w kolorze jasnozielonym, ponieważ są nieco „własną ligą”. Grupy fioletowe i czerwone są również interesujące, ponieważ oba zawierają hakerów danych, ale są podzielone przez jeden kluczowy czynnik: przyjaciele Drew, którzy są w kolorze fioletowym, są wybitnymi członkami społeczności danych, takimi jak Hilary Mason (hmason), Pete Skomoroch ( peteskomoroch) i Jake Hofman (jakehofman), ale żaden z nich nie jest wokalistą społeczności R. Z drugiej strony, węzły w kolorze czerwonym są wokalnymi członkami społeczności R, w tym Hadley Wickham (hadleywickham), David Smith (revodavid) i Gary King (kinggary).

Co więcej, algorytmowi ukierunkowanemu siłą udało się umieścić te elementy blisko siebie i umieścić te, które znajdują się między tymi dwiema społecznościami na krawędziach. Widzimy Johna (johnmyleswhite) w kolorze fioletowym, ale umieszczonego z wieloma innymi czerwonymi węzłami. Jest tak, ponieważ John jest widoczny w obu społecznościach, a dane odzwierciedlają

  1. Inne przykłady to JD Long (cmastication) i Josh Reich (i2pi). Chociaż Drew spędza dużo czasu na interakcjach z członkami społeczności danych – zarówno z użytkownikami R, jak i innymi użytkownikami – Drew używa również Twittera do interakcji ze społecznościami, które zaspokajają jego inne zainteresowania. Jednym z zainteresowań jest w szczególności jego kariera naukowa, która koncentruje się na technologii i polityce bezpieczeństwa narodowego. Na ryc. 11-8 podkreślamy prawą stronę sieci Drew, która obejmuje członków z tych społeczności.

Podobnie jak grupa nerdów danych, obejmuje to dwie podgrupy, jedną w kolorze niebieskim i drugą w kolorze zielonym. Podobnie jak w poprzednim przykładzie kolor partycji i umiejscowienie węzła mogą wiele zilustrować ich rolę w sieci. Użytkownicy Twittera na niebieskiej partycji są rozproszeni: niektórzy są bliżej Drew i lewej strony sieci, inni są bardziej po prawej i blisko zielonej grupy. Dalej po lewej stronie są ludzie, którzy pracują lub mówią o roli technologii w bezpieczeństwie narodowym, w tym Sean Gourley (sgourley), Lewis Shepherd (lewisshepherd) i Jeffrey Carr (Jeffrey Carr). Ci, którzy są bliżej zieleni, bardziej skupiają się na polityce bezpieczeństwa narodowego, podobnie jak inni członkowie zielonej grupy. Na zielono widzimy wielu wysokoprofilowych członków społeczności bezpieczeństwa narodowego na Twitterze, w tym Andrew Exum (abumuqawama), Joshua Foust (joshua Foust) i Daveed Gartenstein-Ross (daveedgr). Co ciekawe, podobnie jak poprzednio, osoby siedzące między tymi grupami są umieszczone blisko krawędzi, takie jak Chris Albon (chrisalbon), który jest wybitny w obu. Jeśli eksplorujesz własne dane, jaką strukturę społeczności lokalnej widzisz? Być może struktura jest dość oczywista, jak ma to miejsce w przypadku sieci Drew, a może społeczności są bardziej subtelne. Szczegółowa analiza tych struktur może być bardzo interesująca i pouczająca. Zachęcamy do tego. W następnej i ostatniej części wykorzystamy te struktury społeczności, aby zbudować własny silnik rekomendacji „kto podążać” na Twitterze.

(II) : Tryb logiczny

Funkcja logical() bez argumentu lub zero dla argumentu zwraca logical (0), który jest logicznym pustym zestawem i ma długość zero. Funkcja logical () z liczbą całkowitą większą niż zero jako argument zwraca wektor FALSE o długości równej liczbie całkowitej. Jeśli argument jest pojedynczym elementem o podwójnej precyzji, element jest zaokrąglany w dół i tworzony jest wektor FAŁSZ o długości równej wynikowej liczbie całkowitej. Jeśli argument jest obiektem numerycznym innym niż pojedyncza liczba lub jeśli argument jest obiektem logicznym, funkcja zwraca FAŁSZ. Jeśli argumentem jest tryb NULL, tryb znakowy, złożony, nieprzetworzony lub tryb nieatomowy, wówczas logical () podaje błąd. Funkcja as.logical () wymusza argument funkcji na logiczną, jeśli

możliwe i zwraca wektor zawierający PRAWDA, FAŁSZ i / lub NA. Jeśli nie ma argumentu lub argument jest zerowy lub NULL, as.logical () zwraca logiczny (0), logiczny pusty zbiór o długości zero. Jeśli argumentem jest tryb numeryczny, zera będą zwracane jako FAŁSZ, a wszystkie inne liczby będą zwracane jako PRAWDA. Jeśli argument jest obiektem złożonym, funkcja zwraca FAŁSZ dla 0 + 0i i PRAWDA dla dowolnej innej liczby zespolonej. Jeśli tryb jest surowy, 00s zwróci FAŁSZ, a każda inna wartość zwróci PRAWDA. Jeśli argument ma charakter trybowy, funkcja zwraca wektor NA o długości równej długości argumentu. Jeśli argument zawiera NA, dla dowolnego trybu poza surowym, NA zostaną zwrócone dla elementów zawierających NA. W trybie surowym nie ma NA, ponieważ NA są interpretowane jako 00s w trybie surowym. W każdym innym trybie as.logical () podaje błąd. Funkcja is.logical () zwraca PRAWDA, jeśli argument jest obiektem logicznym, a FALSE w przeciwnym razie. Wynik is.logical (logiczny (0)) jest PRAWDA. Aby uzyskać więcej informacji o trybie logicznym, wpisz ? logical w wierszu polecenia R.

Struktura społeczności lokalnej

Naszym pierwszym krokiem w procesie analitycznym jest wyodrębnienie podstawowych elementów wykresu. Istnieją dwa przydatne podgrupy pełnego obiektu user.net, które będziemy chcieli wyodrębnić. Najpierw przeprowadzimy analizę k-core, aby wyodrębnić 2-rdzeniowy wykresu. Z definicji analiza k-core rozkłada wykres według połączeń węzłów. Aby znaleźć „rdzeń” wykresu, chcemy wiedzieć, ile węzłów ma określony stopień. Rdzeń k opisuje stopień rozkładu. Tak więc dwurdzeniowy wykres jest podgrafem węzłów, które mają stopień dwa lub więcej. Jesteśmy zainteresowani wydobyciem 2-rdzeniowego, ponieważ naturalnym produktem ubocznym wyszukiwania kuli śnieżnej jest posiadanie wielu wiszących węzłów na zewnątrz sieci. Te wisiorki wnoszą bardzo niewiele, jeśli w ogóle, użytecznych informacji o strukturze sieci, dlatego chcemy je usunąć. Pamiętaj jednak, że wykres Twittera jest skierowany, co oznacza, że ​​węzły mają stopień wejściowy i wyjściowy. Aby znaleźć te węzły, które są bardziej odpowiednie dla tej analizy, użyjemy funkcji graph.coreness do obliczenia rdzenia każdego węzła według jego in-stop poprzez ustawienie parametru mode = „in”. Robimy to, ponieważ chcemy zachować te węzły, które otrzymują co najmniej dwie krawędzie, a nie te, które dają dwie krawędzie. Te wisiorki zmiecione w próbce śnieżki prawdopodobnie będą miały połączenie z siecią, ale nie z niej; dlatego używamy stopnia, aby je znaleźć.

user.cores <- graph.coreness (user.net, mode = “in”)

user.clean <- subgraph (user.net, który (user.cores> 1) -1)

Funkcja podgrafu przyjmuje jako dane wejściowe obiekt wykresu i zestaw węzłów i zwraca podgraf wywołany przez te węzły na przekazanym wykresie. Aby wyodrębnić 2-rdzeniowy wykres z user.net, używamy podstawy R, która działa w celu znalezienia tych węzłów o współczynniku rdzenia większym niż jeden.

user.ego <- subgraph (user.net, c (0neighbors (user.net, user, mode = “out”)))

Jednym z najbardziej frustrujących „problemów” związanych z pracą z igraphem jest to ,że  igraph używa indeksowania zerowego dla węzłów, podczas gdy R rozpoczyna indeksowanie od jednego. W 2-rdzeniowym przykładzie zauważysz, że odejmujemy jeden z wektora zwracanego przez funkcję what, abyśmy nie napotkali przerażającego błędu „off by one”. Drugi kluczowy podgraf, który wyodrębnimy, to sieć ego nasion. Przypomnij sobie, że jest to wykres podrzędny wywołany przez sąsiadów nasion. Na szczęście igraph ma przydatną funkcję wygody sąsiadów do identyfikacji tych węzłów. Znów jednak musimy być świadomi struktury grafów ukierunkowanych na Twitterze. Sąsiedzi węzła mogą być albo wychodzący, więc musimy powiedzieć funkcji sąsiadów, którą chcielibyśmy. W tym przykładzie zbadamy sieć ego wywołaną przez zewnętrznych sąsiadów lub użytkowników Twittera, za którymi podąża ziarno, a nie tych, którzy podążają za ziarnem. Z analitycznego punktu widzenia bardziej interesujące może być zbadanie użytkowników, których ktoś obserwuje, szczególnie jeśli badasz własne dane. To powiedziawszy, alternatywa może być również interesująca i zachęcamy czytelnika do ponownego uruchomienia tego kodu i spojrzenia na niezależnych sąsiadów. Przez resztę tego rozdziału skupimy się na sieci ego, ale 2-rdzeniowy jest bardzo przydatny do innych analiz sieciowych, więc zapiszemy go w ramach pobierania danych. Teraz jesteśmy gotowi przeanalizować sieć ego, aby znaleźć strukturę lokalnej społeczności. W tym ćwiczeniu będziemy używać jednej z najbardziej podstawowych metod określania  członkostwo w społeczności: hierarchiczne grupowanie odległości między węzłami. To kęs, ale koncepcja jest dość prosta. Zakładamy, że węzły, w tym przypadku użytkownicy Twittera, którzy są bliżej połączeni, tj. mają mniej przeskoków między sobą, są bardziej podobni. Ma to sens praktycznie, ponieważ możemy uważać, że osoby o wspólnych zainteresowaniach śledzą się na Twitterze, a zatem mają mniejsze odległości między nimi. Interesujące jest to, jak wygląda ta struktura społeczności dla danego użytkownika, ponieważ ludzie mogą mieć kilka różnych społeczności w swojej sieci ego.

user.sp <- shortest.paths (user.ego)

user.hc <- hclust (dist (user.sp))

Pierwszym krokiem w przeprowadzeniu takiej analizy jest pomiar odległości między wszystkimi węzłami na naszym wykresie. Używamy shortest.paths, która zwraca macierz N na N, gdzie N jest liczbą węzłów na wykresie, a najkrótsza odległość między każdą parą węzłów jest wpisem dla każdej pozycji w macierzy. Wykorzystamy te odległości do obliczenia partycji węzłów na podstawie odległości między nimi. Jak sama nazwa wskazuje, klastrowanie „hierarchiczne” ma wiele poziomów. Proces tworzy te poziomy lub cięcia, próbując utrzymać najbliższe węzły w tych samych partycjach, gdy rośnie liczba partycji. Dla każdej warstwy w dalszej części hierarchii zwiększamy liczbę partycji lub grup węzłów o jeden. Za pomocą tej metody możemy iteracyjnie rozkładać graf na bardziej szczegółowe grupy węzłów, zaczynając od wszystkich węzłów w tej samej grupie i przechodząc w dół hierarchii, aż wszystkie węzły znajdą się w swojej grupie.

R zawiera wiele przydatnych funkcji do tworzenia klastrów. W tej pracy użyjemy kombinacji funkcji dist i hclust. Funkcja dist utworzy macierz odległości z macierzy obserwacji. W naszym przypadku obliczyliśmy już odległości za pomocą funkcji shortest.path, więc funkcja dist służy do konwersji tej macierzy w coś, z czym może współpracować hclust. Funkcja hclust wykonuje klastrowanie i zwraca specjalny obiekt, który zawiera wszystkie potrzebne informacje o klastrowaniu. Jedną przydatną rzeczą do zrobienia po klastrowaniu czegoś hierarchicznie jest przeglądanie dendrogramu partycji. Dendrogram tworzy diagram podobny do drzewa, który pokazuje, w jaki sposób klastry rozdzielają się podczas przechodzenia dalej w dół hierarchii. To da nam pierwszy wgląd w strukturę społeczności naszej sieci ego. Jako przykład przyjrzyjmy się dendrogramowi ego-sieci Johna na Twitterze, który jest zawarty w katalogu danych / tego rozdziału. Aby wyświetlić jego dendrogram, załadujemy jego dane ego-network, przeprowadzimy grupowanie, a następnie przekażemy obiekt hclust do funkcji wydruku, która wie, jak narysować dendrogram.

user.ego <- read.graph(‘data/johnmyleswhite/johnmyleswhite_ego.graphml’, format = ‘graphml’)

user.sp <- shortest.paths(user.ego)

user.hc <- hclust(dist(user.sp))

plot(user.hc)

Patrząc na rysunek 

widzimy już naprawdę interesującą strukturę społeczności wyłaniającą się z sieci Twittera Johna. Wydaje się, że istnieje względnie czysty podział między dwiema społecznościami wysokiego poziomu, a następnie w obrębie tych jest wiele mniejszych, ściśle powiązanych podgrup. Oczywiście, dane wszystkich będą różne, a liczba społeczności, które zobaczysz, będzie w dużej mierze zależna od wielkości i gęstości twojej ego-sieci na Twitterze. Chociaż z naukowego punktu widzenia interesująca jest kontrola klastrów za pomocą dendrogramu, naprawdę chcielibyśmy zobaczyć je w sieci. Aby to zrobić, będziemy musieli dodać dane partycji lustering do węzłów, co zrobimy za pomocą prostej pętli, aby dodać pierwsze 10 nietrywialnych partycji do naszej sieci. Przez nietrywialne rozumiemy, że pomijamy pierwszą partycję, ponieważ partycja ta przypisuje wszystkie węzły do ​​tej samej grupy. Chociaż jest to ważne z punktu widzenia ogólnej hierarchii, nie daje nam żadnej struktury społeczności lokalnej.

for(i in 2:10) {

user.cluster <- as.character(cutree(user.hc, k=i))

user.cluster[1] <- “0”

user.ego <- set.vertex.attribute(user.ego, name=paste(“HC”,i,sep=””),

value=user.cluster)

}

Funkcja cutree zwraca przypisanie partycji dla każdego elementu w hierarchii na danym poziomie, tzn. „Wycina drzewo”. Algorytm klastrowania nie wie, że daliśmy mu sieć ego, w której pojedynczy węzeł jest punktem centralnym, więc zgrupował nasze ziarno z innymi węzłami na każdym poziomie klastrowania. Aby ułatwić identyfikację użytkownika początkowego podczas wizualizacji, przypisujemy go do własnego klastra: „0”. Na koniec ponownie używamy funkcji set.vertex.attributes, aby dodać te informacje do naszego obiektu wykresu. Mamy teraz obiekt wykresu zawierający nazwy Twittera i pierwszych 10 partycji klastrowych z naszej analizy. Zanim będziemy mogli uruchomić Gephi i wizualizować wyniki, musimy zapisać obiekt wykresu w pliku. Użyjemy funkcji write.graph, aby wyeksportować te dane jako pliki GraphML. GraphML jest jednym z wielu formatów plików sieciowych i jest oparty na XML. Jest to przydatne do naszych celów, ponieważ nasz obiekt wykresu zawiera wiele metadanych, takich jak etykiety węzłów i partycje klastra. Podobnie jak w przypadku większości formatów opartych na XML, pliki GraphML mogą szybko się powiększać i nie są idealne do przechowywania danych relacyjnych

write.graph(user.net, paste(“data/”, user, “/”, user, “_net.graphml”, sep=””),

format=”graphml”)

write.graph(user.clean, paste(“data/”, user, “/”, user, “_clean.graphml”, sep=””),

format=”graphml”)

write.graph(user.ego, paste(“data/”, user, “/”, user, “_ego.graphml”, sep=””),

format=”graphml”)

Zapisujemy wszystkie trzy obiekty wykresów wygenerowane podczas tego procesu. W następnej części wizualizujemy te wyniki za pomocą Gephi, korzystając z przykładowych danych

(II) : Niektóre funkcje dla trybów atomowych

Każdy z trybów atomowych, z wyjątkiem NULL, ma trzy funkcje związane z tym trybem: funkcja nazwana dla trybu, name(); funkcja as.name(); oraz funkcja is.name (), gdzie name jest nazwą trybu. Funkcja name() tworzy wektor o długości podanej przez argument lub argumenty, jeśli argument (y) mają poprawny tryb i dopuszczalne wartości. Funkcja as.name () próbuje przekonać argument funkcji do trybu nazwanego. Jeśli przymus nie jest możliwy, funkcja as.name () zwraca wektor NA lub daje błąd. Zauważ, że jeśli argumentem jest macierz  tablicy, zostanie zwrócony wektor elementów macierzy lub tablica, przy czym konwersja na wektor przebiega w dół każdego wymiaru macierzy lub macierzy z kolei (w przypadku macierzy przechodzenie w dół wierszy pierwszej kolumny, potem drugiej kolumny itd.). Funkcja is.name () sprawdza, czy argument funkcji ma nazwany tryb i zwraca PRAWDA, czy FAŁSZ, w zależności od tego, czy argument jest, czy nie.

Tryb NULL

NULL jest zastrzeżonym obiektem w R i jest również trybem. Chociaż w R nie ma funkcji NULL (), as.null () i is.null () są funkcjami. Z dowolnym obiektem używanym jako argument lub bez argumentu as.null () zwraca tylko jedną wartość NULL. Funkcja is.null () zwraca PRAWDA, jeśli argument jest równy NULL; W przeciwnym razie FAŁSZ.