Platform belanja secara daring adalah salah satu jenis platform yang saat ini paling digemari di seluruh penjuru dunia. Keberadaannya mempermudah masyarakat memudahkan pengguna membeli barang yang diinginkannya tanpa harus membuang biaya dan waktu menghampiri toko penjual. Kualitas dan kuantitas barang belanja adalah hal yang utama. Akan tetapi, tidak semua platform belanja daring menjamin kualitas maupun kuantitas dagangan yang ditawarkan di dalamnya. Salah satu platform yang tengah dalam perbincangan adalah Temu. Berdasarkan ulasan pengguna, tingkat problematika suatu platform belanja daring dapat ditelusuri lebih lanjut. Analisis statistika dapat digunakan sebagai alat dalam membedah gaya bahasa pada ulasan pengguna dan admin platform. Salah satu metode analisis statistika yang relevan dalam hal ini adalah metode Naive Bayes dalam bidang text mining. Naive Bayes digunakan sebagai metode yang mempertimbangkan kata pada ulasan pengguna dan respon perusahaan. Hasil dari metode ini dapat berupa probabilitas kecocokan antara label dengan suatu kata. Semakin tinggi probabilitas suatu kata untuk dapat masuk ke dalam suatu label, semakin besar peluang suatu kalimat yang mengandung kata tersebut ke dalam label yang relevan.
Mengetahui sejauh mana Naive Bayes dapat diandalkan dalama membedakan gaya bahasa antara pengguna platform Temu dengan gaya bahasa admin penulis balasan perusahaan sebagai respon terhadap ulasan pengguna.
Data dikumpulkan melalui teknik Web Crawling dimana peneliti menelusuri secara langsung Website yang berisi ulasan pengguna platform Temu, yang bersamaan berisi respon balasan dari pihak perusahaan Temu. Seluruh teks dalam web disalin menggunakan kombinasi Ctrl+A dan Ctrl+C lalu dimasukkan ke dalam file Word sebagai tempat penyimpanan.
Penelitian yang dilakukan merupakan penelitian deskriptif-kuantitatif. Penelitian ini menggunakan pendekatan Text Mining berbasis Naive Bayes Classifier untuk mengklasifikasikan teks ulasan pengguna (User Review) dan respons perusahaan (Company Response) dari platform Trustpilot. Tahapan analisis terdiri dari:
Pada tahap ini, data mentah berupa file .txt hasil
scraping Trustpilot dibaca, dibersihkan dari karakter tidak valid, dan
diekstraksi menjadi dua kelompok: ulasan pengguna dan respons admin
perusahaan.
# -------------------------------------------------------
# 1. SETUP ALAMAT FILE
# -------------------------------------------------------
# Sesuaikan path berikut dengan lokasi file di komputer kamu
path_file <- "C:/Users/LENOVO/Downloads/Tugas Semester 6/Text Mining 01/Trustpilot reviews.txt"
# -------------------------------------------------------
# 2. BACA & MEMBERSIHKAN DATA (PRE-PROCESSING AWAL)
# -------------------------------------------------------
raw_lines <- readLines(path_file, encoding = "UTF-8", warn = FALSE)
raw_lines <- iconv(raw_lines, from = "UTF-8", to = "ASCII//TRANSLIT")
raw_lines <- raw_lines[!is.na(raw_lines)]
raw_lines <- gsub("[^[:alnum:][:punct:][:space:]]", "", raw_lines)
# -------------------------------------------------------
# 3. PENAMPUNG HASIL
# -------------------------------------------------------
list_review <- c()
list_respon <- c()
# -------------------------------------------------------
# 4. PROSES EKSTRAKSI
# -------------------------------------------------------
i <- 1
n <- length(raw_lines)
while (i <= n) {
line <- trimws(raw_lines[i])
# --- LOGIKA REVIEW ---
if (grepl("Rated [0-5] out of 5 stars", line)) {
current_idx <- i + 2
konten_review <- ""
while (current_idx <= n &&
!is.na(raw_lines[current_idx]) &&
trimws(raw_lines[current_idx]) != "") {
konten_review <- paste(konten_review, raw_lines[current_idx])
current_idx <- current_idx + 1
}
if (trimws(konten_review) != "") {
list_review <- c(list_review, trimws(konten_review))
}
i <- current_idx
# --- LOGIKA RESPON PERUSAHAAN ---
} else if (grepl("Reply from Temu", line)) {
current_idx <- i + 1
while (current_idx <= n &&
(is.na(raw_lines[current_idx]) ||
trimws(raw_lines[current_idx]) == "" ||
grepl("ago$", trimws(raw_lines[current_idx])))) {
current_idx <- current_idx + 1
}
konten_respon <- ""
while (current_idx <= n &&
!is.na(raw_lines[current_idx]) &&
trimws(raw_lines[current_idx]) != "") {
konten_respon <- paste(konten_respon, raw_lines[current_idx])
current_idx <- current_idx + 1
}
if (trimws(konten_respon) != "") {
list_respon <- c(list_respon, trimws(konten_respon))
}
i <- current_idx
} else {
i <- i + 1
}
}
# -------------------------------------------------------
# 5. PENGGABUNGAN DATA
# -------------------------------------------------------
df_review <- unique(data.frame(Teks = list_review,
Label = "User Review",
stringsAsFactors = FALSE))
df_respon <- data.frame(Teks = list_respon,
Label = "Company Response",
stringsAsFactors = FALSE)
data_final <- rbind(df_review, df_respon)
# -------------------------------------------------------
# 6. SIMPAN FILE UTAMA
# -------------------------------------------------------
write.csv(data_final, "Trustpilot_Clean_Total.csv",
row.names = FALSE, fileEncoding = "UTF-8")
# -------------------------------------------------------
# 7. LAPORAN RINGKAS
# -------------------------------------------------------
cat("--- SELESAI EKSTRAKSI ---\n")## --- SELESAI EKSTRAKSI ---
## Total Review Unik : 168
## Total Respon Admin : 179
## File Tersimpan : Trustpilot_Clean_Total.csv
Pada tahap ini, teks dibersihkan secara menyeluruh menggunakan pipeline standar NLP: lowercase → hapus angka → hapus tanda baca → hapus stopwords → stemming.
library(tm)
library(SnowballC)
# -------------------------------------------------------
# 1. LOAD DATA
# -------------------------------------------------------
path_csv <- "Trustpilot_Clean_Total.csv" # sesuaikan jika perlu
data_kerja <- read.csv(path_csv, stringsAsFactors = FALSE)
cat("Jumlah baris data:", nrow(data_kerja), "\n")## Jumlah baris data: 347
## Distribusi Label:
##
## Company Response User Review
## 179 168
# -------------------------------------------------------
# 2. PREPROCESSING
# -------------------------------------------------------
cat("\nMelakukan pembersihan teks...\n")##
## Melakukan pembersihan teks...
corpus <- VCorpus(VectorSource(data_kerja$Teks))
corpus_clean <- tm_map(corpus, content_transformer(tolower))
corpus_clean <- tm_map(corpus_clean, removeNumbers)
corpus_clean <- tm_map(corpus_clean, removePunctuation)
corpus_clean <- tm_map(corpus_clean, removeWords, stopwords("english"))
corpus_clean <- tm_map(corpus_clean, stripWhitespace)
# -------------------------------------------------------
# 3. STEMMING (Bahasa Inggris via SnowballC)
# -------------------------------------------------------
cat("Melakukan stemming bahasa Inggris...\n")## Melakukan stemming bahasa Inggris...
corpus_clean <- tm_map(corpus_clean, stemDocument)
# Simpan hasil bersih ke kolom baru
data_kerja$teks_bersih <- sapply(corpus_clean, as.character)
# -------------------------------------------------------
# 4. SIMPAN HASIL PREPROCESSING
# -------------------------------------------------------
write.csv(data_kerja, "data_siap_model_trustpilot.csv", row.names = FALSE)
cat("Selesai! File 'data_siap_model_trustpilot.csv' siap digunakan.\n")## Selesai! File 'data_siap_model_trustpilot.csv' siap digunakan.
# Tampilkan contoh sebelum & sesudah
knitr::kable(
head(data_kerja[, c("Label", "Teks", "teks_bersih")], 5),
caption = "Contoh Teks Sebelum dan Sesudah Preprocessing"
)| Label | Teks | teks_bersih |
|---|---|---|
| User Review | Poor delivery process | poor deliveri process |
| User Review | I ordered quite a few items from Temu. Some were good. Some were poor. I just have a problem with the way charges were made to my credit card and difficulties getting refunds. It’s very hard to follow charges to make sure they’re correct. It has caused me distress and I am sorry I did business with them. | order quit item temu good poor just problem way charg made credit card difficulti get refund hard follow charg make sure theyr correct caus distress sorri busi |
| User Review | Terrible experience. Products do not match photos at all, Sewing machine stops working. Bags smaller than palm of hand. Fake “storage” was just a thin fabric pouch. Nose strips with no box or instructions, toxic smell. Also, the shipping invoice showed lower prices than what I actually paid. Customer service had no solution. They told me I would get refund as Temu credit, not real money. And I would have to pay for return shipping myself. Do not recommend. | terribl experi product match photo sew machin stop work bag smaller palm hand fake storag just thin fabric pouch nose strip box instruct toxic smell also ship invoic show lower price actual paid custom servic solut told get refund temu credit real money pay return ship recommend |
| User Review | I’m giving 2 stars because although I still occasionally shop on Temu, I’m becoming increasingly frustrated with the misleading coupons and false promotional offers. | im give star although still occasion shop temu im becom increas frustrat mislead coupon fals promot offer |
| User Review | Haven’t ever had a problem with Temu, and the savings are amazing. Everyone should use this app to buy their stuff! | havent ever problem temu save amaz everyon use app buy stuff |
Model Naive Bayes dilatih untuk membedakan gaya bahasa User Review vs Company Response menggunakan representasi Bernoulli (Ya/Tidak kemunculan kata).
library(e1071)
library(tm)
# -------------------------------------------------------
# 1. LOAD DATA
# -------------------------------------------------------
data_total <- read.csv("data_siap_model_trustpilot.csv", stringsAsFactors = FALSE)
# -------------------------------------------------------
# 2. PROSES DTM (Document Term Matrix)
# -------------------------------------------------------
corpus <- VCorpus(VectorSource(data_total$teks_bersih))
dtm <- DocumentTermMatrix(corpus)
# Buang kata yang sangat jarang (sparse) agar model lebih efisien
dtm <- removeSparseTerms(dtm, 0.98)
cat("Dimensi DTM (dokumen x fitur kata):", dim(dtm), "\n")## Dimensi DTM (dokumen x fitur kata): 347 335
# Konversi ke representasi Bernoulli (Yes/No)
convert_counts <- function(x) { ifelse(x > 0, "Yes", "No") }
dtm_final <- apply(dtm, 2, convert_counts)
# -------------------------------------------------------
# 3. PEMBAGIAN DATA (80% Training, 20% Testing)
# -------------------------------------------------------
set.seed(123)
n_data <- nrow(data_total)
indeks_train <- sample(1:n_data, 0.8 * n_data)
indeks_test <- setdiff(1:n_data, indeks_train)
train_dtm <- dtm_final[indeks_train, ]
train_label <- as.factor(data_total$Label[indeks_train])
test_dtm <- dtm_final[indeks_test, ]
jawaban_asli <- data_total$Label[indeks_test]
cat("Jumlah data training:", length(indeks_train), "\n")## Jumlah data training: 277
## Jumlah data testing : 70
# -------------------------------------------------------
# 4. TRAINING MODEL NAIVE BAYES
# -------------------------------------------------------
cat("\nSedang melatih model Naive Bayes...\n")##
## Sedang melatih model Naive Bayes...
model_nb <- naiveBayes(train_dtm, train_label, laplace = 1)
# -------------------------------------------------------
# 5. PREDIKSI
# -------------------------------------------------------
tebakan_ai <- predict(model_nb, test_dtm)
# -------------------------------------------------------
# 6. EVALUASI HASIL
# -------------------------------------------------------
evaluasi <- data.frame(
Teks_Asli = data_total$Teks[indeks_test],
Label_Asli = jawaban_asli,
Tebakan_AI = as.character(tebakan_ai),
stringsAsFactors = FALSE
)
evaluasi$Cocok <- evaluasi$Label_Asli == evaluasi$Tebakan_AI
# Sampel hasil uji
cat("\n--- SAMPEL HASIL UJI (10 DATA) ---\n")##
## --- SAMPEL HASIL UJI (10 DATA) ---
knitr::kable(
head(evaluasi[, c("Label_Asli", "Tebakan_AI", "Cocok")], 10),
caption = "Sampel Prediksi Model Naive Bayes"
)| Label_Asli | Tebakan_AI | Cocok |
|---|---|---|
| User Review | User Review | TRUE |
| User Review | User Review | TRUE |
| User Review | User Review | TRUE |
| User Review | User Review | TRUE |
| User Review | User Review | TRUE |
| User Review | User Review | TRUE |
| User Review | User Review | TRUE |
| User Review | User Review | TRUE |
| User Review | User Review | TRUE |
| User Review | User Review | TRUE |
# Hitung Akurasi
akurasi <- sum(evaluasi$Cocok) / nrow(evaluasi) * 100
cat("\n======================================\n")##
## ======================================
## AKURASI FINAL MODEL: 100 %
## ======================================
# Diagram batang perbandingan label asli vs prediksi
library(ggplot2)
# Distribusi Label Asli
df_plot <- data.frame(
Kategori = c(names(table(evaluasi$Label_Asli)),
names(table(evaluasi$Tebakan_AI))),
Jumlah = c(as.numeric(table(evaluasi$Label_Asli)),
as.numeric(table(evaluasi$Tebakan_AI))),
Tipe = c(rep("Label Asli", length(table(evaluasi$Label_Asli))),
rep("Prediksi AI", length(table(evaluasi$Tebakan_AI))))
)
ggplot(df_plot, aes(x = Kategori, y = Jumlah, fill = Tipe)) +
geom_bar(stat = "identity", position = "dodge", width = 0.6) +
scale_fill_manual(values = c("Label Asli" = "#2c7bb6", "Prediksi AI" = "#d7191c")) +
labs(
title = "Perbandingan Label Asli vs Prediksi Naive Bayes",
subtitle = paste0("Akurasi: ", round(akurasi, 2), "%"),
x = "Kategori Teks",
y = "Jumlah Dokumen",
fill = "Sumber"
) +
theme_minimal(base_size = 13) +
theme(plot.title = element_text(face = "bold"))Distribusi Prediksi Model Naive Bayes
Berdasarkan hasil analisis yang telah dilakukan:
Proses pembersihan teks menghasilkan Document-Term Matrix (DTM) dengan dimensi 347 dokumen dan 335 fitur kata unik (setelah penyaringan kata jarang atau sparsity 0.98). Data ini kemudian dibagi secara pragmatis menggunakan proporsi standar 80:20, yaitu 277 dokumen untuk data training dan 70 dokumen untuk data testing.
Setelah model Naive Bayes dilatih dan diuji pada 70 dokumen data testing, diperoleh hasil performa sebagai berikut:
======================================
AKURASI FINAL MODEL: 100 %
======================================
Secara metrik, model berhasil memprediksi seluruh data uji dengan tepat tanpa ada kesalahan tunggal pun (Akurasi 100%). Perbandingan visual sebaran antara label asli dan prediksi AI dapat dilihat pada Gambar 4.1, di mana grafik batang menunjukkan kesesuaian yang mutlak.
Meskipun angka akurasi 100% terlihat ideal secara statistik, evaluasi kritis secara aktual menunjukkan bahwa hasil ini tidak lepas dari karakteristik data hasil ekstraksi yang terlalu homogen. Berikut adalah analisis kemungkinan mengapa model mencapai akurasi sempurna:
Pada tahap preprocessing, data diekstraksi dari file mentah menggunakan fungsi pemotong berbasis teks jangkar seperti “Reply from Temu”. Pihak perusahaan (Temu) secara konsisten menggunakan template balasan otomatis (canned responses) yang sangat kaku, formal, dan repetitif pada platform Trustpilot. Akibatnya, dokumen dalam kategori Company Response memiliki kemunculan kata kunci yang hampir seragam (seperti customer, team, support, check, feedback).
Algoritma Naive Bayes bekerja berdasarkan probabilitas kemunculan kata kunci tunggal (bag-of-words). Karena teks respons admin sangat seragam dan teks ulasan pengguna (User Review) sangat kasual, tidak ada kata kunci yang tumpang tindih (overlapping tokens) di antara kedua kelas setelah dikurangi sparsity. Hal ini membuat model mengalami kondisi overfitting yang menguntungkan pada data uji saat ini, di mana batas keputusan (decision boundary) menjadi sangat hitam-putih.
Jumlah data testing yang relatif kecil (70 dokumen) belum mampu merepresentasikan variasi bahasa yang lebih kompleks di luar ekosistem Trustpilot. Jika model ini diuji menggunakan data dari platform lain dengan gaya bahasa admin yang lebih santai atau personal, akurasi dipastikan akan mengalami penurunan secara realistis.
Secara aktual, model Naive Bayes ini tidak sedang melakukan pemahaman konteks bahasa alami (Natural Language Processing) yang mendalam, melainkan melakukan klasifikasi berbasis aturan probabilistik terhadap template teks yang dominan. Namun, untuk kebutuhan praktis otomatisasi pemisahan peran aktor (Pengguna vs Perusahaan) pada korpus data yang spesifik ini, model ini terbukti sangat instan dan efektif digunakan.
## Informasi Sesi R:
## R version 4.4.2 (2024-10-31 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26100)
##
## Matrix products: default
##
##
## locale:
## [1] LC_COLLATE=English_United States.utf8
## [2] LC_CTYPE=English_United States.utf8
## [3] LC_MONETARY=English_United States.utf8
## [4] LC_NUMERIC=C
## [5] LC_TIME=English_United States.utf8
##
## time zone: Asia/Shanghai
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] ggplot2_4.0.0 e1071_1.7-16 SnowballC_0.7.1 tm_0.7-17
## [5] NLP_0.3-2
##
## loaded via a namespace (and not attached):
## [1] gtable_0.3.6 jsonlite_1.8.8 highr_0.11 dplyr_1.1.4
## [5] compiler_4.4.2 tidyselect_1.2.1 Rcpp_1.0.13 slam_0.1-55
## [9] xml2_1.3.6 parallel_4.4.2 jquerylib_0.1.4 scales_1.4.0
## [13] yaml_2.3.10 fastmap_1.2.0 R6_2.5.1 labeling_0.4.3
## [17] generics_0.1.3 knitr_1.48 tibble_3.2.1 bslib_0.8.0
## [21] pillar_1.9.0 RColorBrewer_1.1-3 rlang_1.1.6 utf8_1.2.4
## [25] cachem_1.1.0 xfun_0.47 sass_0.4.9 S7_0.2.0
## [29] cli_3.6.3 withr_3.0.2 magrittr_2.0.3 class_7.3-22
## [33] digest_0.6.37 grid_4.4.2 rstudioapi_0.16.0 lifecycle_1.0.4
## [37] vctrs_0.6.5 proxy_0.4-27 evaluate_1.0.3 glue_1.7.0
## [41] farver_2.1.2 fansi_1.0.6 rmarkdown_2.29 tools_4.4.2
## [45] pkgconfig_2.0.3 htmltools_0.5.8.1