Drugą funkcją, którą chcemy wyodrębnić z danych, jest aktywność wątku e-mail. Jak już wspomniano, nie mamy możliwości dowiedzieć się, czy użytkownik, dla którego budujemy ranking, odpowiedział na e-maile, ale możemy grupować wiadomości według ich wątków i mierzyć ich aktywność od początku. Ponownie, naszym założeniem w budowaniu tej funkcji jest to, że czas jest ważny, a zatem wątki, które wysyłają więcej wiadomości w krótkim czasie, są bardziej aktywne, a zatem ważniejsze. Wiadomości e-mail w naszym zestawie danych nie zawierają określonych identyfikatorów wątków, ale logicznym sposobem na identyfikację wątków w danych szkoleniowych jest poszukiwanie wiadomości e-mail ze wspólnym tematem. Oznacza to, że jeśli znajdziemy temat zaczynający się od „re:”, wówczas wiemy, że jest to część jakiegoś wątku. Gdy widzimy taki komunikat, możemy rozejrzeć się za innymi wiadomościami w tym wątku i zmierzyć aktywność.
find.threads <- function(email.df) {
response.threads <- strsplit(email.df$Subject, “re: “)
is.thread <- sapply(response.threads, function(subj) ifelse(subj[1] == “”, TRUE,
FALSE))
threads <- response.threads[is.thread]
senders <- email.df$From.EMail[is.thread]
threads <- sapply(threads, function(t) paste(t[2:length(t)], collapse=”re: “))
return(cbind(senders,threads))
}
threads.matrix<-find.threads(priority.train)
Właśnie to próbuje zrobić funkcja find.threads z naszymi danymi treningowymi. Jeśli podzielimy każdy przedmiot w naszych danych treningowych przez „re:”, możemy znaleźć wątki, szukając wektorów znaków podzielonych z pustym znakiem jako pierwszym elementem. Kiedy już wiemy, które obserwacje w danych szkoleniowych są częścią wątków, możemy wyodrębnić nadawców z tych wątków i tematu. Macierz wyników będzie zawierać wszystkich nadawców i początkowy temat wątku w naszych danych szkoleniowych.
email.thread <- function(threads.matrix) {
senders <- threads.matrix[, 1]
senders.freq <- table(senders)
senders.matrix <- cbind(names(senders.freq), senders.freq, log(senders.freq + 1))
senders.df <- data.frame(senders.matrix, stringsAsFactors=FALSE)
row.names(senders.df) <- 1:nrow(senders.df)
names(senders.df) <- c(“From.EMail”, “Freq”, “Weight”)
senders.df$Freq <- as.numeric(senders.df$Freq)
senders.df$Weight <- as.numeric(senders.df$Weight)
return(senders.df)
}
senders.df <- email.thread(threads.matrix)
Następnie stworzymy wagę na podstawie nadawców, którzy są najbardziej aktywni w wątkach. Będzie to uzupełnienie ważenia opartego na wolumenie, które właśnie wykonaliśmy dla całego zestawu danych, ale teraz skupimy się tylko na tych nadawcach obecnych w wątku. Matrix. Funkcja email.thread weźmie inputs.matrix i wygeneruje to wtórne ważenie oparte na wolumenie. Będzie to bardzo podobne do tego, co zrobiliśmy w poprzedniej sekcji, ale tym razem użyjemy funkcji tabeli do zliczenia częstotliwości nadawców w wątkach. Odbywa się to po prostu w celu pokazania innej metody wykonania tego samego obliczenia na macierzy w R, a nie na ramce danych przy użyciu plyr. Większość tej funkcji po prostu wykonuje porządek w ramce danych senders.df, ale zauważ, że ponownie używamy ważenia logu naturalnego. Jako ostatni element funkcji wątku e-mail utworzymy ważenie na podstawie wątków, o których wiemy, że są aktywne. Zidentyfikowaliśmy już wszystkie wątki w naszych danych szkoleniowych i stworzyliśmy ważenie na podstawie warunków w tych wątkach. Teraz chcemy wziąć tę wiedzę i nadać dodatkową wagę znanym wątkom, które są również aktywne. Zakłada się tutaj, że jeśli znamy już wątki, oczekujemy, że użytkownik przywiąże większą wagę do wątków, które są bardziej aktywne.
get.threads <- function(threads.matrix, email.df) {
threads <- unique(threads.matrix[, 2])
thread.counts <- lapply(threads, function(t) thread.counts(t, email.df))
thread.matrix <- do.call(rbind, thread.counts)
return(cbind(threads, thread.matrix))
}
thread.counts <- function(thread, email.df) {
thread.times <- email.df$Date[which(email.df$Subject == thread
| email.df$Subject == paste(“re:”, thread))]
freq <- length(thread.times)
min.time <- min(thread.times)
max.time <- max(thread.times)
time.span <- as.numeric(difftime(max.time, min.time, units=”secs”))
if(freq < 2) {
return(c(NA,NA,NA))
}
else {
trans.weight <- freq / time.span
log.trans.weight <- 10 + log(trans.weight, base=10)
return(c(freq,time.span, log.trans.weight))
}
}
thread.weights <- get.threads(threads.matrix, priority.train)
thread.weights <- data.frame(thread.weights, stringsAsFactors=FALSE)
names(thread.weights) <- c(“Thread”,”Freq”,”Response”,”Weight”)
thread.weights$Freq <- as.numeric(thread.weights$Freq)
thread.weights$Response <- as.numeric(thread.weights$Response)
thread.weights$Weight <- as.numeric(thread.weights$Weight)
thread.weights <- subset(thread.weights, is.na(thread.weights$Freq) == FALSE)
Korzystając z właśnie utworzonego wątku threads.matrix, wrócimy do danych szkoleniowych, aby znaleźć wszystkie wiadomości e-mail w każdym wątku. W tym celu tworzymy funkcję get.threads, która przyjmuje argumenty threads.matrix i nasze dane szkoleniowe. Za pomocą unikalnego polecenia tworzymy wektor wszystkich tematów wątków w naszych danych. Teraz musimy wziąć te informacje i zmierzyć aktywność każdego wątku. Wykona to funkcja thread.counts. Wykorzystując temat wątku i dane szkoleniowe jako parametry, zbierzemy wszystkie daty i znaczniki czasu dla wszystkich wiadomości e-mail pasujących do wątku w wektorze thread.times. Możemy zmierzyć, ile e-maili otrzymano w danych szkoleniowych dla tego wątku, mierząc długość thread.times.
Wreszcie, aby zmierzyć poziom aktywności, musimy wiedzieć, jak długo wątek istniał w naszych danych szkoleniowych. Domniemane jest obcinanie po obu stronach tych danych. Oznacza to, że mogą istnieć wiadomości e-mail otrzymane w wątku przed rozpoczęciem naszych danych szkoleniowych lub po zakończeniu gromadzenia danych. Nic nie możemy na to poradzić, więc weźmiemy minimalną i maksymalną datę / czas dla każdego wątku i wykorzystamy je do pomiaru przedziału czasu. Funkcja difftime obliczy czas, jaki upłynął między dwoma obiektami POSIX w niektórych jednostkach. W naszym przypadku chcemy mieć najmniejszą możliwą jednostkę: sekundy.
Z powodu obcięcia może być tak, że obserwujemy tylko jedną wiadomość w wątku. Może to być wątek, który zakończył się w momencie, gdy dane szkolenia zostały zebrane lub rozpoczęty po zakończeniu gromadzenia. Zanim będziemy mogli utworzyć wagę w oparciu o aktywność w czasie wątku, musimy oflagować te wątki, dla których mamy tylko jedną wiadomość. Ifstatement na końcu Thread.counts sprawdza to i zwraca wektor NA, jeśli bieżący wątek ma tylko jedną wiadomość. Później wykorzystamy to do wyodrębnienia ich z danych ważenia opartych na aktywności. Ostatnim krokiem jest stworzenie wagi dla tych wiadomości, które możemy zmierzyć. Zaczynamy od obliczenia stosunku wiadomości do sekund, które upłynęły w wątku. Więc jeśli wiadomość była wysyłana co sekundę w danym wątku, wynikiem będzie jeden. Oczywiście w praktyce liczba ta jest znacznie niższa: średnia liczba wiadomości w każdym wątku wynosi około 4,5, a średni czas, który upłynął, wynosi około 31 000 sekund (8,5 godziny). Biorąc pod uwagę te skale, zdecydowana większość naszych stosunków to małe ułamki. Tak jak poprzednio, nadal chcemy przekształcić te wartości za pomocą logarytmów, ale ponieważ mamy do czynienia z wartościami ułamkowymi, spowoduje to liczby ujemne. W naszym schemacie nie możemy mieć ujemnej wartości wagowej, więc będziemy musieli przeprowadzić dodatkową transformację, która formalnie nazywana jest transformacją afiniczną. Transformacja afiniczna jest po prostu liniowym ruchem punktów w przestrzeni. Wyobraź sobie kwadrat narysowany na kawałku papieru milimetrowego. Jeśli chcesz przesunąć ten kwadrat w inne miejsce na papierze, możesz to zrobić, definiując funkcję, która przesunęła wszystkie punkty w tym samym kierunku. To jest afiniczna transformacja. Aby uzyskać nieujemne wagi w log.trans.weight, po prostu dodamy 10 do wszystkich wartości przekształconych w log. Zapewni to, że wszystkie wartości będą miały odpowiednią wagę z wartością dodatnią. Tak jak poprzednio, po wygenerowaniu danych wagi za pomocą get.threads i thread.counts, wykonamy podstawowe czynności porządkowe w ramce danych thread.weights, aby zachować spójność nazewnictwa z innymi ramkami danych wagi. W ostatnim kroku używamy funkcji podzestawu, aby usunąć wszystkie wiersze, które odnoszą się do wątków zawierających tylko jeden komunikat (tj. Obcięte wątki). Możemy teraz użyć head (thread.weights) do sprawdzenia wyników
head(thread.weights)
Waga Częstotliwość Odpowiedź Waga
1 please help a newbie compile mplayer 🙂 4 42309 5.975627
2 prob. w/ install/uninstall 4 23745 6.226488
3 http://apt.nixia.no/ 10 265303 5.576258
4 problems with ‘apt-get -f install’ 3 55960 5.729244
5 problems with apt update 2 6347 6.498461
6 about apt, kernel updates and dist-upgrade 5 240238 5.318328
Pierwsze dwa wiersze są dobrymi przykładami tego, jak ten schemat ważenia wycenia aktywność wątku. W obu tych wątkach pojawiły się cztery wiadomości. The prob. W / Wątek instalacji / deinstalacji znajduje się jednak w danych przez około pół sekundy. Biorąc pod uwagę nasze założenia, uważalibyśmy, że ten wątek jest ważniejszy i dlatego powinien mieć większą wagę. W tym przypadku przekazujemy wiadomości z tego wątku o masie około 1,04 razy większej niż wiadomości z pomocy nowicjuszowi w skompilowaniu wątku mplayer :-). Może się to wydawać rozsądne lub nie, i stanowi część wiedzy w zakresie projektowania i stosowania takiego schematu do ogólnego problemu. Być może w tym przypadku nasz użytkownik nie doceniałby rzeczy w ten sposób, podczas gdy inni mogą, ale ponieważ chcemy ogólnego rozwiązania, musimy zaakceptować konsekwencje naszych założeń.
term.counts <- funkcja (term.vec, kontrola) {
vec.corpus <- Corpus (VectorSource (term.vec))
vec.tdm <- TermDocumentMatrix (vec.corpus, control = control)
return (rowSums (as.matrix (vec.tdm)))
}
thread.terms <- term.counts (thread.weights $ Thread,
control = list (stopwords = stopwords ()))
thread.terms <- names (thread.terms)
term.weights <- sapply (thread.terms,
function (t) mean (thread.weights $ Weight [grepl (t, thread.weights $ Thread,
naprawiono = PRAWDA)]))
term.weights <- data.frame (lista (Term = imiona (term.weights), Weight = term.weights),
stringsAsFactors = FALSE, row.names = 1: length (term.weights))
Ostateczne dane ważenia, które uzyskamy z wątków, są częstymi terminami w tych wątkach. Podobnie do tego, co zrobiliśmy wcześniej, tworzymy ogólną funkcję o nazwie term.counts, która pobiera wektor terminów i listę kontrolną TermDocumentMatrix w celu wygenerowania TDM i wyodrębnienia liczby terminów we wszystkich wątkach. Założeniem przy tworzeniu tych danych ważenia jest to, że częste terminy w aktywnych wątkach są ważniejsze niż terminy, które są albo rzadsze, albo nie są aktywne w wątkach. Staramy się dodawać jak najwięcej informacji do naszego rankingu, aby stworzyć bardziej szczegółowe rozwarstwienie wiadomości e-mail. Aby to zrobić, zamiast szukać tylko wątków już aktywnych, chcemy również obciążyć wątki, które „wyglądają” jak poprzednio aktywne wątki, a ważenie terminów jest jednym ze sposobów.
msg.terms <- term.counts (priorytet.train $ Wiadomość,
control = list (stopwords = stopwords (),
removePunctuation = TRUE, removeNumbers = TRUE))
msg.weights <- data.frame (lista (Termin = nazwy (msg.terms),
Weight = log (msg.terms, base = 10)), stringsAsFactors = FALSE,
row.names = 1: length (msg.terms))
msg. wagi <- podzbiór (msg. wagi, waga> 0)
Ostateczne dane ważenia, które zbudujemy, są oparte na częstotliwości terminów we wszystkich wiadomościach e-mail w danych szkoleniowych. Będzie to przebiegać prawie identycznie jak w przypadku naszej metody liczenia terminów w procedurze klasyfikacji spamu; tym razem jednak przypiszemy wagi przekształcone logarytmicznie na podstawie tych liczb. Podobnie jak w przypadku ważenia termin-częstotliwość dla tematów wątków, domyślnym założeniem w ramce danych msg.weights jest to, że nowa wiadomość, która wygląda jak inne wiadomości, które widzieliśmy wcześniej, jest ważniejsza niż wiadomość, która jest dla nas całkowicie obca. Mamy teraz pięć ramek danych wagi, z którymi możemy przeprowadzić nasz ranking! Obejmuje to from.weight (funkcja aktywności społecznościowej), senders.df (aktywność nadawcy w wątkach),
thread.weights (aktywność wiadomości wątkowych), term.weights (terminy z aktywnych wątków) i msg.weights (wspólne terminy we wszystkich wiadomościach e-mail). Jesteśmy teraz gotowi do uruchomienia naszych danych treningowych przez ranking, aby znaleźć próg oznaczenia wiadomości jako ważnej.