""" Nlp.py - Modul Pemrosesan Teks (Natural Language Processing) Proyek Tugas Akhir: Analisis Sentimen Komentar Berbasis TF-IDF & Logistic Regression Modul ini menangani tahapan preprocessing teks: 1. Case Folding 2. Slang Negation Replacement ('t) 3. Reduksi Karakter Berulang 4. Tokenizing (NLTK) 5a. Normalisasi Rumus Alay 1 (Kamus Manual + kamus_alay.csv) 5b. Normalisasi Rumus Alay 2 (new_kamusalay.csv - 15.000+ entri) 6. Cleaning (Hanya huruf alfabet) 7. Stopword Removal (Menyimpan kata negasi & penegas) 8. Stemming Bahasa Indonesia (Sastrawi) 9. Negation Tagging (menggabungkan 'tidak_bagus') """ import os import re import warnings try: import regex except ImportError: regex = None import pandas as pd import nltk from nltk.tokenize import word_tokenize from nltk.corpus import stopwords from Sastrawi.Stemmer.StemmerFactory import StemmerFactory warnings.filterwarnings("ignore") # Kata-kata yang ada di vocabulary model TF-IDF agar tidak ditimpa oleh kamus ke kata yang OOV KATA_PROTEKSI_TFIDF = {'aku', 'lu', 'kasih', 'moga', 'cakep'} # Memastikan korpus NLTK yang dibutuhkan terunduh for resource in ['punkt', 'punkt_tab', 'stopwords']: try: nltk.data.find(f'tokenizers/{resource}' if 'punkt' in resource else f'corpora/{resource}') except LookupError: nltk.download(resource, quiet=True) # ========================================== # 1. KAMUS NORMALISASI DAN SLANG / ALAY # ========================================== normalisasi = { # Singkatan umum 'bgt': 'banget', 'bgtt': 'banget', 'bangett': 'banget', 'bngt': 'banget', 'bngtt': 'banget', 'yg': 'yang', 'yng': 'yang', 'dgn': 'dengan', 'dngn': 'dengan', 'dr': 'dari', 'drpd': 'daripada', 'krn': 'karena', 'karna': 'karena', 'krena': 'karena', 'klo': 'kalau', 'kalo': 'kalau', 'kl': 'kalau', 'klau': 'kalau', 'aja': 'saja', 'aj': 'saja', 'sja': 'saja', 'udah': 'sudah', 'udh': 'sudah', 'sdh': 'sudah', 'dah': 'sudah', 'blm': 'belum', 'blom': 'belum', 'belom': 'belum', 'tp': 'tapi', 'tpi': 'tapi', 'tpp': 'tapi', 'trs': 'terus', 'trus': 'terus', 'terusss': 'terus', 'smp': 'sampai', 'sampe': 'sampai', 'ampe': 'sampai', 'sm': 'sama', 'sma': 'sama', 'ama': 'sama', 'emg': 'memang', 'emang': 'memang', 'emng': 'memang', 'bkn': 'bukan', 'bknnya': 'bukannya', 'tdk': 'tidak', 'gak': 'tidak', 'ga': 'tidak', 'g': 'tidak', 'nggak': 'tidak', 'ngga': 'tidak', 'nggk': 'tidak', 'enggak': 'tidak', 'engga': 'tidak', 'kagak': 'tidak', 'gk': 'tidak', 'gpp': 'tidak apa apa', # Kata ganti 'gw': 'aku', 'gua': 'aku', 'gue': 'aku', 'aku': 'aku', 'aq': 'aku', 'sya': 'saya', 'ane': 'aku', 'ana': 'aku', 'lu': 'lu', 'lo': 'lu', 'elu': 'lu', 'elo': 'lu', 'km': 'kamu', 'kamu': 'kamu', 'mu': 'kamu', 'dia': 'dia', 'dy': 'dia', 'dya': 'dia', 'mereka': 'mereka', 'mrk': 'mereka', # Orang / sapaan 'org': 'orang', 'orng': 'orang', 'org2': 'orang', 'tmn': 'teman', 'temen': 'teman', 'tmen': 'teman', 'kwn': 'kawan', 'sob': 'teman', 'bestie': 'teman', 'besty': 'teman', 'kak': 'kak', 'kk': 'kak', 'kaka': 'kak', 'kakak': 'kak', 'bang': 'abang', 'bng': 'abang', 'bro': 'saudara', 'sis': 'saudari', # Kata kerja / aktivitas 'bikin': 'membuat', 'bwt': 'buat', 'buat': 'buat', 'ngapain': 'ngapain', 'ngapa2in': 'mengapa', 'ngomong': 'berbicara', 'ngomongin': 'membicarakan', 'ngeliat': 'melihat', 'liat': 'lihat', 'liat2': 'lihat', 'ngelihat': 'melihat', 'nonton': 'menonton', 'nyari': 'mencari', 'cari': 'cari', 'nyoba': 'mencoba', 'coba': 'coba', 'pake': 'pakai', 'pakai': 'pakai', 'kasih': 'kasih', 'kasi': 'kasih', 'ngasih': 'memberi', 'dikasih': 'diberi', 'dikasi': 'diberi', 'ngambil': 'mengambil', 'ambil': 'ambil', 'nanya': 'bertanya', 'tanya': 'tanya', 'jawab': 'jawab', 'ngejek': 'mengejek', 'ejek': 'ejek', 'nyindir': 'menyindir', 'sindir': 'sindir', # Kata sifat / ekspresi 'bagusss': 'bagus', 'baguss': 'bagus', 'bgs': 'bagus', 'jelek2': 'jelek', 'jelekk': 'jelek', 'keren2': 'keren', 'kerenn': 'keren', 'keren banget': 'keren banget', 'lucuu': 'lucu', 'lucuuu': 'lucu', 'lucu2': 'lucu', 'gemess': 'gemas', 'gemesss': 'gemas', 'gemoy': 'gemas', 'gemoyy': 'gemas', 'cantikk': 'cantik', 'cantikkk': 'cantik', 'cantiknyaaa': 'cantiknya', 'gantenggg': 'ganteng', 'gantengg': 'ganteng', 'imuttt': 'imut', 'imutttt': 'imut', 'maniss': 'manis', 'manisss': 'manis', 'cakepp': 'cakep', 'cakeppp': 'cakep', 'mantapp': 'mantap', 'mantappp': 'mantap', 'parahh': 'parah', 'parahhh': 'parah', 'anehh': 'aneh', 'anehhh': 'aneh', 'burukkk': 'buruk', # Bahasa gaul 'sok': 'sok', 'alay': 'alay', 'norak': 'norak', 'lebay': 'berlebihan', 'lebayy': 'berlebihan', 'garing': 'garing', 'receh': 'receh', 'gabut': 'tidak ada kegiatan', 'mager': 'malas', 'baper': 'terbawa perasaan', 'bucin': 'budak cinta', 'kepo': 'ingin tahu', 'julid': 'iri', 'nyinyir': 'mencela', 'santuy': 'santai', 'santuyy': 'santai', 'mantul': 'mantap betul', 'wkwk': 'tertawa', 'wkwkwk': 'tertawa', 'wkwwk': 'tertawa', 'haha': 'tertawa', 'hahaha': 'tertawa', # Singkatan internet 'otw': 'dalam perjalanan', 'btw': 'ngomong ngomong', 'fyi': 'sebagai informasi', 'cmiiw': 'koreksi jika saya salah', 'lol': 'tertawa', 'plis': 'tolong', 'pls': 'tolong', 'please': 'tolong', 'thx': 'terima kasih', 'tks': 'terima kasih', 'makasih': 'terima kasih', 'mksh': 'terima kasih', 'mksd': 'maksud', 'mksih': 'terima kasih', 'thanks': 'terima kasih', # Angka / bentuk pendek 'bbrp': 'beberapa', 'brp': 'berapa', 'blh': 'boleh', 'bs': 'bisa', 'bisa': 'bisa', 'hrs': 'harus', 'harus': 'harus', 'jd': 'jadi', 'jdi': 'jadi', 'jdnya': 'jadinya', 'lg': 'lagi', 'lgi': 'lagi', 'sdg': 'sedang', 'dlu': 'dulu', 'dahulu': 'dulu', 'skrg': 'sekarang', 'skrng': 'sekarang', 'sekarang': 'sekarang', 'kpn': 'kapan', 'knp': 'kenapa', 'kenapa': 'kenapa', 'gmn': 'bagaimana', 'gmna': 'bagaimana', 'gimana': 'bagaimana', 'dmn': 'dimana', 'dimana': 'dimana', 'kmn': 'kemana', 'kemana': 'kemana', # Lain-lain 'dpt': 'dapat', 'dapet': 'dapat', 'dri': 'dari', 'mslh': 'masalah', 'masalah': 'masalah', 'msh': 'masih', 'masi': 'masih', 'bnyk': 'banyak', 'byk': 'banyak', 'bnyak': 'banyak', 'sedikit': 'sedikit', 'sdkt': 'sedikit', 'pgn': 'ingin', 'pengen': 'ingin', 'pngn': 'ingin', 'ingin': 'ingin', 'mau': 'mau', 'mw': 'mau', 'udh': 'sudah', 'blm': 'belum', 'jg': 'juga', 'jga': 'juga', 'jgn': 'jangan', 'jangan': 'jangan', 'td': 'tadi', 'tadi': 'tadi', 'smoga': 'moga', 'moga': 'moga', 'semoga': 'semoga', 't': 'tidak', } kamus_alay_path = os.path.join(os.path.dirname(__file__), "kamus_alay.csv") if os.path.exists(kamus_alay_path): try: kamus_df = pd.read_csv(kamus_alay_path)[['slang', 'formal']].dropna() kamus_alay_1_dict = dict(zip( kamus_df['slang'].astype(str).str.lower().str.strip(), kamus_df['formal'].astype(str).str.lower().str.strip() )) except Exception: kamus_alay_1_dict = {} else: kamus_alay_1_dict = {} # Rumus Alay 1: Kamus Manual + kamus_alay.csv normalisasi_1 = kamus_alay_1_dict.copy() normalisasi_1.update(normalisasi) # Rumus Alay 2: new_kamusalay.csv (15.000+ entri kata gaul) new_kamusalay_path = os.path.join(os.path.dirname(__file__), "new_kamusalay.csv") if os.path.exists(new_kamusalay_path): try: new_kamus_df = pd.read_csv(new_kamusalay_path, header=None, encoding='latin-1').dropna() kamus_alay_2_dict = dict(zip( new_kamus_df[0].astype(str).str.lower().str.strip(), new_kamus_df[1].astype(str).str.lower().str.strip() )) except Exception as e: print("[!] Gagal memuat new_kamusalay.csv:", e) kamus_alay_2_dict = {} else: kamus_alay_2_dict = {} # Gabungan kamus lengkap normalisasi_gabungan = kamus_alay_2_dict.copy() normalisasi_gabungan.update(normalisasi_1) # ========================================== # 2. STOPWORDS & STEMMER SETUP # ========================================== stop_words = set(stopwords.words('indonesian')) kata_negasi = { 'tidak', 'bukan', 'jangan', 'belum', 'tanpa', 'gak', 'ga', 'gk', 'nggak', 'ngga', 'enggak', 'engga', 'tak', 'bukanlah', 'tidaklah', 'jangan', 'kurang' } kata_kontras_dan_penegas = { 'tapi', 'namun', 'padahal', 'sangat', 'banget', 'sekali', 'terlalu' } # Kata sentimen bermakna yang tidak boleh dibuang oleh stopword kata_sentiment_simpan = { 'jelas', 'guna', 'manfaat', 'bisa', 'benar', 'pasti', 'penting', 'puas', 'baik', 'bagus', 'suka', 'sayang', 'senang', 'cinta', 'indah', 'hebat', 'tolong', 'bantu' } # Partikel obrolan medsos yang perlu dibuang agar tidak menempel pada negasi (mis: tidak_sih -> tidak_jelas) partikel_tambahan = { 'sih', 'deh', 'kah', 'loh', 'lho', 'tuh', 'nih', 'dong', 'kok', 'kan', 'pun', 'ya', 'yah' } stop_words = (stop_words - kata_negasi - kata_kontras_dan_penegas - kata_sentiment_simpan) | partikel_tambahan factory = StemmerFactory() stemmer = factory.create_stemmer() print("Stemmer berhasil dibuat!") # ========================================== # 6. FUNGSI PREPROCESSING # ========================================== def preprocessing(teks, use_stopword=False): # 1. Pastikan teks berupa string dan case folding ke huruf kecil teks = str(teks).lower() # 2. Menangani bentuk negasi singkatan bahasa gaul: bagus't -> tidak bagus teks = re.sub(r"(\w+)'t\b", r"tidak \1", teks) # 3. Penanganan Teks Berdampingan dengan Emotikon (text terus emote / emote terus text) # Sisipkan spasi di sekeliling karakter emoji/piktogram agar tidak melekat ke huruf if regex is not None: teks = regex.sub(r'(\p{Extended_Pictographic}+|\p{So}+|\p{Sk}+)', r' \1 ', teks) else: teks = re.sub(r'([\U00010000-\U0010ffff\u2600-\u27bf])', r' \1 ', teks) # 4. Tangani tanda baca, petik, atau simbol (misal: assalamu'alaikum -> assalamu alaikum) teks = re.sub(r"[^\w\s]|_", " ", teks) # 5. Reduksi huruf berulang lebih dari 2 (contoh: bngttttt -> bngt, baguuus -> baguus) teks = re.sub(r'(.)\1{2,}', r'\1\1', teks) # 6. Tokenizing tokens = [t for t in teks.split() if t] # 7a. Normalisasi Rumus Alay 1 (Kamus Manual + kamus_alay.csv) tokens_norm_1 = [] for kata in tokens: if kata in KATA_PROTEKSI_TFIDF: tokens_norm_1.append(kata) continue kata_ganti = normalisasi_1.get(kata, kata) tokens_norm_1.extend(kata_ganti.split()) # 7b. Normalisasi Rumus Alay 2 (Kamus new_kamusalay.csv) tokens_norm_2 = [] for kata in tokens_norm_1: if kata in KATA_PROTEKSI_TFIDF: tokens_norm_2.append(kata) continue kata_ganti = kamus_alay_2_dict.get(kata, kata) tokens_norm_2.extend(kata_ganti.split()) # 8. Menghapus karakter selain huruf tokens = [kata for kata in tokens_norm_2 if kata.isalpha()] # 9. Stopword Removal (Default: False / nonaktif sesuai instruksi agar tidak banyak data kosong) if use_stopword: tokens = [kata for kata in tokens if kata not in stop_words] # 10. Stemming menggunakan Sastrawi tokens = [stemmer.stem(kata) for kata in tokens if kata.strip()] # 11. Negation Tagging (menggabungkan kata negasi dengan kata setelahnya, misal: tidak_bagus) kata_negasi_gabung = {'tidak', 'bukan', 'jangan', 'belum', 'kurang', 'tak'} final_tokens = [] skip = False for i in range(len(tokens)): if skip: skip = False continue if tokens[i] in kata_negasi_gabung and i + 1 < len(tokens): final_tokens.append(f"{tokens[i]}_{tokens[i+1]}") skip = True else: final_tokens.append(tokens[i]) # Menggabungkan token kembali teks_hasil = ' '.join(final_tokens) return teks_hasil if __name__ == "__main__": contoh = "sok cantik banget jadi cewek" print("Contoh Masukan :", contoh) print("Hasil Bersih :", preprocessing(contoh))