PerSent 1.4.0__tar.gz → 1.5.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.
Files changed (40) hide show
  1. {persent-1.4.0 → persent-1.5.0}/CHANGELOG.md +27 -2
  2. persent-1.5.0/PKG-INFO +235 -0
  3. persent-1.5.0/README.fa.md +194 -0
  4. persent-1.5.0/README.md +193 -0
  5. {persent-1.4.0 → persent-1.5.0}/pyproject.toml +3 -0
  6. persent-1.5.0/src/PerSent/CommentAnalyzer.py +178 -0
  7. persent-1.5.0/src/PerSent/SentimentAnalyzer.py +166 -0
  8. persent-1.5.0/src/PerSent/__init__.py +46 -0
  9. persent-1.5.0/src/PerSent/__main__.py +3 -0
  10. {persent-1.4.0 → persent-1.5.0}/src/PerSent/_models.py +2 -1
  11. persent-1.5.0/src/PerSent/_text.py +122 -0
  12. {persent-1.4.0 → persent-1.5.0}/src/PerSent/_utils.py +16 -1
  13. persent-1.5.0/src/PerSent/_version.py +1 -0
  14. persent-1.5.0/src/PerSent/cli.py +228 -0
  15. persent-1.5.0/src/PerSent/emotion.py +322 -0
  16. persent-1.5.0/src/PerSent/polarity.py +371 -0
  17. persent-1.5.0/src/PerSent/results.py +146 -0
  18. {persent-1.4.0 → persent-1.5.0}/tests/conftest.py +19 -3
  19. persent-1.5.0/tests/test_cli.py +93 -0
  20. persent-1.5.0/tests/test_emotion.py +127 -0
  21. persent-1.4.0/tests/test_comment_analyzer.py → persent-1.5.0/tests/test_legacy_comment_analyzer.py +4 -0
  22. persent-1.4.0/tests/test_sentiment_analyzer.py → persent-1.5.0/tests/test_legacy_sentiment_analyzer.py +4 -0
  23. persent-1.5.0/tests/test_polarity.py +166 -0
  24. persent-1.4.0/PKG-INFO +0 -203
  25. persent-1.4.0/README.fa.md +0 -163
  26. persent-1.4.0/README.md +0 -161
  27. persent-1.4.0/src/PerSent/CommentAnalyzer.py +0 -282
  28. persent-1.4.0/src/PerSent/SentimentAnalyzer.py +0 -306
  29. persent-1.4.0/src/PerSent/__init__.py +0 -30
  30. persent-1.4.0/src/PerSent/_text.py +0 -52
  31. persent-1.4.0/src/PerSent/_version.py +0 -1
  32. {persent-1.4.0 → persent-1.5.0}/.gitignore +0 -0
  33. {persent-1.4.0 → persent-1.5.0}/LICENSE +0 -0
  34. {persent-1.4.0 → persent-1.5.0}/scripts/convert_legacy_models.py +0 -0
  35. {persent-1.4.0 → persent-1.5.0}/src/PerSent/_stemmer.py +0 -0
  36. {persent-1.4.0 → persent-1.5.0}/src/PerSent/exceptions.py +0 -0
  37. {persent-1.4.0 → persent-1.5.0}/src/PerSent/models/comment_model.npz +0 -0
  38. {persent-1.4.0 → persent-1.5.0}/src/PerSent/models/emotion_lexicon.json +0 -0
  39. {persent-1.4.0 → persent-1.5.0}/src/PerSent/py.typed +0 -0
  40. {persent-1.4.0 → persent-1.5.0}/tests/test_package.py +0 -0
@@ -4,7 +4,31 @@ All notable changes to this project are documented here.
4
4
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
- ## [1.4.0] - Unreleased
7
+ ## [1.5.0] - Unreleased
8
+
9
+ ### Added
10
+ - New API with snake_case methods and result objects:
11
+ - `PolarityClassifier`: `pretrained()`, `load()`, `save()`, `predict()`, `predict_many()`, `predict_proba()`,
12
+ `analyze()` / `analyze_many()` (→ `PolarityResult` with per-label probabilities), `fit()` / `fit_csv()`
13
+ (→ `TrainingReport` with accuracy, macro-F1 and per-label metrics), `evaluate()` / `evaluate_csv()`
14
+ (→ `EvaluationReport` with a confusion matrix), `analyze_csv()` (optional probability columns) and `summarize()`.
15
+ - `EmotionAnalyzer`: `pretrained()`, `load()`, `save()`, `analyze()` / `analyze_many()` (→ `EmotionResult` with
16
+ `dominant`, `matched_words`, English names), `scores()`, `dominant()`, `fit_csv()`, `add_lexicon()`, `add_word()`,
17
+ `analyze_csv()` (one column per emotion plus `dominant_emotion`).
18
+ - Training/analysis methods accept a CSV path or a `pandas.DataFrame`; emotions may be given in Persian or English.
19
+ - `persent` command-line tool (`polarity`, `emotion`, `csv`, `train`, `evaluate`), also as `python -m PerSent`.
20
+ - `PolarityResult`, `EmotionResult`, `TrainingReport`, `EvaluationReport`, `EMOTIONS`, `EMOTION_NAMES_EN`, `LABELS`
21
+ exported from the package.
22
+
23
+ ### Changed
24
+ - Polarity predictions are computed in batches (faster for CSV files); results are unchanged.
25
+ - Emotion scores are always returned in the same order (شادی, غم, ترس, عصبانیت, تنفر, شگفتی, آرامش).
26
+
27
+ ### Deprecated
28
+ - `CommentAnalyzer` and `SentimentAnalyzer` (and their camelCase methods) now emit a `DeprecationWarning`. They
29
+ keep their exact 1.4 behaviour and will be removed in PerSent 2.0. See the migration table in the README.
30
+
31
+ ## [1.4.0] - 2026-11-10
8
32
 
9
33
  ### Added
10
34
  - `pyproject.toml` packaging (hatchling) with a single version source (`src/PerSent/_version.py`).
@@ -48,5 +72,6 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
48
72
  ## [1.3.3] - 2025-08-22
49
73
  - Last release with the original `setup.py` layout and pickle-based models.
50
74
 
51
- [1.4.0]: https://github.com/RezaGooner/PerSent/compare/088b919...HEAD
75
+ [1.5.0]: https://github.com/RezaGooner/PerSent/compare/v1.4.0...HEAD
76
+ [1.4.0]: https://github.com/RezaGooner/PerSent/compare/088b919...v1.4.0
52
77
  [1.3.3]: https://pypi.org/project/PerSent/1.3.3/
persent-1.5.0/PKG-INFO ADDED
@@ -0,0 +1,235 @@
1
+ Metadata-Version: 2.5
2
+ Name: PerSent
3
+ Version: 1.5.0
4
+ Summary: Persian sentiment and emotion analysis toolkit
5
+ Project-URL: Homepage, https://github.com/RezaGooner/PerSent
6
+ Project-URL: Source, https://github.com/RezaGooner/PerSent
7
+ Project-URL: Issues, https://github.com/RezaGooner/PerSent/issues
8
+ Project-URL: Changelog, https://github.com/RezaGooner/PerSent/blob/main/CHANGELOG.md
9
+ Author-email: Reza Asadi <RezaAsadiProgrammer@Gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: emotion-detection,farsi,nlp,persian,sentiment-analysis,text-classification
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Natural Language :: Persian
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
25
+ Classifier: Topic :: Text Processing :: Linguistic
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.10
28
+ Requires-Dist: gensim>=4.3.2
29
+ Requires-Dist: hazm>=0.9.3
30
+ Requires-Dist: joblib>=1.1
31
+ Requires-Dist: numpy>=1.24
32
+ Requires-Dist: pandas>=1.5
33
+ Requires-Dist: scikit-learn>=1.2
34
+ Requires-Dist: tqdm>=4.62
35
+ Provides-Extra: dev
36
+ Requires-Dist: build; extra == 'dev'
37
+ Requires-Dist: pytest-cov; extra == 'dev'
38
+ Requires-Dist: pytest>=7; extra == 'dev'
39
+ Requires-Dist: ruff>=0.6; extra == 'dev'
40
+ Requires-Dist: twine; extra == 'dev'
41
+ Description-Content-Type: text/markdown
42
+
43
+ # PerSent (Persian Sentiment Analyzer)
44
+
45
+ [![PyPI](https://img.shields.io/pypi/v/PerSent.svg)](https://pypi.org/project/PerSent/)
46
+ [![Python](https://img.shields.io/pypi/pyversions/PerSent.svg)](https://pypi.org/project/PerSent/)
47
+ [![CI](https://github.com/RezaGooner/PerSent/actions/workflows/ci.yml/badge.svg)](https://github.com/RezaGooner/PerSent/actions/workflows/ci.yml)
48
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
49
+ [![Persian](https://img.shields.io/badge/Persian-فارسی-blue.svg)](README.fa.md)
50
+
51
+ ![PerSent Logo](https://github.com/user-attachments/assets/6bb1633b-6ed3-47fa-aae2-f97886dc4e22)
52
+
53
+ PerSent is a lightweight Python library and command-line tool for **sentiment and emotion analysis of Persian (Farsi) text**.
54
+
55
+ - **`PolarityClassifier`** labels product/service reviews as `recommended`, `not_recommended` or `no_idea`, with probabilities.
56
+ - **`EmotionAnalyzer`** scores seven emotions with a weighted lexicon:
57
+ شادی (happiness), غم (sadness), ترس (fear), عصبانیت (anger), تنفر (disgust), شگفتی (surprise), آرامش (calmness).
58
+ - Pre-trained models included (no download at runtime), CSV in/out, train and evaluate your own models.
59
+
60
+ ## Installation
61
+
62
+ ```bash
63
+ pip install PerSent
64
+ ```
65
+
66
+ Requires Python 3.10–3.13.
67
+
68
+ ## Quick start
69
+
70
+ ### Review polarity
71
+
72
+ ```python
73
+ from PerSent import PolarityClassifier
74
+
75
+ clf = PolarityClassifier.pretrained()
76
+
77
+ clf.predict("کیفیت عالی داشت") # 'recommended'
78
+ clf.predict_proba("کیفیت عالی داشت") # {'not_recommended': 0.13, 'recommended': 0.74, 'no_idea': 0.13}
79
+
80
+ result = clf.analyze("افتضاح بود")
81
+ result.label, result.confidence # ('not_recommended', 0.9998)
82
+
83
+ clf.predict_many(["خوب بود", "پولم هدر رفت"]) # batch
84
+ ```
85
+
86
+ ### Emotions
87
+
88
+ ```python
89
+ from PerSent import EmotionAnalyzer
90
+
91
+ emo = EmotionAnalyzer.pretrained()
92
+ result = emo.analyze("امتحانم رو خراب کردم. احساس می‌کنم یک شکست خورده‌ی تمام عیارم.")
93
+
94
+ result.scores # {'شادی': 0.0, 'غم': 36.0, 'ترس': 28.0, 'عصبانیت': 36.0, ...}
95
+ result.dominant # 'غم'
96
+ result.matched_words # lexicon words that produced the scores
97
+ result.scores_en() # {'happiness': 0.0, 'sadness': 36.0, 'fear': 28.0, 'anger': 36.0, ...}
98
+ result.to_dict(lang="en")
99
+ ```
100
+
101
+ ### CSV files
102
+
103
+ ```python
104
+ clf.analyze_csv("comments.csv", "results.csv", text_column="نظرات", # name or 0-based / negative index
105
+ include_scores=True, summary_csv="summary.csv")
106
+
107
+ emo.analyze_csv("texts.csv", "emotions.csv", text_column="text", lang="en") # one column per emotion
108
+ ```
109
+
110
+ Both methods also accept a `pandas.DataFrame` and return the resulting DataFrame. Output files are UTF-8 with BOM, so
111
+ they open correctly in Excel.
112
+
113
+ ### Command line
114
+
115
+ ```bash
116
+ persent polarity "کیفیت عالی داشت" "افتضاح بود" --proba
117
+ echo "خیلی خوشحالم" | persent emotion --lang en
118
+ persent emotion --file texts.txt --json # one JSON object per line
119
+ persent csv comments.csv -o results.csv -c نظرات --scores --summary summary.csv
120
+ persent csv texts.csv -o emotions.csv --task emotion -c text
121
+ persent train polarity train.csv -o my_model/ --seed 42
122
+ persent evaluate test.csv -m my_model/
123
+ ```
124
+
125
+ `python -m PerSent ...` works as well. Run `persent <command> --help` for all options.
126
+
127
+ ## Training, evaluating and saving models
128
+
129
+ ```python
130
+ from PerSent import PolarityClassifier, EmotionAnalyzer
131
+
132
+ clf = PolarityClassifier()
133
+ report = clf.fit_csv("dataset/train.csv", random_state=42) # columns: body, recommendation_status
134
+ print(report) # Trained on ... texts, tested on ...: accuracy=..., macro-F1=...
135
+ clf.save("my_model/") # -> my_model/comment_model.npz
136
+ clf = PolarityClassifier.load("my_model/")
137
+ print(clf.evaluate_csv("test.csv"))
138
+
139
+ emo = EmotionAnalyzer.pretrained() # or EmotionAnalyzer() to start empty
140
+ emo.fit_csv("dataset/emotion_dataset.csv") # columns: text, sentiment, weight
141
+ emo.add_lexicon("my_words.csv") # word, emotion, weight
142
+ emo.add_word("سرمست", "happiness", weight=1.5) # single words only
143
+ emo.save("my_emotions.json")
144
+ emo = EmotionAnalyzer.load("my_emotions.json")
145
+ ```
146
+
147
+ Models are stored as plain `.npz` / `.json` files — loading them never executes code.
148
+
149
+ ## API reference
150
+
151
+ ### `PolarityClassifier`
152
+
153
+ | Member | Description |
154
+ |---|---|
155
+ | `PolarityClassifier.pretrained()` | Bundled model. |
156
+ | `PolarityClassifier.load(path)` / `save(path)` | Directory (`comment_model.npz`) or `.npz` file. 1.x pickle folders load with a warning. |
157
+ | `predict(text)` / `predict_many(texts)` | Label(s). Texts without letters return `no_idea`. |
158
+ | `predict_proba(text)` | `{label: probability}`. |
159
+ | `analyze(text)` / `analyze_many(texts)` | `PolarityResult(text, label, scores)` with `.confidence`, `.label_fa`, `.to_dict()`. |
160
+ | `fit(texts, labels, *, vector_size=100, window=5, min_count=1, epochs=5, test_size=0.2, random_state=None, C=1.0, ...)` | Train Word2Vec + logistic regression. Returns a `TrainingReport` (accuracy, macro-F1, per-label metrics on a stratified hold-out split). |
161
+ | `fit_csv(data, text_column="body", label_column="recommendation_status", **fit_kwargs)` | `fit` on a CSV path or DataFrame. |
162
+ | `evaluate(texts, labels)` / `evaluate_csv(data, ...)` | `EvaluationReport` with accuracy, macro-F1, per-label metrics and confusion matrix. |
163
+ | `analyze_csv(input, output=None, *, text_column=0, output_column="sentiment", include_scores=False, summary_csv=None)` | Analyze a column; returns a DataFrame. |
164
+
165
+ ### `EmotionAnalyzer`
166
+
167
+ | Member | Description |
168
+ |---|---|
169
+ | `EmotionAnalyzer.pretrained()` | Bundled lexicon. |
170
+ | `EmotionAnalyzer.load(path)` / `save(path)` | `.json` file or directory. |
171
+ | `analyze(text)` / `analyze_many(texts)` | `EmotionResult(text, scores, matched_words)` with `.dominant`, `.dominant_en`, `.scores_en()`, `.to_dict(lang)`. |
172
+ | `scores(text)` / `dominant(text)` | Shortcuts. |
173
+ | `fit_csv(data, text_column="text", emotion_column=None, weight_column="weight", *, reset=False)` | Learn emotion words from labelled texts (merged into the current lexicon unless `reset=True`). |
174
+ | `add_lexicon(data, word_column=0, emotion_column=1, weight_column=2)` | Add a word/emotion/weight table. Emotions may be Persian or English names. |
175
+ | `add_word(word, emotion, weight=1.0)` | Add a single word. |
176
+ | `analyze_csv(input, output=None, *, text_column="text", expand=True, lang="fa")` | One column per emotion plus `dominant_emotion`. |
177
+
178
+ Errors derive from `PerSent.PerSentError`: `ModelNotLoadedError`, `ModelFileError`, `DataValidationError`
179
+ (which is also a `ValueError`). Messages are logged with the standard `logging` module (logger `"PerSent"`).
180
+
181
+ ## Model quality and limitations
182
+
183
+ - The bundled polarity model reaches about **65 % accuracy** (macro-F1 ≈ 0.64) on a 10 000-review sample of
184
+ [`dataset/train.csv`](dataset/train.csv) — three balanced classes, so chance level is 33 %. Better models are planned.
185
+ - The emotion analyzer is lexicon-based and does **not handle negation** yet ("خوشحال نیستم" is scored as happiness).
186
+ - hazm's stop-word list contains opinion words such as "خوب", "بد" and "عالی", which are removed before
187
+ classification; short reviews made only of such words are therefore unreliable.
188
+
189
+ A larger training set is available [here](https://github.com/RezaGooner/Sentiment-Survey-Analyzer/tree/main/Dataset/big_train).
190
+
191
+ ## Upgrading from 1.x
192
+
193
+ `CommentAnalyzer` and `SentimentAnalyzer` still work exactly as before but emit a `DeprecationWarning`; they will be
194
+ removed in PerSent 2.0.
195
+
196
+ | 1.x | 1.5+ |
197
+ |---|---|
198
+ | `a = CommentAnalyzer(); a.loadModel()` | `clf = PolarityClassifier.pretrained()` |
199
+ | `a.analyzeText(t)` | `clf.predict(t)` (or `clf.analyze(t)` for probabilities) |
200
+ | `a.analyzeTexts(ts)` | `clf.predict_many(ts)` |
201
+ | `a.train(csv)` → accuracy | `clf.fit_csv(csv)` → `TrainingReport` |
202
+ | `a.saveModel()` / `a.loadModel()` with `model_dir` | `clf.save(dir)` / `PolarityClassifier.load(dir)` |
203
+ | `a.analyzeCSV(i, o, s, col)` → DataFrame or `None` | `clf.analyze_csv(i, o, text_column=col, summary_csv=s)` (raises on error) |
204
+ | `s = SentimentAnalyzer(); s.loadModel()` | `emo = EmotionAnalyzer.pretrained()` |
205
+ | `s.analyzeText(t)` | `emo.analyze(t).scores` |
206
+ | `s.train(csv)` → `{"status": ...}` | `emo.fit_csv(csv)` (raises on error) |
207
+ | `s.loadLex(csv)` | `emo.add_lexicon(csv)` |
208
+ | `s.saveModel(name)` / `s.loadModel(name)` | `emo.save(path)` / `EmotionAnalyzer.load(path)` |
209
+ | `s.analyzeCSV(i, o)` → `True` / `False` | `emo.analyze_csv(i, o)` |
210
+
211
+ See [CHANGELOG.md](CHANGELOG.md) for all changes.
212
+
213
+ ## Development
214
+
215
+ ```bash
216
+ git clone https://github.com/RezaGooner/PerSent.git
217
+ cd PerSent
218
+ pip install -e ".[dev]"
219
+ pytest
220
+ ruff check . && ruff format --check .
221
+ ```
222
+
223
+ ## Contributing
224
+
225
+ Bug reports, ideas and pull requests are welcome — see [CONTRIBUTING](.github/CONTRIBUTING.md).
226
+
227
+ - [Open an issue](https://github.com/RezaGooner/PerSent/issues/new/choose)
228
+ - [Fork and send a pull request](https://github.com/RezaGooner/PerSent/fork)
229
+ - Email: `RezaAsadiProgrammer@gmail.com` · Telegram: `@RezaGooner`
230
+
231
+ The project grew out of [Sentiment-Survey-Analyzer](https://github.com/RezaGooner/Sentiment-Survey-Analyzer).
232
+
233
+ ## License
234
+
235
+ [MIT](LICENSE)
@@ -0,0 +1,194 @@
1
+ <div dir="rtl">
2
+
3
+ # PerSent پرسنت
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/PerSent.svg)](https://pypi.org/project/PerSent/)
6
+ [![Python](https://img.shields.io/pypi/pyversions/PerSent.svg)](https://pypi.org/project/PerSent/)
7
+ [![CI](https://github.com/RezaGooner/PerSent/actions/workflows/ci.yml/badge.svg)](https://github.com/RezaGooner/PerSent/actions/workflows/ci.yml)
8
+ [![English](https://img.shields.io/badge/English-انگلیسی-blue.svg)](README.md)
9
+
10
+ ![PerSent Logo](https://github.com/user-attachments/assets/6bb1633b-6ed3-47fa-aae2-f97886dc4e22)
11
+
12
+ پرسنت (Persian Sentiment Analyzer) یک کتابخانهٔ سبک پایتون و ابزار خط فرمان برای **تحلیل احساسات و هیجان‌های متن فارسی** است.
13
+
14
+ - **`PolarityClassifier`**: نظر کاربران دربارهٔ کالا یا خدمات را همراه با احتمال هر دسته در یکی از سه دستهٔ `recommended` (پیشنهاد می‌کنم)، `not_recommended` (پیشنهاد نمی‌کنم) و `no_idea` (نظری ندارم) قرار می‌دهد.
15
+ - **`EmotionAnalyzer`**: درصد هفت حس شادی، غم، ترس، عصبانیت، تنفر، شگفتی و آرامش را با یک واژه‌نامهٔ وزن‌دار محاسبه می‌کند.
16
+ - مدل‌های آماده داخل خود بسته هستند و هنگام اجرا چیزی دانلود نمی‌شود. ورودی و خروجی CSV، و آموزش و ارزیابی مدل با داده‌های خودتان هم پشتیبانی می‌شود.
17
+
18
+ ## نصب
19
+
20
+ </div>
21
+
22
+ ```bash
23
+ pip install PerSent
24
+ ```
25
+
26
+ <div dir="rtl">
27
+
28
+ به پایتون ۳.۱۰ تا ۳.۱۳ نیاز دارد.
29
+
30
+ ## شروع سریع
31
+
32
+ ### تحلیل نظرات
33
+
34
+ </div>
35
+
36
+ ```python
37
+ from PerSent import PolarityClassifier
38
+
39
+ clf = PolarityClassifier.pretrained()
40
+
41
+ clf.predict("کیفیت عالی داشت") # 'recommended'
42
+ clf.predict_proba("کیفیت عالی داشت") # {'not_recommended': 0.13, 'recommended': 0.74, 'no_idea': 0.13}
43
+
44
+ result = clf.analyze("افتضاح بود")
45
+ result.label, result.confidence # ('not_recommended', 0.9998)
46
+ result.label_fa # 'پیشنهاد نمی‌کنم'
47
+
48
+ clf.predict_many(["خوب بود", "پولم هدر رفت"]) # چند متن با هم
49
+ ```
50
+
51
+ <div dir="rtl">
52
+
53
+ ### تحلیل احساس
54
+
55
+ </div>
56
+
57
+ ```python
58
+ from PerSent import EmotionAnalyzer
59
+
60
+ emo = EmotionAnalyzer.pretrained()
61
+ result = emo.analyze("امتحانم رو خراب کردم. احساس می‌کنم یک شکست خورده‌ی تمام عیارم.")
62
+
63
+ result.scores # {'شادی': 0.0, 'غم': 36.0, 'ترس': 28.0, 'عصبانیت': 36.0, ...}
64
+ result.dominant # 'غم'
65
+ result.matched_words # کلمه‌هایی از واژه‌نامه که در متن پیدا شدند
66
+ result.scores_en() # همان امتیازها با نام انگلیسی احساس‌ها
67
+ ```
68
+
69
+ <div dir="rtl">
70
+
71
+ ### فایل CSV
72
+
73
+ </div>
74
+
75
+ ```python
76
+ clf.analyze_csv("comments.csv", "results.csv", text_column="نظرات", # نام ستون یا شمارهٔ آن (از ۰)
77
+ include_scores=True, summary_csv="summary.csv")
78
+
79
+ emo.analyze_csv("texts.csv", "emotions.csv", text_column="text") # یک ستون برای هر احساس
80
+ ```
81
+
82
+ <div dir="rtl">
83
+
84
+ هر دو متد به‌جای مسیر فایل، `DataFrame` پانداس هم می‌پذیرند و نتیجه را به شکل DataFrame برمی‌گردانند. فایل‌های خروجی با کدگذاری UTF-8 همراه BOM ذخیره می‌شوند تا در Excel درست نمایش داده شوند.
85
+
86
+ ### خط فرمان
87
+
88
+ </div>
89
+
90
+ ```bash
91
+ persent polarity "کیفیت عالی داشت" "افتضاح بود" --proba
92
+ echo "خیلی خوشحالم" | persent emotion
93
+ persent emotion --file texts.txt --json
94
+ persent csv comments.csv -o results.csv -c نظرات --scores --summary summary.csv
95
+ persent csv texts.csv -o emotions.csv --task emotion -c text
96
+ persent train polarity train.csv -o my_model/ --seed 42
97
+ persent evaluate test.csv -m my_model/
98
+ ```
99
+
100
+ <div dir="rtl">
101
+
102
+ دستور `python -m PerSent ...` هم همین کار را می‌کند. برای دیدن همهٔ گزینه‌ها `persent <command> --help` را اجرا کنید.
103
+
104
+ ## آموزش، ارزیابی و ذخیرهٔ مدل
105
+
106
+ </div>
107
+
108
+ ```python
109
+ from PerSent import PolarityClassifier, EmotionAnalyzer
110
+
111
+ clf = PolarityClassifier()
112
+ report = clf.fit_csv("dataset/train.csv", random_state=42) # ستون‌ها: body و recommendation_status
113
+ print(report) # دقت و macro-F1 روی دادهٔ آزمون
114
+ clf.save("my_model/")
115
+ clf = PolarityClassifier.load("my_model/")
116
+ print(clf.evaluate_csv("test.csv"))
117
+
118
+ emo = EmotionAnalyzer.pretrained() # یا EmotionAnalyzer() برای شروع از صفر
119
+ emo.fit_csv("dataset/emotion_dataset.csv") # ستون‌ها: text، sentiment و weight
120
+ emo.add_lexicon("my_words.csv") # کلمه، حس، وزن
121
+ emo.add_word("سرمست", "شادی", weight=1.5) # فقط تک‌کلمه
122
+ emo.save("my_emotions.json")
123
+ emo = EmotionAnalyzer.load("my_emotions.json")
124
+ ```
125
+
126
+ <div dir="rtl">
127
+
128
+ مدل‌ها به شکل فایل‌های ساده `.npz` و `.json` ذخیره می‌شوند و بارگذاری آن‌ها هیچ کدی اجرا نمی‌کند.
129
+
130
+ - برچسب‌های آموزش `PolarityClassifier` یکی از `recommended`، `not_recommended` یا `no_idea` هستند و برچسب خالی `no_idea` در نظر گرفته می‌شود.
131
+ - نام احساس‌ها در `EmotionAnalyzer` را می‌توانید فارسی (`شادی`) یا انگلیسی (`happiness`) بنویسید.
132
+
133
+ جدول کامل متدها در [README انگلیسی](README.md#api-reference) آمده است.
134
+
135
+ ## کیفیت مدل و محدودیت‌ها
136
+
137
+ - دقت مدل آمادهٔ تحلیل نظرات روی نمونه‌ای ۱۰٬۰۰۰تایی از [`dataset/train.csv`](dataset/train.csv) حدود **۶۵٪** است (macro-F1 ≈ ۰.۶۴). سه دسته متوازن‌اند و دقت تصادفی ۳۳٪ است. بهبود مدل در برنامهٔ نسخه‌های بعدی است.
138
+ - تحلیلگر احساس بر پایهٔ واژه‌نامه کار می‌کند و **هنوز منفی‌سازی را تشخیص نمی‌دهد**. مثلاً «خوشحال نیستم» شادی حساب می‌شود.
139
+ - لیست ایست‌واژه‌های hazm کلمه‌هایی مثل «خوب»، «بد» و «عالی» را هم شامل می‌شود و این کلمه‌ها پیش از دسته‌بندی حذف می‌شوند. به همین دلیل نتیجه برای نظرهای خیلی کوتاه قابل اعتماد نیست.
140
+
141
+ مجموعه‌دادهٔ بزرگ‌تر برای آموزش: [اینجا](https://github.com/RezaGooner/Sentiment-Survey-Analyzer/tree/main/Dataset/big_train)
142
+
143
+ ## ارتقا از نسخهٔ 1.x
144
+
145
+ `CommentAnalyzer` و `SentimentAnalyzer` دقیقاً مثل قبل کار می‌کنند، ولی یک `DeprecationWarning` نشان می‌دهند و در نسخهٔ 2.0 حذف خواهند شد.
146
+
147
+ </div>
148
+
149
+ | 1.x | 1.5+ |
150
+ |---|---|
151
+ | `a = CommentAnalyzer(); a.loadModel()` | `clf = PolarityClassifier.pretrained()` |
152
+ | `a.analyzeText(t)` | `clf.predict(t)` / `clf.analyze(t)` |
153
+ | `a.train(csv)` | `clf.fit_csv(csv)` |
154
+ | `a.saveModel()` / `a.loadModel()` | `clf.save(dir)` / `PolarityClassifier.load(dir)` |
155
+ | `a.analyzeCSV(i, o, s, col)` | `clf.analyze_csv(i, o, text_column=col, summary_csv=s)` |
156
+ | `s = SentimentAnalyzer(); s.loadModel()` | `emo = EmotionAnalyzer.pretrained()` |
157
+ | `s.analyzeText(t)` | `emo.analyze(t).scores` |
158
+ | `s.train(csv)` / `s.loadLex(csv)` | `emo.fit_csv(csv)` / `emo.add_lexicon(csv)` |
159
+ | `s.saveModel(name)` / `s.loadModel(name)` | `emo.save(path)` / `EmotionAnalyzer.load(path)` |
160
+ | `s.analyzeCSV(i, o)` | `emo.analyze_csv(i, o)` |
161
+
162
+ <div dir="rtl">
163
+
164
+ متدهای جدید در صورت خطا exception می‌دهند، نه `None` یا `False`. فهرست کامل تغییرات در [CHANGELOG.md](CHANGELOG.md) است.
165
+
166
+ ## توسعه
167
+
168
+ </div>
169
+
170
+ ```bash
171
+ git clone https://github.com/RezaGooner/PerSent.git
172
+ cd PerSent
173
+ pip install -e ".[dev]"
174
+ pytest
175
+ ruff check . && ruff format --check .
176
+ ```
177
+
178
+ <div dir="rtl">
179
+
180
+ ## مشارکت
181
+
182
+ خوشحال می‌شوم پیشنهادها، گزارش باگ و Pull Requestهایتان را ببینم. راهنمای مشارکت در [CONTRIBUTING](.github/CONTRIBUTING.md) است.
183
+
184
+ - [ثبت Issue](https://github.com/RezaGooner/PerSent/issues/new/choose)
185
+ - [Fork و ارسال Pull Request](https://github.com/RezaGooner/PerSent/fork)
186
+ - ایمیل: `RezaAsadiProgrammer@gmail.com` · تلگرام: `@RezaGooner`
187
+
188
+ این پروژه از مخزن [Sentiment-Survey-Analyzer](https://github.com/RezaGooner/Sentiment-Survey-Analyzer) شکل گرفته است.
189
+
190
+ ## مجوز
191
+
192
+ [MIT](LICENSE)
193
+
194
+ </div>
@@ -0,0 +1,193 @@
1
+ # PerSent (Persian Sentiment Analyzer)
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/PerSent.svg)](https://pypi.org/project/PerSent/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/PerSent.svg)](https://pypi.org/project/PerSent/)
5
+ [![CI](https://github.com/RezaGooner/PerSent/actions/workflows/ci.yml/badge.svg)](https://github.com/RezaGooner/PerSent/actions/workflows/ci.yml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
7
+ [![Persian](https://img.shields.io/badge/Persian-فارسی-blue.svg)](README.fa.md)
8
+
9
+ ![PerSent Logo](https://github.com/user-attachments/assets/6bb1633b-6ed3-47fa-aae2-f97886dc4e22)
10
+
11
+ PerSent is a lightweight Python library and command-line tool for **sentiment and emotion analysis of Persian (Farsi) text**.
12
+
13
+ - **`PolarityClassifier`** labels product/service reviews as `recommended`, `not_recommended` or `no_idea`, with probabilities.
14
+ - **`EmotionAnalyzer`** scores seven emotions with a weighted lexicon:
15
+ شادی (happiness), غم (sadness), ترس (fear), عصبانیت (anger), تنفر (disgust), شگفتی (surprise), آرامش (calmness).
16
+ - Pre-trained models included (no download at runtime), CSV in/out, train and evaluate your own models.
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pip install PerSent
22
+ ```
23
+
24
+ Requires Python 3.10–3.13.
25
+
26
+ ## Quick start
27
+
28
+ ### Review polarity
29
+
30
+ ```python
31
+ from PerSent import PolarityClassifier
32
+
33
+ clf = PolarityClassifier.pretrained()
34
+
35
+ clf.predict("کیفیت عالی داشت") # 'recommended'
36
+ clf.predict_proba("کیفیت عالی داشت") # {'not_recommended': 0.13, 'recommended': 0.74, 'no_idea': 0.13}
37
+
38
+ result = clf.analyze("افتضاح بود")
39
+ result.label, result.confidence # ('not_recommended', 0.9998)
40
+
41
+ clf.predict_many(["خوب بود", "پولم هدر رفت"]) # batch
42
+ ```
43
+
44
+ ### Emotions
45
+
46
+ ```python
47
+ from PerSent import EmotionAnalyzer
48
+
49
+ emo = EmotionAnalyzer.pretrained()
50
+ result = emo.analyze("امتحانم رو خراب کردم. احساس می‌کنم یک شکست خورده‌ی تمام عیارم.")
51
+
52
+ result.scores # {'شادی': 0.0, 'غم': 36.0, 'ترس': 28.0, 'عصبانیت': 36.0, ...}
53
+ result.dominant # 'غم'
54
+ result.matched_words # lexicon words that produced the scores
55
+ result.scores_en() # {'happiness': 0.0, 'sadness': 36.0, 'fear': 28.0, 'anger': 36.0, ...}
56
+ result.to_dict(lang="en")
57
+ ```
58
+
59
+ ### CSV files
60
+
61
+ ```python
62
+ clf.analyze_csv("comments.csv", "results.csv", text_column="نظرات", # name or 0-based / negative index
63
+ include_scores=True, summary_csv="summary.csv")
64
+
65
+ emo.analyze_csv("texts.csv", "emotions.csv", text_column="text", lang="en") # one column per emotion
66
+ ```
67
+
68
+ Both methods also accept a `pandas.DataFrame` and return the resulting DataFrame. Output files are UTF-8 with BOM, so
69
+ they open correctly in Excel.
70
+
71
+ ### Command line
72
+
73
+ ```bash
74
+ persent polarity "کیفیت عالی داشت" "افتضاح بود" --proba
75
+ echo "خیلی خوشحالم" | persent emotion --lang en
76
+ persent emotion --file texts.txt --json # one JSON object per line
77
+ persent csv comments.csv -o results.csv -c نظرات --scores --summary summary.csv
78
+ persent csv texts.csv -o emotions.csv --task emotion -c text
79
+ persent train polarity train.csv -o my_model/ --seed 42
80
+ persent evaluate test.csv -m my_model/
81
+ ```
82
+
83
+ `python -m PerSent ...` works as well. Run `persent <command> --help` for all options.
84
+
85
+ ## Training, evaluating and saving models
86
+
87
+ ```python
88
+ from PerSent import PolarityClassifier, EmotionAnalyzer
89
+
90
+ clf = PolarityClassifier()
91
+ report = clf.fit_csv("dataset/train.csv", random_state=42) # columns: body, recommendation_status
92
+ print(report) # Trained on ... texts, tested on ...: accuracy=..., macro-F1=...
93
+ clf.save("my_model/") # -> my_model/comment_model.npz
94
+ clf = PolarityClassifier.load("my_model/")
95
+ print(clf.evaluate_csv("test.csv"))
96
+
97
+ emo = EmotionAnalyzer.pretrained() # or EmotionAnalyzer() to start empty
98
+ emo.fit_csv("dataset/emotion_dataset.csv") # columns: text, sentiment, weight
99
+ emo.add_lexicon("my_words.csv") # word, emotion, weight
100
+ emo.add_word("سرمست", "happiness", weight=1.5) # single words only
101
+ emo.save("my_emotions.json")
102
+ emo = EmotionAnalyzer.load("my_emotions.json")
103
+ ```
104
+
105
+ Models are stored as plain `.npz` / `.json` files — loading them never executes code.
106
+
107
+ ## API reference
108
+
109
+ ### `PolarityClassifier`
110
+
111
+ | Member | Description |
112
+ |---|---|
113
+ | `PolarityClassifier.pretrained()` | Bundled model. |
114
+ | `PolarityClassifier.load(path)` / `save(path)` | Directory (`comment_model.npz`) or `.npz` file. 1.x pickle folders load with a warning. |
115
+ | `predict(text)` / `predict_many(texts)` | Label(s). Texts without letters return `no_idea`. |
116
+ | `predict_proba(text)` | `{label: probability}`. |
117
+ | `analyze(text)` / `analyze_many(texts)` | `PolarityResult(text, label, scores)` with `.confidence`, `.label_fa`, `.to_dict()`. |
118
+ | `fit(texts, labels, *, vector_size=100, window=5, min_count=1, epochs=5, test_size=0.2, random_state=None, C=1.0, ...)` | Train Word2Vec + logistic regression. Returns a `TrainingReport` (accuracy, macro-F1, per-label metrics on a stratified hold-out split). |
119
+ | `fit_csv(data, text_column="body", label_column="recommendation_status", **fit_kwargs)` | `fit` on a CSV path or DataFrame. |
120
+ | `evaluate(texts, labels)` / `evaluate_csv(data, ...)` | `EvaluationReport` with accuracy, macro-F1, per-label metrics and confusion matrix. |
121
+ | `analyze_csv(input, output=None, *, text_column=0, output_column="sentiment", include_scores=False, summary_csv=None)` | Analyze a column; returns a DataFrame. |
122
+
123
+ ### `EmotionAnalyzer`
124
+
125
+ | Member | Description |
126
+ |---|---|
127
+ | `EmotionAnalyzer.pretrained()` | Bundled lexicon. |
128
+ | `EmotionAnalyzer.load(path)` / `save(path)` | `.json` file or directory. |
129
+ | `analyze(text)` / `analyze_many(texts)` | `EmotionResult(text, scores, matched_words)` with `.dominant`, `.dominant_en`, `.scores_en()`, `.to_dict(lang)`. |
130
+ | `scores(text)` / `dominant(text)` | Shortcuts. |
131
+ | `fit_csv(data, text_column="text", emotion_column=None, weight_column="weight", *, reset=False)` | Learn emotion words from labelled texts (merged into the current lexicon unless `reset=True`). |
132
+ | `add_lexicon(data, word_column=0, emotion_column=1, weight_column=2)` | Add a word/emotion/weight table. Emotions may be Persian or English names. |
133
+ | `add_word(word, emotion, weight=1.0)` | Add a single word. |
134
+ | `analyze_csv(input, output=None, *, text_column="text", expand=True, lang="fa")` | One column per emotion plus `dominant_emotion`. |
135
+
136
+ Errors derive from `PerSent.PerSentError`: `ModelNotLoadedError`, `ModelFileError`, `DataValidationError`
137
+ (which is also a `ValueError`). Messages are logged with the standard `logging` module (logger `"PerSent"`).
138
+
139
+ ## Model quality and limitations
140
+
141
+ - The bundled polarity model reaches about **65 % accuracy** (macro-F1 ≈ 0.64) on a 10 000-review sample of
142
+ [`dataset/train.csv`](dataset/train.csv) — three balanced classes, so chance level is 33 %. Better models are planned.
143
+ - The emotion analyzer is lexicon-based and does **not handle negation** yet ("خوشحال نیستم" is scored as happiness).
144
+ - hazm's stop-word list contains opinion words such as "خوب", "بد" and "عالی", which are removed before
145
+ classification; short reviews made only of such words are therefore unreliable.
146
+
147
+ A larger training set is available [here](https://github.com/RezaGooner/Sentiment-Survey-Analyzer/tree/main/Dataset/big_train).
148
+
149
+ ## Upgrading from 1.x
150
+
151
+ `CommentAnalyzer` and `SentimentAnalyzer` still work exactly as before but emit a `DeprecationWarning`; they will be
152
+ removed in PerSent 2.0.
153
+
154
+ | 1.x | 1.5+ |
155
+ |---|---|
156
+ | `a = CommentAnalyzer(); a.loadModel()` | `clf = PolarityClassifier.pretrained()` |
157
+ | `a.analyzeText(t)` | `clf.predict(t)` (or `clf.analyze(t)` for probabilities) |
158
+ | `a.analyzeTexts(ts)` | `clf.predict_many(ts)` |
159
+ | `a.train(csv)` → accuracy | `clf.fit_csv(csv)` → `TrainingReport` |
160
+ | `a.saveModel()` / `a.loadModel()` with `model_dir` | `clf.save(dir)` / `PolarityClassifier.load(dir)` |
161
+ | `a.analyzeCSV(i, o, s, col)` → DataFrame or `None` | `clf.analyze_csv(i, o, text_column=col, summary_csv=s)` (raises on error) |
162
+ | `s = SentimentAnalyzer(); s.loadModel()` | `emo = EmotionAnalyzer.pretrained()` |
163
+ | `s.analyzeText(t)` | `emo.analyze(t).scores` |
164
+ | `s.train(csv)` → `{"status": ...}` | `emo.fit_csv(csv)` (raises on error) |
165
+ | `s.loadLex(csv)` | `emo.add_lexicon(csv)` |
166
+ | `s.saveModel(name)` / `s.loadModel(name)` | `emo.save(path)` / `EmotionAnalyzer.load(path)` |
167
+ | `s.analyzeCSV(i, o)` → `True` / `False` | `emo.analyze_csv(i, o)` |
168
+
169
+ See [CHANGELOG.md](CHANGELOG.md) for all changes.
170
+
171
+ ## Development
172
+
173
+ ```bash
174
+ git clone https://github.com/RezaGooner/PerSent.git
175
+ cd PerSent
176
+ pip install -e ".[dev]"
177
+ pytest
178
+ ruff check . && ruff format --check .
179
+ ```
180
+
181
+ ## Contributing
182
+
183
+ Bug reports, ideas and pull requests are welcome — see [CONTRIBUTING](.github/CONTRIBUTING.md).
184
+
185
+ - [Open an issue](https://github.com/RezaGooner/PerSent/issues/new/choose)
186
+ - [Fork and send a pull request](https://github.com/RezaGooner/PerSent/fork)
187
+ - Email: `RezaAsadiProgrammer@gmail.com` · Telegram: `@RezaGooner`
188
+
189
+ The project grew out of [Sentiment-Survey-Analyzer](https://github.com/RezaGooner/Sentiment-Survey-Analyzer).
190
+
191
+ ## License
192
+
193
+ [MIT](LICENSE)
@@ -40,6 +40,9 @@ dependencies = [
40
40
  "tqdm>=4.62",
41
41
  ]
42
42
 
43
+ [project.scripts]
44
+ persent = "PerSent.cli:main"
45
+
43
46
  [project.optional-dependencies]
44
47
  dev = ["pytest>=7", "pytest-cov", "ruff>=0.6", "build", "twine"]
45
48