Laporan ini bertujuan untuk mengklasifikasikan sentimen pada data teks terkait dengan layanan keuangan. Tujuan utama laporan ini adalah untuk menganalisis dan memodelkan sentimen yang terkandung dalam data teks, sehingga dapat memprediksi dengan akurat sentimen yang muncul, seperti positif, negatif terhadap layanan keuangan.
Sentimen pelanggan dalam industri layanan keuangan memiliki peran yang sangat penting. Perasaan positif atau negatif pelanggan dapat memengaruhi reputasi perusahaan, keputusan investasi, dan kepuasan pelanggan secara keseluruhan. Oleh karena itu, memahami sentimen yang terkandung dalam data teks pelanggan sangat berharga.
Tujuan dari pembuatan laporan ini adalah untuk mengklasifikasikan sentimen dari data teks yang berkaitan dengan layanan keuangan. Dalam laporan ini, kami akan menggunakan teknik pemrosesan teks dan pemodelan yang sesuai untuk menganalisis data teks yang ada. Beberapa target dan prediktor yang mungkin kami gunakan untuk mencapai tujuan ini adalah:
label
.kecuali label
.Dalam bagian ini, kita akan membaca data dan melakukan proses pembersihan (jika diperlukan).
Pertama, kita akan memuat dataset latih dan dataset uji yang akan digunakan dalam analisis. Dataset latih akan digunakan untuk melatih model dan dataset uji akan digunakan untuk menguji performa model yang telah dilatih.
data1 <- read.csv("train_data.csv")
data2 <- read.csv("test_data.csv")
# Menggabungkan train_data dan test_data menjadi satu dataset
train_data <- rbind(data1, data2)
# write.csv(train_data, "data_train.csv", row.names = TRUE)
Selanjutnya, kita akan melakukan eksplorasi terhadap struktur data dan variabel yang ada.
💡Data train memiliki 1,377 rows dengan total Features/columns 3.
## 'data.frame': 1573 obs. of 3 variables:
## $ label : chr "negative" "positive" "positive" "negative" ...
## $ en_text: chr "However , the growth margin slowed down due to the financial crisis ." "Bilfinger investors cheered the agreement , pushing shares up 7 % , or & euro ; 3.30 , to & euro ; 50.29 , in a"| __truncated__ "Following the acquisition , Relacom will strengthen its presence in Finland , serving operators and office mark"| __truncated__ "Profit before taxes amounted to EUR 56.5 mn , down from EUR 232.9 mn a year ago ." ...
## $ id_text: chr "Namun, margin pertumbuhan melambat akibat krisis keuangan." "Investor Bilfinger menyambut baik kesepakatan tersebut, mendorong saham naik 7%, atau & euro; 3.30 , ke & euro "| __truncated__ "Setelah akuisisi, Relacom akan memperkuat kehadirannya di Finlandia, melayani pasar operator dan perkantoran de"| __truncated__ "Laba sebelum pajak berjumlah EUR 56,5 juta, turun dari EUR 232,9 juta tahun lalu." ...
💡 Tipe datanya sudah sesuai dengan kasus kali ini, namun perlu dilakukkan penyesuaian lebih lanjut.
Kemudian, kita akan memeriksa apakah terdapat missing values atau nilai yang hilang pada dataset. Jika ada, kita akan menangani missing values tersebut dengan melakukan imputasi atau penghapusan nilai yang hilang, sesuai dengan kebutuhan analisis kita.
# Memeriksa missing values pada dataset latih
colSums(is.na(train_data))
## label en_text id_text
## 0 0 0
💡 tidak ada missing values
dalam dataset.
# Menghitung jumlah frekuensi untuk kategori positive dan negative
positive_freq <- sum(train_data$label == "positive")
negative_freq <- sum(train_data$label == "negative")
# Menghitung persentase untuk positive dan negative
positive_percentage <- positive_freq / nrow(train_data) * 100
negative_percentage <- negative_freq / nrow(train_data) * 100
# Membuat data frame untuk pie chart
pie_data <- data.frame(Category = c("Positive", "Negative"),
Frequency = c(positive_freq, negative_freq),
Percentage = c(positive_percentage, negative_percentage))
# Membuat plot pie chart
library(ggplot2)
ggplot(pie_data, aes(x = "", y = Frequency, fill = Category)) +
geom_bar(stat = "identity", width = 1, alpha = 0.7) +
coord_polar("y", start = 0) +
labs(x = NULL, y = NULL, fill = "label") +
ggtitle("Perbandingan Frekuensi Total antara Sentimen Positif dan Negatif") +
theme_minimal() +
theme(axis.line = element_blank(),
axis.text = element_blank(),
axis.title = element_blank(),
panel.grid = element_blank(),
plot.title = element_text(hjust = 0.5)) +
geom_text(aes(label = paste0(round(Percentage, 1), "%")), position = position_stack(vjust = 0.5))
💡 + Mayoritas sentimen dalam data adalah positif, mengindikasikan optimisme dalam lanskap finansial yang dianalisis. + Perlu memperhatikan sentimen negatif, yang bisa menjadi sumber risiko atau perhatian potensial dalam analisis finansial.
kali ini saya mau melihat karakteristik teks yang terkait dengan
pesan negative menggunakan visualisasi
wordcloud
# Filter data untuk kategori "negative" atau pesan berdampak buruk
negative_text <- train_data$id_text[train_data$label == "negative"]
# Menggabungkan semua teks negatif menjadi satu string
negative_text <- paste(negative_text, collapse = " ")
# Menghapus kata-kata yang tidak relevan atau umum dalam teks negatif
custom_stopwords <- c("dan", "atau", "ini", "itu", "dari", "dengan", "ke", "di", "untuk")
# Membuat wordcloud untuk teks negatif dengan batasan kata
wordcloud(negative_text, stopwords = custom_stopwords, max.words = 100)
💡Dalam kasus ini, saya akan melakukan preprocesing pada kolom
id_text menggunakan pustaka textclean
. Preprocess
data teks melibatkan langkah-langkah seperti tokenisasi, pembersihan
tanda baca & emoticon, mengubah ke huruf kecil, dan lainnya untuk
mempersiapkan data sebelum dilakukan analisis teks lebih lanjut.
# Mengecek keberadaan emoticon dalam kolom "text"
has_emoticon <- any(stringr::str_detect(train_data$id_text, ":\\)|:\\(|;\\)|;\\)|:D"))
has_emoticon <- any(stringr::str_detect(train_data$en_text, ":\\)|:\\(|;\\)|;\\)|:D"))
# Mengecek keberadaan punctuation dalam kolom "text"
has_punctuation <- any(stringr::str_detect(train_data$id_text, "[[:punct:]]"))
# Output hasil pengecekan
has_emoticon
## [1] FALSE
has_punctuation
## [1] TRUE
💡Setelah melakukan pengecekan pada data, tidak ditemukan adanya
emotikon. Namun, ada tanda baca punctuation
yang perlu
ditangani.
# library(katadasaR)
library(textclean) #meembersihkan teks
library(tokenizers) #membagi teks menjadi token2
library(wordcloud) #membuat wodcloud
library(stringr) #memaipulasi teks / regex
library(tm)
head(train_data$id_text)
## [1] "Namun, margin pertumbuhan melambat akibat krisis keuangan."
## [2] "Investor Bilfinger menyambut baik kesepakatan tersebut, mendorong saham naik 7%, atau & euro; 3.30 , ke & euro ; 50.29, pada perdagangan sore."
## [3] "Setelah akuisisi, Relacom akan memperkuat kehadirannya di Finlandia, melayani pasar operator dan perkantoran dengan jasa konstruksi, instalasi dan pemeliharaan jaringan bergerak dan tetap."
## [4] "Laba sebelum pajak berjumlah EUR 56,5 juta, turun dari EUR 232,9 juta tahun lalu."
## [5] "Pada kuartal ketiga, penjualan bersih meningkat sebesar 12% tahun-ke-tahun menjadi EUR159,5 juta, atau sebesar 6% pada pertumbuhan nilai tukar mata uang yang sebanding."
## [6] "Pemasok sistem pipa dan pemanas Finlandia Uponor Corporation (OMX Helsinki: UNR1V) mengatakan pada hari Selasa (12 Agustus) bahwa dewan direksi telah memberi wewenang kepada manajemen untuk melanjutkan program pengurangan biaya di seluruh perusahaan."
# Mengubah teks menjadi huruf kecil.
preprocessed_text <- tolower(train_data$id_text)
# Mengganti kontraksi kata menjadi bentuk lengkap.
preprocessed_text <- replace_contraction(preprocessed_text)
# Menghilangkan karakter selain alphanumeric dan spasi.
preprocessed_text <- gsub("[^[:alnum:][:space:]]", "", preprocessed_text)
# Menghilangkan tanda baca.
preprocessed_text <- gsub("[[:punct:]]", "", preprocessed_text)
# Menghilangkan angka.
preprocessed_text <- gsub("\\d+", "", preprocessed_text)
# Membersihkan teks dari karakter yang tidak diinginkan.
preprocessed_text <- strip(preprocessed_text)
head(preprocessed_text)
## [1] "namun margin pertumbuhan melambat akibat krisis keuangan"
## [2] "investor bilfinger menyambut baik kesepakatan tersebut mendorong saham naik atau euro ke euro pada perdagangan sore"
## [3] "setelah akuisisi relacom akan memperkuat kehadirannya di finlandia melayani pasar operator dan perkantoran dengan jasa konstruksi instalasi dan pemeliharaan jaringan bergerak dan tetap"
## [4] "laba sebelum pajak berjumlah eur juta turun dari eur juta tahun lalu"
## [5] "pada kuartal ketiga penjualan bersih meningkat sebesar tahunketahun menjadi eur juta atau sebesar pada pertumbuhan nilai tukar mata uang yang sebanding"
## [6] "pemasok sistem pipa dan pemanas finlandia uponor corporation omx helsinki unrv mengatakan pada hari selasa agustus bahwa dewan direksi telah memberi wewenang kepada manajemen untuk melanjutkan program pengurangan biaya di seluruh perusahaan"
Setelah itu, saya membuat document-term matrix (DTM) untuk mewakili data teks dengan menggunakan Corpus dan DocumentTermMatrix. DTM membantu mengubah teks menjadi bentuk matriks, Dengan adanya DTM, kita dapat melanjutkan dengan analisis teks lebih lanjut, seperti pemodelan teks menggunakan algoritma seperti Naive Bayes, Random Forest, atau SVM untuk mengklasifikasikan news menjadi positif atau negatif.
# Mengubah teks menjadi Corpus.
corpus <- Corpus(VectorSource(preprocessed_text))
# Membuat Document Term Matrix (DTM).
dtm <- DocumentTermMatrix(corpus)
# Menampilkan hasil DTM
dtm
## <<DocumentTermMatrix (documents: 1573, terms: 3931)>>
## Non-/sparse entries: 25267/6158196
## Sparsity : 100%
## Maximal term length: 22
## Weighting : term frequency (tf)
DTM (DocumentTermMatrix)
menunjukkan bahwa dalam dataset
berisi 2004 pesan teks, terdapat 3931 kata yang berbeda yang muncul.
Kepadatan DTM ini adalah 100%, artinya setiap sel dalam matriks memiliki
nilai (tidak ada entri kosong). DTM ini memberikan gambaran detail
tentang distribusi kata dalam dataset teks, yang bisa digunakan untuk
analisis teks lebih lanjut.
Kali ini saya akan coba membandingkan beberapa pendekatan metode untuk tugas klasifikasi teks (misalnya: Naive Bayes, Random Forest, SVM)
Dataset dibagi menjadi 70% untuk train dan 30% untuk test dengan
fungsi createDataPartition
.
# Memuat library yang diperlukan
library(caret)
library(randomForest)
library(e1071)
library(kernlab) # Untuk SVM
library(tm) # Untuk Text Mining (membuat Corpus dan DTM)
# Convert response variable to a factor
train_data$label <- factor(train_data$label)
# Split data menjadi data training dan data testing(validasi)
set.seed(123)
train_index <- createDataPartition(train_data$label, p = 0.7, list = FALSE)
train_set <- train_data[train_index, ]
test_set <- train_data[-train_index, ]
pada tahap ini saya melakukan beberapa pendekatan untuk membersihkan
data yang sudah di split, seperti :
konversi teks menjadi huruf kecil, penghilangan kontraksi, menghapus angka, tanda baca, dan kata-kata umum menggunakan *stopword*
.
kali ini saya tidak melakukan normalisasi
karena model yang
digunakan untuk klasifikasi teks (Naive Bayes, Random Forest, dan
Support Vector Machine) tidak memerlukan normalisasi pada data masukan
(DTM).
library(tm)
test_data <- read.csv("validation_data.csv", stringsAsFactors = FALSE)
# Preprocessing pada teks dataset yang akan diprediksi
preprocess_text <- function(id_text) {
# Mengubah teks menjadi huruf kecil
text <- tolower(id_text)
# Mengganti kontraksi kata menjadi bentuk lengkap
text <- replace_contraction(text)
# Menghilangkan karakter selain alphanumeric dan spasi
text <- gsub("[^[:alnum:][:space:]]", "", text)
# Menghilangkan tanda baca
text <- gsub("[[:punct:]]", "", text)
# Menghilangkan angka
text <- gsub("\\d+", "", text)
# Membersihkan teks dari karakter yang tidak diinginkan
text <- strip(text)
return(text)
}
# Membuat Corpus dari data training dan data testing
corpus_train <- Corpus(VectorSource(train_set$id_text))
corpus_test <- Corpus(VectorSource(test_set$id_text)) #corpus_valid
test_corpus <- Corpus(VectorSource(test_data$id_text))
# Preprocessing Corpus
# Buat daftar kata bawaan untuk bahasa Indonesia // tidak tau library/fungsinya
stopwords_id <- c("dan", "atau", "adalah", "dari", "di", "ke", "sebuah", "itu", "ini", "yang")
corpus_train <- tm_map(corpus_train, content_transformer(tolower))
corpus_train <- tm_map(corpus_train, content_transformer(replace_contraction))
corpus_train <- tm_map(corpus_train, removeNumbers)
corpus_train <- tm_map(corpus_train, removePunctuation)
corpus_train <- tm_map(corpus_train, removeWords, stopwords_id) # bagaimmana indonesia?
corpus_train <- tm_map(corpus_train, stripWhitespace)
corpus_valid <- tm_map(corpus_test, content_transformer(tolower))
corpus_valid <- tm_map(corpus_test, content_transformer(replace_contraction))
corpus_valid <- tm_map(corpus_test, removeNumbers)
corpus_valid <- tm_map(corpus_test, removePunctuation)
corpus_valid <- tm_map(corpus_test, removeWords, stopwords_id)
corpus_valid <- tm_map(corpus_test, stripWhitespace)
test_corpus <- tm_map(test_corpus, content_transformer(tolower))
test_corpus <- tm_map(corpus_test, content_transformer(replace_contraction))
test_corpus <- tm_map(test_corpus, removePunctuation)
test_corpus <- tm_map(test_corpus, removeNumbers)
test_corpus <- tm_map(test_corpus, removeWords, stopwords_id)
test_corpus <- tm_map(test_corpus, stripWhitespace)
# Membuat Document Term Matrix (DTM).
dtm_train <- DocumentTermMatrix(corpus_train)
dtm_valid <- DocumentTermMatrix(corpus_valid, control = list(dictionary = Terms(dtm_train))) # data_valid
# Buat Document Term Matrix (DTM) untuk dataset uji
dtm_test <- DocumentTermMatrix(test_corpus, control = list(dictionary = Terms(dtm_train)))
dtm_test_mat <- as.matrix(dtm_test)
# Konversi DTM menjadi matriksmethod
dtm_train_mat <- as.matrix(dtm_train)
dtm_valid_mat <- as.matrix(dtm_valid)
saya menggunakan 3 model klasifikasi, yaitu
Naive Bayes, Random Forest, dan Support Vector Machine (SVM).
Evaluasi model dilakukan dengan menghitung akurasi pada data training
dan data testing menggunakan predict dan mean.
Pemilihan model untuk klasifikasi news didasarkan pada pertimbangan beberapa metode. Pertama, Naive Bayes dipilih karena sederhana dan efisien dalam mengatasi data teks. Kedua, Random Forest digunakan karena kekuatan sebagai model ensemble yang mengatasi overfitting dan cocok untuk data teks kompleks. Terakhir, SVM dipertimbangkan karena kemampuannya menemukan batas keputusan optimal dan dapat menangani data yang tidak linier.
# Memilih model untuk dibandingkan
models <- c("Naive Bayes", "Random Forest", "Support Vector Machine")
# Inisialisasi list untuk menyimpan hasil model
model_results <- list()
# Looping untuk membangun dan mengevaluasi model
for (model_name in models) {
# Membangun model menggunakan caret
model <- NULL
if (model_name == "Naive Bayes") {
model <- naiveBayes(as.matrix(dtm_train), train_set$label)
} else if (model_name == "Random Forest") {
model <- randomForest(as.matrix(dtm_train), train_set$label)
} else if (model_name == "Support Vector Machine") {
model <- svm(as.matrix(dtm_train), train_set$label)
}
# Evaluasi model pada data training
train_pred <- predict(model, dtm_train_mat)
train_acc <- mean(train_pred == train_set$label)
# Evaluasi model pada data validasi
valid_pred <- predict(model, dtm_valid_mat)
valid_acc <- mean(valid_pred == test_set$label)
# Simpan hasil evaluasi ke dalam list
model_results[[model_name]] <- list(train_accuracy = train_acc, valid_accuracy = valid_acc)
}
# Tampilkan hasil evaluasi model
result_df <- data.frame(Model = character(), Training_Accuracy = numeric(), Validation_Accuracy = numeric(), stringsAsFactors = FALSE)
for (model_name in models) {
cat("Model:", model_name, "\n")
cat("Training Accuracy:", model_results[[model_name]]$train_accuracy, "\n")
cat("Validation Accuracy:", model_results[[model_name]]$valid_accuracy, "\n\n")
# Simpan hasil evaluasi ke dalam dataframe
result_df <- rbind(result_df, data.frame(Model = model_name,
Training_Accuracy = model_results[[model_name]]$train_accuracy,
Validation_Accuracy = model_results[[model_name]]$valid_accuracy,
stringsAsFactors = FALSE))
}
## Model: Naive Bayes
## Training Accuracy: 0.3076225
## Validation Accuracy: 0.3078556
##
## Model: Random Forest
## Training Accuracy: 0.991833
## Validation Accuracy: 0.866242
##
## Model: Support Vector Machine
## Training Accuracy: 0.9147005
## Validation Accuracy: 0.6921444
Hasil evaluasi menunjukkan bahwa Random Forest memiliki akurasi yang sangat tinggi pada data training (99.0%) namun sedikit menurun pada data validasi (86.6%), mengindikasikan kemungkinan adanya overfitting. Meskipun begitu, model ini masih memiliki performa yang lebih baik pada data testing dibandingkan dengan model SVM dan Naive Bayes. Dengan demikian, model Random Forest mungkin merupakan pilihan terbaik untuk mengklasifikasikan teks dalam kasus ini.
Berikut kata-kata yang memiliki pengaruh besar dalam hasil prediksi menggunakan model Random Forest. Informasi ini sangat berharga karena dapat membantu kita dalam memahami faktor-faktor apa saja yang berperan dalam mempengaruhi hasil prediksi dan dapat digunakan untuk mengoptimalkan atau meningkatkan performa model.
# Melatih model Random Forest pada data training
model_rf <- randomForest(as.matrix(dtm_train), train_set$label)
# Mendapatkan tingkat penting variabel dari model Random Forest
rf_var_imp <- importance(model_rf)
# Buat data frame untuk variable importance
rf_var_imp_df <- data.frame(word = row.names(rf_var_imp), importance = rf_var_imp[, 1])
# Mengurutkan dan memilih 15 kata-kata penting teratas
rf_var_imp_df <- rf_var_imp_df %>%
arrange(desc(importance)) %>%
head(15)
# Tampilkan kata-kata penting dari model Random Forest
print(rf_var_imp_df)
## word importance
## turun turun 49.395546
## naik naik 6.199154
## karena karena 4.475542
## kerugian kerugian 4.260509
## menurun menurun 3.669357
## penurunan penurunan 3.474317
## staf staf 3.393288
## juta juta 3.375429
## eur eur 3.135424
## sementara sementara 3.104340
## dibandingkan dibandingkan 2.918781
## untuk untuk 2.884136
## meningkat meningkat 2.868628
## orang orang 2.857049
## memberhentikan memberhentikan 2.851048
💡 Tingkat penting variabel menggambarkan seberapa besar kontribusi setiap kata terhadap prediksi. Semakin tinggi tingkat kepentingannya, semakin berpengaruh kata tersebut dalam proses pengambilan keputusan model.
# Mengukur performa model pada dataset validasi
valid_pred <- predict(model_rf, dtm_valid_mat)
valid_cm <- confusionMatrix(valid_pred, test_set$label) # Ganti test_set dengan valid_data
# Menghitung akurasi, sensitivitas, spesifisitas, dan presisi pada dataset validasi
valid_accuracy <- valid_cm$overall["Accuracy"]
valid_sensitivity <- valid_cm$byClass["Sensitivity"]
valid_specificity <- valid_cm$byClass["Specificity"]
valid_precision <- valid_cm$byClass["Precision"]
# Melaporkan performa model pada dataset validasi
cat("Performa model pada dataset validasi:\n")
## Performa model pada dataset validasi:
cat("Akurasi:", valid_accuracy, "\n")
## Akurasi: 0.866242
cat("Sensitivitas:", valid_sensitivity, "\n")
## Sensitivitas: 0.6344828
cat("Spesifisitas:", valid_specificity, "\n")
## Spesifisitas: 0.9693252
cat("Presisi:", valid_precision, "\n\n")
## Presisi: 0.9019608
# Evaluasi model terbaik pada dataset uji
test_pred <- predict(model_rf, dtm_test_mat)
test_pred <- factor(test_pred, levels = levels(test_set$label))
# Jika level faktor test_pred dan test_data$label tidak sama, atur levelnya agar sama
levels(test_pred) <- levels(test_set$label)
test_cm <- confusionMatrix(test_pred, test_set$label)
# Menghitung akurasi, sensitivitas, spesifisitas, dan presisi pada dataset uji
test_accuracy <- test_cm$overall["Accuracy"]
test_sensitivity <- test_cm$byClass["Sensitivity"]
test_specificity <- test_cm$byClass["Specificity"]
test_precision <- test_cm$byClass["Precision"]
# Melaporkan performa model pada dataset uji
cat("Performa model pada dataset uji:\n")
## Performa model pada dataset uji:
cat("Akurasi:", test_accuracy, "\n")
## Akurasi: 0.8789809
cat("Sensitivitas:", test_sensitivity, "\n")
## Sensitivitas: 0.6758621
cat("Spesifisitas:", test_specificity, "\n")
## Spesifisitas: 0.9693252
cat("Presisi:", test_precision, "\n")
## Presisi: 0.9074074
Pada kode berikut, pengaturan jumlah kata yang muncul dalam wordcloud
dilakukan dengan menggunakan fungsi min.freq.
Argumen ini
menentukan frekuensi minimum kata yang harus muncul agar dimasukkan ke
dalam wordcloud. Kata-kata yang muncul dengan frekuensi lebih rendah
dari nilai min.freq akan diabaikan.
# Mengidentifikasi SMS yang diprediksi dengan tidak benar
misclassified_sms <- test_data[test_pred != test_set$label, "id_text"]
# head(misclassified_sms)
# Buat Wordcloud dari teks yang diprediksi tidak benar
wordcloud(misclassified_sms, scale = c(5, 0.5), min.freq = 2, random.order = FALSE, colors = brewer.pal(8, "Dark2"))
💡 Insight singkat dari hasil evaluasi performa model:
+++++++++++++++++++++
Singkatnya meskipun persentase sentimen positif dalam dataset lebih tinggi, jumlah kata-kata tertentu yang umumnya terkait dengan sentimen negatif, seperti “turun,” mungkin lebih sering muncul dalam teks. Ini mungkin tampak kontradiktif, tetapi ada beberapa penjelasan mungkin:
Kemungkinan Adanya Dominasi Beberapa Kata: Meskipun jumlah sentimen positif lebih tinggi dalam persentase, mungkin ada beberapa kata kunci tertentu yang muncul lebih sering dalam konteks negatif, seperti “turun,” yang mungkin menjadi kata kunci yang signifikan dalam teks berlabel negatif.
maka dari itu kami rasa perlu menambah jumlah data, agar sentimen katanya lebih beragam dalam ekspresi, sehingga tidak ada kata kunci negatif yang mendominasi persentase yang tinggi.