Rpubs ini mendemonstrasikan penerapan analisis mikro pada suatu teks dengan menggunakan bahasa pemrograman R sebagaimana diuraikan buku “Text Analysis with R for Students of Literature”.
Bahan teks yang dijadikan acuan adalah: Cerpen Perkara Kebatilan yang Agung karya Sasmito Yudha Husada.
Cerpen tersebut merupakan kiriman partisipasi saya untuk Lomba Cerpen Fantasi LCDP 2018 yang bertemakan “The Greatest Lie”.
batil.v <- scan("data/plainText/syh_perkara_kebatilan.txt",
what = "character",
sep = "\n",
encoding = "UTF-8")
Gabungkan setiap linebreak menjadi 1 vektor, dengan spasi sebagai pemisah.
nolb.batil.v <- paste(batil.v, collapse = " ")
length(nolb.batil.v)
## [1] 1
Ubah semua huruf menjadi huruf kecil.
lower.nolb.batil.v <- tolower(nolb.batil.v)
Pecah setiap kata menjadi list.
batil.words.l <- strsplit(lower.nolb.batil.v, "\\W")
Lalu ubah list menjadi vector.
batil.words.v <- unlist(batil.words.l)
Cari indeks kata yang tidak kosong.
not.blank.bwv <- which(batil.words.v != "")
Gunakan indeks untuk memilih kata pada teks.
batil.words.v <- batil.words.v[not.blank.bwv]
Mari kita lihat seberapa luas perbendaharaan kata pada teks.
length(unique(batil.words.v))
## [1] 1425
Kata-kata apa yang sering digunakan? Mari periksa tabel frekuensi kata pada teks.
tabel.frek.batil <- table(batil.words.v)
sorted.bf <- sort(tabel.frek.batil, decreasing = T)
Menurut hukum Zipf, kata paling sering muncul kedua pada sebuah corpus berjumlah setengah dibanding kata yang paling sering muncul pertama.
Mari kita lihat.
sorted.bf[1:10]
## batil.words.v
## yang dan itu ia kapten di dengan dari ada tak
## 108 71 56 53 53 44 37 33 28 27
plot(sorted.bf[1:10])
Sepertinya tidak plek mengikuti hukum tersebut, namun juga tidak bisa dibilang melenceng jauh.
Mari membuat relatif frekuensi kata pada teks.
rf.bf <- 100 * (sorted.bf / sum(sorted.bf))
plot(rf.bf[1:10],
type = "b",
xlab = "Sepuluh Kata Tersering di Cerpen Perkara Kebatilan yang Agung",
ylab = "Persentase dari Teks",
xaxt = "n")
axis(1,
1:10,
labels = names(rf.bf[1:10]))
Buat urutan angka sepanjang teks untuk sumbu x.
bl <- length(batil.words.v)
num.batil <- seq(1:bl)
Cari indeks lokasi kata tertentu.
batil.in.batil <- which(batil.words.v == "batil")
Buat urutan data kosong (NA) untuk inisiasi ruang sumbu y.
batil.count <- rep(NA, length(num.batil))
Isi data kosong pada indeks lokasi kata yang ditentukan untuk mengisi sumbu y.
batil.count[batil.in.batil] <- 1
Ilustrasi sebaran kata target pada teks.
plot(batil.count,
main = "Ilustrasi Sebaran Kata \"Batil\" pada Perkara Kebatilan yang Agung",
xlab = "Selang Waktu Teks",
ylab = "\"Batil\"",
type = "h",
ylim = c(0,1),
yaxt = "n")
Mari tinjau sebaran kata lain.
agung.in.batil <- which(batil.words.v == "agung")
batil.count[agung.in.batil] <- 1
plot(batil.count,
main = "Ilustrasi Sebaran Kata \"Agung\" pada Perkara Kebatilan yang Agung",
xlab = "Selang Waktu Teks",
ylab = "\"Kapten\"",
type = "h",
ylim = c(0,1),
yaxt = "n")
Mari menentukan indeks pemecah cerita dengan regex.
i.batil.break.v <- grep("\\[\\.\\d\\]",
batil.v)
Cek kebenaran indeks.
batil.v[i.batil.break.v]
## [1] "[.1]" "[.2]" "[.3]" "[.4]"
Untuk keperluan analisis pada pecahan teks dengan indeks nanti, kita dapat menggunakan awal pemecah berikutnya yang dikurangi angka 1. Agar pecahan cerita terakhir dapat dideteksi, kita perlu tambahkan 1 pemecah penanda bagian akhir.
batil.v <- c(batil.v, "END")
last.break.batil <- length(batil.v)
i.batil.break.v <- c(i.batil.break.v, last.break.batil)
Buat ruang untuk pemrosesan analisis tiap pecahan teks dalam bentuk list
break.raw.batil.l <- list()
break.relative.freq.batil.l <- list()
Buat fungsi untuk analisis tiap pecahan teks.
for(n in 1:length(i.batil.break.v)){
if(n != length(i.batil.break.v)){
break.title <- batil.v[i.batil.break.v[n]]
break.start <- i.batil.break.v[n] + 1
break.end <- i.batil.break.v[n + 1] - 1
break.lines.v <- batil.v[break.start:break.end]
break.words.v <- tolower(paste(break.lines.v, collapse = " "))
break.words.l <- strsplit(break.words.v, "\\W")
break.token.word.v <- unlist(break.words.l)
break.token.word.v <- break.token.word.v[which(break.token.word.v != "")]
break.frek.t <- sort(table(break.token.word.v),decreasing = T)
break.raw.batil.l[[break.title]] <- break.frek.t
break.frek.rel.t <- (break.frek.t/sum(break.frek.t)) * 100
break.relative.freq.batil.l[[break.title]] <- break.frek.rel.t
}
}
Cek hasil analisis tiap pecahan teks.
break.raw.batil.l[[1]][1:10] # 10 kata terbanyak pada pecahan pertama
## break.token.word.v
## itu yang anak dan di ibu tanah agung dari ke
## 26 19 16 16 14 14 10 8 8 8
break.raw.batil.l[[2]][1:10] # 10 kata terbanyak pada pecahan kedua
## break.token.word.v
## yang kapten dan dengan di ia ada dari paman tak
## 32 31 19 17 16 16 14 11 11 11
break.raw.batil.l[[3]][1:10] # 10 kata terbanyak pada pecahan ketiga
## break.token.word.v
## yang dan ia itu jiwa dalam di juga kapten tak
## 25 18 16 11 11 10 9 9 9 9
break.relative.freq.batil.l[[1]][1:10] # 10 kata terbanyak pada pecahan pertama
## break.token.word.v
## itu yang anak dan di ibu tanah agung
## 3.471295 2.536716 2.136182 2.136182 1.869159 1.869159 1.335113 1.068091
## dari ke
## 1.068091 1.068091
break.relative.freq.batil.l[[2]][1:10] # 10 kata terbanyak pada pecahan kedua
## break.token.word.v
## yang kapten dan dengan di ia ada dari
## 2.946593 2.854512 1.749540 1.565378 1.473297 1.473297 1.289134 1.012891
## paman tak
## 1.012891 1.012891
break.relative.freq.batil.l[[3]][1:10] # 10 kata terbanyak pada pecahan ketiga
## break.token.word.v
## yang dan ia itu jiwa dalam di juga
## 3.209243 2.310655 2.053915 1.412067 1.412067 1.283697 1.155327 1.155327
## kapten tak
## 1.155327 1.155327
Gunakan list yang sudah dibuat untuk mencari frekuensi kata tertentu.
break.raw.batil.l[[1]]["batil"]
## batil
## 3
break.raw.batil.l[[2]]["batil"]
## batil
## 5
break.raw.batil.l[[3]]["batil"]
## batil
## 7
break.relative.freq.batil.l[[1]]["agung"]
## agung
## 1.068091
break.relative.freq.batil.l[[2]]["agung"]
## agung
## 0.6445672
break.relative.freq.batil.l[[3]]["agung"]
## agung
## 0.2567394
Untuk menerapkan pencarian kata pada semua pecahan teks, kita gunakan lapply.
raw.batil.l <- lapply(break.raw.batil.l,
"[",
"batil")
raw.agung.l <- lapply(break.raw.batil.l,
"[",
"agung")
rf.batil.l <- lapply(break.relative.freq.batil.l,
"[",
"batil")
rf.agung.l <- lapply(break.relative.freq.batil.l,
"[",
"agung")
Dengan do.call dan rbind, kita ubah hasil pencarian sebelumnya menjadi matrix.
raw.batil.m <- do.call(rbind,
raw.batil.l)
raw.agung.m <- do.call(rbind,
raw.agung.l)
rf.batil.m <- do.call(rbind,
rf.batil.l)
rf.agung.m <- do.call(rbind,
rf.agung.l)
raw.batil.m[1:5]
## [1] 3 5 7 10 NA
raw.agung.m[1:5]
## [1] 8 7 2 8 NA
rf.batil.m[1:5]
## [1] 0.4005340 0.4604052 0.8985879 1.2836970 NA
rf.agung.m[1:5]
## [1] 1.0680908 0.6445672 0.2567394 1.0269576 NA
Kemudian kita bisa kombinasikan pilihan kata untuk ditelusuri lebih lanjut.
batil.v <- rf.batil.m[,1]
agung.v <- rf.agung.m[,1]
batil.agung.m <- cbind(batil.v, agung.v)
colnames(batil.agung.m) <- c("Batil", "Agung")
Ilustrasikan distribusi kata pada tiap pemecah kata dengan diagram.
barplot(batil.agung.m,
beside = T,
col = "grey")
Sebelum melakukan perhitungan korelasi. Kita harus mengganti NA dengan angka 0.
batil.agung.m[which(is.na(batil.agung.m))] <- 0
cor(batil.agung.m[,1], batil.agung.m[,2])
## [1] -0.01289829
Kata yang dipilih memiliki hubungan berbanding terbalik yang lemah. Namun apakah korelasi ini benar adanya? Bagaimana dengan kata-kata lain dalam teks yang berfungsi sebagai kata pengganti yang merujuk pada makna kata yang sama? Untuk menguji hal ini, kita perlu lakukan randomisasi.
Ubah dulu matriks menjadi data frame.
batil.agung.df <- as.data.frame(batil.agung.m)
Randomisasi.
set.seed(1)
randomed.batil1 <- sample(batil.agung.df$Batil)
randomed.agung1 <- sample(batil.agung.df$Agung)
set.seed(2)
randomed.batil2 <- sample(batil.agung.df$Batil)
randomed.agung2 <- sample(batil.agung.df$Agung)
set.seed(3)
randomed.batil3 <- sample(batil.agung.df$Batil)
randomed.agung3 <- sample(batil.agung.df$Agung)
Uji apakah nilai korelasi masih sama dengan sebelumnya?
Nilai korelasi sesudah randomisasi:
cor(randomed.batil1, randomed.agung1)
## [1] -0.01289829
cor(randomed.batil2, randomed.agung2)
## [1] 0.07880945
cor(randomed.batil3, randomed.agung3)
## [1] 0.06428399
Bandingkan dengan nilai korelasi sebelum randomisasi:
Sebelum randomisasi.
cor(batil.agung.m[,1], batil.agung.m[,2])
## [1] -0.01289829
Nilainya sungguh tidak tentu, namun 3 kali randomisasi belum cukup. Mari kita coba dengan 10.000 kali randomisasi.
batil.agung.10k.cor <- NULL
for(i in 1:10000){
batil.agung.10k.cor<- c(batil.agung.10k.cor,
cor(sample(batil.agung.df$Batil), (batil.agung.df$Agung)))
}
min(batil.agung.10k.cor )
## [1] -0.9991016
max(batil.agung.10k.cor )
## [1] 0.8793164
range(batil.agung.10k.cor )
## [1] -0.9991016 0.8793164
mean(batil.agung.10k.cor )
## [1] -0.003952307
sd(batil.agung.10k.cor )
## [1] 0.5791403
Korelasi batil dan agung pada data awal memiliki nilai -0.01289829. Namun setelah dilakukan 10.000 kali randomisasi, kita dapatkan rata-rata korelasi -0.005962922 dengan standar deviasi 0.5736525.
hist(batil.agung.10k.cor,
breaks = 100,
xlab = "Korelasi \"Batil\" dan \"Agung\"",
main = "Korelasi dari 10.000 Randomisasi \"Batil\" dan \"Agung\"")
abline(v = -0.01289829, col="red", lwd=3, lty=2)
Sebaran korelasi Batil dan Agung menunjukan keseimbangan antara tidak adanya korelasi dan adanya korelasi positif/negatif. Dengan standar deviasi yang cukup besar 0.5736525, hubungan batil dan agung ini begitu tidak stabil, antara ada dan tiada, antara sejalan dan berlawanan.
Terlepas dari analisis di atas, sebagai penulis teks tersebut, saya berpendapat distribusi korelasi itu bisa dianggap sebagai karikatur hubungan kata kunci Batil dan Agung sebagaimana tersurat dalam cerpen itu sendiri: narasi kebalikan konsep problem of evil menjadi problem of good. Segala dalih yang digunakan untuk menepis konsekuensi dari bukti-bukti kebatilan dunia, dapat dijungkirbalikkan pada bukti-bukti keagungan dunia untuk menatap spekulasi konsekuensi yang kelam.