Proyek ini menganalisis sentimen komentar pengguna tentang semua layanan Traveloka (pesawat, hotel, kereta, bus, rental, aktivitas, paylater, dll) yang diambil dari media sosial X (Twitter).
Metode: Multinomial Naive Bayes (MNB). ComplementNB tersedia sebagai
pembanding opsional lewat --compare.
Ringkas hasil (588 dokumen, 22 Juli 2026): macro-F1 0,4253 (95% CI 0,324–0,526) terhadap baseline 0,2548. Sentimen dominan netral 62,1%. Pelabelan otomatis berbasis lexicon diuji dan tidak valid (Cohen's Kappa 0,055), sehingga model dilatih pada label hasil anotasi manusia. Rincian di Hasil Penelitian.
- Yang Perlu Disiapkan
- Instalasi
- Setup Session X
- Alur Lengkap
- Daftar Opsi
- Struktur Folder
- Catatan Metodologi
- Rincian Teknis & Rumus
- Panduan Penyelarasan Naskah
- Sumber Data dan Kamus
- Hasil Penelitian
- Pemetaan Naskah vs Implementasi
- Troubleshooting
- Python 3.11 ke atas — https://www.python.org/downloads/ (saat install di Windows, centang "Add Python to PATH")
- Akun X (Twitter) — untuk mengambil data
- Browser Chrome/Edge — untuk mengekspor cookie
- Koneksi internet
Linux / macOS
cd /path/ke/scraper-x
python3 -m venv .venv
source .venv/bin/activateWindows
cd D:\scraper-x
python -m venv myenv
myenv\Scripts\activateKalau berhasil, akan muncul (.venv) atau (myenv) di depan prompt terminal.
Penting: setiap kali membuka terminal baru, aktifkan dulu virtual environment sebelum menjalankan apapun.
pip install -r requirements.txtSemua versi sudah dikunci di requirements.txt agar hasil penelitian dapat
direproduksi.
playwright install chromiumFolder lexicon/ harus berisi positive.tsv dan negative.tsv. Kalau belum ada:
mkdir -p lexicon && cd lexicon
curl -LO https://raw.githubusercontent.com/fajri91/InSet/master/positive.tsv
curl -LO https://raw.githubusercontent.com/fajri91/InSet/master/negative.tsv
cd ..X tidak mengizinkan login otomatis, jadi session diambil dari cookie browser.
- Pasang ekstensi Cookie-Editor di Chrome/Edge (cari di Chrome Web Store)
- Buka https://x.com dan login seperti biasa
- Setelah masuk beranda, klik ikon Cookie-Editor di pojok kanan atas
- Klik tombol Export (ikon panah ke bawah) — cookie tersalin ke clipboard
- Buat file
session/cookies.json, paste isinya, lalu simpan
Amankan file tersebut (berisi auth_token yang setara akses penuh ke akun Anda):
chmod 600 session/cookies.json # Linux/macOSJika session expired (biasanya setelah beberapa hari atau setelah logout), ulangi langkah 2–5.
python main.py --scrape --max 130- Mengambil tweet dari 33 kata kunci layanan Traveloka
- Hasil:
data/raw/traveloka_raw_TANGGAL.csv - Kolom:
id, username, date, text, likes, retweets, replies, query
Perlu waktu lama. Ada jeda 2,5 detik tiap scroll. 33 query x 130 tweet bisa memakan 1–2 jam. Browser akan terbuka — jangan ditutup.
Berapa banyak yang dibutuhkan? Sekitar 45-50% hasil scraping bukan
Bahasa Indonesia dan akan dibuang di STEP 2. Untuk mendapat ~1.000 data
Indonesia, scrape minimal 2.000 tweet mentah (--max 130 ke atas).
Catatan penting soal query. Operator
lang:idtidak dipakai. Diuji 21 Juli 2026, operator itu membuat X mengembalikan indeks lama — tweet 2013–2014, 19 dari 20 berasal dari akun resmi@traveloka. Tanpa operator tersebut, hasil pencarian adalah tweet hari itu juga. Penyaringan bahasa dilakukan di STEP 2 memakailangdetect.Query juga memakai
-from:travelokauntuk membuang balasan customer service akun resmi, yang isinya teks PR dan akan mencemari analisis sentimen pelanggan. Balasan dari pengguna biasa tetap diambil.
python main.py --preprocessOtomatis mengambil file raw terbaru. Untuk menentukan file secara manual:
python main.py --preprocess --input data/raw/traveloka_raw_TANGGAL.csvProses yang dijalankan:
- Hapus URL, mention (
@user), hashtag (#) - Hapus karakter non-ASCII, angka, dan tanda baca
- Lowercase
- Filter bahasa — buang dokumen non-Indonesia (langdetect, confidence >= 0.70)
- Normalisasi slang — ubah singkatan ke bentuk baku (
bgt->banget,gk->tidak) memakai Kamus Alay, 15.115 pemetaan - Tokenisasi
- Hapus stopword — kata negasi sengaja DIPERTAHANKAN
- Stemming dengan PySastrawi
Hasil: data/processed/
Perhatikan output-nya. Akan tercetak distribusi bahasa dan berapa baris yang dibuang. Kalau data Indonesia tersisa < 300, akan muncul peringatan — scrape lagi sebelum melanjutkan.
Untuk melewati filter bahasa (tidak disarankan): tambahkan --no-lang-filter
Untuk melewati normalisasi slang: tambahkan --no-normalize
Mengapa normalisasi slang penting. InSet berisi kata baku, sedangkan tweet penuh singkatan. Tanpa normalisasi,
"gk bgs"tidak terdeteksi lexicon dan dokumennya salah dilabeli netral. Setelah normalisasi menjadi"tidak bagus", skornya −7 (negatif) — lengkap dengan pembalikan negasi.Normalisasi dijalankan setelah filter bahasa, karena kamus slang hanya valid untuk teks Indonesia.
Kamus Alay bersifat umum sehingga beberapa singkatan domain justru salah diterjemahkan (
cs→rekan,apk→apakah). Istilah tersebut dilindungi lewatPROTECTED_TERMSdimain.py.
PENTING — hasil validasi pada penelitian ini. Pelabelan lexicon diuji terhadap 588 dokumen berlabel manusia dan memperoleh Cohen's Kappa = 0,055 (kecocokan 31,3%) — tergolong slight menurut Landis & Koch. Penyetelan ambang tidak menolong: 10 nilai diuji, terbaik hanya κ = 0,105.
Akibatnya lexicon tidak dipakai sebagai sumber label penelitian. Perannya berubah menjadi (a) objek yang dievaluasi, dan (b) usulan pra-anotasi yang mempercepat pelabelan manual.
Label untuk pelatihan model berasal dari anotasi manusia (
data/labeled/final_manual_588_labeled.csv).--trainakan menampilkan peringatan bila diberi berkas berlabel lexicon.
python main.py --autolabelMenggunakan lexicon InSet (positive.tsv 3.607 + negative.tsv 6.606, bobot
-5 sampai +5) ditambah lexicon/custom.tsv untuk istilah domain:
- Pencocokan berbasis token, bukan substring
- Mendukung frasa multi-kata (677 entri), pencocokan terpanjang didahulukan
- Penanganan negasi: kata sentimen yang didahului kata negasi dalam jarak 3 token akan dibalik polaritasnya
- Label:
0= negatif,1= netral,2= positif - Ambang netral:
|skor| <= 2(ubah dengan--threshold)
Penting — InSet mengandung kontradiksi internal. 1.142 kata muncul di
positive.tsvdannegative.tsvdengan bobot berlawanan (mis.bagus= +2 dan −4). Proyek ini mendahulukanpositive.tsv, dipilih setelah menguji tiga strategi terhadap 20 kata umum: positive didahulukan 16/20, |bobot| maksimum 15/20, dijumlahkan 14/20.Selain itu beberapa istilah penting tidak ada di InSet sama sekali (
kecewa,tipu,penipuan,refund,komplain), dan beberapa salah polaritas (lambatdanmahalbernilai +1). Keduanya dikoreksi lewatlexicon/custom.tsv, yang menaikkan akurasi polaritas pada 20 kata uji tersebut dari 14/20 menjadi 20/20.
custom.tsvwajib dilaporkan di skripsi sebagai "InSet augmented with domain terms", lengkap dengan daftar kata dan alasan penambahannya.
Hasil: data/labeled/, dengan kolom tambahan sentiment_score dan lexicon_hits.
Output juga melaporkan berapa label "netral" yang sebenarnya tidak terdeteksi lexicon — angka ini penting untuk dibahas di skripsi.
Perlindungan data: perintah ini menolak menimpa file berlabel yang sudah
ada, agar koreksi manual Anda tidak hilang. Gunakan --force bila memang ingin
menimpa (backup dulu).
Pelabelan otomatis bukan ground truth. Validitasnya wajib diukur.
4a. Ambil sampel untuk dilabeli manual
python main.py --sample-gold 200 # angka opsional, default 200Hasil: data/gold/gold_sample_TANGGAL.csv (stratified per kelas)
4b. Isi label manual
Buka file tersebut di Excel/LibreOffice, isi kolom label_manual:
0 = negatif, 1 = netral, 2 = positif
Baca kolom
text(teks asli). Jangan lihat kolomlabel_autodulu agar penilaian Anda tidak terpengaruh. Simpan kembali sebagai CSV.
4c. Ukur kesepakatan
python main.py --kappaMenghasilkan Cohen's Kappa beserta interpretasi Landis & Koch (1977):
| Kappa | Interpretasi |
|---|---|
| < 0.00 | lebih buruk dari tebakan acak |
| 0.00 – 0.20 | sangat rendah (slight) |
| 0.20 – 0.40 | rendah (fair) |
| 0.40 – 0.60 | sedang (moderate) |
| 0.60 – 0.80 | baik (substantial) |
| 0.80 – 1.00 | sangat baik (almost perfect) |
Jika Kappa < 0.40, label otomatis tidak cukup valid. Gunakan label manual untuk training, atau perbaiki ambang/lexicon terlebih dahulu.
python main.py --trainProses yang dijalankan:
- Split dulu (stratified), data uji dibiarkan timpang sesuai kondisi nyata
- Oversampling hanya pada data latih — mencegah data leakage
- Baseline kelas mayoritas dihitung sebagai acuan minimum
- TF-IDF (unigram + bigram,
sublinear_tf,max_df=0.95) - GridSearch alpha dengan
scoring="f1_macro" - Melatih MultinomialNB (utama) dan ComplementNB (pembanding)
- Cross-validation 10-fold dengan oversampling di dalam tiap fold
- Evaluasi: accuracy, macro-F1, weighted-F1, macro-precision, macro-recall
Hasil:
models/mnb_model_TANGGAL.pkldanmodels/cnb_model_TANGGAL.pklresults/confusion_matrix_multinomialnb_TANGGAL.png(dancomplementnb)results/metrics_TANGGAL.csv— satu baris per model, lengkap dengan baseline
python main.py --predict --input data/processed/file_baru.csvDengan model tertentu:
python main.py --predict --model models/mnb_model_xxx.pkl --input data/processed/file_baru.csv# Aktifkan virtual environment dulu
source .venv/bin/activate # Windows: myenv\Scripts\activate
# Pastikan session/cookies.json sudah diisi (lihat Setup Session X)
python main.py --scrape --max 130
python main.py --preprocess
python main.py --autolabel
python main.py --sample-gold 200
# -> isi kolom 'label_manual' di Excel, simpan sebagai CSV
python main.py --kappa
python main.py --trainJangan gunakan
--alluntuk penelitian. Opsi itu melompati validasi Cohen's Kappa, padahal validasi tersebut yang membuat hasil penelitian dapat dipertanggungjawabkan.
| Opsi | Fungsi |
|---|---|
--scrape |
Scrape komentar Traveloka dari X |
--preprocess |
Preprocessing teks + filter bahasa |
--autolabel |
Pelabelan otomatis dengan lexicon InSet |
--sample-gold |
Ambil sampel untuk dilabeli manual |
--kappa |
Ukur Cohen's Kappa (otomatis vs manual) |
--train |
Latih MultinomialNB + ComplementNB |
--all |
Scrape + Preprocess + AutoLabel + Train |
--predict |
Prediksi sentimen file baru |
| Parameter | Fungsi | Default |
|---|---|---|
--max N |
Tweet per query | 100 |
--test-size R |
Rasio data uji | 0.2 |
--threshold N |
Ambang netral skor InSet | 2 |
--input FILE |
File input CSV manual | otomatis |
--labeled FILE |
File berlabel untuk --train |
otomatis |
--gold FILE |
File gold untuk --kappa |
otomatis |
--model FILE |
Model .pkl untuk --predict |
terbaru |
--force |
Izinkan menimpa file berlabel | mati |
--headless |
Browser tanpa tampilan (untuk server) | mati |
--compare |
Latih juga ComplementNB sebagai pembanding | mati |
--no-normalize |
Jangan normalisasi slang ke kata baku | mati |
--no-lang-filter |
Jangan buang teks non-Indonesia | mati |
scraper-x/
├── main.py ← Semua proses ada di sini
├── requirements.txt ← Library (versi terkunci)
├── README.md
│
├── lexicon/ ← Lexicon sentimen
│ ├── positive.tsv (3.607 kata, dari InSet)
│ ├── negative.tsv (6.606 kata, dari InSet)
│ ├── custom.tsv (istilah domain + koreksi polaritas)
│ └── kamus_alay.tsv (15.115 pemetaan slang -> baku)
│
├── data/
│ ├── raw/ ← Hasil scraping mentah
│ ├── processed/ ← Hasil preprocessing
│ ├── labeled/ ← Data berlabel otomatis
│ └── gold/ ← Sampel untuk label manual
│
├── models/ ← Model terlatih (.pkl)
├── results/ ← Confusion matrix & metrik
├── referensi/ ← Paper pendukung
└── session/
└── cookies.json ← Session X (dibuat manual)
Oversampling sebelum pembagian data menyebabkan data leakage: baris hasil duplikasi kelas minoritas bocor ke data uji, sehingga akurasi yang dilaporkan adalah hafalan, bukan generalisasi. Pada dataset awal proyek ini, 49,2% baris data uji ternyata identik dengan baris di data latih.
Karena itu urutannya: split → oversampling hanya pada data latih. Data uji dibiarkan timpang agar mencerminkan distribusi nyata.
Pada data timpang, accuracy menyesatkan. Model yang selalu menebak kelas mayoritas bisa mendapat accuracy tinggi tanpa mempelajari apapun. Karena itu setiap run mencetak baseline kelas mayoritas — model hanya berguna bila melampauinya, dan macro-F1 dipakai sebagai metrik utama.
Menghapus "tidak", "bukan", "gak" membuat "tidak bagus" menyusut menjadi
"bagus", sehingga model tidak mungkin mempelajari pembalikan polaritas.
Daftar stopword bawaan Sastrawi memuat kata-kata ini, jadi dikurangkan secara
eksplisit di NEGATION_WORDS.
Filter lang:id milik X tidak dapat diandalkan — banyak tweet promosi berbahasa
Inggris tetap lolos. Menganalisis teks Inggris memakai Sastrawi (stemmer
Indonesia) dan InSet (lexicon Indonesia) menghasilkan skor yang tidak bermakna.
langdetect dipakai dengan DetectorFactory.seed = 0 agar hasilnya dapat
direproduksi.
Semua rumus di bawah sudah diverifikasi secara numerik terhadap scikit-learn 1.9.0 yang benar-benar dipakai, bukan disalin dari buku teks.
| Keterangan | |
|---|---|
| Sumber | Media sosial X (Twitter), tab Latest |
| Alat | Playwright (Chromium), session cookie |
| Kata kunci | 33 query netral (nama layanan + frasa pemakaian) |
| Kolom | id, username, date, text, likes, retweets, replies, query |
| Filter query | -from:traveloka (buang balasan CS resmi) |
| Filter bahasa | langdetect, hanya id dengan confidence ≥ 0.70 |
| Target | ≥ 500 dokumen Bahasa Indonesia |
InSet (Koto & Rahmaningtyas, 2017) — kamus sentimen Bahasa Indonesia:
| File | Jumlah | Bobot |
|---|---|---|
positive.tsv |
3.607 kata | +1 … +5 |
negative.tsv |
6.606 kata | −5 … −1 |
custom.tsv |
16 entri | koreksi domain |
| Efektif | 9.082 entri | 677 frasa multi-kata |
Skor sentimen dokumen:
skor(d) = Σ w(t) · neg(t)
t∈d
w(t)= bobot kata/frasatmenurut lexiconneg(t)= −1 bila ada kata negasi dalam 3 token sebelumt, selain itu +1- Pencocokan berbasis token, frasa terpanjang didahulukan (maks 4 kata)
Aturan label:
label = 2 (positif) bila skor > 2
0 (negatif) bila skor < -2
1 (netral) bila -2 ≤ skor ≤ 2
scikit-learn tidak memakai log(N/df). Yang benar-benar dihitung:
Term Frequency (sublinear_tf=True):
tf(t,d) = 1 + ln( f(t,d) ) f = frekuensi mentah t dalam d
Inverse Document Frequency (smooth_idf=True, default):
idf(t) = ln( (1 + n) / (1 + df(t)) ) + 1
Bobot & normalisasi (norm='l2'):
w(t,d) = tf(t,d) × idf(t) lalu dinormalisasi:
w_norm(t,d) = w(t,d) / √( Σ w(t',d)² )
t'∈d
Bukti numerik (n=4 dokumen, traveloka df=3):
| Rumus | Hasil |
|---|---|
Buku teks log₁₀(N/df) |
0,124939 |
sklearn ln((1+n)/(1+df))+1 |
1,223144 ✅ |
idf_ aktual dari sklearn |
1,223144 |
📌 Penting untuk skripsi. Bila Bab II Anda menuliskan
IDF = log(N/DF), itu tidak sesuai dengan yang dihitung program. Pilih salah satu: perbaiki rumus di Bab II agar sesuai sklearn, atau sebutkan eksplisit bahwa perhitungan manual memakai rumus klasik sedangkan implementasi memakai varian smoothed milik scikit-learn.
Parameter aktif:
| Parameter | Nilai | Arti |
|---|---|---|
ngram_range |
(1, 2) | unigram + bigram |
min_df |
1 | kata muncul ≥ 1 dokumen |
max_df |
0.95 | buang kata yang muncul di > 95% dokumen |
sublinear_tf |
True | redam frekuensi dengan log |
norm |
l2 | tiap dokumen dinormalisasi |
max_df=0.95berarti kata "traveloka" sendiri kemungkinan besar dibuang, karena muncul di hampir semua dokumen sehingga tidak membedakan kelas.
Klasifikasi:
ĉ = argmax [ log P(c) + Σ log P(t|c) ]
c∈C t∈d
Prior (fit_prior=True):
P(c) = N_c / N N_c = jumlah dokumen kelas c
Likelihood dengan Lidstone smoothing:
P(t|c) = ( N_tc + α ) / ( N_c + α·V )
N_tc= total bobot katatpada kelascN_c= total bobot semua kata pada kelascV= jumlah kata unik (ukuran vocabulary)α= parameter smoothing;α=1disebut Laplace smoothing
α dicari otomatis lewat GridSearch: [0.01, 0.05, 0.1, 0.3, 0.5, 1.0, 2.0],
5-fold, scoring="f1_macro".
Rennie dkk. (2003). Alih-alih menghitung statistik kelas c, ia menghitung statistik komplemen — semua kelas selain c:
( N_t~c + α )
θ_~c,t = ───────────────────
( N_~c + α·V )
w_ct = log θ_~c,t lalu dinormalisasi: w_ct / Σ|w_ct|
ĉ = argmin Σ f_t · w_ct (argMIN, bukan argmax)
c t∈d
Dirancang khusus untuk data timpang — estimasi parameternya lebih stabil karena kelas komplemen selalu punya lebih banyak sampel daripada kelas itu sendiri.
Urutan wajib: split → oversampling hanya data latih.
Random Oversampling: tiap kelas minoritas diduplikasi acak (dengan pengembalian)
sampai jumlahnya = kelas terbanyak
Data uji tidak disentuh agar tetap mencerminkan distribusi nyata.
Confusion matrix 3×3 — C[i][j] = jumlah dokumen kelas asli i yang
diprediksi sebagai j.
Accuracy = Σ C[i][i] / Σ C[i][j] (diagonal / total)
i i,j
Precision_i = C[i][i] / Σ C[j][i] (kolom)
j
Recall_i = C[i][i] / Σ C[i][j] (baris)
j
F1_i = 2 · (Precision_i × Recall_i) / (Precision_i + Recall_i)
Macro-F1 — metrik utama:
Macro-F1 = (1/k) · Σ F1_i k = 3 kelas
i
Rata-rata tanpa bobot, sehingga kelas kecil punya pengaruh sama besar dengan kelas besar. Inilah sebabnya macro-F1 dipakai, bukan weighted-F1 yang justru menyembunyikan kegagalan pada kelas minoritas.
Baseline kelas mayoritas — acuan minimum yang wajib dilampaui:
Baseline_acc = N_mayoritas / N_total
Cross-validation 10-fold, stratified, dengan oversampling di dalam tiap fold latih (turun otomatis bila kelas terkecil < 10 sampel).
Mengukur kesepakatan label otomatis vs label manual, dikoreksi terhadap kesepakatan yang terjadi secara kebetulan:
p_o − p_e
κ = ─────────────
1 − p_e
p_o= proporsi label yang sama (kesepakatan teramati)p_e= proporsi kesepakatan yang diharapkan secara kebetulan:p_e = Σ ( n_manual,i / N ) × ( n_auto,i / N ) i
Contoh terverifikasi (10 sampel): p_o = 0,800 · p_e = 0,340 →
κ = (0,800 − 0,340) / (1 − 0,340) = 0,6970 — identik dengan
sklearn.metrics.cohen_kappa_score.
Kenapa perlu? Accuracy mentah menipu. Kalau 80% data berlabel netral, menebak "netral" terus sudah memberi 80% kecocokan tanpa pemahaman apa pun. Kappa mengoreksi hal ini: κ = 0 berarti tidak lebih baik daripada menebak.
Interpretasi Landis & Koch (1977): < 0,20 sangat rendah · 0,20–0,40 rendah · 0,40–0,60 sedang · 0,60–0,80 baik · > 0,80 sangat baik.
| Library | Versi | Dipakai untuk | Fungsi/kelas spesifik |
|---|---|---|---|
playwright |
1.61.0 | Otomasi browser Chromium untuk scraping X | sync_playwright, page.query_selector_all |
pandas |
3.0.3 | Baca/tulis CSV, manipulasi tabel | read_csv, DataFrame, drop_duplicates |
numpy |
2.5.1 | Operasi numerik rata-rata & simpangan baku | mean, std |
scikit-learn |
1.9.0 | Ekstraksi fitur, model, evaluasi | lihat rincian di bawah |
nltk |
3.10.0 | Tokenisasi teks | word_tokenize, korpus punkt |
PySastrawi |
1.2.1 | Stemming & stopword Bahasa Indonesia | StemmerFactory, StopWordRemoverFactory |
langdetect |
1.0.9 | Deteksi bahasa dokumen | detect_langs, DetectorFactory.seed |
matplotlib |
3.11.1 | Render gambar confusion matrix | pyplot.subplots, savefig |
seaborn |
0.13.2 | Heatmap confusion matrix | heatmap |
tqdm |
4.69.0 | Progress bar | tqdm, progress_apply |
openpyxl |
3.1.5 | Dukungan file Excel (opsional) | — |
wordcloud |
1.9.6 | Visualisasi kata (opsional) | — |
| Modul | Fungsi dalam penelitian ini |
|---|---|
TfidfVectorizer |
Ubah teks → matriks bobot TF-IDF (unigram + bigram) |
MultinomialNB |
Model utama sesuai judul penelitian |
ComplementNB |
Model pembanding untuk data timpang |
Pipeline |
Rangkai TF-IDF + classifier jadi satu objek |
train_test_split |
Bagi data 80/20, stratify menjaga proporsi kelas |
GridSearchCV |
Cari alpha terbaik, 5-fold, scoring="f1_macro" |
StratifiedKFold |
Cross-validation 10-fold berimbang |
resample |
Random oversampling hanya pada data latih |
accuracy_score |
Proporsi prediksi benar |
precision_score / recall_score |
Presisi & recall (average="macro") |
f1_score |
Macro-F1, metrik utama |
classification_report |
Tabel metrik per kelas |
confusion_matrix |
Matriks 3×3 aktual vs prediksi |
cohen_kappa_score |
Validasi label otomatis vs manual |
clone |
Salin pipeline bersih tiap fold CV |
Bagian ini untuk memastikan naskah skripsi menjelaskan apa yang benar-benar dijalankan program. Ketidakcocokan antara naskah dan kode adalah hal pertama yang dicari penguji, dan paling sulit dijelaskan saat sidang.
Gunakan sebagai daftar periksa sebelum menyerahkan naskah.
| Bagian naskah | Sering keliru ditulis | Yang benar-benar dihitung |
|---|---|---|
| IDF | log(N/DF) atau log₂(D/df) |
ln((1+n)/(1+df)) + 1 — varian smoothed sklearn |
| TF | frekuensi mentah / total kata | 1 + ln(f) karena sublinear_tf=True |
| Bobot akhir | TF × IDF saja |
TF × IDF lalu dinormalisasi L2 |
| Accuracy | (TP+TN)/(...) gaya 2 kelas |
Σ diagonal / Σ semua sel (3 kelas) |
| Metrik utama | accuracy | macro-F1 + baseline kelas mayoritas |
Bila Bab II memakai rumus klasik untuk perhitungan manual, tetap boleh — asalkan ditambahkan satu kalimat: "perhitungan manual menggunakan rumus klasik untuk ilustrasi, sedangkan implementasi menggunakan varian smoothed pada scikit-learn." Tanpa kalimat itu, angka manual dan angka program tidak akan pernah cocok bila diverifikasi penguji.
Pastikan angka berikut sama di naskah dan kode:
| Hal | Nilai di kode |
|---|---|
| Jumlah kata kunci pencarian | 33 |
| Rasio data latih : uji | 80 : 20 |
| Ambang netral skor lexicon | |skor| ≤ 2 |
| Jendela deteksi negasi | 3 token |
| Rentang bobot InSet | −5 … +5 |
| Cross-validation | 10-fold stratified |
| Grid pencarian alpha | 0.01 · 0.05 · 0.1 · 0.3 · 0.5 · 1.0 · 2.0 |
| Confidence minimum deteksi bahasa | 0.70 |
Semua nilai di atas dapat dikonfirmasi langsung dari main.py.
Tahapan berikut dijalankan program tetapi sering belum tertulis di naskah:
- Penyaringan bahasa — beserta alasan operator
lang:idtidak dipakai - Penyaringan akun resmi (
-from:traveloka) — beserta alasannya - Oversampling hanya pada data latih — dan mengapa urutannya tidak boleh dibalik
- Baseline kelas mayoritas sebagai acuan minimum
- Macro-F1 sebagai metrik utama, bukan accuracy
- Cohen's Kappa untuk validasi pelabelan otomatis
custom.tsv— dilaporkan sebagai "InSet augmented with domain terms", lengkap dengan daftar kata dan alasan penambahannya- Perbandingan MultinomialNB vs ComplementNB
- Tangkapan layar hasil crawling — dari program ini, bukan dari tool lain.
Kolom yang benar:
id, username, date, text, likes, retweets, replies, query - Bagian "Dilewati / gagal" dari laporan scraping — sebagai keterbatasan pengumpulan data
- Distribusi bahasa sebelum dan sesudah penyaringan
- Nilai Cohen's Kappa beserta interpretasinya
- Tabel
results/metrics_*.csv— memuat kedua model dan baseline sekaligus
Tabel yang berisi contoh perhitungan manual wajib diberi keterangan tegas, misalnya "Contoh Perhitungan Manual (bukan hasil penelitian)".
Ini penting karena perhitungan manual dengan satu atau dua data uji akan menghasilkan accuracy, precision, recall, dan F1 bernilai 1,00 (100%) — angka yang mustahil pada data sebenarnya. Tanpa keterangan, angka tersebut sangat mudah disalahpahami sebagai hasil penelitian.
Hal yang sama berlaku untuk tabel berisi nama pengguna dan ulasan rekaan yang dipakai sebagai ilustrasi tahapan — beri label "Data Ilustrasi".
Nilai yang wajar untuk dilaporkan apa adanya:
- Cohen's Kappa rendah — menunjukkan keterbatasan pelabelan berbasis lexicon, bukan kegagalan penelitian
- Macro-F1 di kisaran 0,6 pada data timpang — realistis untuk 3 kelas
- Jumlah dokumen yang gagal diambil saat scraping
Angka tinggi tanpa penjelasan asal-usulnya jauh lebih berisiko di sidang daripada angka sedang yang dapat dipertanggungjawabkan.
- Pelabelan InSet bukan ground truth — wajib divalidasi dengan Cohen's Kappa
- InSet mengandung 1.142 kata dengan polaritas bertentangan di kedua filenya,
dan tidak memuat sebagian istilah penting domain ini — sebagian dikoreksi
lewat
custom.tsv, tetapi sisa kesalahan pasti masih ada langdetectkurang akurat pada teks pendek; sebagian tweet Indonesia gaul mungkin ikut terbuang- Dataset < 1.000 dokumen menghasilkan interval kepercayaan yang sangat lebar
- Scraping X melanggar Terms of Service X; gunakan hanya untuk keperluan
akademis, dan pertimbangkan anonimisasi kolom
usernamepada lampiran skripsi
---|
| playwright | Scraping browser otomatis |
| pandas, numpy | Manipulasi data |
| scikit-learn | MultinomialNB, ComplementNB, TF-IDF, evaluasi |
| nltk | Tokenisasi |
| PySastrawi | Stemming Bahasa Indonesia |
| langdetect | Deteksi bahasa |
| matplotlib, seaborn | Visualisasi confusion matrix |
| wordcloud | Visualisasi kata (opsional) |
| tqdm | Progress bar |
Semua sumber daya eksternal beserta metode, ukuran, dan sitasinya.
| Sumber | x.com, tab Latest (f=live) |
| Alat | Playwright 1.61.0 (Chromium), session cookie manual |
| Kata kunci | 33 query netral (nama layanan + frasa pemakaian) |
| Filter query | -from:traveloka |
| Operator TIDAK dipakai | lang:id — mengembalikan indeks 2013–2014 |
| Metode | Kamus berbobot — bukan model machine learning |
| Ukuran | 3.607 positif (+1…+5) · 6.606 negatif (−5…−1) |
| Efektif setelah digabung | 9.082 entri, 677 frasa multi-kata |
| Cara kerja | Pencocokan token, frasa terpanjang didahulukan (maks 4 kata) |
| Sitasi | Koto, F. & Rahmaningtyas, G.Y. (2017). InSet Lexicon: Evaluation of a Word List for Indonesian Sentiment Analysis in Microblogs. IALP |
| Berkas | lexicon/positive.tsv, lexicon/negative.tsv |
Ini BUKAN model. Tidak ada pembelajaran mesin, tidak ada jaringan saraf, tidak ada pelatihan. Ini kamus pencarian (dictionary lookup) sederhana: setiap token dicocokkan ke tabel, bila ada padanannya diganti, bila tidak dibiarkan apa adanya.
| Metode | Substitusi token berbasis kamus (rule-based) |
| Ukuran asli | 15.167 pemetaan slang → baku |
| Aktif dipakai | 15.115 (kunci 1 huruf dibuang, istilah domain dilindungi) |
| Cara kerja | token = kamus.get(token, token) per kata |
| Contoh | bgt→banget · gk→tidak · yg→yang · bnyk→banyak |
| Sitasi | Ibrohim, M.O. & Budi, I. (2019). Multi-label Hate Speech and Abusive Language Detection in Indonesian Twitter. ALW3 @ ACL |
| Dihimpun oleh | AI Lab, Telkom University — Bahasa Indonesia NLP Resources |
| Berkas | lexicon/kamus_alay.tsv |
Penyesuaian yang dilakukan proyek ini (wajib dilaporkan di skripsi):
PROTECTED_TERMS— 20 istilah domain dikecualikan, karena kamus umum salah menerjemahkannya:cs→rekan,apk→apakah. Terbukti terpakai:csmuncul pada 15 tweet,apkpada 7 tweet- Kunci 1 huruf dibuang — terlalu agresif (
g→tidakmengubah token acak)
Koreksi kontekstual sengaja TIDAK dilakukan. Sempat ditambahkan
mrh→murahdanrmh→ramahdengan alasan konteks perjalanan, lalu dibatalkan. Singkatan kerangka konsonan tidak dapat diselesaikan tanpa konteks:mrhbisa berarti "murah" ("tiketnya mrh banget") maupun "marah" ("aku mrh sama traveloka kalau refund tidak bisa"). Mengganti tebakan kamus dengan tebakan peneliti hanya memindahkan sumber kesalahan. Diperiksa pula pada korpus 1.394 tweet: kedua singkatan muncul 0 kali, sehingga koreksi itu tidak pernah berpengaruh.Prinsip: bila kamus tidak dapat memutuskan dan konteks diperlukan, jangan menebak — ikuti kamus terpublikasi yang dapat disitasi.
| Metode | Kamus manual, disusun untuk penelitian ini |
| Ukuran | 16 entri |
| Alasan | InSet tidak memuat kecewa, tipu, penipuan, refund, komplain; dan salah polaritas pada lambat, mahal (bernilai +1) |
| Hasil | Akurasi polaritas pada 20 kata uji: 14/20 → 20/20 |
| Berkas | lexicon/custom.tsv |
Wajib dilaporkan sebagai "InSet augmented with domain-specific terms", lengkap dengan daftar kata dan alasan penambahannya.
| Metode | Algoritma Nazief-Adriani berbasis aturan imbuhan — bukan model |
| Versi | PySastrawi 1.2.1 |
| Stopword | Daftar bawaan Sastrawi dikurangi 26 kata negasi |
| Metode | Naive Bayes atas profil n-gram karakter (port dari Google language-detection) |
| Versi | 1.0.9, 55 bahasa |
| Ambang | confidence ≥ 0,70, hanya id dipertahankan |
| Reproducibility | DetectorFactory.seed = 0 — wajib, tanpa ini hasil berubah tiap dijalankan |
1.928 tweet mentah (3 sesi scraping)
→ 1.394 buang duplikat id (−534)
→ 1.062 Bahasa Indonesia, conf ≥0,70 (−332, 23,8%)
→ 852 menyebut Traveloka (−210, 19,8%)
→ 691 bukan akun korporat (−161, 18,9%)
→ 599 maks 3 dokumen/penulis (−92)
→ 588 buang duplikat teks
Dataset final: 588 dokumen · 498 penulis · 2017-07-19 s/d 2026-07-21
Cohen's Kappa = 0,0551 (slight, Landis & Koch 1977)
Kecocokan = 31,29% (184 dari 588)
Dikoreksi = 404 dokumen
Penyetelan ambang tidak menolong — 10 nilai diuji, terbaik κ = 0,105 pada ambang 10.
| Lexicon | Manusia | |
|---|---|---|
| Negatif | 172 (29,3%) | 96 (16,3%) |
| Netral | 105 (17,9%) | 365 (62,1%) |
| Positif | 311 (52,9%) | 127 (21,6%) |
Kesimpulannya berlawanan: lexicon menyatakan positif dominan, manusia menyatakan netral dominan. Penyebab utama: iklan promo dan kalimat pertanyaan dihitung lexicon sebagai kepuasan pelanggan.
Konsekuensi metodologis: label lexicon tidak dipakai sebagai sumber pelatihan. Model dilatih pada 588 label hasil anotasi manusia.
| Metrik | MultinomialNB | Baseline |
|---|---|---|
| Accuracy | 0,5763 | 0,6186 |
| Macro-F1 | 0,4253 | 0,2548 |
| Weighted-F1 | 0,5538 | — |
| Macro-Precision | 0,4547 | — |
| Macro-Recall | 0,4167 | — |
| CV-10 Macro-F1 | 0,5084 ± 0,0597 | — |
| 95% CI Macro-F1 | [0,3236 – 0,5258] | — |
Konfigurasi: α = 0,01 (GridSearch f1_macro) · latih 470 → 876 setelah
oversampling · uji 118 (dibiarkan timpang).
Per kelas: netral F1 0,73 · negatif 0,33 · positif 0,21.
Accuracy model berada DI BAWAH baseline (0,576 vs 0,619). Ini harus dilaporkan apa adanya. Model unggul pada macro-F1 (0,425 vs 0,255) karena mempelajari ketiga kelas, sedangkan baseline hanya menebak kelas mayoritas. Pada data timpang, macro-F1 adalah metrik yang bermakna — tetapi kekalahan pada accuracy tidak boleh disembunyikan.
- Anotator tunggal. Standar emas adalah ≥2 anotator independen.
- Pergeseran standar antar-sesi. Proporsi positif pada 201 sampel pertama 28,4%, pada 387 berikutnya 18,1% — indikasi standar mengetat.
- Pra-anotasi otomatis dipakai. Wajib disebut sebagai "pelabelan manual dengan bantuan pra-anotasi otomatis", bukan pelabelan manual murni.
- Sampling tidak acak. Tab Latest bersifat bias-kebaruan.
- Rentang waktu 9 tahun (2017–2026), bukan periode tunggal.
- Kelas minoritas tipis — 96 negatif dan 127 positif membatasi performa.
- X memotong tweet panjang; sebagian konteks hilang.
- Scraping melanggar ToS X — hanya untuk keperluan akademis.
Diverifikasi terhadap naskah 39 halaman.
- 3.2 Sumber Data — Playwright, session cookie,
main.py, output CSV - 3.6 Pemodelan — proporsi 80:20
| Bagian | Naskah | Kenyataan |
|---|---|---|
| 1.5.3 | "500 ulasan" | 588 dokumen |
| Gambar 3.2 | Screenshot tweet-harvest (15 kolom, Des 2024) | Kode ini 8 kolom: id, username, date, text, likes, retweets, replies, query |
| 3.3 Pelabelan | "library yang tersedia di python" | InSet + custom.tsv, dan terbukti tidak valid (κ=0,055) |
| 3.4 Preprocessing | 5 tahap | 8 tahap |
| 2.8 IDF | log(N/DF) dan log2(D/df) |
ln((1+n)/(1+df))+1 |
| 2.8 | "N = 3" | contohnya memakai 5 dokumen |
| 2.4 | "140 karakter" | X kini 280+ |
| Butir | Naskah | Benar |
|---|---|---|
| Total kata unik (V) | 31 | 33 — lumayan & alam terlewat |
| Total kata Positif (N) | 27 | 23 |
Frekuensi banget |
Positif 2 | Positif 1 |
Frekuensi lama |
Netral 1, Negatif 1 | Netral 1 saja |
| Penyebut Laplace | (f+1)/58 | (f+1)/56 |
| P(Positif|ulasan1) | 0,0000000277 | 0,0000000349 |
Tabel 3.17 juga memilih 5 kata dari 18 kata yang nilainya identik (0,0999) tanpa menyebutkan dasar pemilihannya.
Tabel 3.6 memperlihatkan stemming membalik polaritas: diperbaiki → baik,
sehingga keluhan berubah menjadi kata positif. Layak dibahas sebagai
keterbatasan, bukan disembunyikan.
- Penyaringan bahasa, normalisasi slang, filter relevansi, filter akun korporat, batas dokumen per penulis
- Validasi label dengan Cohen's Kappa — temuan utama
- Oversampling hanya pada data latih, baseline kelas mayoritas, macro-F1, interval kepercayaan bootstrap
- Jawaban Rumusan Masalah 3: sentimen dominan NETRAL (62,1%)
- Delapan keterbatasan di atas
ERROR: Session tidak ditemukan
File session/cookies.json belum ada. Lihat Setup Session X.
Session expired atau tidak valid!
Cookie sudah kedaluwarsa. Ambil ulang lewat Cookie-Editor.
ERROR: Lexicon InSet tidak ditemukan
Unduh positive.tsv dan negative.tsv ke folder lexicon/ (lihat Instalasi #4).
BERHENTI: File berlabel sudah ada
Perlindungan agar koreksi manual tidak tertimpa. Backup dulu, lalu tambahkan --force.
Browser tidak terbuka saat scraping
playwright install chromiumScraper membutuhkan tampilan layar — tidak bisa dijalankan lewat SSH tanpa display.
Hasil preprocessing tinggal sedikit
Wajar — sekitar 57% hasil scraping bukan Bahasa Indonesia. Scrape lebih banyak
dengan --max yang lebih besar.
- Rennie, J.D.M., Shih, L., Teevan, J., & Karger, D.R. (2003). Tackling the Poor Assumptions of Naive Bayes Text Classifiers. ICML, 616–623.
- Koto, F., & Rahmaningtyas, G.Y. (2017). InSet Lexicon: Evaluation of a Word List for Indonesian Sentiment Analysis in Microblogs. IALP. https://github.com/fajri91/InSet
- Ibrohim, M.O., & Budi, I. (2019). Multi-label Hate Speech and Abusive Language Detection in Indonesian Twitter. ALW3 @ ACL. — sumber Kamus Alay. https://github.com/okkyibrohim/id-multi-label-hate-speech-and-abusive-language-detection
- AI Lab, Telkom University. Bahasa Indonesia NLP Resources. https://github.com/ailabtelkom/id-NLP-resources
- Landis, J.R., & Koch, G.G. (1977). The Measurement of Observer Agreement for Categorical Data. Biometrics, 33(1), 159–174.
- Dokumentasi scikit-learn: https://scikit-learn.org/stable/modules/naive_bayes.html