Mecz Polska - Rumunia na Twitterze

Wydarzenia sportowe (lub inne dziejące się tu i teraz) są bardzo wdzięcznym tematem analizy tego, co dzieje się w mediach społecznościowych. Jak wyglądał Twitter podczas meczu Polska - Rumunia?

Przygotowanie tokenu

Aby zebrać dane z Twittera potrzebujemy zbudować aplikację, która wykorzysta API Twittera i jej tokenu. W samym R wykorzystamy pakiet rtweet. Koniec końców potrzebny nam jest klucz API (para Consumer Key i Consumer Sectet) - jak je zdobyć można przeczytać tutaj (z obrazkami). Odpowiednie wartości należy przypisać do zmiennych poniżej. Kod przygotuje nam token i zapisze w lokalnym pliku, dzięki temu w przyszłości będziemy mogli taki token wykorzystać:

# devtools::install_github("mkearney/rtweet")
library(rtweet)

appname <- "lemurR" # nazwa aplikacji
key <- "xxxx" ## Consumer Key
secret <- "yyyy" ## Consumer Secret

## przygotowanie tokenu
twitter_token <- create_token(
    app = appname,
    consumer_key = key,
    consumer_secret = secret)

save(twitter_token, file="twitter_token.rdata")

Mając zapisany token możemy zacząć

zbieranie twittów

Wykorzystamy “strumień” płynący z Twittera, który zostanie zapisany do lokalnego pliku JSON. Pakiet rtweet ma gotową funkcję, która działa tak, że od momentu jej wywołania przez podaną liczbę sekund pobiera wszystkie kolejne twitty zgodne z zadanymi kryteriami. Potrzebujemy wiedzieć jak długo ma to robić (ja skrypt uruchomiłem około dwóch godzin przed meczem i chciałem aby mniej więcej tyle samo czasu po meczu zakończyło się zbieranie danych) oraz jakie jest kryterium wyboru twittów. W naszym przypadku kryterium to twitty z hashtagiem #polrom.

library(rtweet)

# token przygotowany wcześniej
load("twitter_token.rdata")

# nazwa pliku na twitty
twitts_filename <- "polrom_twitts_full"

# do kiedy od teraz zbierać twitty?
start_time <- Sys.time() # teraz
end_time <- as.POSIXct("2017-06-11 00:00:00 CEST")
n_secs <- as.integer(difftime(end_time, start_time, units = "secs"))

# pobranie twittów - ciągły strumień
stream_tweets("#polrom",
              timeout = n_secs,
              file_name = twitts_filename,
              parse = FALSE)

Można było zrobić nieco inaczej - bez korzystania ze strumienia: już po meczu (na przykład dzisiaj) pobrać wszystkie twitty. Ale to jest badanie post factum. Ja w czasie meczu obserwowałem dane (i wrzucałem na TT) - potrzebowałem więc ciągłego strumienia. Jeden proces generował dane (ten kod powyżej), a drugi - robił wykresiki. Zobaczmy co można wyłowić, a przede wszystkim - jakie są zagrożenia i jak do nich podejść.

Analiza zebranych danych

setwd("~/RProjects/PolRom")

library(rtweet)
library(tidyverse)
library(lubridate)
library(tidytext)
library(wordcloud)

theme_set(theme_minimal())

# polskie stop-words
pl_stop_words <- read_lines("polish_stopwords.txt")


# nazwa pliku na twitty
twitts_filename <- "polrom_twitts_full"
tweets <- parse_stream(paste0(twitts_filename, ".json"))

W tabeli tweets mamy wszystkie potrzebne dane. Na początek wybierzmy tylko to co kluczowe i uporządkujmy daty - przede wszystkim godzina jest przesunięta (inna strefa czasowa). Dodamy przy okazji oznaczenie “okresów” podczas meczu - konkretna połowa, przerwa i czas przed o po meczu (tutaj godziny się nie zgadzają znowu ze względu na przesunięcie stref czasowych - trochę eksperymentowania było potrzebne).

tweets_an <- tweets %>%
    select(screen_name, created_at, text, is_retweet, source) %>%
   # twitty są przesunięte o 2 godziny względem polskiego czasu!
    mutate(created_at = hours(2) + created_at) %>%
   mutate(created_hour = hour(created_at),
             created_minute = minute(created_at),
             created_time = sprintf("%02d:%02d", created_hour, created_minute))

# okresy meczu - rozdzielenie według zegara
tweets_an$period <- ifelse(tweets_an$created_at > "2017-06-11 00:37:00", "po meczu",
                    ifelse(tweets_an$created_at > as.POSIXct("2017-06-10 23:49:00 "), "II połowa",
                    ifelse(tweets_an$created_at > as.POSIXct("2017-06-10 23:33:00"), "przerwa",
                    ifelse(tweets_an$created_at > as.POSIXct("2017-06-10 22:45:00"), "I połowa",
                           "przed meczem"))))

tweets_an$period <- factor(tweets_an$period,
                           levels = c("przed meczem", "I połowa", "przerwa", "II połowa", "po meczu"))

Zobaczmy jakie słowa są najpopularniejsze? Od razu odpuścimy sobie retweety.

# chmurka słów
words <- tweets_an %>%
   filter(is_retweet == FALSE) %>%
   # bez linków
   mutate(text = gsub(" ?(f|ht)tp(s?)://(.*)[.][a-z]+", "", text)) %>%
   unnest_tokens(words, text, token="words") %>%
   count(words, screen_name) %>%
   ungroup() %>%
   # bez tagów i stop-words
   filter(!words %in% c("polrom", "polrum", "polrou")) %>%
   filter(!words %in% pl_stop_words) %>%
   filter(!words %in% stop_words$word)

wordcloud(words$words, words$n,
          scale = c(1.8, 0.5),
          max.words = 100,
          colors = RColorBrewer::brewer.pal(9, "YlOrRd")[4:9])

“Ogień i lód” nie dotyczy meczu, podobnie cała reszta tych największych w chmurce. W tym momencie warto przejrzeć ręcznie tabelę words która nam powstała, zobaczyć które słowa są najpopularniejsze i nie pasują nam do wydarzenia, a następnie wyłowić kto jest autorem twittów z tymi słowami. Wyłoniłem kilkanaście takich osób:

# identyfikujemy spamerów
spammers <- c("99Emcia", "iPourpreCroyant", "wejdan0722", "E_mcia", "laveme224",
              "kiczka17", "moh_sarty", "naszewlasnenieb", "sabrina20008", "didemYahya",
              "yamani_alali", "mahoora1994", "Magda_Jak", "monikapolec",
              "Patrykkkkkkkkk1", "trendinaliaPL", "MAzN1711794")

W dalszych analizach zostawimy sobie twitty bez re-twittów i nie pochodzące z puli nazwanej spammers. Teraz najpopularniejsze słowa wyglądają następująco:

# twitty do analizy
tweets_an <- tweets_an %>% filter(!screen_name %in% spammers) %>% filter(!is_retweet)

Wygląda bardziej prawdopodobnie, prawda? Zobaczmy jak wyglądała aktywność podczas meczu (liczba twittów w poszczególnych minutach):

tweets_an %>%
   mutate(created_time = make_datetime(2017, 6, day(created_at),
                                       hour(created_at),
                                       minute(created_at))) %>%
   count(created_time, period) %>%
    ungroup() %>%
    ggplot() +
    geom_bar(aes(created_time, n, fill=period),
                stat="identity") +
    theme_minimal() +
    labs(x="Godzina", y="Liczba tweetów",
          title = "Liczba tweetów z hashtagiem #POLROM",
          fill = "okres") +
    scale_x_datetime(date_breaks = "hours", date_labels = "%H:%M")

Piki w momentach kiedy padały bramki, spore górki po zakończeniu meczu i tuż przed jego rozpoczęciem. Aktywność zaczyna się godzinę przed i kończy godzinę po. Tego typu wydarzenia są dynamiczne , a dynamika na boisku przekłada się na aktywność w social media. Wniosek dość oczywisty, udowodniony liczbowo :) Teraz jeszcze raz zerknijmy na chmurkę najpopularniejszych słów (po 200), tym razem w rozbiciu na poszczególne okresy meczu:

# pojedyncze słowa
words <- tweets_an %>%
    mutate(text = gsub(" ?(f|ht)tp(s?)://(.*)[.][a-z]+", "", text)) %>%
    unnest_tokens(words, text, token="words") %>%
    count(words, period) %>%
    ungroup() %>%
    filter(!words %in% c("polrom", "polrum", "polrou")) %>%
    filter(!words %in% pl_stop_words) %>%
    filter(!words %in% stop_words$word)

by(words,
   words$period,
   function(x) {
      wordcloud(x$words, x$n,
                scale = c(1.8, 0.5),
                max.words = 200,
                colors = RColorBrewer::brewer.pal(9, "YlOrRd")[4:9])
      text(0.05, 0.95, unique(x$period), col="darkred", cex=1.3,  adj=c(0,0))
   }
)

Po meczu - radość (brawo panowie, mamy mundial). W drugiej połowie - wyniki (3:0, wcześniej 2:0, później dochodzi jedynka do 3:1) i strzelec dwóch bramek. Pierwsza połowa to nieodzowne “kurwa” (dziwne że tak mało - jakbyśmy przegrywali to byłoby pewnie więcej), “Lewy” i pierwsza bramka - karny. Przed meczem standardowe “gopoland, do boju, Szczęsny w bramce, hymn, ciary” ;) Na koniec zobaczmy jak często wymieniany był bohater w trakcie trwania meczu:

Lewandowski w czasie meczu

Po zerknięciu w dane (już rozdzielone na słowa i z policzoną częstotliwością ich występowania) możemy wybrać kilka wyrazów, które określają Roberta Lewandowskiego. Oczywiście nazwisko razem z odmianą przez przypadki, ale też nick w social mediach, oraz “Lewy” jako skrót nazwiska.

tweets_an %>%
   mutate(created_time = make_datetime(2017, 6, day(created_at),
                                       hour(created_at),
                                       minute(created_at))) %>%
   filter(created_time > "2017-06-10 22:45",
          created_time < "2017-06-11 00:37:00") %>%
   mutate(text = gsub(" ?(f|ht)tp(s?)://(.*)[.][a-z]+", "", text)) %>%
   unnest_tokens(words, text, token="words") %>%
   count(words, created_time) %>%
   ungroup() %>%
   filter(tolower(words) %in% c("lewy", "lewandowski", "lewy_official", "lewandowskiego")) %>%
   group_by(created_time) %>%
   summarise(n = sum(n)) %>%
   ungroup() %>%
   ggplot() +
   geom_area(aes(created_time, n), color="darkgreen", fill="lightgreen") +
   scale_x_datetime(date_labels = "%H:%M")

Nie ma nic dziwnego: Lewandowski wymieniany był najczęściej wtedy, kiedy strzelał bramki. Jeśli zrobimy to samo dla słowa “kurwa” otrzymamy podobny układ pików. Jakże to słowo jest uniwersalne…