Twitter Analysis Menggunakan R - Studi Kasus Twitter Para Capres 2019

Hand-on Time

Raden Muhammad Hadi

2019-02-28

Catatan:

Mempersiapkan Package

Pertama, install beberapa package berikut:

install.packages("tidyverse") # Data Science Tool
install.packages("tidytext") # Untuk Text Mining dan Preprocessing
install.packages("rtweet") # Untuk akses ke Twitter API
install.packages("wordcloud2") # Untuk membuat wordcloud
install.packages("graphTweets") # Untuk membuat objek graph/network 
install.packages("sigmajs") # Untuk memvisualisasikan graph/network

Setelah itu, panggil semua package dengan fungsi library(nama_package):

library(tidyverse) # Data Science Tool
## ── Attaching packages ────────────────────────────────────────────── tidyverse 1.2.1 ──
## ✔ ggplot2 3.1.0       ✔ purrr   0.3.0  
## ✔ tibble  2.0.1       ✔ dplyr   0.8.0.1
## ✔ tidyr   0.8.2       ✔ stringr 1.4.0  
## ✔ readr   1.3.1       ✔ forcats 0.4.0
## ── Conflicts ───────────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
library(tidytext) # Untuk Text Mining dan Preprocessing
library(rtweet) # Untuk akses ke Twitter API
## 
## Attaching package: 'rtweet'
## The following object is masked from 'package:purrr':
## 
##     flatten
library(wordcloud2) # Untuk membuat wordcloud
library(graphTweets) # Untuk membuat objek graph/network 
## help('graphTweets') for examples
library(sigmajs) # Untuk memvisualisasikan graph/network
## Welcome to sigmajs
## 
## Docs: sigmajs.john-coene.com

Menggunakan access token untuk menggunakan Twitter API

Agar bisa mengakses API Twitter, kita perlu membuat access token yang sudah diperoleh melalui https://developer.twitter.com menggunakan fungsi create_token() dari library rtweet1 Untuk mengetahui fungsionalitas dan penggunaan lainnya dari rtweet bisa melihat dokumentasi pada https://rtweet.info/. seperti berikut:

create_token(app = "masukkan nama aplikasi",
             consumer_key = "masukkan consumer key",
             consumer_secret = "masukkan consumer secret",
             access_token = "masukkan access token",
             access_secret = "masukkan access secret")

Jalankan perintah diatas melalui console maupun R script sehingga muncul output berikut:

Keluaran fungsi create_token()

Keluaran fungsi create_token()

Setelah itu kita siap untuk melakukan scrapping data Twitter.

Analisa Tweet Berdasarkan Hashtag, Mention atau Keyword

Kita bisa melakukan pencarian untuk topik tertentu berdasarkan hashtag, mention maupun kata kunci apapun menggunakan fungsi search_tweet. Sebagai contoh saya mencari hashtag #rstats:

# Cari tweet dengan hashtag #rstats sebanyak 100 lalu masukkan ke variabel 'hasil_pencarian'
hasil_pencarian <- search_tweets(q = "#rstats", n = 100)
## Searching for tweets...
## Finished collecting tweets!
# perlihatkan nilai dari hasil_pencarian
hasil_pencarian
## # A tibble: 100 x 88
##    user_id status_id created_at          screen_name text  source
##    <chr>   <chr>     <dttm>              <chr>       <chr> <chr> 
##  1 701540… 11009748… 2019-02-28 04:23:50 fatma_cina… "Gel… Twitt…
##  2 240847… 11009743… 2019-02-28 04:21:44 SamStiyer   "Gre… Twitt…
##  3 973888… 11009742… 2019-02-28 04:21:33 PD_MobileA… "Tec… Twitt…
##  4 901509… 11009740… 2019-02-28 04:20:51 electivenow "Mul… Twitt…
##  5 904618… 11009739… 2019-02-28 04:20:10 VuePackage  "Tec… VuePa…
##  6 188811… 11009734… 2019-02-28 04:18:22 jsonbaik    "Gre… Twitt…
##  7 162410… 11009731… 2019-02-28 04:17:11 HydrePrever "Pyt… Twitt…
##  8 192103… 11009728… 2019-02-28 04:16:03 Dlobdog     "Tec… Twitt…
##  9 172276… 11009726… 2019-02-28 04:14:57 MattDabrow… "Ove… Twitt…
## 10 737164… 11009720… 2019-02-28 04:12:57 christineb… "Tod… Twitt…
## # … with 90 more rows, and 82 more variables: display_text_width <dbl>,
## #   reply_to_status_id <lgl>, reply_to_user_id <lgl>,
## #   reply_to_screen_name <lgl>, is_quote <lgl>, is_retweet <lgl>,
## #   favorite_count <int>, retweet_count <int>, hashtags <list>,
## #   symbols <list>, urls_url <list>, urls_t.co <list>,
## #   urls_expanded_url <list>, media_url <list>, media_t.co <list>,
## #   media_expanded_url <list>, media_type <list>, ext_media_url <list>,
## #   ext_media_t.co <list>, ext_media_expanded_url <list>,
## #   ext_media_type <chr>, mentions_user_id <list>,
## #   mentions_screen_name <list>, lang <chr>, quoted_status_id <chr>,
## #   quoted_text <chr>, quoted_created_at <dttm>, quoted_source <chr>,
## #   quoted_favorite_count <int>, quoted_retweet_count <int>,
## #   quoted_user_id <chr>, quoted_screen_name <chr>, quoted_name <chr>,
## #   quoted_followers_count <int>, quoted_friends_count <int>,
## #   quoted_statuses_count <int>, quoted_location <chr>,
## #   quoted_description <chr>, quoted_verified <lgl>,
## #   retweet_status_id <chr>, retweet_text <chr>,
## #   retweet_created_at <dttm>, retweet_source <chr>,
## #   retweet_favorite_count <int>, retweet_retweet_count <int>,
## #   retweet_user_id <chr>, retweet_screen_name <chr>, retweet_name <chr>,
## #   retweet_followers_count <int>, retweet_friends_count <int>,
## #   retweet_statuses_count <int>, retweet_location <chr>,
## #   retweet_description <chr>, retweet_verified <lgl>, place_url <chr>,
## #   place_name <chr>, place_full_name <chr>, place_type <chr>,
## #   country <chr>, country_code <chr>, geo_coords <list>,
## #   coords_coords <list>, bbox_coords <list>, status_url <chr>,
## #   name <chr>, location <chr>, description <chr>, url <chr>,
## #   protected <lgl>, followers_count <int>, friends_count <int>,
## #   listed_count <int>, statuses_count <int>, favourites_count <int>,
## #   account_created_at <dttm>, verified <lgl>, profile_url <chr>,
## #   profile_expanded_url <chr>, account_lang <chr>,
## #   profile_banner_url <chr>, profile_background_url <chr>,
## #   profile_image_url <chr>

Kita bisa melihat struktur data dari hasil_pencarian dengan menggunakan glimpse():

# Dari variabel hasil_pencarian
hasil_pencarian %>% 
  # perlihatkan struktur data
  glimpse()
## Observations: 100
## Variables: 88
## $ user_id                 <chr> "701540016", "2408471545", "973888390050…
## $ status_id               <chr> "1100974835843383299", "1100974305431769…
## $ created_at              <dttm> 2019-02-28 04:23:50, 2019-02-28 04:21:4…
## $ screen_name             <chr> "fatma_cinar_ftm", "SamStiyer", "PD_Mobi…
## $ text                    <chr> "Geleceğin Mesleği: Büyük Verinin Analiz…
## $ source                  <chr> "Twitter for iPhone", "Twitter for iPhon…
## $ display_text_width      <dbl> 277, 140, 140, 140, 140, 185, 269, 140, …
## $ reply_to_status_id      <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ reply_to_user_id        <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ reply_to_screen_name    <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ is_quote                <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE…
## $ is_retweet              <lgl> FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FA…
## $ favorite_count          <int> 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0…
## $ retweet_count           <int> 1, 1, 16, 30, 16, 1, 0, 16, 105, 9, 4, 1…
## $ hashtags                <list> [<"DataAnalytics", "DataViz", "DataScie…
## $ symbols                 <list> [NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ urls_url                <list> ["linkedin.com/pulse/gelece%C", NA, NA,…
## $ urls_t.co               <list> ["https://t.co/wweIexOjCv", NA, NA, NA,…
## $ urls_expanded_url       <list> ["http://linkedin.com/pulse/gelece%C", …
## $ media_url               <list> [NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ media_t.co              <list> [NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ media_expanded_url      <list> [NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ media_type              <list> [NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ext_media_url           <list> [NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ext_media_t.co          <list> [NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ext_media_expanded_url  <list> [NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ ext_media_type          <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ mentions_user_id        <list> ["4823923209", "1888111382", "973892651…
## $ mentions_screen_name    <list> ["DataLabTR", "jsonbaik", "Harry_Robots…
## $ lang                    <chr> "tr", "en", "en", "en", "en", "en", "en"…
## $ quoted_status_id        <chr> NA, NA, NA, NA, NA, NA, "110091054903717…
## $ quoted_text             <chr> NA, NA, NA, NA, NA, NA, "@nnstats Lookin…
## $ quoted_created_at       <dttm> NA, NA, NA, NA, NA, NA, 2019-02-28 00:0…
## $ quoted_source           <chr> NA, NA, NA, NA, NA, NA, "Twitter for And…
## $ quoted_favorite_count   <int> NA, NA, NA, NA, NA, NA, 5, NA, NA, NA, N…
## $ quoted_retweet_count    <int> NA, NA, NA, NA, NA, NA, 1, NA, NA, NA, N…
## $ quoted_user_id          <chr> NA, NA, NA, NA, NA, NA, "282435042", NA,…
## $ quoted_screen_name      <chr> NA, NA, NA, NA, NA, NA, "juanDenver_", N…
## $ quoted_name             <chr> NA, NA, NA, NA, NA, NA, "Juan Denver", N…
## $ quoted_followers_count  <int> NA, NA, NA, NA, NA, NA, 857, NA, NA, NA,…
## $ quoted_friends_count    <int> NA, NA, NA, NA, NA, NA, 1221, NA, NA, NA…
## $ quoted_statuses_count   <int> NA, NA, NA, NA, NA, NA, 47210, NA, NA, N…
## $ quoted_location         <chr> NA, NA, NA, NA, NA, NA, "Denver, CO | 52…
## $ quoted_description      <chr> NA, NA, NA, NA, NA, NA, "Born and raised…
## $ quoted_verified         <lgl> NA, NA, NA, NA, NA, NA, FALSE, NA, NA, N…
## $ retweet_status_id       <chr> NA, "1100973459042172928", "110092156098…
## $ retweet_text            <chr> NA, "Great read on how a PhD in Statisti…
## $ retweet_created_at      <dttm> NA, 2019-02-28 04:18:22, 2019-02-28 00:…
## $ retweet_source          <chr> NA, "Twitter for iPhone", "Twitter Web C…
## $ retweet_favorite_count  <int> NA, 1, 13, 37, 13, NA, NA, 13, 358, 16, …
## $ retweet_retweet_count   <int> NA, 1, 16, 30, 16, NA, NA, 16, 105, 9, 4…
## $ retweet_user_id         <chr> NA, "1888111382", "973892651522166785", …
## $ retweet_screen_name     <chr> NA, "jsonbaik", "Harry_Robots", "gp_puli…
## $ retweet_name            <chr> NA, "Jason Baik", "Harry Miller", "Dr. G…
## $ retweet_followers_count <int> NA, 555, 11701, 54532, 11701, NA, NA, 11…
## $ retweet_friends_count   <int> NA, 1042, 12877, 28488, 12877, NA, NA, 1…
## $ retweet_statuses_count  <int> NA, 957, 19664, 43300, 19664, NA, NA, 19…
## $ retweet_location        <chr> NA, "Pittsburgh, PA", "Renton, WA", "Los…
## $ retweet_description     <chr> NA, "Author @HockeyGraphs | #rstats Lear…
## $ retweet_verified        <lgl> NA, FALSE, FALSE, FALSE, FALSE, NA, NA, …
## $ place_url               <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ place_name              <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ place_full_name         <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ place_type              <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ country                 <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ country_code            <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ geo_coords              <list> [<NA, NA>, <NA, NA>, <NA, NA>, <NA, NA>…
## $ coords_coords           <list> [<NA, NA>, <NA, NA>, <NA, NA>, <NA, NA>…
## $ bbox_coords             <list> [<NA, NA, NA, NA, NA, NA, NA, NA>, <NA,…
## $ status_url              <chr> "https://twitter.com/fatma_cinar_ftm/sta…
## $ name                    <chr> "Fatma Çınar", "Sam Stiyer", "Peter Dyer…
## $ location                <chr> "İstanbul", "Atlanta, GA", "Washington, …
## $ description             <chr> "Areas of Interest #DataManagement #Rsta…
## $ url                     <chr> "https://t.co/fHU2o8D3Qx", "https://t.co…
## $ protected               <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE…
## $ followers_count         <int> 1221, 61, 8221, 541, 1182, 555, 288, 97,…
## $ friends_count           <int> 1191, 106, 9635, 594, 162, 1042, 398, 22…
## $ listed_count            <int> 281, 3, 92, 9, 33, 23, 3, 4, 75, 11, 11,…
## $ statuses_count          <int> 17221, 205, 16406, 10745, 14434, 957, 74…
## $ favourites_count        <int> 12305, 754, 7101, 13885, 14092, 1696, 11…
## $ account_created_at      <dttm> 2012-07-17 18:31:22, 2014-03-24 06:15:4…
## $ verified                <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE…
## $ profile_url             <chr> "https://t.co/fHU2o8D3Qx", "https://t.co…
## $ profile_expanded_url    <chr> "https://www.linkedin.com/in/fatmacinar"…
## $ account_lang            <chr> "tr", "en", "en", "en", "en", "en", "fr"…
## $ profile_banner_url      <chr> "https://pbs.twimg.com/profile_banners/7…
## $ profile_background_url  <chr> "http://abs.twimg.com/images/themes/them…
## $ profile_image_url       <chr> "http://pbs.twimg.com/profile_images/105…

Untuk sekali pemanggilan, terdapat 88 variabel dengan diantaranya:

Kita juga bisa melakukan pencarian dengan menggunakan kata kunci (keyword), misalnya kata ‘politik’ dengan tweet berbahasa indonesia sebanyak 3200:

# Cari tweet dengan kata kunci 'politik' sebanyak 3200 tweet lalu masukkan ke variabel 'hasil_pencarian2' dengan bahasa yang dipakai adalah bahasa indonesia
hasil_pencarian2 <- search_tweets(q = "politik", n = 3200, lang = "id")
## Searching for tweets...
## Finished collecting tweets!
# perlihatkan nilai dari hasil_pencarian2
hasil_pencarian2
## # A tibble: 3,117 x 88
##    user_id status_id created_at          screen_name text  source
##    <chr>   <chr>     <dttm>              <chr>       <chr> <chr> 
##  1 116787… 11009748… 2019-02-28 04:24:05 nyinyir_ba… Dedi… Twitt…
##  2 788978… 11009748… 2019-02-28 04:24:04 diditjunai… "✅Pa… Twitt…
##  3 788978… 11009676… 2019-02-28 03:55:27 diditjunai… "Tak… Twitt…
##  4 867438… 11009748… 2019-02-28 04:24:03 KholidZaif… Ratn… dlvr.…
##  5 106060… 11009748… 2019-02-28 04:24:02 imunk_wae   "Ema… Twitt…
##  6 216161… 11009748… 2019-02-28 04:24:01 hrs_bkd     "Pak… Twitt…
##  7 609616… 11009748… 2019-02-28 04:24:01 ryanadsty   Hidu… Twitt…
##  8 828144… 11009748… 2019-02-28 04:24:01 muSlimATjkt "Kat… Twitt…
##  9 235168… 11009748… 2019-02-28 04:24:00 ysminsuraya "Tak… Twitt…
## 10 331481… 11009744… 2019-02-28 04:22:25 NissaLatte  Dari… Twitt…
## # … with 3,107 more rows, and 82 more variables: display_text_width <dbl>,
## #   reply_to_status_id <chr>, reply_to_user_id <chr>,
## #   reply_to_screen_name <chr>, is_quote <lgl>, is_retweet <lgl>,
## #   favorite_count <int>, retweet_count <int>, hashtags <list>,
## #   symbols <list>, urls_url <list>, urls_t.co <list>,
## #   urls_expanded_url <list>, media_url <list>, media_t.co <list>,
## #   media_expanded_url <list>, media_type <list>, ext_media_url <list>,
## #   ext_media_t.co <list>, ext_media_expanded_url <list>,
## #   ext_media_type <chr>, mentions_user_id <list>,
## #   mentions_screen_name <list>, lang <chr>, quoted_status_id <chr>,
## #   quoted_text <chr>, quoted_created_at <dttm>, quoted_source <chr>,
## #   quoted_favorite_count <int>, quoted_retweet_count <int>,
## #   quoted_user_id <chr>, quoted_screen_name <chr>, quoted_name <chr>,
## #   quoted_followers_count <int>, quoted_friends_count <int>,
## #   quoted_statuses_count <int>, quoted_location <chr>,
## #   quoted_description <chr>, quoted_verified <lgl>,
## #   retweet_status_id <chr>, retweet_text <chr>,
## #   retweet_created_at <dttm>, retweet_source <chr>,
## #   retweet_favorite_count <int>, retweet_retweet_count <int>,
## #   retweet_user_id <chr>, retweet_screen_name <chr>, retweet_name <chr>,
## #   retweet_followers_count <int>, retweet_friends_count <int>,
## #   retweet_statuses_count <int>, retweet_location <chr>,
## #   retweet_description <chr>, retweet_verified <lgl>, place_url <chr>,
## #   place_name <chr>, place_full_name <chr>, place_type <chr>,
## #   country <chr>, country_code <chr>, geo_coords <list>,
## #   coords_coords <list>, bbox_coords <list>, status_url <chr>,
## #   name <chr>, location <chr>, description <chr>, url <chr>,
## #   protected <lgl>, followers_count <int>, friends_count <int>,
## #   listed_count <int>, statuses_count <int>, favourites_count <int>,
## #   account_created_at <dttm>, verified <lgl>, profile_url <chr>,
## #   profile_expanded_url <chr>, account_lang <chr>,
## #   profile_banner_url <chr>, profile_background_url <chr>,
## #   profile_image_url <chr>

Catatan: Terkadang tweet yang diperoleh tidak selalu sama dengan jumlah maksimal, jika ingin mengambil lebih dari 3200 maka harus menggunakan parameter retryonratelimit = TRUE.

Selanjutnya kita juga dapat melihat frekuensi keyword tersebut digunakan menggunakan fungsi ts_plot():

# Dari hasil pencarian
hasil_pencarian2 %>% 
  # buatkan grafik time-series frekuensi kata kunci tersebut 
  # dipakai dalam tweet per menit
  ts_plot(by = "minutes")

Kita bisa mengganti nilai dari parameter by dengan seconds, minutes, hours, days, weeks, bahkan dengan keterangan waktu semisal 3 seconds, 3 days dan lainnya.

Sepertinya hasil plot kurang menarik. Kita bisa menambahkan judul dan memperbaiki tema dari grafiknya dengan menggunakan beberapa fungsi dari ggplot24 Dokumentasi terkait ggplot2 dapat dilihat pada situs https://ggplot2.tidyverse.org/. seperti berikut:

# Dari hasil pencarian
hasil_pencarian2 %>% 
  # buatkan grafik time-series frekuensi 
  # kata kunci tersebut dipakai dalam 
  # tweet per menit
  ts_plot(by = "minutes") +
  # Menggunakan theme_minimal
  theme_minimal() +
  # Memberikan judul plot dengan 
  # elemen teks tebal (bold)
  theme(plot.title = element_text(face = "bold")) +
  # Menambahkan beberapa elemen
  labs(
    # Berikan label untuk x
    x = "waktu dalam menit",
    # Berikan label untuk y
    y = "frekuensi",
    # Berikan judul
    title = "Frekuensi Tweet dengan kata Kunci 'Politik'",
    # Memberi sub-judul
    subtitle = "per menit",
    # Memberi caption
    caption = paste0("Sumber: Twitter, tanggal: ", Sys.Date())
  )

Selanjutnya kita akan melihat semua tweet tersebut dengan melihat variabel text:

# Dari hasil_pencarian2
hasil_pencarian2 %>% 
  # Pilih kolom teks
  select(text)
## # A tibble: 3,117 x 1
##    text                                                                    
##    <chr>                                                                   
##  1 Dedikasi ibu Ratna sarumpaet hancur karena politik hoax #Emak2Korban02 …
##  2 "✅Para Suami/Bapak.\nIngat kan istri untuk berhati hati kalau ada ajaka…
##  3 "Tak akan terjadi 😉\nMamakke sdh teredukasi cukup baik tntg politik di …
##  4 Ratna Sarumpaet: Ada Ketegangan Luar Biasa Saat Penyidikan, Ini Politik…
##  5 "Emak emak gk bakalan hirau dgn politik kalo keadaan baik2 saja, ini mh…
##  6 "Pakar LIPI : ''Ada beberapa data saat debat yang disebutkan Jokowi kel…
##  7 Hidup, milenial! https://t.co/Cdsism6qQG                                
##  8 "Kata orang, tidak ada kebetulan dalam Politik\n\nSaya setuju https://t…
##  9 "Tak sensitif langsung kepada perasaan keluarga Arwah.  \nBukan semua p…
## 10 Daripada harus susah-susah mengabdi untuk partai politik, lebih baik la…
## # … with 3,107 more rows

Jika dilihat, terdapat beberapa teks yang mengandung hashtag (#), link, mention (@) dan kadangkala terdapat teks yang memuat emoticon. Hal ini bisa dibersihkan dengan menggunakan fungsi mutate, gsub() dan plain_tweets():

# Dari hasil_pencarian2
hasil_pencarian2 %>% 
  # Pilih kolom text
  select(text) %>% 
  # Ubah elemen pada kolom text dengan mengganti
  # semua link dengan karakter kosong
  mutate(text = gsub(pattern = "http\\S+", 
                     replacement = "", 
                     x = text)) %>% 
  # Ubah elemen pada kolom text dengan mengganti 
  # semua hashtag dengan karakter kosong
  mutate(text = gsub(pattern = "#", 
                     replacement = "", 
                     x = text)) %>% 
  # Ubah elemen pada kolom text dengan mengganti 
  # semua mention dengan karakter kosong
  mutate(text = gsub(pattern = "@", 
                     replacement = "", 
                     x = text)) %>% 
  # Bersihkan karakter lainnya (contoh: emoticon) 
  # lalu simpan ke dalam variabel text_cleaned
  plain_tweets() -> text_cleaned

Catatan:

Selanjutnya kita akan melihat wordcloud dari teks yang sudah dibersihkan dengan memotongnya menjadi per kata dengan fungsi unnest_tokens() dan menghitung frekuensu huruf dengan fungsi count()5 fungsi unnest_tokens() dan count() adalah bagian dari tidytext. Referensi terkait tidytext dapat melihat situs https://www.tidytextmining.com/.:

# Dari variabel text_cleaned
text_cleaned %>%   
  # tokenize setiap kalimat menjadi per kata
  unnest_tokens(input = text, output = token) %>% 
  # hitung frekuensi semua huruf lalu urutkan 
  # dari yang paling besar frekuensinya
  count(token, sort = T)
## # A tibble: 6,654 x 2
##    token       n
##    <chr>   <int>
##  1 politik  3098
##  2 dan      1320
##  3 yang     1097
##  4 di       1020
##  5 emak     1018
##  6 saya      770
##  7 tidak     757
##  8 yg        702
##  9 ini       663
## 10 ada       568
## # … with 6,644 more rows

Jika diperhatikan, terdapat kata-kata seperti kata ‘ini’, ‘di’, ‘dari’, ‘itu’ dan kata-kata lainnya yang tidak memiliki makna atau memuat topik tertentu di dalam sebuah teks, kata-kata ini biasa disebut sebagai stopwords. Stopwords dapat dihilangkan dengan mudah selama kita memiliki korpus6 Korpus (corpus) adalah koleksi teks yang besar dan terstruktur. Biasanya digunakan untuk analisa statistik dan pengujian hipotesis terkait stopwords. Korpus ini bisa kita buat sendiri atau menggunakan yang sudah ada seperti yang bisa diperoleh dari github masdevid berikut dengan menyalin link github tersebut lalu masukkan ke dalam fungsi read_csv() agar korpus tersebut bisa diambil:

# Ambil stopwords dari link, beri nama kolomnya 'stopwords'
# hasilnya disimpan ke dalam variabel 'stopword_indo'
stopword_indo <- read_csv("https://raw.githubusercontent.com/masdevid/ID-Stopwords/master/id.stopwords.02.01.2016.txt", 
                          col_names = "stopwords")
## Parsed with column specification:
## cols(
##   stopwords = col_character()
## )
# Melihat sebagian isi dari stopword_indo
stopword_indo
## # A tibble: 758 x 1
##    stopwords
##    <chr>    
##  1 ada      
##  2 adalah   
##  3 adanya   
##  4 adapun   
##  5 agak     
##  6 agaknya  
##  7 agar     
##  8 akan     
##  9 akankah  
## 10 akhir    
## # … with 748 more rows

Selanjutnya kita akan membuang semua stopwords pada teks dengan menggunakan fungsi anti_join():

# Dari variabel text_cleaned
text_cleaned %>% 
  # tokenize setiap kalimat menjadi per kata
  unnest_tokens(input = text, output = token) %>% 
  # buang setiap stopword yang ada pada kolom 
  # token jika terdapat kata yang sama dengan 
  # yang ada pada variabel stopword_indo
  anti_join(stopword_indo, by = c("token" = "stopwords"))
## # A tibble: 55,065 x 1
##    token        
##    <chr>        
##  1 dedikasi     
##  2 ratna        
##  3 sarumpaet    
##  4 hancur       
##  5 politik      
##  6 hoax         
##  7 emak2korban02
##  8 suami        
##  9 istri        
## 10 berhati      
## # … with 55,055 more rows

Dapat dilihat bahwa beberapa stopwords sudah dihilangkan walaupun masih terdapat kata seperti ‘yg’, ‘ya’, ‘nih’ dan kata-kata lainnya yang harusnya tidak perlu ada. Hal ini bisa diantisipasi dengan melengkapi korpus yang sudah ada. Misalnya menyimpan korpus tersebut ke dalam file CSV lalu melengkapi korpusnya melalui Excel atau program spreadsheet lainnya yang lebih mudah digunakan.

Selanjutnya kita akan mencoba membuat wordcloud dari teks yang sudah dibersihkan dari stopwords:

# Dari variabel text_cleaned
text_cleaned %>% 
  # tokenize setiap kalimat menjadi per kata
  unnest_tokens(input = text, output = token) %>% 
  # buang setiap stopword yang ada pada kolom 
  # token jika terdapat kata yang sama dengan 
  # yang ada pada variabel stopword_indo
  anti_join(stopword_indo, by = c("token" = "stopwords")) %>% 
  # Hitung frekuensi huruf lalu urutkan dari yang terbesar
  count(token, sort = T) %>% 
  # visualisasikan dalam bentuk wordcloud
  wordcloud2(size = 1)

Analisa Jaringan

Masih dengan menggunakan variabel hasil_pencarian2, kita akan mencoba untuk membuat visualisasi sederhana terkait kata kunci ‘politik’. Untuk membangun sebuah graf, hal yang perlu dilakukan adalah menyusun data yang terdiri dari sumber (source), tujuan (target) dan nilai bobot antara sumber dan tujuan. Hal ini bisa dibantu dengan menggunakan fungsi dari library graphTweets dan visualisasinya bisa dibantu dengan library sigmajs7 Untuk mengetahui lebih lanjut fungsi-fungsi pada kedua library ini silahkan kunjungi dokumentasi untuk graphTweets http://graphtweets.john-coene.com/ dan dokumentasi untuk sigmajshttp://sigmajs.john-coene.com/.:

# Dari hasil_pencarian2
hasil_pencarian2 %>% 
  # Buat edge dari screen_name dan mentions_screen_name
  gt_edges(screen_name, mentions_screen_name) %>% 
  # buat node
  gt_nodes() %>%
  # gabungkan hasilnya menjadi satu lalu masukkan 
  # ke variabel jaringan_sosial
  gt_collect() -> jaringan_sosial

# Dari jaringan_sosial dan variabel nodes
nodes <- jaringan_sosial$nodes %>%
  # buat variabel baru terdiri dari
  mutate(
    # id dengan nilai sama dengan kolom nodes
    id = nodes,
    # label dengan nilai sama dengan kolom nodes
    label = nodes,
    # dengan ukuran sentralitas n
    size = n
    ) 

# Dari jaringan sosial dan variabel edges
edges <- jaringan_sosial$edges %>% 
  # buat variabel baru terdiri dari
  mutate(
    # id dengan nilai terurut dari 1 sampai n
    id = 1:n()
  )

# Dengan menggunakan sigmajs
sigmajs() %>% 
  # inisiasi graf
  sg_force_start() %>% 
  # inisiasi nodes
  sg_nodes(nodes, id, label, size) %>%
  # inisiasi edges
  sg_edges(edges, id, source, target) %>%
  # beri layout pada graf
  sg_layout() %>% 
  # tentukan cluster pada graf
  sg_cluster() %>% 
  # hentikan animasi
  sg_force_stop(10000)

Dapat dilihat dari grafik bahwa terdapat beberapa titik yang memiliki ukuran paling besar. Hal ini dikarenakan titik tersebut memiliki sentralitas tinggi, artinya titik tersebut terhubung dengan banyak titik lainnya. Bisa diartikan bahwa titik tersebut memiliki pengaruh sangat besar dan merupakan pusat dari kelompok tertentu. Namun perlu diperhatikan juga bahwa graf yang dibuat tidak memiliki arah jadi tidak diketahui siapa mempengaruhi siapa8 Untuk mempelajari hal-hal terkait analisa jaringan (Network Analysis) dapat mengunjungi referensi http://rpubs.com/wctucker/302110 dan http://sachaepskamp.com/files/Cookbook.html atau membaca buku karya David Easley dan Jon Kleinberg yang berjudul Networks, Crowds, and Markets..

Materi Tambahan

Streaming Twitter Secara Realtime

Kita bisa melakukan scraping Twitter secara real-time dengan menggunakan fungsi stream_tweets():

# Lakukan scrape secara streaming
stream_tweets(
  # untuk keyword politik
  q = "politik",
  # selama 120 detik
  timeout = 120,
  # simpan kedalam file bernama 'hasil_streaming.json'
  file_name = "hasil_streaming.json",
  # jangan di-parsing untuk menghemat resource
  parse = FALSE)
## Streaming tweets for 120 seconds...
## Finished streaming tweets!
## streaming data saved as hasil_streaming.json

Kita bisa membaca hasil yang disimpan menggunakan parse_stream():

# Lakukan parsing pada 
parse_stream("hasil_streaming.json")
## opening file input connection.
## 
 Found 81 records...
 Imported 81 records. Simplifying...
## closing file input connection.
## # A tibble: 81 x 88
##    user_id status_id created_at          screen_name text  source
##    <chr>   <chr>     <dttm>              <chr>       <chr> <chr> 
##  1 108916… 11009751… 2019-02-28 04:24:59 Yudi986195… Rezi… Twitt…
##  2 632018… 11009751… 2019-02-28 04:24:59 faridund    "@th… Twitt…
##  3 228020… 11009751… 2019-02-28 04:25:02 FreieWeltEu "Mer… Freie…
##  4 228020… 11009751… 2019-02-28 04:25:02 FreieWeltEu "2. … Freie…
##  5 228020… 11009751… 2019-02-28 04:25:02 FreieWeltEu "Jou… Freie…
##  6 228020… 11009751… 2019-02-28 04:25:02 FreieWeltEu "Kam… Freie…
##  7 228020… 11009751… 2019-02-28 04:25:03 FreieWeltEu "Wen… Freie…
##  8 108426… 11009751… 2019-02-28 04:25:02 Sri_Wuland… "Ada… Twitt…
##  9 115249… 11009751… 2019-02-28 04:25:06 obatpusing… Pera… Twitt…
## 10 109480… 11009751… 2019-02-28 04:25:09 Mama_Balqi… "Ada… Twitt…
## # … with 71 more rows, and 82 more variables: display_text_width <dbl>,
## #   reply_to_status_id <chr>, reply_to_user_id <chr>,
## #   reply_to_screen_name <chr>, is_quote <lgl>, is_retweet <lgl>,
## #   favorite_count <int>, retweet_count <int>, hashtags <list>,
## #   symbols <list>, urls_url <list>, urls_t.co <list>,
## #   urls_expanded_url <list>, media_url <list>, media_t.co <list>,
## #   media_expanded_url <list>, media_type <list>, ext_media_url <list>,
## #   ext_media_t.co <list>, ext_media_expanded_url <list>,
## #   ext_media_type <chr>, mentions_user_id <list>,
## #   mentions_screen_name <list>, lang <chr>, quoted_status_id <chr>,
## #   quoted_text <chr>, quoted_created_at <dttm>, quoted_source <chr>,
## #   quoted_favorite_count <int>, quoted_retweet_count <int>,
## #   quoted_user_id <chr>, quoted_screen_name <chr>, quoted_name <chr>,
## #   quoted_followers_count <int>, quoted_friends_count <int>,
## #   quoted_statuses_count <int>, quoted_location <chr>,
## #   quoted_description <chr>, quoted_verified <lgl>,
## #   retweet_status_id <chr>, retweet_text <chr>,
## #   retweet_created_at <dttm>, retweet_source <chr>,
## #   retweet_favorite_count <int>, retweet_retweet_count <int>,
## #   retweet_user_id <chr>, retweet_screen_name <chr>, retweet_name <chr>,
## #   retweet_followers_count <int>, retweet_friends_count <int>,
## #   retweet_statuses_count <int>, retweet_location <chr>,
## #   retweet_description <chr>, retweet_verified <lgl>, place_url <chr>,
## #   place_name <chr>, place_full_name <chr>, place_type <chr>,
## #   country <chr>, country_code <chr>, geo_coords <list>,
## #   coords_coords <list>, bbox_coords <list>, status_url <chr>,
## #   name <chr>, location <chr>, description <chr>, url <chr>,
## #   protected <lgl>, followers_count <int>, friends_count <int>,
## #   listed_count <int>, statuses_count <int>, favourites_count <int>,
## #   account_created_at <dttm>, verified <lgl>, profile_url <chr>,
## #   profile_expanded_url <chr>, account_lang <chr>,
## #   profile_banner_url <chr>, profile_background_url <chr>,
## #   profile_image_url <chr>

Studi Kasus: Analisa Sederhana Twitter Kedua Capres 2019

Kita bisa melakukan scrape terhadap timeline seseorang dengan menggunakan get_timeline(). Untuk ini kita akan melakukan scraping pada dua pasangan capres: Joko Widodo(jokowi) dan Prabowo Subianto(prabowo):

Timeline Joko Widodo

# lihat timeline jokowi dan ambil tweet sebanyak 10000
# atau sebanyak-banyaknya
timeline_jokowi <- get_timeline(user = "jokowi", n = 10000)

# lihat isi dari timeline_jokowi
timeline_jokowi
## # A tibble: 1,490 x 88
##    user_id status_id created_at          screen_name text  source
##    <chr>   <chr>     <dttm>              <chr>       <chr> <chr> 
##  1 366987… 11009654… 2019-02-28 03:46:22 jokowi      "Sim… Twitt…
##  2 366987… 11007359… 2019-02-27 12:34:41 jokowi      "Di … Twitt…
##  3 366987… 11006713… 2019-02-27 08:17:59 jokowi      "Tib… Twitt…
##  4 366987… 11005852… 2019-02-27 02:35:56 jokowi      "Pem… Twitt…
##  5 366987… 11004424… 2019-02-26 17:08:30 jokowi      "Ind… Twitt…
##  6 366987… 11003491… 2019-02-26 10:57:42 jokowi      Memb… Twitt…
##  7 366987… 11002382… 2019-02-26 03:36:44 jokowi      "Blu… Twitt…
##  8 366987… 10999872… 2019-02-25 10:59:23 jokowi      Bers… Twitt…
##  9 366987… 10999528… 2019-02-25 08:42:54 jokowi      "Emp… Twitt…
## 10 366987… 10999161… 2019-02-25 06:16:53 jokowi      "Tib… Twitt…
## # … with 1,480 more rows, and 82 more variables: display_text_width <dbl>,
## #   reply_to_status_id <chr>, reply_to_user_id <chr>,
## #   reply_to_screen_name <chr>, is_quote <lgl>, is_retweet <lgl>,
## #   favorite_count <int>, retweet_count <int>, hashtags <list>,
## #   symbols <list>, urls_url <list>, urls_t.co <list>,
## #   urls_expanded_url <list>, media_url <list>, media_t.co <list>,
## #   media_expanded_url <list>, media_type <list>, ext_media_url <list>,
## #   ext_media_t.co <list>, ext_media_expanded_url <list>,
## #   ext_media_type <chr>, mentions_user_id <list>,
## #   mentions_screen_name <list>, lang <chr>, quoted_status_id <chr>,
## #   quoted_text <chr>, quoted_created_at <dttm>, quoted_source <chr>,
## #   quoted_favorite_count <int>, quoted_retweet_count <int>,
## #   quoted_user_id <chr>, quoted_screen_name <chr>, quoted_name <chr>,
## #   quoted_followers_count <int>, quoted_friends_count <int>,
## #   quoted_statuses_count <int>, quoted_location <chr>,
## #   quoted_description <chr>, quoted_verified <lgl>,
## #   retweet_status_id <chr>, retweet_text <chr>,
## #   retweet_created_at <dttm>, retweet_source <chr>,
## #   retweet_favorite_count <int>, retweet_retweet_count <int>,
## #   retweet_user_id <chr>, retweet_screen_name <chr>, retweet_name <chr>,
## #   retweet_followers_count <int>, retweet_friends_count <int>,
## #   retweet_statuses_count <int>, retweet_location <chr>,
## #   retweet_description <chr>, retweet_verified <lgl>, place_url <chr>,
## #   place_name <chr>, place_full_name <chr>, place_type <chr>,
## #   country <chr>, country_code <chr>, geo_coords <list>,
## #   coords_coords <list>, bbox_coords <list>, status_url <chr>,
## #   name <chr>, location <chr>, description <chr>, url <lgl>,
## #   protected <lgl>, followers_count <int>, friends_count <int>,
## #   listed_count <int>, statuses_count <int>, favourites_count <int>,
## #   account_created_at <dttm>, verified <lgl>, profile_url <chr>,
## #   profile_expanded_url <chr>, account_lang <chr>,
## #   profile_banner_url <chr>, profile_background_url <chr>,
## #   profile_image_url <chr>

Timeline Prabowo Subianto

# lihat timeline jokowi dan ambil tweet sebanyak 10000
# atau sebanyak-banyaknya
timeline_prabowo <- get_timeline(user = "prabowo", n = 10000)

# lihat isi dari timeline_jokowi
timeline_prabowo
## # A tibble: 3,233 x 88
##    user_id status_id created_at          screen_name text  source
##    <chr>   <chr>     <dttm>              <chr>       <chr> <chr> 
##  1 405807… 11005897… 2019-02-27 02:53:31 prabowo     Bagi… Twitt…
##  2 405807… 11005877… 2019-02-27 02:45:32 prabowo     Agen… Twitt…
##  3 405807… 11005858… 2019-02-27 02:38:00 prabowo     @Tit… Twitt…
##  4 405807… 11005852… 2019-02-27 02:35:46 prabowo     @as_… Twitt…
##  5 405807… 11005839… 2019-02-27 02:30:24 prabowo     "Iba… Twitt…
##  6 405807… 11005818… 2019-02-27 02:22:06 prabowo     Sela… Twitt…
##  7 405807… 10956199… 2019-02-13 09:45:21 prabowo     @zai… Twitt…
##  8 405807… 10956191… 2019-02-13 09:42:26 prabowo     @zai… Twitt…
##  9 405807… 10956175… 2019-02-13 09:35:49 prabowo     @Ram… Twitt…
## 10 405807… 10956067… 2019-02-13 08:53:08 prabowo     Hati… Twitt…
## # … with 3,223 more rows, and 82 more variables: display_text_width <dbl>,
## #   reply_to_status_id <chr>, reply_to_user_id <chr>,
## #   reply_to_screen_name <chr>, is_quote <lgl>, is_retweet <lgl>,
## #   favorite_count <int>, retweet_count <int>, hashtags <list>,
## #   symbols <list>, urls_url <list>, urls_t.co <list>,
## #   urls_expanded_url <list>, media_url <list>, media_t.co <list>,
## #   media_expanded_url <list>, media_type <list>, ext_media_url <list>,
## #   ext_media_t.co <list>, ext_media_expanded_url <list>,
## #   ext_media_type <chr>, mentions_user_id <list>,
## #   mentions_screen_name <list>, lang <chr>, quoted_status_id <chr>,
## #   quoted_text <chr>, quoted_created_at <dttm>, quoted_source <chr>,
## #   quoted_favorite_count <int>, quoted_retweet_count <int>,
## #   quoted_user_id <chr>, quoted_screen_name <chr>, quoted_name <chr>,
## #   quoted_followers_count <int>, quoted_friends_count <int>,
## #   quoted_statuses_count <int>, quoted_location <chr>,
## #   quoted_description <chr>, quoted_verified <lgl>,
## #   retweet_status_id <chr>, retweet_text <chr>,
## #   retweet_created_at <dttm>, retweet_source <chr>,
## #   retweet_favorite_count <int>, retweet_retweet_count <int>,
## #   retweet_user_id <chr>, retweet_screen_name <chr>, retweet_name <chr>,
## #   retweet_followers_count <int>, retweet_friends_count <int>,
## #   retweet_statuses_count <int>, retweet_location <chr>,
## #   retweet_description <chr>, retweet_verified <lgl>, place_url <chr>,
## #   place_name <chr>, place_full_name <chr>, place_type <chr>,
## #   country <chr>, country_code <chr>, geo_coords <list>,
## #   coords_coords <list>, bbox_coords <list>, status_url <chr>,
## #   name <chr>, location <chr>, description <chr>, url <chr>,
## #   protected <lgl>, followers_count <int>, friends_count <int>,
## #   listed_count <int>, statuses_count <int>, favourites_count <int>,
## #   account_created_at <dttm>, verified <lgl>, profile_url <chr>,
## #   profile_expanded_url <chr>, account_lang <chr>,
## #   profile_banner_url <chr>, profile_background_url <chr>,
## #   profile_image_url <chr>

Selanjutnya kita akan menganalisa frekuensi tweet per minggunya:

Frekuensi Tweet Joko Widodo Per Minggu

timeline_jokowi %>% 
  ts_plot("weeks") + 
  theme_light() +
  labs(
    x = "waktu dalam minggu",
    y = "frekuensi tweets",
    title = "Frekuensi Jumlah Tweet Joko Widodo",
    subtitle = "dihitung per minggu",
    caption = paste0("Sumber: Twitter, tanggal: ", Sys.Date())
  )

Frekuensi Tweet Prabowo Subianto Per Minggu

timeline_prabowo %>% 
  ts_plot("weeks") + 
  theme_light() +
  labs(
    x = "waktu dalam minggu",
    y = "frekuensi tweets",
    title = "Frekuensi Jumlah Tweet Prabowo Subianto",
    subtitle = "dihitung per minggu",
    caption = paste0("Sumber: Twitter, tanggal: ", Sys.Date())
  )

Selanjutnya kita akan mencari tahu seberapa banyak pengikut (follower) dari masing-masing capres dengan menggunakan fungsi lookup_users() lalu memplot hasilnya menggunakan ggplot2:

# Ambil informasi jokowi
bio_jokowi <- lookup_users("jokowi")
# Ambil informasi prabowo
bio_prabowo <- lookup_users("prabowo")

# Buat vektor jumlah_follower
jumlah_follower <- c(bio_jokowi$followers_count, bio_prabowo$followers_count)
# Buat vektor nama_kandidat
nama_kandidat <- c("Joko Widodo", "Prabowo Subianto")

data_frame(nama_kandidat, jumlah_follower) %>% 
  ggplot(aes(x = nama_kandidat, 
             y = jumlah_follower, 
             fill = nama_kandidat)) +
  geom_col() +
  geom_text(aes(label = format(jumlah_follower, 
                               big.mark = ",")), 
            vjust = 1.6, color = "white") +
  theme_minimal() +
  labs(
    x = "Nama Kandidat",
    y = "Jumlah Follower",
    title = "Jumlah Follower Masing-Masing Capres",
    subtitle = paste0("Terhitung, ", Sys.Date()),
    caption = "Sumber: Twitter") + 
  theme(legend.title = element_blank(),legend.position = "none")
## Warning: `data_frame()` is deprecated, use `tibble()`.
## This warning is displayed once per session.