From 68319b9d23f153caca67d34722d4ba42b5a70cd0 Mon Sep 17 00:00:00 2001 From: adistya13 Date: Fri, 29 May 2026 14:21:45 +0700 Subject: [PATCH] revisi model --- indonesian-stopwords-complete.txt | 2 +- model_naive_bayes.pkl | Bin 31559 -> 168455 bytes model_naive_bayes_old.pkl | Bin 0 -> 31559 bytes nrt_activation.json | 2 +- page_modules/preprocessing_page.py | 593 ++++++++++++++++------ page_modules/sentiment_page.py | 215 +++----- sentiment_service.py | 515 ++++++++++--------- tfidf_vectorizer.pkl | Bin 37921 -> 142407 bytes tfidf_vectorizer_old.pkl | Bin 0 -> 37921 bytes tweets-data/Error-15-05-2026_19-53-14.png | Bin 79471 -> 0 bytes tweets-data/Error-15-05-2026_19-54-19.png | Bin 79481 -> 0 bytes tweets-data/Error-15-05-2026_19-55-06.png | Bin 79465 -> 0 bytes tweets-data/Error-15-05-2026_19-55-20.png | Bin 79492 -> 0 bytes tweets-data/Error-15-05-2026_19-56-42.png | Bin 79708 -> 0 bytes tweets-data/Error-15-05-2026_19-57-16.png | Bin 79708 -> 0 bytes tweets-data/Error-15-05-2026_19-57-24.png | Bin 79714 -> 0 bytes tweets-data/Error-15-05-2026_19-57-37.png | Bin 79708 -> 0 bytes tweets-data/Error-15-05-2026_19-57-38.png | Bin 79708 -> 0 bytes tweets-data/Error-15-05-2026_20-01-34.png | Bin 87471 -> 0 bytes tweets-data/Error-15-05-2026_20-02-11.png | Bin 97615 -> 0 bytes tweets-data/Error-15-05-2026_20-02-48.png | Bin 89400 -> 0 bytes tweets-data/Error-15-05-2026_20-02-56.png | Bin 97122 -> 0 bytes tweets-data/Error-15-05-2026_20-03-24.png | Bin 78765 -> 0 bytes tweets-data/Error-15-05-2026_20-03-25.png | Bin 78762 -> 0 bytes tweets-data/Error-15-05-2026_20-03-36.png | Bin 86301 -> 0 bytes tweets-data/Error-15-05-2026_20-03-38.png | Bin 77098 -> 0 bytes tweets-data/Error-15-05-2026_20-04-05.png | Bin 104100 -> 0 bytes tweets-data/Error-15-05-2026_20-04-32.png | Bin 78778 -> 0 bytes tweets-data/Error-15-05-2026_20-06-42.png | Bin 78778 -> 0 bytes tweets-data/Error-15-05-2026_20-08-49.png | Bin 78555 -> 0 bytes tweets-data/Error-15-05-2026_20-09-08.png | Bin 78533 -> 0 bytes tweets-data/Error-15-05-2026_20-09-18.png | Bin 78550 -> 0 bytes tweets-data/Error-15-05-2026_20-09-25.png | Bin 78555 -> 0 bytes tweets-data/Error-15-05-2026_20-09-28.png | Bin 78550 -> 0 bytes tweets-data/Error-15-05-2026_20-09-35.png | Bin 74887 -> 0 bytes tweets-data/Error-15-05-2026_20-09-44.png | Bin 78533 -> 0 bytes tweets-data/Error-15-05-2026_20-09-57.png | Bin 78555 -> 0 bytes tweets-data/Error-15-05-2026_20-10-26.png | Bin 78778 -> 0 bytes tweets-data/Error-15-05-2026_20-10-38.png | Bin 78778 -> 0 bytes tweets-data/Error-15-05-2026_20-11-00.png | Bin 74868 -> 0 bytes tweets-data/Error-15-05-2026_20-11-36.png | Bin 77098 -> 0 bytes tweets-data/Error-15-05-2026_20-11-37.png | Bin 78778 -> 0 bytes tweets-data/Error-15-05-2026_20-11-38.png | Bin 78783 -> 0 bytes tweets-data/Error-15-05-2026_20-11-54.png | Bin 78783 -> 0 bytes tweets-data/Error-15-05-2026_20-12-40.png | Bin 78778 -> 0 bytes tweets-data/Error-15-05-2026_20-12-54.png | Bin 78765 -> 0 bytes tweets-data/Error-15-05-2026_20-13-05.png | Bin 78802 -> 0 bytes tweets-data/Error-15-05-2026_20-13-37.png | Bin 78765 -> 0 bytes tweets-data/Error-15-05-2026_20-17-43.png | Bin 80219 -> 0 bytes tweets-data/Error-15-05-2026_20-18-09.png | Bin 80219 -> 0 bytes tweets-data/Error-15-05-2026_20-18-28.png | Bin 80219 -> 0 bytes tweets-data/Error-15-05-2026_20-20-04.png | Bin 78469 -> 0 bytes tweets-data/Error-15-05-2026_20-21-05.png | Bin 80177 -> 0 bytes tweets-data/Error-15-05-2026_20-21-25.png | Bin 80172 -> 0 bytes tweets-data/Error-15-05-2026_20-22-40.png | Bin 78866 -> 0 bytes tweets-data/Error-15-05-2026_20-23-22.png | Bin 79932 -> 0 bytes tweets-data/Error-15-05-2026_20-23-32.png | Bin 73333 -> 0 bytes tweets-data/Error-15-05-2026_20-23-34.png | Bin 78861 -> 0 bytes tweets-data/hasil_latest.csv | 3 +- tweets-data/hasil_latest.old.csv | 3 +- tweets-data/hasil_top.csv | 13 +- tweets-data/hasil_top.old.csv | 13 +- 62 files changed, 786 insertions(+), 573 deletions(-) create mode 100644 model_naive_bayes_old.pkl create mode 100644 tfidf_vectorizer_old.pkl delete mode 100644 tweets-data/Error-15-05-2026_19-53-14.png delete mode 100644 tweets-data/Error-15-05-2026_19-54-19.png delete mode 100644 tweets-data/Error-15-05-2026_19-55-06.png delete mode 100644 tweets-data/Error-15-05-2026_19-55-20.png delete mode 100644 tweets-data/Error-15-05-2026_19-56-42.png delete mode 100644 tweets-data/Error-15-05-2026_19-57-16.png delete mode 100644 tweets-data/Error-15-05-2026_19-57-24.png delete mode 100644 tweets-data/Error-15-05-2026_19-57-37.png delete mode 100644 tweets-data/Error-15-05-2026_19-57-38.png delete mode 100644 tweets-data/Error-15-05-2026_20-01-34.png delete mode 100644 tweets-data/Error-15-05-2026_20-02-11.png delete mode 100644 tweets-data/Error-15-05-2026_20-02-48.png delete mode 100644 tweets-data/Error-15-05-2026_20-02-56.png delete mode 100644 tweets-data/Error-15-05-2026_20-03-24.png delete mode 100644 tweets-data/Error-15-05-2026_20-03-25.png delete mode 100644 tweets-data/Error-15-05-2026_20-03-36.png delete mode 100644 tweets-data/Error-15-05-2026_20-03-38.png delete mode 100644 tweets-data/Error-15-05-2026_20-04-05.png delete mode 100644 tweets-data/Error-15-05-2026_20-04-32.png delete mode 100644 tweets-data/Error-15-05-2026_20-06-42.png delete mode 100644 tweets-data/Error-15-05-2026_20-08-49.png delete mode 100644 tweets-data/Error-15-05-2026_20-09-08.png delete mode 100644 tweets-data/Error-15-05-2026_20-09-18.png delete mode 100644 tweets-data/Error-15-05-2026_20-09-25.png delete mode 100644 tweets-data/Error-15-05-2026_20-09-28.png delete mode 100644 tweets-data/Error-15-05-2026_20-09-35.png delete mode 100644 tweets-data/Error-15-05-2026_20-09-44.png delete mode 100644 tweets-data/Error-15-05-2026_20-09-57.png delete mode 100644 tweets-data/Error-15-05-2026_20-10-26.png delete mode 100644 tweets-data/Error-15-05-2026_20-10-38.png delete mode 100644 tweets-data/Error-15-05-2026_20-11-00.png delete mode 100644 tweets-data/Error-15-05-2026_20-11-36.png delete mode 100644 tweets-data/Error-15-05-2026_20-11-37.png delete mode 100644 tweets-data/Error-15-05-2026_20-11-38.png delete mode 100644 tweets-data/Error-15-05-2026_20-11-54.png delete mode 100644 tweets-data/Error-15-05-2026_20-12-40.png delete mode 100644 tweets-data/Error-15-05-2026_20-12-54.png delete mode 100644 tweets-data/Error-15-05-2026_20-13-05.png delete mode 100644 tweets-data/Error-15-05-2026_20-13-37.png delete mode 100644 tweets-data/Error-15-05-2026_20-17-43.png delete mode 100644 tweets-data/Error-15-05-2026_20-18-09.png delete mode 100644 tweets-data/Error-15-05-2026_20-18-28.png delete mode 100644 tweets-data/Error-15-05-2026_20-20-04.png delete mode 100644 tweets-data/Error-15-05-2026_20-21-05.png delete mode 100644 tweets-data/Error-15-05-2026_20-21-25.png delete mode 100644 tweets-data/Error-15-05-2026_20-22-40.png delete mode 100644 tweets-data/Error-15-05-2026_20-23-22.png delete mode 100644 tweets-data/Error-15-05-2026_20-23-32.png delete mode 100644 tweets-data/Error-15-05-2026_20-23-34.png diff --git a/indonesian-stopwords-complete.txt b/indonesian-stopwords-complete.txt index d671116..f99e212 100644 --- a/indonesian-stopwords-complete.txt +++ b/indonesian-stopwords-complete.txt @@ -1116,7 +1116,7 @@ yang selagi kebijakan pemerintah -komdigi + kementerian kemendag aturan diff --git a/model_naive_bayes.pkl b/model_naive_bayes.pkl index a05c6b221589e9de340bb2243adbe5b4f7189be1..065a77a39620272f44ee0fa7eca3087b91acfa57 100644 GIT binary patch literal 168455 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 EA4_nLApigX literal 31559 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 b>#^g@Qj3Z+^Yeh$M;q#y=^0KbP0|AZKv^O+ diff --git a/model_naive_bayes_old.pkl b/model_naive_bayes_old.pkl new file mode 100644 index 0000000000000000000000000000000000000000..a05c6b221589e9de340bb2243adbe5b4f7189be1 GIT binary patch literal 31559 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 b>#^g@Qj3Z+^Yeh$M;q#y=^0KbP0|AZKv^O+ literal 0 HcmV?d00001 diff --git a/nrt_activation.json b/nrt_activation.json index 498a33d..54df601 100644 --- a/nrt_activation.json +++ b/nrt_activation.json @@ -1 +1 @@ -{"activated": false, "updated_at": "2026-05-22T16:01:11.643950+00:00"} \ No newline at end of file +{"activated": true, "updated_at": "2026-05-29T05:53:53.439233+00:00"} \ No newline at end of file diff --git a/page_modules/preprocessing_page.py b/page_modules/preprocessing_page.py index 017f36d..033356c 100644 --- a/page_modules/preprocessing_page.py +++ b/page_modules/preprocessing_page.py @@ -3,17 +3,24 @@ preprocessing_page.py ===================== Halaman Bersihkan Data — NLP Pipeline 5 Tahap. -PIPELINE 5 TAHAP (selaras dengan sentiment_service.py): +PIPELINE 5 TAHAP: 1. Case Folding — ubah semua huruf jadi lowercase 2. Cleaning — hapus URL, mention, hashtag, angka, emoji, tanda baca - 3. Normalisasi — singkatan/slang → kata baku - 4. Stopword Removal — hapus kata umum; JAGA kata sentimen penting + 3. Normalisasi — singkatan/slang → kata baku (DARI FILE normalisasi) + 4. Stopword Removal — hapus kata umum (DARI FILE stopword); JAGA kata sentimen 5. Stemming — bentuk dasar kata via Sastrawi ECS -Catatan: Tokenizing tidak ditampilkan sebagai tahap tersendiri karena: - • Stopword removal & stemming sudah melakukan split() secara internal - • TF-IDF melakukan tokenisasi sendiri saat inferensi - → Tokenizing adalah proses teknis, bukan tahap utama pipeline +PERUBAHAN DARI VERSI SEBELUMNYA: + - Normalisasi kini dimuat dari 'indonesian-normalisasi-slangword-complete.txt' + (1.700+ entri), menggantikan dict hardcoded yang hanya ~60 entri. + - Stopword kini murni dari 'indonesian-stopwords-complete.txt', ditambah + noise Twitter yang spesifik — tidak ada penghapusan manual acak. + - KATA_SENTIMEN_PENTING diperluas dengan kata domain e-commerce/ongkir. + - Semua fungsi preprocessing menerima parameter eksplisit (tidak pakai global). + +CATATAN PENTING: + Pipeline ini HARUS IDENTIK dengan sentiment_service.py agar token yang + dihasilkan di sini konsisten dengan token saat training model. """ import streamlit as st @@ -33,18 +40,15 @@ from timezone_utils import ( get_timezone_name, ) - # ═══════════════════════════════════════════════════════════ # TIMEZONE HELPERS # ═══════════════════════════════════════════════════════════ - def parse_dt(series): return parse_dt_with_tz( series, st.session_state.get("user_timezone", "WIB (UTC+7)") ) - def parse_crawled_dt(series): return parse_dt_with_source_tz( series, @@ -52,7 +56,6 @@ def parse_crawled_dt(series): os.getenv("APP_TIMEZONE", "Asia/Jakarta") ) - def format_dt(value): if value is None or pd.isna(value): return "Belum ada" @@ -96,63 +99,224 @@ def _sync_dynamic_period(): # ═══════════════════════════════════════════════════════════ # PREPROCESSING PIPELINE — 5 TAHAP -# PENTING: Pipeline ini harus IDENTIK dengan sentiment_service.py -# agar token yang dihasilkan konsisten dengan training model. # -# URUTAN: -# 1. Case Folding → lowercase dulu sebelum cleaning -# 2. Cleaning → hapus noise setelah lowercase -# 3. Normalisasi → singkatan/slang → kata baku -# 4. Stopword Removal → buang kata umum, jaga kata sentimen -# 5. Stemming → bentuk dasar via Sastrawi ECS +# ┌─────────────────────────────────────────────────────┐ +# │ PENTING: Pipeline ini HARUS identik dengan │ +# │ sentiment_service.py agar token konsisten! │ +# │ │ +# │ URUTAN WAJIB: │ +# │ 1. Case Folding → lowercase dulu │ +# │ 2. Cleaning → hapus noise setelah lowercase │ +# │ 3. Normalisasi → slang→baku setelah bersih │ +# │ 4. Stopword → buang kata umum, jaga sentimen │ +# │ 5. Stemming → bentuk dasar via Sastrawi ECS │ +# └─────────────────────────────────────────────────────┘ # ═══════════════════════════════════════════════════════════ + +# ─────────────────────────────────────────────────────────── +# KATA SENTIMEN PENTING +# Kata-kata ini WAJIB DIJAGA dan tidak boleh dihapus saat +# stopword removal, meskipun ada di file stopword. +# +# Kenapa perlu? Karena file stopword mengandung kata seperti +# "tidak", "belum", "sangat" yang justru krusial untuk +# menentukan sentimen positif/negatif suatu kalimat. +# ─────────────────────────────────────────────────────────── KATA_SENTIMEN_PENTING = { - # Negasi + # ── Negasi (pembalik makna kalimat) ────────────────── + # "tidak bagus" ≠ "bagus" → "tidak" wajib ada "tidak", "bukan", "jangan", "kurang", "belum", "tanpa", - # Positif + # ── Intensitas (penguat/pelemah sentimen) ───────────── + # "sangat bagus" lebih positif dari "bagus" saja + "sangat", "banget", "sekali", "paling", "amat", + "luar", "biasa", # ← "luar biasa" = dua token, keduanya dijaga + # ── Positif umum ────────────────────────────────────── "keren", "bagus", "mantap", "setuju", "dukung", "mendukung", "andal", "handal", "gercep", "bangga", "senang", "suka", - "baik", "benar", "tepat", "oke", + "baik", "benar", "tepat", "oke", "puas", "sejahtera", "berkembang", "maju", "inovatif", "tegas", "sigap", "tanggap", "adil", "bijak", "bermanfaat", "untung", "berhasil", "sukses", "solusi", "manfaat", "berguna", "membantu", "bantu", "pro", "lanjut", - "sangat", "banget", "sekali", "paling", "amat", "luar", "biasa", - # Negatif + # ── Positif domain e-commerce / ongkir ──────────────── + "gratis", "murah", "hemat", "terjangkau", "cepat", + "aman", "mudah", "praktis", "terpercaya", + # ── Negatif umum ────────────────────────────────────── "kecewa", "buruk", "jelek", "parah", "gagal", "hancur", "rusak", "bohong", "tipu", "korupsi", - # Emosi - "marah", "sedih", "khawatir", + # ── Negatif domain e-commerce / ongkir ──────────────── + "mahal", "lambat", "lelet", "ribet", "susah", "repot", + "rugi", "boros", + # ── Emosi ───────────────────────────────────────────── + "marah", "sedih", "khawatir", "kecewa", } -def _load_stopwords(): +# ─────────────────────────────────────────────────────────── +# LOAD NORMALIZATION DARI FILE +# File: indonesian-normalisasi-slangword-complete.txt +# Format per baris: slang,kata_baku +# Contoh: gk,tidak | ongkir,ongkos kirim | free,gratis +# ─────────────────────────────────────────────────────────── +def _load_normalization() -> dict: + """ + Muat kamus normalisasi dari file eksternal. + + KENAPA DARI FILE? + File berisi 1.700+ pasang slang→baku yang jauh lebih lengkap + dibanding dict hardcoded. Dengan ini, kata seperti: + gk/ga/gak/kagak/ngga → semua jadi "tidak" + bgt/bngt/bget → semua jadi "sangat" + ongkir/ongkr → jadi "ongkos kirim" + ...dan ribuan kasus lainnya tertangani otomatis. + + Setelah file dimuat, override dengan entri khusus domain + (nama platform, singkatan kebijakan) yang mungkin belum ada + di file generik. + """ + norm_file = "indonesian-normalisasi-slangword-complete.txt" + norm_dict: dict = {} + + try: + with open(norm_file, "r", encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line: + continue + # Split hanya pada koma pertama — nilai bisa mengandung koma + # Contoh: "on the way, sedang di jalan,dijalan" → split jadi 2 bagian + parts = line.split(",", 1) + if len(parts) != 2: + continue + # Bersihkan tanda kutip liar di awal/akhir (ada di beberapa baris file) + slang = parts[0].strip().strip("'\"").lower() + normal = parts[1].strip().lower() + if slang and normal: + norm_dict[slang] = normal + except FileNotFoundError: + # Jika file tidak ditemukan, lanjut dengan dict kosong. + # Entri domain di bawah tetap akan ditambahkan. + st.warning( + "⚠️ File normalisasi tidak ditemukan: " + f"'{norm_file}'. Hanya entri domain yang aktif." + ) + + # ── Override khusus domain ─────────────────────────── + # Entri ini menimpa file generik karena domain spesifik + # membutuhkan perlakuan khusus (nama platform tidak diubah, + # singkatan kebijakan punya padanan resmi, dll.) + DOMAIN_OVERRIDES: dict = { + # Nama platform — pertahankan apa adanya + "shopee": "shopee", + "tokopedia": "tokopedia", + "lazada": "lazada", + "tiktok": "tiktok", + "bukalapak": "bukalapak", + "blibli": "blibli", + # Logistik — pertahankan apa adanya + "sicepat": "sicepat", + "jne": "jne", + "jnt": "jnt", + "anteraja": "anteraja", + "ninja": "ninja", + # Ongkir & belanja + "freeongkir": "gratis ongkos kirim", + "gratisongkir": "gratis ongkos kirim", + "ongkir": "ongkos kirim", + "ongkr": "ongkos kirim", + "bykrm": "biaya kirim", + "biayakirim": "biaya pengiriman", + # Kebijakan & lembaga + "komdigi": "komdigi", + "kemendag": "kementerian perdagangan", + "kominfo": "kementerian komunikasi", + # E-commerce umum + "ecommerce": "e commerce", + "marketplace": "marketplace", + "seller": "penjual", + "buyer": "pembeli", + "online": "online", + } + norm_dict.update(DOMAIN_OVERRIDES) + + return norm_dict + + +# ─────────────────────────────────────────────────────────── +# LOAD STOPWORDS DARI FILE +# File: indonesian-stopwords-complete.txt +# Format: satu kata per baris +# ─────────────────────────────────────────────────────────── +def _load_stopwords() -> set: + """ + Muat daftar stopword dari file eksternal. + + PROSES SETELAH MUAT FILE: + 1. Hapus KATA_SENTIMEN_PENTING dari daftar + → Agar "tidak", "belum", "sangat", dll. tidak ikut dibuang + 2. Tambahkan noise Twitter/sosmed yang memang harus dibuang + → "rt", "amp", sisa URL, suara tawa, partikel informal + + KENAPA DARI FILE? + File berisi 700+ stopword Indonesia yang lebih lengkap dan + terstandar dibanding daftar manual. Kita tidak perlu menambah/ + mengurangi secara manual kecuali untuk dua kategori di atas. + """ stopword_file = "indonesian-stopwords-complete.txt" - base = set() + base: set = set() + try: with open(stopword_file, "r", encoding="utf-8") as f: - base = set(f.read().splitlines()) + for line in f: + word = line.strip().lower() + if word: + base.add(word) except FileNotFoundError: + # Fallback minimal — cukup untuk tetap jalan + st.warning( + "⚠️ File stopword tidak ditemukan: " + f"'{stopword_file}'. Menggunakan daftar minimal." + ) base = { "yang", "dan", "di", "ke", "dari", "ini", "itu", "dengan", "untuk", "pada", "adalah", "oleh", "ada", "ya", "akan", "atau", "juga", "sama", "karena", - "jika", "sudah", "telah", + "jika", "sudah", "telah", "jadi", "bisa", } + + # ── Langkah 1: Lindungi kata sentimen ──────────────── + # Beberapa kata sentimen penting ADA di file stopword + # (misal: "tidak", "belum", "sangat", "paling", "kurang"). + # Kita HAPUS dari stopword agar tidak ikut dibuang. for kata in KATA_SENTIMEN_PENTING: base.discard(kata) - base.update({ - "rt", "amp", "https", "http", "co", "t", - "wkwk", "wkwkwk", "haha", "hehe", "xixi", - "yg", "dgn", "utk", "dr", "krn", "tp", "jd", "sdh", - "aja", "doang", "nih", "sih", "dong", "deh", - "loh", "lah", "tuh", "kak", "gan", - }) + + # ── Langkah 2: Tambah noise Twitter/sosmed ──────────── + # Ini bukan stopword bahasa Indonesia biasa, tapi noise + # yang sangat sering muncul di tweet dan tidak bermakna. + TWITTER_NOISE: set = { + # Artefak Twitter + "rt", "amp", + # Sisa URL setelah cleaning (kadang lolos) + "https", "http", "co", "pic", + # Suara tawa (tidak bermakna untuk sentimen) + "wkwk", "wkwkwk", "wkwkwkwk", + "haha", "hahaha", "hehe", "hihi", "huhu", "xixi", + # Partikel informal yang tidak bermakna + "nih", "sih", "dong", "deh", "loh", "lah", "tuh", + "kak", "gan", "bro", "sob", "min", + } + base.update(TWITTER_NOISE) + return base +# ─────────────────────────────────────────────────────────── +# LOAD STEMMER +# ─────────────────────────────────────────────────────────── def _load_stemmer(): + """Muat stemmer Sastrawi. Return None jika tidak tersedia.""" try: from Sastrawi.Stemmer.StemmerFactory import StemmerFactory return StemmerFactory().create_stemmer() @@ -160,131 +324,198 @@ def _load_stemmer(): return None -NORMALISASI = { - # Negasi - "gk": "tidak", "ga": "tidak", "gak": "tidak", - "nggak": "tidak", "ngga": "tidak", "tdk": "tidak", - "tak": "tidak", "enggak": "tidak", "engga": "tidak", - "kagak": "tidak", "kaga": "tidak", "ndak": "tidak", - "gkk": "tidak", "ngak": "tidak", - # Kata ganti - "yg": "yang", "dgn": "dengan", "utk": "untuk", - "org": "orang", "krn": "karena", "dr": "dari", - "sm": "sama", "pd": "pada", "dlm": "dalam", - "bwt": "buat", "trm": "terima", - # Verba - "tp": "tapi", "tpi": "tapi", "jd": "jadi", - "sdh": "sudah", "blm": "belum", "emg": "memang", - "emang": "memang", "gimana": "bagaimana", - "gitu": "begitu", "gini": "begini", - "udah": "sudah", "udh": "sudah", - "mau": "mau", - # Intensitas - "bgt": "banget", "bngt": "banget", "bget": "banget", "bgtt": "banget", - # Positif informal - "bener": "benar", "beneran": "benar", - "mantep": "mantap", "mntap": "mantap", - "kece": "keren", "kece bgt": "keren banget", - "cucok": "cocok", "cucuk": "cocok", - "cakep": "bagus", "oke bgt": "oke banget", - "sip": "baik", "siipp": "baik", - "top": "terbaik", "topbgt": "terbaik banget", - "jos": "bagus", "josss": "bagus", - "goks": "luar biasa", - "setujuu": "setuju", "stuju": "setuju", - "dukung": "dukung", - "proud": "bangga", - "mantul": "mantap betul", - # Negatif informal - "ancur": "hancur", "ancrr": "hancur", - "parahh": "parah", "parahhh": "parah", - "gagall": "gagal", - "ngaco": "tidak benar", - "ngasal": "tidak benar", - "receh": "tidak penting", - "gaje": "tidak jelas", - "asal": "sembarangan", - # Domain - "ongkir": "ongkos kirim", - "freeongkir": "gratis ongkos kirim", - "gratisongkir": "gratis ongkos kirim", - "free": "gratis", - "ecommerce": "e commerce", - "komdigi": "komdigi", - "subsidi": "subsidi", - "marketplace": "marketplace", - "seller": "penjual", - "buyer": "pembeli", - "online": "online", - "shopee": "shopee", - "tokopedia": "tokopedia", - "lazada": "lazada", - "tiktok": "tiktok", -} +# ═══════════════════════════════════════════════════════════ +# FUNGSI 5 TAHAP PREPROCESSING +# Setiap fungsi bertanggung jawab SATU tahap saja. +# Input & output setiap tahap dijelaskan di docstring. +# ═══════════════════════════════════════════════════════════ - -# ── Tahap 1: Case Folding ─────────────────────────────────────────────────── def step1_case_folding(text: str) -> str: + """ + TAHAP 1 — CASE FOLDING + Input : teks asli (campuran huruf besar/kecil) + Output: semua huruf jadi lowercase + + Kenapa pertama? + Agar tahap berikutnya (cleaning, normalisasi) bekerja + secara konsisten — regex dan dict lookup case-sensitive. + Contoh: "Gratis" → "gratis", "ONGKIR" → "ongkir" + """ return str(text).lower() -# ── Tahap 2: Cleaning ─────────────────────────────────────────────────────── def step2_cleaning(text: str) -> str: + """ + TAHAP 2 — CLEANING + Input : teks lowercase + Output: teks bersih dari semua elemen noise + + Urutan pembersihan PENTING: + 1. URL dulu (sebelum @ dan # agar tidak salah potong) + 2. Mention (@username) + 3. Hashtag (#topik) + 4. Angka + 5. Emoji & simbol unicode + 6. Tanda baca + 7. Karakter non-latin (huruf Arab, Cina, dll.) + 8. Spasi berlebih + """ + # 1. Hapus URL (http, https, www) text = re.sub(r"http\S+|www\S+|https\S+", "", text) + # 2. Hapus mention Twitter (@username) text = re.sub(r"@\w+", "", text) + # 3. Hapus hashtag (#topik) text = re.sub(r"#\w+", "", text) + # 4. Hapus angka dan digit text = re.sub(r"\d+", "", text) + # 5. Hapus emoji & simbol unicode (berbagai range) text = re.sub( - r"[\U00010000-\U0010ffff" - r"\U0001F600-\U0001F64F" - r"\U0001F300-\U0001F5FF" - r"\U0001F680-\U0001F6FF" - r"\U0001F1E0-\U0001F1FF" - r"\u2600-\u26FF\u2700-\u27BF" - r"]+", "", text, flags=re.UNICODE + r"[" + r"\U00010000-\U0010ffff" # Suplemen karakter unicode + r"\U0001F600-\U0001F64F" # Emotikon wajah + r"\U0001F300-\U0001F5FF" # Simbol & piktogram + r"\U0001F680-\U0001F6FF" # Transport & peta + r"\U0001F1E0-\U0001F1FF" # Bendera negara + r"\u2600-\u26FF" # Simbol campuran + r"\u2700-\u27BF" # Dingbats + r"]+", + "", text, flags=re.UNICODE ) + # 6. Hapus tanda baca (.,!?;: dll.) text = text.translate(str.maketrans("", "", string.punctuation)) + # 7. Hapus karakter non-latin (hanya sisakan huruf a-z dan spasi) text = re.sub(r"[^a-zA-Z\s]", "", text) + # 8. Normalisasi spasi berlebih → satu spasi, lalu strip text = re.sub(r"\s+", " ", text).strip() return text -# ── Tahap 3: Normalisasi ──────────────────────────────────────────────────── -def step3_normalization(text: str) -> str: - return " ".join(NORMALISASI.get(word, word) for word in text.split()) +def step3_normalization(text: str, norm_dict: dict) -> str: + """ + TAHAP 3 — NORMALISASI + Input : teks bersih (sudah case fold + cleaning) + norm_dict : kamus {slang: kata_baku} dari file + Output: teks dengan slang/singkatan sudah diganti kata baku + + Cara kerja: token per token (word by word). + Setiap token dicari di norm_dict. + Jika ada → ganti. Jika tidak ada → biarkan. + + Contoh: + "gk bs ongkir" → "tidak bisa ongkos kirim" + "mantep bgt" → "mantap sangat" + + Kenapa setelah Cleaning? + Karena slang di file ditulis dalam bentuk sudah lowercase + dan sudah tanpa tanda baca. Jika normalisasi dilakukan + sebelum cleaning, banyak entri tidak cocok. + """ + tokens = text.split() + normalized = [norm_dict.get(token, token) for token in tokens] + return " ".join(normalized) -# ── Tahap 4: Stopword Removal ─────────────────────────────────────────────── def step4_stopword_removal(tokens: list, stopwords: set) -> list: - return [ - w for w in tokens - if (w not in stopwords or w in KATA_SENTIMEN_PENTING) and len(w) > 2 - ] + """ + TAHAP 4 — STOPWORD REMOVAL + Input : list token (hasil split dari teks ternormalisasi) + stopwords : set kata yang harus dibuang (dari file) + Output: list token bersih + + ATURAN PENYARINGAN (prioritas urutan): + 1. JAGA token yang ada di KATA_SENTIMEN_PENTING + → meskipun juga ada di stopwords, tetap disimpan + 2. BUANG token yang ada di stopwords + 3. BUANG token dengan panjang ≤ 2 karakter + → menghilangkan sisa noise seperti "rt", "yg", "di" + → PENGECUALIAN: token di KATA_SENTIMEN_PENTING tetap disimpan + walau ≤ 2 karakter (contoh: "ok" jika masuk sentimen) + + Kenapa setelah Normalisasi? + Agar "gak" yang sudah dinormalisasi jadi "tidak" tidak ikut + dibuang — "tidak" dilindungi di KATA_SENTIMEN_PENTING. + """ + result = [] + for token in tokens: + # Prioritas 1: selalu simpan jika kata sentimen penting + if token in KATA_SENTIMEN_PENTING: + result.append(token) + continue + # Prioritas 2: buang jika stopword + if token in stopwords: + continue + # Prioritas 3: buang jika terlalu pendek (noise) + if len(token) <= 2: + continue + # Lolos semua filter → simpan + result.append(token) + return result -# ── Tahap 5: Stemming ─────────────────────────────────────────────────────── def step5_stemming(tokens: list, stemmer) -> list: + """ + TAHAP 5 — STEMMING + Input : list token setelah stopword removal + stemmer : objek Sastrawi (atau None) + Output: list token dalam bentuk kata dasar + + Algoritma: Enhanced Confix Stripping (ECS) via Sastrawi + Contoh: + "pengiriman" → "kirim" + "pembatasan" → "batas" + "berlari" → "lari" + "makanan" → "makan" + + Jika stemmer None (Sastrawi tidak terinstal), token dikembalikan + apa adanya tanpa error. + """ if stemmer is None: return tokens - return [stemmer.stem(w) for w in tokens] + return [stemmer.stem(token) for token in tokens] -def full_preprocessing(text: str, stopwords: set, stemmer): +# ─────────────────────────────────────────────────────────── +# FUNGSI UTAMA — JALANKAN SEMUA 5 TAHAP +# ─────────────────────────────────────────────────────────── +def full_preprocessing( + text: str, + stopwords: set, + stemmer, + norm_dict: dict, +) -> dict: """ - Jalankan 5 tahap preprocessing dan kembalikan dict hasil setiap tahap. + Jalankan 5 tahap preprocessing secara berurutan dan kembalikan + hasil setiap tahap sebagai dict (untuk ditampilkan di tabel). - URUTAN TAHAP: - 1. Case Folding → lowercase - 2. Cleaning → hapus noise - 3. Normalisasi → normalisasi kata - 4. Stopword Removal → buang stopword (split() dilakukan internal) - 5. Stemming → bentuk dasar + Parameter: + text : teks tweet asli + stopwords : set stopword (dari _load_stopwords) + stemmer : objek Sastrawi (dari _load_stemmer) + norm_dict : kamus normalisasi (dari _load_normalization) + + Return dict berisi: + setelah_casefolding : hasil Tahap 1 + setelah_cleaning : hasil Tahap 2 + setelah_normalisasi : hasil Tahap 3 + setelah_stopword : hasil Tahap 4 (joined string) + clean_text : hasil akhir Tahap 5 (joined string) + _tokens_clean : hasil Tahap 5 sebagai list (untuk analisis) """ - s1_fold = step1_case_folding(text) - s2_clean = step2_cleaning(s1_fold) - s3_norm = step3_normalization(s2_clean) - s4_filtered = step4_stopword_removal(s3_norm.split(), stopwords) - s5_stemmed = step5_stemming(s4_filtered, stemmer) + # Tahap 1 — Case Folding + s1_fold = step1_case_folding(text) + + # Tahap 2 — Cleaning + s2_clean = step2_cleaning(s1_fold) + + # Tahap 3 — Normalisasi (perlu norm_dict) + s3_norm = step3_normalization(s2_clean, norm_dict) + + # Tahap 4 — Stopword Removal (split → filter → simpan sebagai list) + s4_tokens = s3_norm.split() + s4_filtered = step4_stopword_removal(s4_tokens, stopwords) + + # Tahap 5 — Stemming + s5_stemmed = step5_stemming(s4_filtered, stemmer) return { "setelah_casefolding": s1_fold, @@ -292,7 +523,7 @@ def full_preprocessing(text: str, stopwords: set, stemmer): "setelah_normalisasi": s3_norm, "setelah_stopword": " ".join(s4_filtered), "clean_text": " ".join(s5_stemmed), - "_tokens_clean": s5_stemmed, + "_tokens_clean": s5_stemmed, # list, untuk Counter frekuensi kata } @@ -443,7 +674,7 @@ def _render_page_header(): # PIPELINE STEPS CARDS # ═══════════════════════════════════════════════════════════ -def _render_pipeline_steps(stemmer_ok): +def _render_pipeline_steps(stemmer_ok: bool, norm_count: int, sw_count: int): steps = [ { "num": "01", "anim": "pipe-1", @@ -456,8 +687,8 @@ def _render_pipeline_steps(stemmer_ok): '"ONGKIR" → "ongkir"', '"KEREN" → "keren"', "Seluruh karakter → huruf kecil", - "Dilakukan pertama agar cleaning konsisten", - "Basis untuk normalisasi & stopword", + "Dilakukan PERTAMA agar regex & dict konsisten", + "Fondasi seluruh tahap berikutnya", ], }, { @@ -479,30 +710,30 @@ def _render_pipeline_steps(stemmer_ok): "num": "03", "anim": "pipe-3", "icon": "🔄", "color": "#16a34a", "dark": "#14532d", "bg": "linear-gradient(135deg,#f0fdf4,#dcfce7)", "border": "#86efac", - "title": 'Normalisasi ✦ Diperluas', - "desc": "Mengubah kata tidak baku, singkatan, dan slang menjadi kata baku.", + "title": f'Normalisasi ✦ {norm_count:,} entri', + "desc": "Mengubah kata tidak baku, singkatan, dan slang menjadi kata baku (dari file).", "items": [ - "gk/ga/gak/kagak → tidak", + "gk/ga/gak/kagak/ngga → tidak", + "bgt/bngt/bget → sangat", "ongkir → ongkos kirim", "mantep → mantap", - "kece → keren", - "jos/josss → bagus", "free → gratis", + f"Total: {norm_count:,} pasang slang→baku dimuat", ], }, { "num": "04", "anim": "pipe-4", "icon": "🚫", "color": "#ea580c", "dark": "#7c2d12", "bg": "linear-gradient(135deg,#fff7ed,#ffedd5)", "border": "#fed7aa", - "title": 'Stopword Removal ✦ Diperbaiki', - "desc": "Membuang kata umum; kata sentimen penting DIJAGA.", + "title": f'Stopword Removal ✦ {sw_count:,} kata', + "desc": "Membuang kata umum dari file; kata sentimen DIJAGA.", "items": [ - "Hapus kata umum (dan, di, ke...)", - "Hapus token < 3 karakter", - "JAGA negasi: tidak, bukan, jangan", - "JAGA positif: keren, bagus, mantap", - "JAGA evaluatif: setuju, dukung, bijak", - "JAGA intensitas: banget, sangat, sekali", + f"{sw_count:,} stopword dimuat dari file", + "JAGA negasi: tidak, bukan, jangan, belum", + "JAGA positif: keren, bagus, mantap, gratis", + "JAGA negatif: kecewa, buruk, gagal, mahal, mending, malah", + "JAGA intensitas: sangat, banget, sekali", + "Hapus token ≤ 2 karakter (noise)", ], }, { @@ -678,7 +909,6 @@ def _render_live_example(df_c): "Contoh tweet acak dari dataset — refresh halaman untuk contoh berbeda" ) - # Urutan tampilan sesuai pipeline: CF → Clean → Norm → Stop → Stem steps_ex = [ ("📄 Teks Asli", "text_asli", "#0f172a", "#f8fafc", "#e2e8f0"), ("① Setelah Case Folding", "setelah_casefolding", "#0c4a6e", "#eff6ff", "#bfdbfe"), @@ -727,10 +957,14 @@ def _render_top_words_chart(df_c): ) if "_tokens_clean" in df_c.columns: - all_words = [w for tokens in df_c["_tokens_clean"] for w in (tokens if isinstance(tokens, list) else [])] + all_words = [ + w for tokens in df_c["_tokens_clean"] + for w in (tokens if isinstance(tokens, list) else []) + ] else: all_words = " ".join(df_c["clean_text"].fillna("")).split() + # Filter minimum 3 karakter (konsisten dengan step4) filtered_words = [w for w in all_words if len(w) > 2] word_freq = Counter(filtered_words).most_common(20) @@ -821,19 +1055,29 @@ def show(): unsafe_allow_html=True ) - # ── Pipeline Overview ───────────────────────────────────── + # ── Muat resource preprocessing (sekali per session) ────── + # Semua tiga resource dimuat di sini, bukan di dalam loop, + # agar tidak memuat ulang setiap tweet. + stemmer = _load_stemmer() + stopwords = _load_stopwords() + norm_dict = _load_normalization() + + # ── Pipeline Overview ────────────────────────────────────── _section_header( "🔬 Alur NLP Pipeline — 5 Tahap Preprocessing", "Setiap tweet diproses berurutan melalui 5 tahap sebelum siap dianalisis sentimennya" ) - stemmer_tmp = _load_stemmer() - _render_pipeline_steps(stemmer_tmp is not None) + _render_pipeline_steps( + stemmer_ok=stemmer is not None, + norm_count=len(norm_dict), + sw_count=len(stopwords), + ) _gap("sm") _render_flow_arrow() _gap("md") - # ── Load data ───────────────────────────────────────────── + # ── Load data dari database ──────────────────────────────── try: df_all = pd.read_sql("SELECT * FROM tweets ORDER BY created_at DESC", engine) if df_all.empty: @@ -848,13 +1092,17 @@ def show(): s_dt = pd.Timestamp(start_date) e_dt = pd.Timestamp(end_date) - df = df_all[(df_all["created_at"] >= s_dt) & (df_all["created_at"] <= e_dt)].copy() + df = df_all[ + (df_all["created_at"] >= s_dt) & (df_all["created_at"] <= e_dt) + ].copy() if df.empty: st.warning(f"⚠️ Tidak ada tweet dengan tanggal asli dalam periode {filter_label}.") return - # ── Cache preprocessing ─────────────────────────────────── + # ── Cache preprocessing ──────────────────────────────────── + # Cache key: kombinasi mode + periode + jumlah tweet di DB + # Jika ada tweet baru → cache otomatis invalid → proses ulang total_tweets_in_db = get_tweet_count() latest_crawl_marker = get_latest_crawl_time() or "no-crawl" data_marker = (total_tweets_in_db, latest_crawl_marker) @@ -864,6 +1112,7 @@ def show(): f"{start_date}_{end_date}_{total_tweets_in_db}_{latest_crawl_marker}" ) + # Bersihkan cache lama untuk mode/periode yang sudah tidak aktif for old_key in list(st.session_state.keys()): if old_key.startswith("pp5_") and old_key != cache_key: del st.session_state[old_key] @@ -871,13 +1120,16 @@ def show(): force_refresh = data_marker != st.session_state.get("_pp_last_data_marker") if cache_key not in st.session_state or force_refresh: - stemmer = _load_stemmer() - stopwords = _load_stopwords() - with st.spinner("🧹 Menjalankan 5 tahap preprocessing…"): results = [] for _, row in df.iterrows(): - r = full_preprocessing(row["text"], stopwords, stemmer) + # Jalankan 5 tahap — norm_dict diteruskan sebagai parameter + r = full_preprocessing( + text = row["text"], + stopwords = stopwords, + stemmer = stemmer, + norm_dict = norm_dict, + ) r["tweet_id"] = row.get("tweet_id", "") r["text_asli"] = row["text"] r["created_at"] = row["created_at"] @@ -885,16 +1137,19 @@ def show(): results.append(r) df_c = pd.DataFrame(results) + # Buang baris yang clean_text-nya kosong setelah semua 5 tahap df_c = df_c[df_c["clean_text"].str.strip().str.len() > 0].copy() + # Reset index agar rapi + df_c = df_c.reset_index(drop=True) - st.session_state[cache_key] = df_c - st.session_state[cache_key + "_stemmer_ok"] = stemmer is not None - st.session_state["_pp_last_data_marker"] = data_marker + st.session_state[cache_key] = df_c + st.session_state[cache_key + "_sw_ok"] = stemmer is not None + st.session_state["_pp_last_data_marker"] = data_marker df_c = st.session_state[cache_key] - stemmer_ok = st.session_state.get(cache_key + "_stemmer_ok", False) + stemmer_ok = st.session_state.get(cache_key + "_sw_ok", False) - # ── Statistik ───────────────────────────────────────────── + # ── Statistik ────────────────────────────────────────────── removed = len(df) - len(df_c) _section_header( @@ -908,7 +1163,7 @@ def show(): _render_live_example(df_c) _gap("lg") - # ── Tabel ───────────────────────────────────────────────── + # ── Tabel ────────────────────────────────────────────────── _section_header( "📋 Tabel Perbandingan Teks per Tahap", f"{len(df_c):,} tweet · {filter_label} — scroll horizontal untuk lihat semua kolom" @@ -918,7 +1173,7 @@ def show(): df_c = df_c.copy() df_c["crawled_at"] = pd.NaT - # Kolom ditampilkan sesuai urutan pipeline: CF → Clean → Norm → Stop → Stem + # Kolom ditampilkan sesuai urutan pipeline disp = df_c[[ "tweet_id", "created_at", "crawled_at", "text_asli", @@ -968,10 +1223,10 @@ def show(): _render_top_words_chart(df_c) _gap("lg") - # ── Simpan ke session state ─────────────────────────────── + # ── Simpan ke session state untuk halaman sentimen ───────── st.session_state["preprocessed_df"] = df_c - # ── Download ────────────────────────────────────────────── + # ── Download ─────────────────────────────────────────────── st.markdown("""
list[tuple[str, float]]: - """ - Prediksi sentimen menggunakan Hybrid Classifier dari sentiment_service. - - Pipeline per tweet: - 1. preprocess_for_model() → teks untuk TF-IDF + NB - 2. preprocess_untuk_lexicon() → teks untuk pengecekan lexicon - 3. _hitung_skor_lexicon() → hitung sinyal positif/negatif kuat - 4. _klasifikasi_hybrid() → putuskan label + confidence - - Return: list of (label, confidence) - """ +def predict_batch_hybrid(texts): results = [] for text in texts: teks_model = preprocess_for_model(text) teks_lexicon = preprocess_untuk_lexicon(text) teks_lower = str(text).lower() - - skor = _hitung_skor_lexicon(teks_lexicon) - label, conf = _klasifikasi_hybrid(teks_model, skor, teks_lower) + skor = _hitung_skor_lexicon(teks_lexicon) + label, conf = _klasifikasi_hybrid(teks_model, skor, teks_lower) results.append((label, conf)) return results -def preprocess_single(text: str) -> str: - """Preprocess satu teks untuk disimpan ke kolom clean_text.""" +def preprocess_single(text): return preprocess_for_model(text) # ───────────────────────────────────────────────────────────── -# Shared UI helpers +# UI helpers # ───────────────────────────────────────────────────────────── def _section_header(title, subtitle=""): @@ -173,6 +138,20 @@ def _section_gap(size="md"): st.markdown(f'
', unsafe_allow_html=True) +def _card_open(extra_style=""): + """Buka div card pengganti st.container(border=True).""" + st.markdown( + f'
', + unsafe_allow_html=True, + ) + + +def _card_close(): + st.markdown('
', unsafe_allow_html=True) + + def _render_sentiment_styles(): st.markdown("""