nltk-vader-sentiment
A modern, dependency-free reimplementation of the VADER sentiment
analyzer (Hutto & Gilbert, ICWSM-14) — with the sentiment and emoji lexicons
bundled as Python data. No downloads, no pickles, no file parsing at
runtime, no dependencies, and no nltk import anywhere.
pip install nltk-vader-sentiment
>>> from vader import polarity_scores
>>> polarity_scores("VADER is smart, handsome, and funny!")
{'neg': 0.0, 'neu': 0.248, 'pos': 0.752, 'compound': 0.8439}
>>> from vader import SentimentIntensityAnalyzer
>>> analyzer = SentimentIntensityAnalyzer() # reuse this; it is thread-safe
>>> analyzer.polarity_scores("Not bad at all :) ❤️")
{'neg': 0.0, 'neu': 0.285, 'pos': 0.715, 'compound': 0.8765}
There is also a CLI:
$ vader "This is amazingly good!"
{"neg": 0.0, "neu": 0.463, "pos": 0.537, "compound": 0.54}
$ vader < reviews.txt # one JSON object per line
Why this package
The two maintained homes of VADER have both drifted and stalled:
- cjhutto/vaderSentiment —
the original. Its last PyPI release (3.3.2) is from 2020, the repository
is essentially dormant, it installs
requestsfor a demo block, and its issue tracker documents years of unfixed scoring bugs. - nltk.sentiment.vader —
a 2016-era snapshot that has drifted from upstream
(nltk#2592): no emoji
handling, a different tokenizer that misses
(super)andword!!(nltk#3071), and avader_lexiconthat must be fetched withnltk.download()at runtime.
This package reimplements the algorithm from scratch against the upstream
reference, fixes the catalogued defects (each fix carries a regression test
naming its issue), and ships everything needed at pip install time. It is
built to the same standard as
nltk-punkt-tokenize, so
that NLTK could one day vendor sentiment analysis out to it — which is why
nothing here may ever import nltk (enforced by tests three ways).
Parity with upstream
Correctness is measured, not claimed. Against a pinned snapshot of upstream
vaderSentiment on 1,219 cases (the upstream demo sentences plus samples
from all four ICWSM-14 ground-truth datasets):
- 1,189/1,219 (97.5%) identical compound scores. Every one of the 30 divergences is traced to a deliberate bug fix (see below).
- Correlation with the ICWSM-14 human ground truth matches upstream to within ±0.0003 on all four datasets:
| dataset | n | upstream r | ours r |
|---|---|---|---|
| tweets | 4,200 | 0.8722 | 0.8719 |
| movie reviews | 10,605 | 0.4276 | 0.4273 |
| Amazon reviews | 3,708 | 0.5896 | 0.5893 |
| NYT editorials | 5,190 | 0.5029 | 0.5031 |
Fixed defects
Engine fixes (relative to upstream master and/or NLTK):
| fix | reference |
|---|---|
| Repeated tokens scored with the first occurrence's negation/booster context | nltk#3638 semantics |
| O(n²) token scan (CPU-DoS on long text) and O(n²) re-lowercasing per rule | nltk#3638, vaderSentiment#110 |
Tokenizer strips only one punctuation char/side, so (super), good!!!! miss the lexicon |
nltk#3071 |
Hashtags scored as neutral (#bad) |
nltk#2637 |
ok!, No, and other short tokens kept with punctuation, missing the lexicon |
upstream <=2 chars rule |
Emoticons with trailing punctuation (:),) never match |
— |
Typographic apostrophes: don’t not recognised as negation |
vaderSentiment#66 |
Multi-code-point emoji (❤️, ZWJ sequences, skin tones) never match |
vaderSentiment#117, #99 |
| Emoji description fused with following word | vaderSentiment#86 |
_but_check scales the first value-equal element; only deterministic since 2020 |
nltk#2581 |
| "never so/this" ×1.25 boost fires without any "never" (operator precedence) | found in source; see tests/test_known_issues.py |
| ALLCAPS emphasis applied to zero-valence words | vaderSentiment#130 |
IndexError when a sentence ends with "no" (unguarded builds) |
vaderSentiment#146 |
negated() matches "n't" anywhere in a token, not as a suffix |
— |
| Special-case idioms mostly unreachable ("to die for", "bus stop") | vaderSentiment#144 |
| Sentiment-laden idioms ("break a leg") declared future work, never wired | vaderSentiment#144 |
| Multiword lexicon entries ("can't stand", "fed up", "screwed up") dead weight | vaderSentiment#124 |
Non-str input coerced through str()/broken bytes branch instead of raising |
vaderSentiment#107 |
Lexicon fixes (resolved at build time by tools/build_data.py, every
resolution logged):
- 14 duplicate keys with conflicting scores → upstream's effective
(last-wins) value kept for parity
(vaderSentiment#122);
the two sign-flipped emoticons
d:/d=additionally get the mean over both rating rounds via the curated layer. - 14 uppercase keys were unreachable (lookups lowercase the token): keys are
case-folded, keeping the previously-reachable value on collision;
:Þand:-Þ, which had no lowercase twin and were dead data, now work. - Curated additions (details):
kindly→ 0.0 (vaderSentiment#155); theslaughter*family, absent whilekilled/murderedscore −3.5/−3.4 (nltk#3507). Disable all curated changes withSentimentIntensityAnalyzer(curated=False).
Customisation
Every word list is per-instance constructor data — no module-global mutation (vaderSentiment#102, #105, #111):
analyzer = SentimentIntensityAnalyzer(
extra_lexicon={"rugpull": -2.9}, # domain terms on top of the default
negations=[...], # replace the negation list
booster={...}, # replace degree adverbs
special_cases={...}, # phrase overrides
idioms={}, # {} disables sentiment-laden idioms
alpha=15.0, # compound normalisation constant
)
Instances are immutable after construction and safe to share across threads.
Design and security
Same rules as nltk-punkt-tokenize:
- Nothing imports
nltk— not the library, not the tests. Enforced by AST scan, by a child interpreter whereimport nltkraises, and by an empty dependency list. The intended dependency arrow isnltk → nltk-vader-sentiment, never a cycle back. - Zero dependencies, stdlib only. The upstream package installs
requestsfor a demo; this one installs nothing. - Lexicons are code-like data: generated Python literal modules under
vader/data/, imported, never parsed. The generator re-parses its own output and rejects any AST node that is not literal data. - No file parser ships:
tools/(the only code that reads lexicon files) is excluded from both the wheel and the sdist. Get it from the repository, deliberately. - No pickles, no downloader, no network, no
eval, no runtime file IO — all test-enforced.
Known limitations (documented, not "fixed")
Inherent to a lexicon-and-rules model — if these matter, use a trained model:
- Sarcasm/irony: "The movie was funny... not" stays positive.
- Compound saturation:
compoundissum/√(sum²+α), so long texts saturate towards ±1 and scores are not comparable across lengths (vaderSentiment#151). Score per sentence and aggregate. - Domain sensitivity: tuned for social media; weaker on finance, health
and news (SentiBench; use
extra_lexiconfor domain terms). - English only. Do not feed it other languages or machine translations.
- Lexicon bias: published audits find disability-related terms skew negative and queer identity terms are absent (Ungless et al. 2023; TrustNLP 2023). The curated layer fixes only clear-cut cases; audit against your own data before making decisions about people.
Development
pip install -e ".[dev]"
pytest # package suite (runs without tools/, as in an sdist)
pytest tools/tests # maintainer suite for the data converters
ruff check src tests tools && mypy
Regenerate the data modules from an upstream checkout:
python tools/build_data.py /path/to/vaderSentiment/vaderSentiment
python tools/generate_parity_fixtures.py /path/to/vaderSentiment
Citation and license
Licensed under the Apache License 2.0 (see LICENSE). The VADER
algorithm and lexicons are by C.J. Hutto, MIT-licensed (see NOTICE). If
you use VADER, please cite:
Hutto, C.J. & Gilbert, E.E. (2014). VADER: A Parsimonious Rule-based Model for Sentiment Analysis of Social Media Text. Eighth International Conference on Weblogs and Social Media (ICWSM-14). Ann Arbor, MI, June 2014.
Release files for nltk-vader-sentiment 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| nltk_vader_sentiment-1.0.0.tar.gz | 159.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| nltk_vader_sentiment-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 246.6 kB
Release files / nltk_vader_sentiment-1.0.0.tar.gz
| Download URL | nltk_vader_sentiment-1.0.0.tar.gz |
|---|---|
| Size | 159.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ab92f068bb1f5ad87a7620120a5f45482617a875faa601f5ce35ac956985a860
|
|
BLAKE2b-256 checksum How to use checksums |
2b131948cd8da4b9cbfa6ca827852358f39e41763a1e5293a7b673a0a8941157
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.12
|
Release files / nltk_vader_sentiment-1.0.0-py3-none-any.whl
| Download URL | nltk_vader_sentiment-1.0.0-py3-none-any.whl |
|---|---|
| Size | 86.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3b23f37e04f9d193fd31fe22a382fcd601fbe4f0bacbb238052e436ca325becb
|
|
BLAKE2b-256 checksum How to use checksums |
115e05afcca1413a51af3b87b0a2d18a2fb9b8fcc3fa2610aa2332403d909654
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.12
|