Topic modelling merupakan suatu metode untuk mengklasifikasikan data teks menjadi beberapa bagian topik tertentu serta menemukan apakah suatu teks mempunyai hubungan pada teks lainnya dalam satu topik

0.1 Memuat Libraries

Proses pertama yaitu kita membutuhkan beberapa library untuk proses pengumpulan data Twitter, pembersihan, dan modelisasi.

library(rtweet)
library(quanteda)
## Warning in .recacheSubclasses(def@className, def, env): undefined subclass
## "packedMatrix" of class "replValueSp"; definition not updated
## Warning in .recacheSubclasses(def@className, def, env): undefined subclass
## "packedMatrix" of class "mMatrix"; definition not updated
## Package version: 3.2.1
## Unicode version: 13.0
## ICU version: 69.1
## Parallel computing: 4 of 4 threads used.
## See https://quanteda.io for tutorials and examples.
library(tm)
## Loading required package: NLP
## 
## Attaching package: 'NLP'
## The following objects are masked from 'package:quanteda':
## 
##     meta, meta<-
## 
## Attaching package: 'tm'
## The following object is masked from 'package:quanteda':
## 
##     stopwords
library(tidyverse)
## -- Attaching packages --------------------------------------- tidyverse 1.3.1 --
## v ggplot2 3.3.6     v purrr   0.3.4
## v tibble  3.1.7     v dplyr   1.0.9
## v tidyr   1.2.0     v stringr 1.4.0
## v readr   2.1.2     v forcats 0.5.1
## -- Conflicts ------------------------------------------ tidyverse_conflicts() --
## x ggplot2::annotate() masks NLP::annotate()
## x dplyr::filter()     masks stats::filter()
## x purrr::flatten()    masks rtweet::flatten()
## x dplyr::lag()        masks stats::lag()
library(xml2)
library(katadasaR)
library(quanteda.textstats)
library(quanteda.corpora)
library(seededlda)
## Loading required package: proxyC
## 
## Attaching package: 'proxyC'
## The following object is masked from 'package:stats':
## 
##     dist
## 
## Attaching package: 'seededlda'
## The following object is masked from 'package:stats':
## 
##     terms
library(wordcloud2)
library(knitr)
library(rmarkdown)

0.2 Twitter

0.2.1 Authoriasasi API

Authorisasi API (Application Programming Interface) dilakukan agar bisa mendapatkan izin dari Twitter terhadap akses data Twitter seperti twit, nama pengguna, detail waktu twit dan sebagainya.

# insert your API key
api_key <- "XXXXXXXXXXXXXX"
api_secret_key <- "XXXXXXXXXXXXXX"
access_token <- "XXXXXXXXXXXXXX"
access_token_secret <- "XXXXXXXXXXXXXX"

## insert your app name
token <- create_token(
  app = "XXXXXXXXXXXXXX",
  consumer_key = api_key,
  consumer_secret = api_secret_key)
## save token to home directory
path_to_token <- file.path(path.expand("~"), ".twitter_token.rds")

saveRDS(token, path_to_token)
## create env variable TWITTER_PAT (with path to saved token)
env_var <- paste0("TWITTER_PAT=", path_to_token)

## save as .Renviron file (or append if the file already exists)
cat(env_var, file = file.path(path.expand("~"), ".Renviron"), fill = TRUE, append = TRUE)

## refresh .Renviron variables
readRenviron("~/.Renviron")

Setelah autorisasi selesai dilakukan, maka proses pengumpulan data bisa dilakukan. Apabila selanjutnya ingin melakukan pengumpulan data Twitter tidak diperlukan autorisasi lagi, cukup untuk memuat library-nya (rtweet) saja.

0.2.2 Pengumpulan Data Twitter

Dikarenakan topik yang akan saya gunakan adalah topik tentang intoleransi maka kata kunci yang saya gunakan adalah “intoleransi”, data yang saya coba ambil adalah 2000 twit.

# collect twitter data
df <- search_tweets(
  "intoleransi", n = 2000, include_rts = TRUE, lang = "id", type = "mixed"
)

Data Twitter yang dikoleksi berbentuk dataframe dan perlu dilakukan perataan agar bisa disimpan sebagai file CSV (Comma Separated Values)

#flaten the tweets
flatenned_tweets <- data.frame(lapply(df, as.character), stringsAsFactors = FALSE)

Lalu data frame yang telah diratakan disimpan sebagai file CSV

write.csv(x = flatenned_tweets,
          file = '~/Projects/Training/Datasets/raw_tweets.csv',
          row.names = FALSE)

0.3 Pembersihan

Proses pembersihan dilakukan agar konten yang memang tidak dibutuhkan dihilangkan. Proses pembersihan sendiri melibatkan beberapa tahap.

0.3.1 Penghilangan Duplikat

Twit yang mempunyai duplikasi akan dihilangkan. Pada proses ini juga dilakukan pemilihan kolom yang berisi twit sedangkan kolom lain yang berisikan username dan sebagainya tidak digunakan.

# remove duplicate and select single column for only text
df_twitter <- df %>% 
  distinct(text, .keep_all = FALSE)

0.3.2 Transfromasi Dataframe menjadi Korpus

Jika sebelumnya saya mempunyai dataframe, maka saya akan mengubahnya menjadi korpus volatile. Tipe data korpus volatile digunakan agar proses pembersihan teks lebih mudah.

# transform data frame into a corpus
corpus_twitter <- VCorpus(VectorSource(df_twitter$text))
inspect(corpus_twitter[[1]])
## <<PlainTextDocument>>
## Metadata:  7
## Content:  chars: 284
## 
## Manusia makin hari makin sensitif aja, yg dulu ga pernah mslh skrg malah jd mslh.Bukannya mengecilkan mslh bsar &amp; menghilangkan mslh kcil, malah digedein. Disapa salah, ga disapa salah. Blngnya bertoleransi tp nyatanya intoleransi. Blngnya cinta damai tp perang semakin ramai <U+0001F468><U+200D><U+0001F3A4><U+0001F643>

0.3.3 Pembuatan Fungsi

Fungsi merupakan kumpulan argumen yang digunakan untuk melakukan tugas tertentu. Tugas yang dilakukan oleh fungsi yang akan dibuat adalah tugas pembersihan.

0.3.3.1 Pembersihan URL

Twit seperti berita biasanya mengandung URL atau link, maka dari itu dibuatlah fungsi remove_url untuk membersihkannya.

remove_url <- function(x){
  gsub("http[^[:space:]]*", "", x)
}

0.3.3.2 Pembersihan Tanda Mention

Tanda @ (at) atau mention juga dibersihkan

remove_mention <- function(x){
  gsub("@\\w+", "", x)
}

0.3.3.3 Pembersihan Carriage Return atau Enter

Carriage return merupakan jarak antar baris yang dihasilkan melalui tombol enter.

remove_carriage <- function(x){
  gsub("[\r\n]", "", x)
}

0.3.3.4 Pembersihan Emotikon

Emotikon adalah tipografi tertentu yang digunakan untuk mengekspresikan diri seperti sedih, senang, bahagia, dan tertawa.

remove_emoticon <- function(x){
  gsub("[^\x01-\x7F]", "", x)
}

0.3.3.5 Pembersihan Invoice

remove_invoice <- function(x){
  gsub("inv/[0-9]+/+[xvi]+/[xvi]+/[0-9]+", "", x, ignore.case = T)
}

0.3.3.6 Unescape HTML

Proses ini merupakan proses untuk mengubah HTML character entity encodings

unescape_html <- function(str) {
  xml2::xml_text(xml2::read_html(paste0("<x>", str, "</x>")))
}

0.3.3.7 To Space

to_space <- content_transformer(function(x, pattern){
  gsub(pattern, " ", x)
})

0.3.3.8 Pembersihan White Space pada Awal dan Akhir Twit

leading_whitespace = function(x){gsub("^[[:space:]]*", "", x)}  ## Remove leading whitespaces
trailing_whitespace = function(x){gsub("[[:space:]]*$", "", x)}  ## Remove trailing whitespaces

0.3.3.9 Stemming

Proses ini mencari kata dasar dari sebuah kata, misal kata “mengubah” menjadi “ubah”

stemming <- function(x){
  paste(sapply(unlist(str_split(x,'\\s+')),katadasar),collapse = " ")
}

0.3.3.10 Pengkoreksian Ejaan

Bahasa Indonesia mempunyai ejaan yang non-formal ataupun singkatan yang biasa digunakan di internet.

spelling_lexicon <- read.csv(file = "colloquial.csv", header = T, stringsAsFactors = F)

spell_correction <- content_transformer(function(x, dict){
  words = sapply(unlist(str_split(x, "\\s+")),function(x){
    if(is.na(spelling_lexicon[match(x, dict$slang),"formal"])){
      x = x
    } else{
      x = spelling_lexicon[match(x, dict$slang),"formal"]
    }
  })
  x = paste(words, collapse = " ")
})

0.3.3.11 Probabilitas Topik LDA

get_doc_topic_probs <- function(slda) {
  out <- slda$theta %>% 
    as_tibble(rownames = "doc_id")
  return(out)
}

get_word_topic_probs <- function(slda) {
  out <- slda$phi %>% 
    as_tibble(rownames = "topic") %>%
    pivot_longer(cols = !matches("topic"), names_to = "token", values_to = "prob")
  return(out)
}

0.3.4 Pembersihan melalui Korpus

Setelah setiap fungsi berhasil dibuat saatnya melakukan proses pembersihan

ct_clean <- tm_map(corpus_twitter, content_transformer(tolower))
ct_clean <- tm_map(ct_clean, content_transformer(remove_url))
ct_clean <- tm_map(ct_clean, content_transformer(unescape_html))
ct_clean <- tm_map(ct_clean, content_transformer(remove_mention))
ct_clean <- tm_map(ct_clean, content_transformer(remove_carriage))
ct_clean <- tm_map(ct_clean, content_transformer(remove_emoticon))
ct_clean <- tm_map(ct_clean, content_transformer(remove_invoice))
ct_clean <- tm_map(ct_clean, to_space, "[[:punct:]]")
ct_clean <- tm_map(ct_clean, to_space, "[[:digit:]]")
ct_clean <- tm_map(ct_clean, stripWhitespace)
ct_clean <- tm_map(ct_clean, spell_correction, spelling_lexicon)
ct_clean <- tm_map(ct_clean, content_transformer(stemming))
ct_clean <- tm_map(ct_clean, content_transformer(trailing_whitespace))
ct_clean <- tm_map(ct_clean, content_transformer(leading_whitespace))

inspect(ct_clean[[1]])
## <<PlainTextDocument>>
## Metadata:  7
## Content:  chars: 273
## 
## manusia makin hari makin sensitif saja yang dulu enggak pernah masalah sekarang malah jadi masalah bukan kecil masalah bsar hilang masalah kecil malah digedein sapa salah enggak sapa salah blngnya toleransi tapi nyata intoleransi blngnya cinta damai tapi perang makin ramai

0.3.5 Pembersihan Stopwords

Stopwords merupakan kata-kata yang sering muncul dalam sebuah teks dan tidak mempunyai arti penting.

0.3.5.1 Impor Stopwords

Hal yang pertama harus dilakukan adalah mengimpor file txt yang berisikan stopwords

stopwords_id <- as.character(read.table(file = "stopwords.txt"))
## Warning in read.table(file = "stopwords.txt"): incomplete final line found by
## readTableHeader on 'stopwords.txt'

0.3.5.2 Pengaplikasian Data Stopwords terhadap Korpus

ct_stopwords <- tm_map(ct_clean, removeWords, stopwords_id)
inspect(corpus_twitter[[1]])
## <<PlainTextDocument>>
## Metadata:  7
## Content:  chars: 284
## 
## Manusia makin hari makin sensitif aja, yg dulu ga pernah mslh skrg malah jd mslh.Bukannya mengecilkan mslh bsar &amp; menghilangkan mslh kcil, malah digedein. Disapa salah, ga disapa salah. Blngnya bertoleransi tp nyatanya intoleransi. Blngnya cinta damai tp perang semakin ramai <U+0001F468><U+200D><U+0001F3A4><U+0001F643>

0.4 Topic Modelling

Setelah proses pembersihan selesai dilakukan, maka selanjutnya adalah proses modelisasi topik menjadi 10 topik.

0.4.1 Mengubah Korpus menjadi Document-Feature Matrix

Namun sebelum modelisasi dilakukan data corpus diubah menjadi DFM (Document-Feature Matrix). DFM sendiri merupakan matriks yang berisi dokumen yang berupa kata-kata pada baris dan fitur pada kolom.

0.4.1.1 Korpus Volatile ke Dataframe

dft_clean <- data.frame(text = sapply(ct_stopwords, as.character),
                      stringsAsFactors = FALSE)

0.4.1.2 Dataframe menjadi Korpus Quanteda

ct_quanteda <- corpus(dft_clean, text_field = "text")

0.4.1.3 Korpus Quanteda menjadi Tokens

Tokens sendiri merupakan memisahkan data korpus teks menjadi per kata

tt_quanteda <- tokens(ct_quanteda)

0.4.1.4 Tokens menjadi Document-Feature Matrix

dfm_twitter <- dfm(tt_quanteda) %>%
  dfm_trim(min_termfreq = 0.8, termfreq_type = "quantile",
           max_docfreq = 0.1, docfreq_type = "prop")

head(dfm_twitter)
## Document-feature matrix of: 6 documents, 476 features (98.42% sparse) and 0 docvars.
##     features
## docs manusia hilang salah toleransi nyata cinta damai gus cegah bentuk
##    1       1      1     2         1     1     1     1   0     0      0
##    2       0      0     0         0     0     0     0   1     1      1
##    3       0      0     0         0     0     0     0   0     0      0
##    4       0      0     0         0     0     0     0   0     0      0
##    5       0      0     0         0     0     0     0   0     0      0
##    6       0      0     0         0     0     0     0   1     1      1
## [ reached max_nfeat ... 466 more features ]

0.4.2 Topic Modelling: Latent Dirichlet Allocation

text_model_lda_twitter <- textmodel_lda(dfm_twitter, k = 10)
paged_table(as.data.frame(terms(text_model_lda_twitter, 10)))
paged_table(get_doc_topic_probs(text_model_lda_twitter))
paged_table(get_word_topic_probs(text_model_lda_twitter))

0.4.3 Topic Modelling: Seeded LDA

Seeded-LDA memberi keleluasaan pada pengguna untuk memilih topik yang akan dimodelkan dengan menggunakan beberapa kata kunci yang berhubungan

0.4.3.1 Kamus Kecil berisi Kata Kunci yang Berhubungan pada setiap Topik

dictionary_lda <- dictionary(file = "topik.yml")
## Warning in readLines(con, warn = readLines.warn): incomplete final line found on
## 'topik.yml'
print(dictionary_lda)
## Dictionary object with 5 key entries.
## - [politik]:
##   - anies, jokowi, pks, hti
## - [agama]:
##   - islam, non*, kafir
## - [islamisme]:
##   - khilafah, hti
## - [slur]:
##   - kadrun, cebong
## - [nasionalisme]:
##   - nkri, bangsa, indonesia

0.4.3.2 Modelisasi Seeded LDA

text_model_slda_twitter <- textmodel_seededlda(dfm_twitter, dictionary = dictionary_lda)
paged_table(as.data.frame(terms(text_model_slda_twitter, 10)))
paged_table(as.data.frame(get_doc_topic_probs(text_model_slda_twitter)))
paged_table(as.data.frame(get_word_topic_probs(text_model_slda_twitter)))

0.5 Wordcloud

word_frequency <- as.data.frame(textstat_frequency(dfm_twitter, n = 200))
wordcloud2(word_frequency)

0.6 Referensi

[1]         M. Kearney, “rtweet: Collecting and analyzing Twitter data,” J. Open Source Softw., vol. 4, no. 42, p. 1829, 2019, doi: 10.21105/joss.01829.

[2]         K. Benoit et al., “quanteda: An R package for the quantitative analysis of textual data,” J. Open Source Softw., vol. 3, no. 30, p. 774, Oct. 2018, doi: 10.21105/joss.00774.

[3]         I. Feinerer, K. Hornik, and D. Meyer, “Text Mining Infrastructure in R,” J. Stat. Softw., vol. 25, no. 5, 2008, doi: 10.18637/jss.v025.i05.

[4]         N. A. Setiabudi, “katadasaR: Function for word stemming Bahasa Indonesia Setiabudi, Nur Andi,” 2015.

[5]         K. Benoit, “quanteda.corpora: A collection of corpora for quanteda,” 2020, [Online]. Available: http://github.com/quanteda/quanteda.corpora

[6]         K. Benoit, K. Watanabe, H. Wang, J. W. Lua, and J. Kuha, “Package ‘quanteda. textstats,’” Res. Bull., vol. 27, no. 2, pp. 37–54, 2021.

[7]         K. Watanabe and P. Xuan-Hieu, “Package ‘seededla,’” 2022.

[8]         D. Lang, G. Chien, and D. Lang, “Package ‘wordcloud2,’” 2018.

[9]         Y. Xie, “knitr: A Comprehensive Tool for Reproducible Research in R,” in Implementing Reproducible Computational Research, V. Stodden, F. Leisch, and R. D. Peng, Eds. Chapman and Hall/CRC, 2014.

[10]      Y. Xie, C. Dervieux, and E. Riederer, R Markdown Cookbook. Chapman and Hall/CRC, 2020.

[11]      N. Aliyah Salsabila, Y. Ardhito Winatmoko, A. Akbar Septiandri, and A. Jamal, “Colloquial Indonesian Lexicon,” in 2018 International Conference on Asian Language Processing (IALP), Nov. 2018, pp. 226–229. doi: 10.1109/IALP.2018.8629151.