281 lines
12 KiB
Python
281 lines
12 KiB
Python
"""
|
|
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))
|