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
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)
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)
Proses pembersihan dilakukan agar konten yang memang tidak dibutuhkan dihilangkan. Proses pembersihan sendiri melibatkan beberapa tahap.
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)
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 & 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>
Fungsi merupakan kumpulan argumen yang digunakan untuk melakukan tugas tertentu. Tugas yang dilakukan oleh fungsi yang akan dibuat adalah tugas pembersihan.
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)
}
Tanda @ (at) atau mention juga dibersihkan
remove_mention <- function(x){
gsub("@\\w+", "", x)
}
Carriage return merupakan jarak antar baris yang dihasilkan melalui tombol enter.
remove_carriage <- function(x){
gsub("[\r\n]", "", x)
}
Emotikon adalah tipografi tertentu yang digunakan untuk mengekspresikan diri seperti sedih, senang, bahagia, dan tertawa.
remove_emoticon <- function(x){
gsub("[^\x01-\x7F]", "", x)
}
remove_invoice <- function(x){
gsub("inv/[0-9]+/+[xvi]+/[xvi]+/[0-9]+", "", x, ignore.case = T)
}
Proses ini merupakan proses untuk mengubah HTML character entity encodings
unescape_html <- function(str) {
xml2::xml_text(xml2::read_html(paste0("<x>", str, "</x>")))
}
to_space <- content_transformer(function(x, pattern){
gsub(pattern, " ", x)
})
leading_whitespace = function(x){gsub("^[[:space:]]*", "", x)} ## Remove leading whitespaces
trailing_whitespace = function(x){gsub("[[:space:]]*$", "", x)} ## Remove trailing whitespaces
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 = " ")
}
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 = " ")
})
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)
}
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
Stopwords merupakan kata-kata yang sering muncul dalam sebuah teks dan tidak mempunyai arti penting.
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'
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 & 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>
Setelah proses pembersihan selesai dilakukan, maka selanjutnya adalah proses modelisasi topik menjadi 10 topik.
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.
dft_clean <- data.frame(text = sapply(ct_stopwords, as.character),
stringsAsFactors = FALSE)
ct_quanteda <- corpus(dft_clean, text_field = "text")
Tokens sendiri merupakan memisahkan data korpus teks menjadi per kata
tt_quanteda <- tokens(ct_quanteda)
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 ]
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))
Seeded-LDA memberi keleluasaan pada pengguna untuk memilih topik yang akan dimodelkan dengan menggunakan beberapa kata kunci yang berhubungan
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
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)))
word_frequency <- as.data.frame(textstat_frequency(dfm_twitter, n = 200))
wordcloud2(word_frequency)
[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.