|
import os |
|
import re |
|
import warnings |
|
from collections import Counter |
|
from datetime import datetime |
|
|
|
import matplotlib.pyplot as plt |
|
import nltk |
|
import numpy as np |
|
import pandas as pd |
|
import requests |
|
import seaborn as sns |
|
from nltk.corpus import stopwords |
|
from nltk.tokenize import word_tokenize |
|
from nltk.util import ngrams |
|
from textblob import TextBlob |
|
from wordcloud import WordCloud |
|
|
|
warnings.filterwarnings("ignore") |
|
plt.style.use("seaborn") |
|
|
|
nltk.download("stopwords") |
|
nltk.download("punkt") |
|
|
|
|
|
class ReviewAnalyzer: |
|
def __init__(self, file_path): |
|
self.df = pd.read_csv(file_path) |
|
self.turkish_stopwords = self.get_turkish_stopwords() |
|
|
|
|
|
self.logistics_seller_words = { |
|
|
|
"kargo", |
|
"kargocu", |
|
"paket", |
|
"paketleme", |
|
"teslimat", |
|
"teslim", |
|
"gönderi", |
|
"gönderim", |
|
"ulaştı", |
|
"ulaşım", |
|
"geldi", |
|
"kurye", |
|
"dağıtım", |
|
"hasarlı", |
|
"hasar", |
|
"kutu", |
|
"ambalaj", |
|
"zamanında", |
|
"geç", |
|
"hızlı", |
|
"yavaş", |
|
"günde", |
|
"saatte", |
|
|
|
"satıcı", |
|
"mağaza", |
|
"sipariş", |
|
"trendyol", |
|
"tedarik", |
|
"stok", |
|
"garanti", |
|
"fatura", |
|
"iade", |
|
"geri", |
|
"müşteri", |
|
"hizmet", |
|
"destek", |
|
"iletişim", |
|
"şikayet", |
|
"sorun", |
|
"çözüm", |
|
"hediye", |
|
|
|
"fiyat", |
|
"ücret", |
|
"para", |
|
"bedava", |
|
"ücretsiz", |
|
"indirim", |
|
"kampanya", |
|
"taksit", |
|
"ödeme", |
|
"bütçe", |
|
"hesap", |
|
"kur", |
|
|
|
"bugün", |
|
"yarın", |
|
"dün", |
|
"hafta", |
|
"gün", |
|
"saat", |
|
"süre", |
|
"bekleme", |
|
"gecikme", |
|
"erken", |
|
"geç", |
|
} |
|
|
|
|
|
self.positive_words = { |
|
"güzel", |
|
"harika", |
|
"mükemmel", |
|
"süper", |
|
"iyi", |
|
"muhteşem", |
|
"teşekkür", |
|
"memnun", |
|
"başarılı", |
|
"kaliteli", |
|
"kusursuz", |
|
"özgün", |
|
"şahane", |
|
"enfes", |
|
"ideal", |
|
} |
|
|
|
self.negative_words = { |
|
"kötü", |
|
"berbat", |
|
"rezalet", |
|
"yetersiz", |
|
"başarısız", |
|
"vasat", |
|
"korkunç", |
|
"düşük", |
|
"zayıf", |
|
"çöp", |
|
"pişman", |
|
"kırık", |
|
"bozuk", |
|
} |
|
|
|
|
|
self.month_map = { |
|
"Ocak": "January", |
|
"Şubat": "February", |
|
"Mart": "March", |
|
"Nisan": "April", |
|
"Mayıs": "May", |
|
"Haziran": "June", |
|
"Temmuz": "July", |
|
"Ağustos": "August", |
|
"Eylül": "September", |
|
"Ekim": "October", |
|
"Kasım": "November", |
|
"Aralık": "December", |
|
} |
|
|
|
def get_turkish_stopwords(self): |
|
"""Türkçe stop words listesini oluştur""" |
|
turkish_stops = set(stopwords.words("turkish")) |
|
|
|
github_url = "https://raw.githubusercontent.com/sgsinclair/trombone/master/src/main/resources/org/voyanttools/trombone/keywords/stop.tr.turkish-lucene.txt" |
|
try: |
|
response = requests.get(github_url) |
|
if response.status_code == 200: |
|
github_stops = set( |
|
word.strip() for word in response.text.split("\n") if word.strip() |
|
) |
|
turkish_stops.update(github_stops) |
|
except Exception as e: |
|
print(f"GitHub'dan stop words çekilirken hata oluştu: {e}") |
|
|
|
custom_stops = { |
|
"bir", |
|
"ve", |
|
"çok", |
|
"bu", |
|
"de", |
|
"da", |
|
"için", |
|
"ile", |
|
"ben", |
|
"sen", |
|
"o", |
|
"biz", |
|
"siz", |
|
"onlar", |
|
"bu", |
|
"şu", |
|
"ama", |
|
"fakat", |
|
"ancak", |
|
"lakin", |
|
"ki", |
|
"dahi", |
|
"mi", |
|
"mı", |
|
"mu", |
|
"mü", |
|
} |
|
turkish_stops.update(custom_stops) |
|
|
|
return turkish_stops |
|
|
|
def filter_product_reviews(self): |
|
"""Salt ürün yorumlarını filtrele""" |
|
|
|
def is_pure_product_review(text): |
|
if not isinstance(text, str): |
|
return False |
|
|
|
text_lower = text.lower() |
|
return not any(word in text_lower for word in self.logistics_seller_words) |
|
|
|
|
|
original_count = len(self.df) |
|
self.df = self.df[self.df["Yorum"].apply(is_pure_product_review)] |
|
filtered_count = len(self.df) |
|
|
|
print(f"\nFiltreleme İstatistikleri:") |
|
print(f"Orijinal yorum sayısı: {original_count}") |
|
print(f"Salt ürün yorumu sayısı: {filtered_count}") |
|
print(f"Çıkarılan yorum sayısı: {original_count - filtered_count}") |
|
print( |
|
f"Filtreleme oranı: {((original_count - filtered_count) / original_count * 100):.2f}%" |
|
) |
|
|
|
print("\nÖrnek Salt Ürün Yorumları:") |
|
sample_reviews = self.df["Yorum"].sample(min(3, len(self.df))) |
|
for idx, review in enumerate(sample_reviews, 1): |
|
print(f"{idx}. {review[:100]}...") |
|
|
|
def convert_turkish_date(self, date_str): |
|
"""Türkçe tarihleri İngilizce'ye çevir""" |
|
try: |
|
day, month, year = date_str.split() |
|
english_month = self.month_map[month] |
|
return f"{day} {english_month} {year}" |
|
except: |
|
return None |
|
|
|
def preprocess_text(self, text): |
|
"""Metin ön işleme""" |
|
if isinstance(text, str): |
|
text = text.lower() |
|
text = re.sub(r"[^\w\s]", "", text) |
|
text = re.sub(r"\d+", "", text) |
|
text = re.sub(r"\s+", " ", text).strip() |
|
return text |
|
return "" |
|
|
|
def analyze_timestamps(self): |
|
"""Zaman bazlı analizler""" |
|
|
|
self.df["Tarih"] = self.df["Tarih"].apply(self.convert_turkish_date) |
|
self.df["Tarih"] = pd.to_datetime(self.df["Tarih"], format="%d %B %Y") |
|
|
|
|
|
plt.figure(figsize=(12, 6)) |
|
plt.hist(self.df["Tarih"], bins=20, edgecolor="black") |
|
plt.title("Yorumların Zaman İçindeki Dağılımı") |
|
plt.xlabel("Tarih") |
|
plt.ylabel("Yorum Sayısı") |
|
plt.xticks(rotation=45) |
|
plt.tight_layout() |
|
plt.savefig("images/yorum_zaman_dagilimi.png") |
|
plt.close() |
|
|
|
|
|
monthly_reviews = self.df.groupby(self.df["Tarih"].dt.to_period("M")).size() |
|
plt.figure(figsize=(12, 6)) |
|
monthly_reviews.plot(kind="bar") |
|
plt.title("Aylık Yorum Dağılımı") |
|
plt.xlabel("Ay") |
|
plt.ylabel("Yorum Sayısı") |
|
plt.xticks(rotation=45) |
|
plt.tight_layout() |
|
plt.savefig("images/aylik_yorum_dagilimi.png") |
|
plt.close() |
|
|
|
|
|
self.df["Mevsim"] = self.df["Tarih"].dt.month.map( |
|
{ |
|
12: "Kış", |
|
1: "Kış", |
|
2: "Kış", |
|
3: "İlkbahar", |
|
4: "İlkbahar", |
|
5: "İlkbahar", |
|
6: "Yaz", |
|
7: "Yaz", |
|
8: "Yaz", |
|
9: "Sonbahar", |
|
10: "Sonbahar", |
|
11: "Sonbahar", |
|
} |
|
) |
|
seasonal_reviews = self.df.groupby("Mevsim").size() |
|
plt.figure(figsize=(10, 6)) |
|
seasonal_reviews.plot(kind="bar") |
|
plt.title("Mevsimsel Yorum Dağılımı") |
|
plt.xlabel("Mevsim") |
|
plt.ylabel("Yorum Sayısı") |
|
plt.tight_layout() |
|
plt.savefig("images/mevsimsel_dagilim.png") |
|
plt.close() |
|
|
|
def analyze_ratings(self): |
|
"""Yıldız bazlı analizler""" |
|
plt.figure(figsize=(10, 6)) |
|
sns.countplot(data=self.df, x="Yıldız Sayısı") |
|
plt.title("Yıldız Dağılımı") |
|
plt.xlabel("Yıldız Sayısı") |
|
plt.ylabel("Yorum Sayısı") |
|
plt.savefig("images/yildiz_dagilimi.png") |
|
plt.close() |
|
|
|
return { |
|
"Ortalama Yıldız": self.df["Yıldız Sayısı"].mean(), |
|
"Medyan Yıldız": self.df["Yıldız Sayısı"].median(), |
|
"Mod Yıldız": self.df["Yıldız Sayısı"].mode()[0], |
|
"Standart Sapma": self.df["Yıldız Sayısı"].std(), |
|
} |
|
|
|
def create_wordcloud(self): |
|
"""Kelime bulutu oluştur""" |
|
all_comments = " ".join( |
|
[self.preprocess_text(str(comment)) for comment in self.df["Yorum"]] |
|
) |
|
|
|
words = word_tokenize(all_comments) |
|
filtered_words = [word for word in words if word not in self.turkish_stopwords] |
|
clean_text = " ".join(filtered_words) |
|
|
|
wordcloud = WordCloud( |
|
width=800, |
|
height=400, |
|
background_color="white", |
|
max_words=100, |
|
font_path="C:/Windows/Fonts/arial.ttf", |
|
).generate(clean_text) |
|
|
|
plt.figure(figsize=(15, 8)) |
|
plt.imshow(wordcloud, interpolation="bilinear") |
|
plt.axis("off") |
|
plt.savefig("images/wordcloud.png") |
|
plt.close() |
|
|
|
def analyze_ngrams(self, max_n=3, top_n=10): |
|
"""N-gram analizi""" |
|
all_texts = [] |
|
for comment in self.df["Yorum"]: |
|
if isinstance(comment, str): |
|
words = self.preprocess_text(comment).split() |
|
filtered_words = [ |
|
word for word in words if word not in self.turkish_stopwords |
|
] |
|
all_texts.extend(filtered_words) |
|
|
|
for n in range(1, max_n + 1): |
|
print(f"\n{n}-gram Analizi:") |
|
|
|
if n == 1: |
|
ngrams_list = all_texts |
|
else: |
|
ngrams_list = list(ngrams(all_texts, n)) |
|
|
|
ngram_freq = Counter(ngrams_list).most_common(top_n) |
|
|
|
if n == 1: |
|
labels = [item[0] for item in ngram_freq] |
|
else: |
|
labels = [" ".join(item[0]) for item in ngram_freq] |
|
|
|
values = [item[1] for item in ngram_freq] |
|
|
|
plt.figure(figsize=(12, 6)) |
|
bars = plt.barh(range(len(values)), values) |
|
plt.yticks(range(len(labels)), labels) |
|
plt.title(f"En Sık Kullanılan {n}-gramlar") |
|
plt.xlabel("Frekans") |
|
|
|
for i, bar in enumerate(bars): |
|
width = bar.get_width() |
|
plt.text( |
|
width, |
|
bar.get_y() + bar.get_height() / 2, |
|
f"{int(width)}", |
|
ha="left", |
|
va="center", |
|
fontweight="bold", |
|
) |
|
|
|
plt.tight_layout() |
|
plt.savefig(f"images/{n}gram_analizi.png") |
|
plt.close() |
|
|
|
print(f"\nEn sık kullanılan {n}-gramlar:") |
|
for ngram, freq in ngram_freq: |
|
if n == 1: |
|
print(f"{ngram}: {freq}") |
|
else: |
|
print(f"{' '.join(ngram)}: {freq}") |
|
|
|
def analyze_sentiment(self): |
|
"""Duygu analizi""" |
|
|
|
def count_sentiment_words(text): |
|
if not isinstance(text, str): |
|
return 0, 0 |
|
|
|
text_lower = text.lower() |
|
words = text_lower.split() |
|
positive_count = sum(1 for word in words if word in self.positive_words) |
|
negative_count = sum(1 for word in words if word in self.negative_words) |
|
return positive_count, negative_count |
|
|
|
sentiment_counts = self.df["Yorum"].apply(count_sentiment_words) |
|
self.df["Pozitif_Kelime_Sayisi"] = [count[0] for count in sentiment_counts] |
|
self.df["Negatif_Kelime_Sayisi"] = [count[1] for count in sentiment_counts] |
|
self.df["Sentiment_Skor"] = ( |
|
self.df["Pozitif_Kelime_Sayisi"] - self.df["Negatif_Kelime_Sayisi"] |
|
) |
|
|
|
plt.figure(figsize=(10, 6)) |
|
sns.boxplot(data=self.df, x="Yıldız Sayısı", y="Sentiment_Skor") |
|
plt.title("Yıldız Sayısı ve Sentiment Skoru İlişkisi") |
|
plt.savefig("images/sentiment_yildiz_iliskisi.png") |
|
plt.close() |
|
|
|
plt.figure(figsize=(10, 6)) |
|
plt.hist(self.df["Sentiment_Skor"], bins=20) |
|
plt.title("Sentiment Skor Dağılımı") |
|
plt.xlabel("Sentiment Skoru") |
|
plt.ylabel("Yorum Sayısı") |
|
plt.savefig("images/sentiment_dagilimi.png") |
|
plt.close() |
|
|
|
def analyze_comment_lengths(self): |
|
"""Yorum uzunluğu analizi""" |
|
self.df["Yorum_Uzunlugu"] = self.df["Yorum"].str.len() |
|
|
|
plt.figure(figsize=(10, 6)) |
|
plt.hist(self.df["Yorum_Uzunlugu"].dropna(), bins=30) |
|
plt.title("Yorum Uzunluğu Dağılımı") |
|
plt.xlabel("Karakter Sayısı") |
|
plt.ylabel("Yorum Sayısı") |
|
plt.savefig("images/yorum_uzunluk_dagilimi.png") |
|
plt.close() |
|
|
|
plt.figure(figsize=(10, 6)) |
|
sns.boxplot(data=self.df, x="Yıldız Sayısı", y="Yorum_Uzunlugu") |
|
plt.title("Yıldız Sayısı ve Yorum Uzunluğu İlişkisi") |
|
plt.xlabel("Yıldız") |
|
plt.ylabel("Yorum Uzunluğu (Karakter)") |
|
plt.savefig("images/yildiz_uzunluk_iliskisi.png") |
|
plt.close() |
|
|
|
def run_analysis(self): |
|
"""Ana analiz fonksiyonu""" |
|
print("Analiz başlatılıyor...") |
|
|
|
if not os.path.exists("images"): |
|
os.makedirs("images") |
|
|
|
print("\nÜrün odaklı yorum filtresi uygulanıyor...") |
|
self.filter_product_reviews() |
|
|
|
print("\n1. Yorum Uzunluğu Analizi") |
|
self.analyze_comment_lengths() |
|
|
|
print("\n2. Zaman Analizi") |
|
self.analyze_timestamps() |
|
|
|
print("\n3. Yıldız Analizi") |
|
rating_stats = self.analyze_ratings() |
|
print("\nYıldız İstatistikleri:") |
|
for key, value in rating_stats.items(): |
|
print(f"{key}: {value:.2f}") |
|
|
|
print("\n4. Kelime Bulutu Oluşturuluyor") |
|
self.create_wordcloud() |
|
|
|
print("\n5. N-gram Analizleri") |
|
self.analyze_ngrams(max_n=3, top_n=10) |
|
|
|
print("\n6. Duygu Analizi") |
|
self.analyze_sentiment() |
|
|
|
print("\nAnaliz tamamlandı! Tüm görseller 'images' klasörüne kaydedildi.") |
|
|
|
|
|
if __name__ == "__main__": |
|
analyzer = ReviewAnalyzer("data/macbook_product_comments_with_ratings.csv") |
|
analyzer.run_analysis() |
|
|