PerSent 0.2.4__tar.gz → 0.299.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- persent-0.299.0/PKG-INFO +365 -0
- persent-0.299.0/PerSent/CommentAnalyzer.py +263 -0
- persent-0.299.0/PerSent/SentimentAnalyzer.py +350 -0
- persent-0.299.0/PerSent/__init__.py +10 -0
- persent-0.299.0/PerSent/model/classifier.joblib +0 -0
- persent-0.299.0/PerSent/model/weighted_sentiment_model.joblib +0 -0
- persent-0.299.0/PerSent/model/word2vec.model +0 -0
- persent-0.299.0/PerSent.egg-info/PKG-INFO +365 -0
- {persent-0.2.4 → persent-0.299.0}/PerSent.egg-info/SOURCES.txt +6 -1
- {persent-0.2.4 → persent-0.299.0}/PerSent.egg-info/requires.txt +1 -0
- persent-0.299.0/README.md +334 -0
- persent-0.299.0/setup.py +73 -0
- persent-0.2.4/PKG-INFO +0 -28
- persent-0.2.4/PerSent/CommentAnalyzer.py +0 -199
- persent-0.2.4/PerSent/__init__.py +0 -4
- persent-0.2.4/PerSent.egg-info/PKG-INFO +0 -28
- persent-0.2.4/README.md +0 -0
- persent-0.2.4/setup.py +0 -34
- {persent-0.2.4 → persent-0.299.0}/PerSent.egg-info/dependency_links.txt +0 -0
- {persent-0.2.4 → persent-0.299.0}/PerSent.egg-info/top_level.txt +0 -0
- {persent-0.2.4 → persent-0.299.0}/setup.cfg +0 -0
persent-0.299.0/PKG-INFO
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PerSent
|
|
3
|
+
Version: 0.299.0
|
|
4
|
+
Summary: Persian Sentiment Analysis Toolkit
|
|
5
|
+
Home-page: https://github.com/RezaGooner/PerSent
|
|
6
|
+
Author: RezaGooner
|
|
7
|
+
Author-email: RezaAsadiProgrammer@Gmail.com
|
|
8
|
+
Keywords: persian sentiment analysis nlp
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: hazm>=0.7.0
|
|
15
|
+
Requires-Dist: gensim>=4.0.0
|
|
16
|
+
Requires-Dist: scikit-learn>=1.0.0
|
|
17
|
+
Requires-Dist: pandas>=1.3.0
|
|
18
|
+
Requires-Dist: tqdm>=4.62.0
|
|
19
|
+
Requires-Dist: joblib>=1.1.0
|
|
20
|
+
Requires-Dist: requests>=2.26.0
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: author-email
|
|
23
|
+
Dynamic: classifier
|
|
24
|
+
Dynamic: description
|
|
25
|
+
Dynamic: description-content-type
|
|
26
|
+
Dynamic: home-page
|
|
27
|
+
Dynamic: keywords
|
|
28
|
+
Dynamic: requires-dist
|
|
29
|
+
Dynamic: requires-python
|
|
30
|
+
Dynamic: summary
|
|
31
|
+
|
|
32
|
+
# PerSent (Persian Sentiment Analyzer)
|
|
33
|
+
|
|
34
|
+
[](README.fa.md)
|
|
35
|
+
|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
## Introduction
|
|
39
|
+
PerSent is a practical Python library designed for Persian sentiment analysis. The name stands for "Persian Sentiment Analyzer". Currently in its early testing phase, the library provides basic functionality and is available on [PyPI](https://pypi.org/project/PerSent/). Install it using:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install PerSent
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Current capabilities include:
|
|
46
|
+
|
|
47
|
+
- Sentiment analysis of opinions/comments
|
|
48
|
+
|
|
49
|
+
- Emotion analysis of texts (happiness, sadness, anger, surprise, fear, disgust, calmness)
|
|
50
|
+
|
|
51
|
+
- Analysis of product/service reviews (recommended/not recommended/no idea)
|
|
52
|
+
|
|
53
|
+
- Both single-text and batch CSV processing
|
|
54
|
+
|
|
55
|
+
- Output displayed in terminal or saved to CSV with summary statistics
|
|
56
|
+
|
|
57
|
+
Initial repository that evolved into this library:
|
|
58
|
+
[Click here](https://github.com/RezaGooner/Sentiment-Survey-Analyzer)
|
|
59
|
+
|
|
60
|
+
We welcome user testing and feedback to improve the library. If you encounter bugs or have suggestions, please:
|
|
61
|
+
|
|
62
|
+
- [Contribute](#contribution)
|
|
63
|
+
|
|
64
|
+
For installation issues due to dependency conflicts (especially with mingw-w64), consider using online platforms like DeepNote.com.
|
|
65
|
+
|
|
66
|
+
## Structure
|
|
67
|
+
### Comment Analysis Functions
|
|
68
|
+
|
|
69
|
+
```train(train_csv, test_size=0.2, vector_size=100, window=5)```
|
|
70
|
+
|
|
71
|
+
| Parameter | Data Type | Default Value | Description | Optional/Required |
|
|
72
|
+
|--------------|-----------|---------------|-----------------------------------------------------------------------------|-------------------|
|
|
73
|
+
| `train_csv` | str | - | Path to CSV file containing training data with `body` and `recommendation_status` columns | Required |
|
|
74
|
+
| `test_size` | float | 0.2 | Proportion of test data (between 0.0 and 1.0) | Optional |
|
|
75
|
+
| `vector_size`| int | 100 | Output vector dimension for Word2Vec model (embedding size) | Optional |
|
|
76
|
+
| `window` | int | 5 | Context window size for Word2Vec model | Optional |
|
|
77
|
+
|
|
78
|
+
recommendation_status must be one of:
|
|
79
|
+
|
|
80
|
+
- no_idea
|
|
81
|
+
|
|
82
|
+
- recommended
|
|
83
|
+
|
|
84
|
+
- not_recommended
|
|
85
|
+
|
|
86
|
+
Null/NaN values are converted to no_idea, affecting model accuracy.
|
|
87
|
+
|
|
88
|
+
- Returns test accuracy score.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
```analyzeText(text)```
|
|
93
|
+
|
|
94
|
+
| Parameter | Data Type | Description | Optional/Required |
|
|
95
|
+
|-----------|-----------|--------------------------------------|-------------------|
|
|
96
|
+
| `text` | str | The Persian text to be analyzed | Required |
|
|
97
|
+
|
|
98
|
+
The core function that analyzes a text and returns one of:
|
|
99
|
+
"not_recommended", "recommended", or "no_idea".
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
```saveModel()```
|
|
104
|
+
|
|
105
|
+
```loadModel()```
|
|
106
|
+
|
|
107
|
+
Model persistence functions. Models are saved in the model directory.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
```analyzeCSV(input_csv, output_path, summary_path=None, text_column=0)```
|
|
112
|
+
|
|
113
|
+
| Parameter | Data Type | Default Value | Description | Optional/Required |
|
|
114
|
+
|----------------|-----------------|---------------|-----------------------------------------------------------------------------|-------------------|
|
|
115
|
+
| `input_csv` | str | - | Path to input CSV file containing comments to analyze | Required |
|
|
116
|
+
| `output_path` | str | - | Path where analyzed results CSV will be saved | Required |
|
|
117
|
+
| `summary_path` | str or None | None | Optional path to save summary statistics CSV | Optional |
|
|
118
|
+
| `text_column` | int or str | 0 | Column index (int) or name (str) containing the text to analyze | Optional |
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
Batch processes comments from a CSV file. For single-column files, text_column isn't needed. Otherwise specify column name/index (0-based, negative indices supported). Output contains:
|
|
122
|
+
|
|
123
|
+
1- Original text
|
|
124
|
+
|
|
125
|
+
2- Recommendation status
|
|
126
|
+
Optional summary_path generates statistics:
|
|
127
|
+
|
|
128
|
+
- Total count
|
|
129
|
+
|
|
130
|
+
- Recommended count
|
|
131
|
+
|
|
132
|
+
- Not recommended count
|
|
133
|
+
|
|
134
|
+
- No idea count
|
|
135
|
+
|
|
136
|
+
- Model accuracy (not implemented in current version)
|
|
137
|
+
|
|
138
|
+
Returns a DataFrame and saves results.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
### Emotion Analysis Functions
|
|
143
|
+
|
|
144
|
+
```loadLex(csv_file, word_col=0, emotion_col=1, weight_col=2)```
|
|
145
|
+
|
|
146
|
+
| Parameter | Data Type | Default Value | Description | Optional/Required |
|
|
147
|
+
|----------------|-----------------|---------------|-----------------------------------------------------------------------------|-------------------|
|
|
148
|
+
| `csv_file` | str | - | Path to CSV lexicon file | Required |
|
|
149
|
+
| `word_col` | int or str | 0 | Column index (int) or name (str) containing words | Optional |
|
|
150
|
+
| `emotion_col` | int or str | 1 | Column index (int) or name (str) containing emotion labels | Optional |
|
|
151
|
+
| `weight_col` | int or str | 2 | Column index (int) or name (str) containing weight values | Optional |
|
|
152
|
+
|
|
153
|
+
Loads a CSV with three columns:
|
|
154
|
+
|
|
155
|
+
1- Keywords
|
|
156
|
+
|
|
157
|
+
2- Emotion (happiness, sadness, anger, fear, disgust, calmness)
|
|
158
|
+
|
|
159
|
+
3- Emotion weight (defaults to 1 if unspecified, affecting accuracy)
|
|
160
|
+
|
|
161
|
+
Column indices are optional.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
```train(train_csv, text_col='text', emotion_col='sentiment', weight_col='weight')```
|
|
166
|
+
|
|
167
|
+
| Parameter | Data Type | Default Value | Description | Optional/Required |
|
|
168
|
+
|----------------|-----------------|---------------|-----------------------------------------------------------------------------|-------------------|
|
|
169
|
+
| `train_csv` | str | - | Path to training CSV file | Required |
|
|
170
|
+
| `text_col` | str or int | 'text' | Column name/index containing text data | Optional |
|
|
171
|
+
| `emotion_col` | str or int | 'emotion' | Column name/index containing emotion labels | Optional |
|
|
172
|
+
| `weight_col` | str or int | 'weight' | Column name/index containing weight values | Optional |
|
|
173
|
+
|
|
174
|
+
Trains the emotion model using a CSV with specified column names (optional).
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
```saveModel(model_name='weighted_sentiment_model')```
|
|
179
|
+
|
|
180
|
+
| Parameter | Type | Default Value | Description | Optional/Required |
|
|
181
|
+
|--------------|-------|-----------------------------|-----------------------------------------------------------------------------|-------------------|
|
|
182
|
+
| `model_name` | str | 'weighted_sentiment_model' | Base filename for saving model (without extension) | Optional |
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
```loadModel(model_name='weighted_sentiment_model')```
|
|
186
|
+
|
|
187
|
+
| Parameter | Type | Default Value | Description | Optional/Required |
|
|
188
|
+
|--------------|-------|-----------------------------|-----------------------------------------------------------------------------|-------------------|
|
|
189
|
+
| `model_name` | str | 'weighted_sentiment_model' | Base filename of model to load (without extension) | Optional |
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
Model persistence functions (saved in model directory).
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
```analyzeText(text)```
|
|
197
|
+
|
|
198
|
+
| Parameter | Type | Description | Optional/Required |
|
|
199
|
+
|-----------|------|--------------------------------------|-------------------|
|
|
200
|
+
| `text` | str | Persian text to analyze | Required |
|
|
201
|
+
|
|
202
|
+
Analyzes a single text, returning percentage scores for each emotion.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
```analyzeCSV(input_csv, output_csv, text_col='text', output_col='sentiment_analysis')```
|
|
207
|
+
|
|
208
|
+
| Parameter | Type | Default Value | Description | Optional/Required |
|
|
209
|
+
|---------------------|---------------|------------------------|-----------------------------------------------------------------------------|-------------------|
|
|
210
|
+
| `input_csv` | str | - | Path to input CSV file containing text to analyze | Required |
|
|
211
|
+
| `output_csv` | str | - | Path to save analyzed results | Required |
|
|
212
|
+
| `text_col` | str/int | 'text' | Column name/index containing text to analyze | Optional |
|
|
213
|
+
| `output_col` | str | 'sentiment_analysis' | Column name for output results | Optional |
|
|
214
|
+
|
|
215
|
+
Batch processes texts from CSV. Returns True on success. Requires:
|
|
216
|
+
|
|
217
|
+
- input_csv path
|
|
218
|
+
|
|
219
|
+
- output_csv path
|
|
220
|
+
Optional column names.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Installation
|
|
225
|
+
Install via pip:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
pip install PerSent
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
For specific versions:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
pip install PerSent==<VERSION_NUMBER>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Usage
|
|
238
|
+
- Comment Analysis
|
|
239
|
+
|
|
240
|
+
Basic single-text analysis:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
from PerSent import CommentAnalyzer
|
|
244
|
+
|
|
245
|
+
analyzer = CommentAnalyzer()
|
|
246
|
+
|
|
247
|
+
'''
|
|
248
|
+
Training (if you have data):
|
|
249
|
+
Requires CSV with comments and recommendation status columns
|
|
250
|
+
Status must be: recommended/not_recommended/no_idea
|
|
251
|
+
'''
|
|
252
|
+
analyzer.train("train.csv")
|
|
253
|
+
|
|
254
|
+
# Load pre-trained model
|
|
255
|
+
analyzer.loadModel()
|
|
256
|
+
|
|
257
|
+
# Predict
|
|
258
|
+
text = "کیفیت عالی داشت" # "Excellent quality"
|
|
259
|
+
result = analyzer.analyzeText(text)
|
|
260
|
+
print(f"Sentiment: {result}") # Output: Sentiment: recommended
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The included pre-trained model has ~70% accuracy. For better results, you can train with larger datasets. I've prepared a split dataset (due to size):
|
|
264
|
+
|
|
265
|
+
[Download Here](https://github.com/RezaGooner/Sentiment-Survey-Analyzer/tree/main/Dataset/big_train)
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
Batch CSV processing:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
from PerSent import CommentAnalyzer
|
|
273
|
+
analyzer = CommentAnalyzer()
|
|
274
|
+
analyzer.loadModel()
|
|
275
|
+
|
|
276
|
+
# Basic usage (single-column CSV)
|
|
277
|
+
analyzer.analyzeCSV(
|
|
278
|
+
input_csv="comments.csv",
|
|
279
|
+
output_path="results.csv"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Alternative usage patterns:
|
|
283
|
+
# 1. Using column index (0-based)
|
|
284
|
+
analyzer.analyzeCSV("comments.csv", "results.csv", None, 0)
|
|
285
|
+
|
|
286
|
+
# 2. Negative indices (count from end)
|
|
287
|
+
analyzer.analyzeCSV("comments.csv", "results.csv", None, -1)
|
|
288
|
+
|
|
289
|
+
# 3. Column name
|
|
290
|
+
analyzer.analyzeCSV("comments.csv", "results.csv", None, "نظرات") # "Comments" column
|
|
291
|
+
|
|
292
|
+
# 4. With summary (single-column)
|
|
293
|
+
analyzer.analyzeCSV("comments.csv", "results.csv", "summary.csv")
|
|
294
|
+
|
|
295
|
+
# 5. With summary and column specification
|
|
296
|
+
analyzer.analyzeCSV("comments.csv", "results.csv", "summary.csv", 2)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
- Emotion Analysis
|
|
300
|
+
|
|
301
|
+
Single text analysis with pre-trained model:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
from PerSent import SentimentAnalyzer
|
|
305
|
+
|
|
306
|
+
analyzer = SentimentAnalyzer()
|
|
307
|
+
analyzer.loadModel()
|
|
308
|
+
|
|
309
|
+
sample_text = "امتحانم رو خراب کردم. احساس میکنم یک شکست خوردهی تمام عیارم."
|
|
310
|
+
# "I failed my exam. I feel like a complete failure."
|
|
311
|
+
|
|
312
|
+
result = analyzer.analyzeText(sample_text)
|
|
313
|
+
for emotion, score in sorted(result.items(), key=lambda x: x[1], reverse=True):
|
|
314
|
+
print(f"{emotion}: {score:.2f}%")
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
output :
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
غم: 36.00% #Sadness
|
|
321
|
+
عصبانیت: 36.00% #anger
|
|
322
|
+
ترس: 28.00% #fear
|
|
323
|
+
شادی: 0.00% #happiness
|
|
324
|
+
تنفر: 0.00% #disgust
|
|
325
|
+
شگفتی: 0.00% #surprise
|
|
326
|
+
آرامش: 0.00% #calmness
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
To train your own model:
|
|
330
|
+
|
|
331
|
+
``` bash
|
|
332
|
+
analyzer.train('emotion_dataset.csv')
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Required CSV columns:
|
|
336
|
+
|
|
337
|
+
1- Keywords
|
|
338
|
+
|
|
339
|
+
2- Emotion (happiness, sadness, anger, disgust, fear, calmness)
|
|
340
|
+
|
|
341
|
+
3- Emotion weight
|
|
342
|
+
|
|
343
|
+
Model persistence:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
analyzer.saveModel("custom_model_name")
|
|
347
|
+
analyzer.loadModel("custom_model_name")
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Batch CSV processing:
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
analyzer.analyzeCSV("input.csv", "output.csv")
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Contribution
|
|
357
|
+
As mentioned, this library needs community collaboration. Please share suggestions, bugs, or feedback via:
|
|
358
|
+
|
|
359
|
+
- [Fork Repository & Pull Request](https://github.com/RezaGooner/PerSent/fork)
|
|
360
|
+
|
|
361
|
+
- [Create Issue](https://github.com/RezaGooner/PerSent/issues/new)
|
|
362
|
+
|
|
363
|
+
- Email: ```RezaAsadiProgrammer@gmail.com```
|
|
364
|
+
|
|
365
|
+
- Telegram: ```@RezaGooner```
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#import necessary library
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from hazm import Normalizer, word_tokenize, Stemmer, stopwords_list
|
|
4
|
+
import re
|
|
5
|
+
from tqdm import tqdm
|
|
6
|
+
from gensim.models import Word2Vec
|
|
7
|
+
import numpy as np
|
|
8
|
+
from sklearn.linear_model import LogisticRegression
|
|
9
|
+
from sklearn.model_selection import train_test_split
|
|
10
|
+
import os
|
|
11
|
+
import joblib
|
|
12
|
+
|
|
13
|
+
class CommentAnalyzer:
|
|
14
|
+
def __init__(self, model_dir='PerSent/model'):
|
|
15
|
+
self.normalizer = Normalizer()
|
|
16
|
+
self.stemmer = Stemmer()
|
|
17
|
+
self.stopwords = set(stopwords_list())
|
|
18
|
+
self.model_dir = model_dir
|
|
19
|
+
self.vectorizer = None
|
|
20
|
+
self.classifier = None
|
|
21
|
+
|
|
22
|
+
# make /model Directory if not exist
|
|
23
|
+
os.makedirs(self.model_dir, exist_ok=True)
|
|
24
|
+
|
|
25
|
+
def _preprocess_text(self, text):
|
|
26
|
+
"""PreProcess Persian Text"""
|
|
27
|
+
try:
|
|
28
|
+
# Normalizing
|
|
29
|
+
text = self.normalizer.normalize(str(text))
|
|
30
|
+
|
|
31
|
+
# remove number and sign
|
|
32
|
+
text = re.sub(r'[!()-\[\]{};:\'",؟<>./?@#$%^&*_~۰-۹\d]+', ' ', text)
|
|
33
|
+
text = re.sub(r'\s+', ' ', text).strip()
|
|
34
|
+
|
|
35
|
+
# tokenize and stemming
|
|
36
|
+
tokens = word_tokenize(text)
|
|
37
|
+
processed_tokens = [
|
|
38
|
+
self.stemmer.stem(token)
|
|
39
|
+
for token in tokens
|
|
40
|
+
if token not in self.stopwords and len(token) > 1
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
return processed_tokens
|
|
44
|
+
except Exception as e:
|
|
45
|
+
raise Exception(f"Text preprocessing failed: {str(e)}")
|
|
46
|
+
|
|
47
|
+
def _sentence_vector(self, sentence, model):
|
|
48
|
+
"""convert sentences to vector by word2vec model"""
|
|
49
|
+
try:
|
|
50
|
+
vectors = []
|
|
51
|
+
for word in sentence:
|
|
52
|
+
try:
|
|
53
|
+
vectors.append(model.wv[word])
|
|
54
|
+
except KeyError:
|
|
55
|
+
vectors.append(np.zeros(100))
|
|
56
|
+
return np.mean(vectors, axis=0) if vectors else np.zeros(100)
|
|
57
|
+
except Exception as e:
|
|
58
|
+
raise Exception(f"Sentence vectorization failed: {str(e)}")
|
|
59
|
+
|
|
60
|
+
def train(self, train_csv, test_size=0.2, vector_size=100, window=5):
|
|
61
|
+
"""Train model"""
|
|
62
|
+
try:
|
|
63
|
+
# read data
|
|
64
|
+
if not os.path.exists(train_csv):
|
|
65
|
+
raise FileNotFoundError(f"Training file {train_csv} not found")
|
|
66
|
+
|
|
67
|
+
df = pd.read_csv(train_csv)
|
|
68
|
+
|
|
69
|
+
if 'body' not in df.columns or 'recommendation_status' not in df.columns:
|
|
70
|
+
raise ValueError("CSV file must contain 'body' and 'recommendation_status' columns")
|
|
71
|
+
|
|
72
|
+
df['tokens'] = df['body'].apply(self._preprocess_text)
|
|
73
|
+
|
|
74
|
+
# train Word2Vec model
|
|
75
|
+
self.vectorizer = Word2Vec(
|
|
76
|
+
sentences=df['tokens'],
|
|
77
|
+
vector_size=vector_size,
|
|
78
|
+
window=window,
|
|
79
|
+
min_count=1,
|
|
80
|
+
workers=4
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# convert sentences to vector
|
|
84
|
+
X = np.array([self._sentence_vector(s, self.vectorizer) for s in df['tokens']])
|
|
85
|
+
y = df['recommendation_status'].map({
|
|
86
|
+
"no_idea": 2,
|
|
87
|
+
"recommended": 1,
|
|
88
|
+
"not_recommended": 0
|
|
89
|
+
}).values
|
|
90
|
+
|
|
91
|
+
# make train and test data
|
|
92
|
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size)
|
|
93
|
+
|
|
94
|
+
self.classifier = LogisticRegression(max_iter=1000)
|
|
95
|
+
self.classifier.fit(X_train, y_train)
|
|
96
|
+
|
|
97
|
+
# save model
|
|
98
|
+
self.saveModel()
|
|
99
|
+
|
|
100
|
+
# evaluation
|
|
101
|
+
accuracy = self.classifier.score(X_test, y_test)
|
|
102
|
+
return accuracy
|
|
103
|
+
|
|
104
|
+
except FileNotFoundError as e:
|
|
105
|
+
raise FileNotFoundError(f"Training file error: {str(e)}")
|
|
106
|
+
except ValueError as e:
|
|
107
|
+
raise ValueError(f"Data validation error: {str(e)}")
|
|
108
|
+
except Exception as e:
|
|
109
|
+
raise Exception(f"Training failed: {str(e)}")
|
|
110
|
+
|
|
111
|
+
def analyzeText(self, text):
|
|
112
|
+
"""Predict text sentiment"""
|
|
113
|
+
try:
|
|
114
|
+
if not self.classifier or not self.vectorizer:
|
|
115
|
+
raise Exception("Model not trained! Call train() first or load a pretrained model.")
|
|
116
|
+
|
|
117
|
+
tokens = self._preprocess_text(text)
|
|
118
|
+
vector = self._sentence_vector(tokens, self.vectorizer)
|
|
119
|
+
prediction = self.classifier.predict([vector])[0]
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
0: "not_recommended",
|
|
123
|
+
1: "recommended",
|
|
124
|
+
2: "no_idea"
|
|
125
|
+
}[prediction]
|
|
126
|
+
|
|
127
|
+
except Exception as e:
|
|
128
|
+
raise Exception(f"Text analysis failed: {str(e)}")
|
|
129
|
+
|
|
130
|
+
def saveModel(self):
|
|
131
|
+
"""save trained model"""
|
|
132
|
+
try:
|
|
133
|
+
if not self.classifier or not self.vectorizer:
|
|
134
|
+
raise Exception("No trained model to save")
|
|
135
|
+
|
|
136
|
+
joblib.dump(self.classifier, os.path.join(self.model_dir, 'classifier.joblib'))
|
|
137
|
+
self.vectorizer.save(os.path.join(self.model_dir, 'word2vec.model'))
|
|
138
|
+
except IOError as e:
|
|
139
|
+
raise IOError(f"Failed to save model: {str(e)}")
|
|
140
|
+
except Exception as e:
|
|
141
|
+
raise Exception(f"Error while saving model: {str(e)}")
|
|
142
|
+
|
|
143
|
+
def loadModel(self):
|
|
144
|
+
"""reload from file"""
|
|
145
|
+
try:
|
|
146
|
+
classifier_path = os.path.join(self.model_dir, 'classifier.joblib')
|
|
147
|
+
vectorizer_path = os.path.join(self.model_dir, 'word2vec.model')
|
|
148
|
+
|
|
149
|
+
if not os.path.exists(classifier_path) or not os.path.exists(vectorizer_path):
|
|
150
|
+
raise FileNotFoundError("Model files not found in the specified directory")
|
|
151
|
+
|
|
152
|
+
self.classifier = joblib.load(classifier_path)
|
|
153
|
+
self.vectorizer = Word2Vec.load(vectorizer_path)
|
|
154
|
+
except FileNotFoundError as e:
|
|
155
|
+
raise FileNotFoundError(f"Model files not found: {str(e)}")
|
|
156
|
+
except Exception as e:
|
|
157
|
+
raise Exception(f"Failed to load model: {str(e)}")
|
|
158
|
+
|
|
159
|
+
def analyzeCSV(self, input_csv, output_path, summary_path=None, text_column=0):
|
|
160
|
+
"""
|
|
161
|
+
Analyze sentiment for comments in a CSV file and save results
|
|
162
|
+
|
|
163
|
+
Parameters:
|
|
164
|
+
input_csv (str): Path to input CSV file
|
|
165
|
+
output_path (str): Path to save output CSV file
|
|
166
|
+
text_column (str/int, optional): Name or index (0-based) of column containing comments.
|
|
167
|
+
Defaults to 0 (first column).
|
|
168
|
+
summary_path (str, optional): Path to save prediction summary report.
|
|
169
|
+
If None, no summary will be saved.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
pd.DataFrame: DataFrame containing analysis results or None if failed
|
|
173
|
+
|
|
174
|
+
Raises:
|
|
175
|
+
FileNotFoundError: If input file not found
|
|
176
|
+
ValueError: If invalid column specified
|
|
177
|
+
Exception: For other processing errors
|
|
178
|
+
"""
|
|
179
|
+
try:
|
|
180
|
+
# Read input CSV
|
|
181
|
+
if not os.path.exists(input_csv):
|
|
182
|
+
raise FileNotFoundError(f"Input file {input_csv} not found")
|
|
183
|
+
|
|
184
|
+
df = pd.read_csv(input_csv)
|
|
185
|
+
|
|
186
|
+
# Determine the correct column
|
|
187
|
+
if isinstance(text_column, int):
|
|
188
|
+
# Handle negative indices
|
|
189
|
+
if text_column < 0:
|
|
190
|
+
text_column = len(df.columns) + text_column
|
|
191
|
+
|
|
192
|
+
if text_column >= len(df.columns) or text_column < 0:
|
|
193
|
+
raise ValueError(f"Column index {text_column} is out of range")
|
|
194
|
+
|
|
195
|
+
column_name = df.columns[text_column]
|
|
196
|
+
else:
|
|
197
|
+
if text_column not in df.columns:
|
|
198
|
+
raise ValueError(f"Column '{text_column}' not found in CSV file")
|
|
199
|
+
column_name = text_column
|
|
200
|
+
|
|
201
|
+
# Analyze each comment
|
|
202
|
+
tqdm.pandas(desc="Analyzing comments")
|
|
203
|
+
df['sentiment'] = df[column_name].progress_apply(self.analyzeText)
|
|
204
|
+
|
|
205
|
+
# Save results
|
|
206
|
+
df.to_csv(output_path, index=False, encoding='utf-8-sig')
|
|
207
|
+
print(f"Results saved to {output_path}")
|
|
208
|
+
|
|
209
|
+
# Generate and save summary if requested
|
|
210
|
+
if summary_path:
|
|
211
|
+
summary = self._generate_summary(df)
|
|
212
|
+
summary.to_csv(summary_path, index=False, encoding='utf-8-sig')
|
|
213
|
+
print(f"Summary report saved to {summary_path}")
|
|
214
|
+
|
|
215
|
+
return df
|
|
216
|
+
|
|
217
|
+
except FileNotFoundError as e:
|
|
218
|
+
print(f"Error: File not found - {str(e)}")
|
|
219
|
+
return None
|
|
220
|
+
except ValueError as e:
|
|
221
|
+
print(f"Error: Invalid column specification - {str(e)}")
|
|
222
|
+
return None
|
|
223
|
+
except Exception as e:
|
|
224
|
+
print(f"Error during CSV analysis: {str(e)}")
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
def _generate_summary(self, df):
|
|
228
|
+
"""Generate prediction summary statistics"""
|
|
229
|
+
try:
|
|
230
|
+
# Count each sentiment
|
|
231
|
+
counts = df['sentiment'].value_counts().to_dict()
|
|
232
|
+
|
|
233
|
+
# Create summary dataframe
|
|
234
|
+
summary = pd.DataFrame({
|
|
235
|
+
'Category': [
|
|
236
|
+
'Recommended',
|
|
237
|
+
'Not Recommended',
|
|
238
|
+
'No Idea',
|
|
239
|
+
'Total',
|
|
240
|
+
'Model Accuracy'
|
|
241
|
+
],
|
|
242
|
+
'Count': [
|
|
243
|
+
counts.get('recommended', 0),
|
|
244
|
+
counts.get('not_recommended', 0),
|
|
245
|
+
counts.get('no_idea', 0),
|
|
246
|
+
len(df),
|
|
247
|
+
'N/A' # Accuracy needs to be calculated during training
|
|
248
|
+
],
|
|
249
|
+
'Percentage': [
|
|
250
|
+
f"{100 * counts.get('recommended', 0) / len(df):.2f}%",
|
|
251
|
+
f"{100 * counts.get('not_recommended', 0) / len(df):.2f}%",
|
|
252
|
+
f"{100 * counts.get('no_idea', 0) / len(df):.2f}%",
|
|
253
|
+
'100%',
|
|
254
|
+
'N/A'
|
|
255
|
+
]
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
return summary
|
|
259
|
+
except Exception as e:
|
|
260
|
+
raise Exception(f"Failed to generate summary: {str(e)}")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# Github : RezaGooner
|