---
title: Sentiment and Affect Lexicons
module: Text Classification
moduleNumber: 2
lessonNumber: 4
order: 204
summary: >
  A sentiment lexicon is a list of words annotated with the affective meaning
  they carry — positive or negative, or scores along valence, arousal, and
  dominance. We fix what "emotion" means (basic-emotion versus dimensional
  models), survey the standard lexicons, and then build lexicons three ways: by
  human labeling with best-worst scaling, by semi-supervised induction from seed
  words over an embedding space, and by supervised learning from starred
  reviews. We close on connotation frames, which record the sentiment a verb
  implies about each of its arguments.
topics: [Classification]
sources:
  - book: Jurafsky
    ref: "Ch. 20 — Lexicons for Sentiment, Affect, and Connotation; §20.1 Defining Emotion; §20.2 Available Lexicons"
  - book: Jurafsky
    ref: "§20.3 Creating Affect Lexicons by Human Labeling; §20.4 Semi-supervised Induction; §20.5 Supervised Learning of Word Sentiment"
  - book: Jurafsky
    ref: "§20.6–20.7 Using Lexicons; §20.9 Connotation Frames"
---

[Naive Bayes](/natural-language-processing/classification/naive-bayes-and-sentiment)
classified a whole document by pooling every word it contained: the review is
positive because the product of per-word likelihoods came out that way. That
model has no notion of whether any single word _means_ something positive; it only
learns, from labeled documents, that _delicious_ tends to co-occur with the
positive class. This lesson zooms in one level, to the word itself. The claim is
that words carry **affective meaning** — a fixed emotional coloring, independent
of the document they land in — and that we can write this meaning down in a
**lexicon**: a list of words annotated with the sentiment or emotion each one
evokes.[^jm-intro]

A sentiment lexicon is the smallest possible sentiment resource. Instead of
using every word as a feature, we keep only the words that carry a strong cue to
affect, and we attach a label or a score to each. The word _excellent_ is
positive; _horrible_ is negative; _vacation_ scores high on pleasantness and
_torture_ scores low. The rest of the lesson is about what those annotations
should be (which requires a theory of emotion), which lexicons already exist,
and — the real work — the three ways to _build_ one.

## Defining emotion

Before we can annotate a word with its emotion, we have to decide what the set
of emotions _is_. Computational models in NLP draw on two families of theories
from affective science.[^jm-emotion]

The first family treats emotions as **basic emotions**: a small fixed set of
atomic units, present in all cultures, out of which everything else is built. The
best-known list is Ekman's six — _surprise, happiness, anger, fear, disgust,
sadness_ — proposed as universal. Plutchik's wheel is a richer variant: eight
basic emotions arranged in four opposing pairs (joy–sadness, anger–fear,
trust–disgust, anticipation–surprise), with blends filling the space between
neighbors.

$$
% caption: Plutchik's eight basic emotions in four opposing pairs. Each emotion
% sits across the wheel from its opposite; blends (love = joy + trust) fill the
% gaps between neighbors.
\begin{tikzpicture}[>=stealth, font=\footnotesize,
  emo/.style={draw, fill=white, minimum width=17mm, minimum height=6mm, align=center, font=\scriptsize}]
  \definecolor{acc}{HTML}{2348F2}
  \definecolor{red}{HTML}{C0392B}
  \node[emo, draw=acc, text=acc] (joy)  at (90:2.7)  {joy};
  \node[emo] (trust) at (45:2.7)  {trust};
  \node[emo] (fear)  at (0:2.7)   {fear};
  \node[emo] (surp)  at (-45:2.7) {surprise};
  \node[emo, draw=red, text=red] (sad) at (-90:2.7) {sadness};
  \node[emo] (disg)  at (-135:2.7){disgust};
  \node[emo] (anger) at (180:2.7) {anger};
  \node[emo] (antic) at (135:2.7) {anticipation};
  % opposing-pair connectors through the center
  \draw[black, dashed] (joy) -- (sad);
  \draw[black, dashed] (anger) -- (fear);
  \draw[black, dashed] (trust) -- (disg);
  \draw[black, dashed] (antic) -- (surp);
  \node[font=\scriptsize, black, anchor=west] at (3.6,-2.7) {dashed = opposing pairs};
\end{tikzpicture}
$$

The second family treats emotion not as a set of atoms but as a **point in a
continuous space** of two or three dimensions. Almost every dimensional model
includes _valence_ and _arousal_, and many add a third, _dominance_:

> **Definition (Valence, arousal, dominance).** Three axes for locating a word
> in affective space. **Valence** is the pleasantness of the stimulus (how good
> or bad it feels); **arousal** is the intensity of emotion it provokes (calm
> versus excited); **dominance** is the degree of control it exerts (submissive
> versus in-command).

Sentiment falls out of this second view as a special case: the **valence** axis,
measuring how pleasant or unpleasant a word is, _is_ what we usually mean by
sentiment. Positive versus negative is just the sign of valence. The
valence–arousal plane is the standard way to picture it: pleasant-and-calm
(_serene_) sits apart from pleasant-and-intense (_ecstatic_), and the two
negative quadrants split the same way.

$$
% caption: The valence-arousal plane. Valence (horizontal) is pleasantness;
% arousal (vertical) is intensity. Sentiment is just the valence axis. Words like
% serene and terrified sit in different quadrants of the same 2-D space.
\begin{tikzpicture}[>=stealth, font=\footnotesize]
  \definecolor{acc}{HTML}{2348F2}
  \definecolor{red}{HTML}{C0392B}
  % axes
  \draw[->, black] (-3.4,0) -- (3.7,0) node[anchor=west] {valence};
  \draw[->, black] (0,-2.7) -- (0,2.9) node[anchor=south] {arousal};
  \node[black, anchor=north east] at (-0.1,-0.1) {calm};
  \node[black, anchor=east, font=\scriptsize] at (-0.15,2.3) {intense};
  \node[black, anchor=north, font=\scriptsize] at (-2.7,-0.15) {unpleasant};
  \node[black, anchor=north, font=\scriptsize] at (2.7,-0.15) {pleasant};
  % sample words as dots + labels, kept clear of axes
  \fill[acc] (2.2,1.9) circle (2pt); \node[acc, anchor=south west] at (2.28,1.9) {ecstatic};
  \fill[acc] (2.6,-1.5) circle (2pt); \node[acc, anchor=north west] at (2.66,-1.5) {serene};
  \fill[red] (-2.3,2.1) circle (2pt); \node[red, anchor=south east] at (-2.38,2.1) {terrif\/ied};
  \fill[red] (-2.6,-1.6) circle (2pt); \node[red, anchor=north east] at (-2.68,-1.6) {gloomy};
  \fill[black] (0.6,0.5) circle (1.6pt); \node[black, anchor=south west, font=\scriptsize] at (0.66,0.5) {content};
\end{tikzpicture}
$$

The two families differ in resolution rather than substance. Basic-emotion
lexicons give a word a discrete tag (this word is _anger_); dimensional lexicons
give it real-valued coordinates. Both share a strong simplifying assumption: the
affective meaning is **fixed**, the same regardless of the sentence, dialect, or
culture the word appears in. Richer appraisal-theory models — where an emotion
is a process that weighs an event against a person's goals — drop that
assumption, but the lexicons in this lesson keep it, and it is what makes them
lookup tables.

## The lexicons that already exist

A great many affect lexicons have been released; you rarely have to start from
nothing. The simplest label words along a single dimension — call it "sentiment"
or "valence" — as a binary split into a positive wordlist and a negative
wordlist.[^jm-lexicons]

| Lexicon | What it records | Size |
| --- | --- | --- |
| General Inquirer (1966) | positive / negative wordlists (plus strong/weak, active/passive, and more) | 1915 pos, 2291 neg |
| MPQA Subjectivity (2005) | pos / neg, each tagged strongly- or weakly-subjective | 2718 pos, 4912 neg |
| Hu & Liu opinion lexicon (2004) | pos / neg, bootstrapped from product reviews via WordNet | 2006 pos, 4783 neg |
| AFINN | integer valence from $-5$ to $+5$ per word | ~2500 |
| NRC EmoLex (2013) | binary tags for Plutchik's 8 emotions + pos/neg | ~14,000 |
| NRC VAD (2018) | real-valued valence, arousal, dominance | ~20,000 |
| LIWC (2007) | 73 word-category lists (emotion, anger, cognition, ...) | ~2300 |

The oldest, the **General Inquirer**, is just two hand-built wordlists; **MPQA
Subjectivity** adds a strong/weak reliability tag, **Bing Liu's** was bootstrapped
from product reviews, and **AFINN** attaches a small integer valence. All are
one-dimensional.

The richer lexicons go multi-dimensional. The **NRC VAD** lexicon scores 20,000
words on all three affective dimensions at once; a word is now a triple, not a
tag.

$$
% caption: Sample entries from the NRC VAD lexicon (Mohammad 2018). Each word
% gets a real-valued score in 0 to 1 on valence, arousal, and dominance; the three
% axes vary independently.
\begin{tikzpicture}[>=stealth, font=\footnotesize]
  \definecolor{acc}{HTML}{2348F2}
  \def\rw{2.6}
  % header
  \node[anchor=west, acc] at (0,0) {\textbf{Valence}};
  \node[anchor=west, acc] at (\rw,0) {\textbf{Arousal}};
  \node[anchor=west, acc] at (2*\rw,0) {\textbf{Dominance}};
  % rows: word score | word score | word score
  \foreach \y/\va/\vs/\aa/\as/\da/\ds in {
      -0.6/vacation/.840/enraged/.962/powerful/.991,
      -1.1/delightful/.918/party/.840/authority/.935,
      -1.6/whistle/.653/organized/.337/saxophone/.482,
      -2.1/consolation/.408/ef\/fortless/.120/discouraged/.009,
      -2.6/torture/.115/napping/.046/weak/.045}{
    \node[anchor=west] at (0,\y) {\va};   \node[anchor=east, black] at (\rw-0.25,\y) {\vs};
    \node[anchor=west] at (\rw,\y) {\aa}; \node[anchor=east, black] at (2*\rw-0.25,\y) {\as};
    \node[anchor=west] at (2*\rw,\y) {\da}; \node[anchor=east, black] at (3*\rw-0.25,\y) {\ds};
  }
  \draw[black] (-0.1,-0.3) -- (3*\rw-0.3,-0.3);
\end{tikzpicture}
$$

The **NRC Word-Emotion Association Lexicon**, or **EmoLex**, takes the
basic-emotion route instead: for each of ~14,000 words it records a binary
0/1 for each of Plutchik's eight emotions plus positive/negative. A word like
_reward_ lights up anticipation, joy, surprise, trust, and positive; _garbage_
lights up disgust and negative.

$$
% caption: EmoLex (NRC Word-Emotion Association Lexicon) entries. Each word gets a
% binary 0/1 for each of Plutchik's 8 emotions plus positive/negative; a filled
% cell means the word is associated with that emotion.
\begin{tikzpicture}[>=stealth, font=\scriptsize]
  \definecolor{acc}{HTML}{2348F2}
  \def\c{0.62}
  % column headers (rotated, ASCII only)
  \foreach \i/\lab in {1/anger,2/antic,3/disgust,4/fear,5/joy,6/sad,7/surprise,8/trust,9/pos,10/neg}{
    \node[rotate=55, anchor=west, font=\scriptsize] at (\i*\c,0.15) {\lab};
  }
  % rows: word then 10 binary cells
  \foreach \r/\word/\bits in {
      1/reward/{0,1,0,0,1,0,1,1,1,0},
      2/worry/{0,1,0,1,0,1,0,0,0,1},
      3/sweetheart/{0,1,0,0,1,1,0,1,1,0},
      4/garbage/{0,0,1,0,0,0,0,0,0,1}}{
    \node[anchor=east] at (0.55,-\r*\c) {\word};
    \foreach \b [count=\i] in \bits{
      \ifnum\b=1
        \fill[acc!30] (\i*\c-0.27,-\r*\c-0.22) rectangle (\i*\c+0.27,-\r*\c+0.22);
        \draw[acc] (\i*\c-0.27,-\r*\c-0.22) rectangle (\i*\c+0.27,-\r*\c+0.22);
        \node[acc] at (\i*\c,-\r*\c) {1};
      \else
        \draw[black] (\i*\c-0.27,-\r*\c-0.22) rectangle (\i*\c+0.27,-\r*\c+0.22);
        \node[black] at (\i*\c,-\r*\c) {0};
      \fi
    }
  }
\end{tikzpicture}
$$

**LIWC** (Linguistic Inquiry and Word Count) is different in kind: 73 curated
category lists — negative-emotion, positive-emotion, anger, sadness, cognitive
mechanisms, tentativeness, negation — built for social-psychology research
rather than sentiment per se, but widely reused as features. Entries can be word
prefixes (`happy*` matches _happy, happiness, happily_).

For many tasks the right first move is to use one of these off the
shelf. But when the genre is unusual (financial text, a historical corpus, a
new language) the pre-built lexicons miss, and you build your own. There are
three ways.

## Building a lexicon (1): human labeling

The oldest method, still standard, is to have humans label each word, now almost
always via **crowdsourcing** — split the job into tiny questions and farm them to
many annotators.[^jm-human] The direct question — "how positive is _sublime_, on a
scale of 1 to 9?" — gives noisy answers: people
use the scale differently, anchor inconsistently, and struggle to be precise
about an absolute number.

**Best-worst scaling** sidesteps this. Show an annotator four words at a time and
ask only for the two extremes — which is _most_ positive and which is _least_.
Relative judgments among a few items are far more reliable than absolute ratings
on a long scale.

$$
% caption: Best-worst scaling. The annotator sees 4 words and marks only the best
% (most positive) and worst (least positive); a word's score is the fraction of
% tuples it was picked best minus the fraction it was picked worst.
\begin{tikzpicture}[>=stealth, font=\footnotesize,
  wbox/.style={draw, minimum width=24mm, minimum height=8mm, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  \definecolor{red}{HTML}{C0392B}
  \node[font=\footnotesize, anchor=west] at (-0.2,2.3) {Which is MOST / LEAST positive?};
  \node[wbox, draw=acc, text=acc] (a) at (0,1.3) {delightful};
  \node[wbox] (b) at (0,0.4) {ordinary};
  \node[wbox] (c) at (0,-0.5) {tolerable};
  \node[wbox, draw=red, text=red] (d) at (0,-1.4) {dreadful};
  \node[acc, anchor=west, font=\scriptsize] at (1.7,1.3) {best (most)};
  \node[red, anchor=west, font=\scriptsize] at (1.7,-1.4) {worst (least)};
  % scoring on the right
  \node[anchor=west, align=left, font=\scriptsize] at (5.2,0.6)
    {score(w) = \\ (\% best) - (\% worst)};
  \draw[->, black] (3.4,-0.05) -- (5.0,0.3);
\end{tikzpicture}
$$

Across many overlapping four-word tuples, a word's final score is the proportion
of tuples in which it was chosen best minus the proportion in which it was chosen
worst — a real number in $[-1, 1]$. The NRC VAD lexicon was built by this
procedure, run once per dimension.[^jm-bws] EmoLex used a related
two-step scheme: a priming multiple-choice question fixed the intended word sense
first (_startle_ is closest to _shake_, not _automobile_), then annotators rated
association with each of the eight emotions on a not/weak/moderate/strong scale,
collapsed to binary. Annotation quality is checked by **split-half reliability**:
split the annotators in two, and see whether the two halves' scores correlate.

## Building a lexicon (2): semi-supervised induction

Human labeling is accurate but expensive. **Semi-supervised induction** produces a
large lexicon from a few dozen labeled words. Hand-pick a small set
of **seed words** at each pole of the axis — _good, excellent, love_ for positive;
_bad, horrible, hate_ for negative — and then score every other word by how
similar it is to the positive seeds and how dissimilar to the negative
ones.[^jm-semisup] "Similar" is where the methods differ.

Seeds are chosen by hand, and the right seeds depend on genre. General sentiment,
Twitter, and finance each need their own poles:

| Domain | Positive seeds | Negative seeds |
| --- | --- | --- |
| General | good, lovely, excellent, perfect, happy | bad, horrible, poor, disgusting, unhappy |
| Twitter | love, awesome, nice, amazing, best | hate, terrible, nasty, awful, worst |
| Finance | profit, gains, beneficial, improving, success | loss, volatile, litigation, damages, failure |

### The semantic-axis method

The simplest similarity metric is [embedding](/natural-language-processing/semantics/vector-semantics-and-embeddings)
cosine. Embed every word, then turn the seed sets into a single **axis** vector
pointing from negative to positive. Take the centroid (mean vector) of the
positive-seed embeddings, $V^+ = \tfrac1n\sum_i E(w_i^+)$, and of the negative
seeds, $V^- = \tfrac1m\sum_i E(w_i^-)$, and subtract:

$$
V_{\text{axis}} = V^+ - V^-.
$$

$V_{\text{axis}}$ points in the direction of increasing positivity. Score any word
$w$ by the cosine between its embedding and the axis:

$$
\score(w) = \cos\!\big(E(w),\, V_{\text{axis}}\big)
= \frac{E(w)\cdot V_{\text{axis}}}{\lVert E(w)\rVert\,\lVert V_{\text{axis}}\rVert}.
$$

A word whose embedding aligns with the axis is positive; one pointing the other
way is negative; one orthogonal is neutral.[^jm-axis]

$$
% caption: The semantic-axis method. Average the positive-seed embeddings and the
% negative-seed embeddings into pole vectors, subtract to get the axis, then score
% each word by the cosine between its embedding and the axis.
\begin{tikzpicture}[>=stealth, font=\footnotesize]
  \definecolor{acc}{HTML}{2348F2}
  \definecolor{red}{HTML}{C0392B}
  % pole centroids
  \fill[red] (-3,0) circle (2pt); \node[red, anchor=east] at (-3.15,0) {V- (neg centroid)};
  \fill[acc] (3,0) circle (2pt);  \node[acc, anchor=west] at (3.15,0) {V+ (pos centroid)};
  % axis
  \draw[->, black, very thick] (-3,0) -- (3,0);
  \node[black, anchor=north] at (0,-0.18) {semantic axis  V+ minus V-};
  % a word embedding projected onto the axis
  \coordinate (w) at (1.4,1.5);
  \fill[black] (w) circle (2pt); \node[anchor=south west] at (w) {word w};
  \draw[black, dashed] (w) -- (1.4,0);
  \draw[->, acc] (0,0) -- (w) node[midway, anchor=south east, font=\scriptsize] {E(w)};
  % angle marker
  \draw[acc] (0.7,0) arc (0:47:0.7);
  \node[acc, font=\scriptsize] at (0.95,0.28) {cos};
\end{tikzpicture}
$$

### Label propagation on a graph

An alternative, **SentProp**, propagates polarity over a graph instead of
projecting onto an axis.[^jm-sentprop] Build a lexical graph: each word is a node,
connected to its $k$ nearest neighbors by cosine, edge weights set from the
angle between embeddings. Drop the seed labels onto their nodes and run a
**random walk** from each pole: a word's positive score is proportional to how
often a walk started from the positive seeds lands on it. Words tightly clustered
around _love_ and _adore_ accumulate positive walks; those near _hate_ and _loathe_
accumulate negative ones; and a word in between, like _find_ or _notice_, stays
neutral because both walks reach it about equally.

$$
% caption: Label propagation (SentProp). Seed words (double outline) are planted
% at each pole; a random walk over the k-nearest-neighbor graph carries polarity
% to nearby words. Blue = positive-visited, red = negative-visited, black = neutral.
\begin{tikzpicture}[>=stealth, font=\scriptsize,
  n/.style={circle, draw, minimum size=6mm, inner sep=0pt, font=\scriptsize},
  seed/.style={circle, draw, double, minimum size=6mm, inner sep=0pt, font=\scriptsize}]
  \definecolor{acc}{HTML}{2348F2}
  \definecolor{red}{HTML}{C0392B}
  % positive cluster (left)
  \node[seed, draw=acc, text=acc] (love)  at (-3,0.9) {love};
  \node[n, draw=acc, text=acc]    (adore) at (-3.6,-0.3) {adore};
  \node[n, draw=acc, text=acc]    (like)  at (-2,0) {like};
  \node[n, draw=acc, text=acc]    (appr)  at (-2.4,1.4) {like2};
  \draw[acc] (love)--(adore); \draw[acc] (love)--(like); \draw[acc] (love)--(appr); \draw[acc] (like)--(adore);
  % negative cluster (right)
  \node[seed, draw=red, text=red] (hate)  at (3,0.9) {hate};
  \node[n, draw=red, text=red]    (loathe)at (3.6,-0.3) {loathe};
  \node[n, draw=red, text=red]    (dislike) at (2,0) {dislike};
  \node[n, draw=red, text=red]    (abhor) at (2.4,1.4) {abhor};
  \draw[red] (hate)--(loathe); \draw[red] (hate)--(dislike); \draw[red] (hate)--(abhor); \draw[red] (dislike)--(loathe);
  % neutral bridge (defined after both clusters)
  \node[n] (find)   at (0,0.4) {f\/ind};
  \node[n] (notice) at (0,-0.9) {notice};
  \draw[black] (like)--(find); \draw[black] (find)--(notice); \draw[black] (notice)--(dislike);
  \draw[black] (find)--(dislike);
\end{tikzpicture}
$$

Both raw scores (positive and negative walk frequencies) are combined into a
single polarity, $\score^+(w) = \frac{r^+(w)}{r^+(w)+r^-(w)}$, and
confidence is estimated by re-running on random subsets of the seeds (bootstrap):
a word whose score wobbles as the seeds change is one to trust less.

### PMI from co-occurrence: Turney's SO-PMI

The seed idea predates embeddings. Turney's **SO-PMI** (semantic orientation from
pointwise mutual information) needs only a search engine and two seed words:
_excellent_ and _poor_. A word is positive to the degree it co-occurs with
_excellent_ more than with _poor_.[^jm-pmi] Pointwise mutual information measures
that association,

$$
\PMI(w_1, w_2) = \log_2 \frac{P(w_1, w_2)}{P(w_1)\,P(w_2)},
$$

and the **semantic orientation** of a phrase is its PMI with the positive seed
minus its PMI with the negative seed:

$$
\operatorname{SO\text{-}PMI}(w) =
\PMI(w, \text{``excellent''}) - \PMI(w, \text{``poor''}).
$$

Estimate each probability from hit counts: $P(w_1,w_2)$ from how often the two
appear near each other, $P(w)$ from how often $w$ appears at all. Positive SO
means positive sentiment.

For a worked example: Turney estimated the counts by issuing queries to a web
search engine and reading off the number of returned pages, using the `NEAR`
operator for co-occurrence. Suppose a corpus (or index) of $N = 10^{9}$ documents,
and for the target word _romantic_ these hit counts:

| Query | Hits | Interpretation |
| --- | --- | --- |
| `romantic` | $2{,}000{,}000$ | $P(\text{romantic}) = 2\times 10^{-3}$ |
| `excellent` | $8{,}000{,}000$ | $P(\text{excellent}) = 8\times 10^{-3}$ |
| `poor` | $6{,}000{,}000$ | $P(\text{poor}) = 6\times 10^{-3}$ |
| `romantic NEAR excellent` | $60{,}000$ | $P(\cdot,\cdot) = 6\times 10^{-5}$ |
| `romantic NEAR poor` | $9{,}000$ | $P(\cdot,\cdot) = 9\times 10^{-6}$ |

Divide each count by $N$ to get a probability, then compute each PMI. For the
positive seed,

$$
\PMI(\text{romantic}, \text{excellent})
= \log_2 \frac{6\times 10^{-5}}{(2\times 10^{-3})(8\times 10^{-3})}
= \log_2 \frac{6\times 10^{-5}}{1.6\times 10^{-5}}
= \log_2 3.75 = 1.91,
$$

and for the negative seed,

$$
\PMI(\text{romantic}, \text{poor})
= \log_2 \frac{9\times 10^{-6}}{(2\times 10^{-3})(6\times 10^{-3})}
= \log_2 \frac{9\times 10^{-6}}{1.2\times 10^{-5}}
= \log_2 0.75 = -0.42.
$$

The semantic orientation is the difference, $\operatorname{SO\text{-}PMI}(\text{romantic})
= 1.91 - (-0.42) = 2.33$: strongly positive, because _romantic_ co-occurs with
_excellent_ far more than chance and with _poor_ less than chance. Run the same three
queries for _unpredictable_ and the sign flips — it appears near _poor_ more than near
_excellent_, so its SO comes out negative — recovering the polarity of both words from
nothing but a search engine and two seed words. The $N$ cancels out of the difference,
which is why Turney could estimate SO-PMI without ever knowing the true corpus size.

$$
% caption: A worked SO-PMI trace. "romantic" co-occurs far more with the positive
% seed "excellent" than with "poor", giving positive semantic orientation;
% "unpredictable" leans the other way.
\begin{tikzpicture}[>=stealth, font=\footnotesize,
  box/.style={draw, minimum width=22mm, minimum height=8mm, align=center}]
  \definecolor{acc}{HTML}{2348F2}
  \definecolor{red}{HTML}{C0392B}
  % target word
  \node[box, draw=acc, text=acc] (w) at (0,0) {romantic};
  % seeds
  \node[box] (exc) at (4.3,1.1) {excellent};
  \node[box] (poor) at (4.3,-1.1) {poor};
  \draw[->, acc, thick] (w) -- (exc) node[midway, anchor=south east, font=\scriptsize] {PMI = 1.91};
  \draw[->, red, thick] (w) -- (poor) node[midway, anchor=north east, font=\scriptsize] {PMI = -0.42};
  % result
  \node[anchor=west, align=left] at (6.6,0)
    {SO = 1.91 - (-0.42) \\ = +2.33  (positive)};
  \draw[->, black] (6.0,0.1) -- (6.5,0.1);
\end{tikzpicture}
$$

Other similarity cues work in place of cosine or co-occurrence. Two adjectives
conjoined by _and_ ("fair and legitimate") usually share polarity, while _but_
("fair but brutal") flips it; a morphological negative (_adequate / inadequate_)
flips it; a thesaurus lets you add the synonyms of positive seeds and the
antonyms of negative seeds. All follow the same pattern: a similarity metric plus
seed poles.

## Building a lexicon (3): supervised learning from reviews

Sometimes supervision comes free. Online reviews carry a
**star rating** — 1 to 5, or 1 to 10 — and the rating is a label for the whole
review's sentiment. Positive words cluster in 5-star reviews; negative words in
1-star reviews. We can learn word sentiment directly from the counts, and get
something richer than a binary tag: a **distribution over ratings**.[^jm-super]

$$
% caption: Supervised word sentiment from starred reviews. Counting how often each
% word appears at each star level gives, per word, a distribution over ratings —
% the whole polarity profile, not just a positive/negative bit.
\begin{tikzpicture}[>=stealth, font=\footnotesize,
  rev/.style={draw, minimum width=30mm, minimum height=7mm, align=left, font=\scriptsize}]
  \definecolor{acc}{HTML}{2348F2}
  \definecolor{red}{HTML}{C0392B}
  % starred reviews on the left
  \node[rev] (r5) at (0,1.3) {5*: a great, wonderful f\/ilm};
  \node[rev] (r3) at (0,0.3) {3*: it was fairly good};
  \node[rev] (r1) at (0,-0.7) {1*: the worst, disappointing};
  \node[anchor=west, black, font=\scriptsize] at (-1.8,2.1) {reviews + stars};
  % arrow to counts
  \draw[->, acc, thick] (2.1,0.3) -- (3.3,0.3) node[midway, above, font=\scriptsize] {count};
  % a per-word bar chart on the right (word "disappointing")
  \begin{scope}[xshift=4cm, yshift=-0.9cm]
    \draw[->, black] (0,0) -- (0,2.2) node[anchor=south, font=\scriptsize] {P(w $\mid$ c)};
    \draw[->, black] (0,0) -- (4.6,0) node[anchor=west, font=\scriptsize] {stars};
    \foreach \c/\h in {1/1.7,2/1.4,3/0.9,4/0.5,5/0.3}{
      \fill[red!35] (\c*0.8-0.2,0) rectangle (\c*0.8+0.2,\h);
      \draw[red] (\c*0.8-0.2,0) rectangle (\c*0.8+0.2,\h);
      \node[black, anchor=north, font=\scriptsize] at (\c*0.8,-0.02) {\c};
    }
    \node[red, anchor=south, font=\scriptsize] at (2.0,1.9) {disappointing};
  \end{scope}
\end{tikzpicture}
$$

Concretely, count how often word $w$ appears in reviews of each class $c$, and
turn counts into a likelihood $P(w \mid c)$. Then normalize across classes into
the **Potts score**, which reads as the word's sentiment profile:

$$
P(w \mid c) = \frac{\count(w, c)}{\sum_{w' \in c}\count(w', c)},
\qquad
\PottsScore(w, c) = \frac{P(w \mid c)}{\sum_{c'} P(w \mid c')}.
$$

Work the normalization on one word. Suppose across a review corpus the word
_disappointing_ occurs with these raw counts by star rating, and each rating class
has the shown total word count:

| Rating $c$ | $\count(\text{disappointing}, c)$ | tokens in class $c$ | $P(w \mid c) = \count/\text{tokens}$ |
| --- | --- | --- | --- |
| 1 star | $500$ | $1{,}000{,}000$ | $5.0\times 10^{-4}$ |
| 2 star | $400$ | $1{,}000{,}000$ | $4.0\times 10^{-4}$ |
| 3 star | $250$ | $1{,}000{,}000$ | $2.5\times 10^{-4}$ |
| 4 star | $150$ | $1{,}000{,}000$ | $1.5\times 10^{-4}$ |
| 5 star | $100$ | $1{,}000{,}000$ | $1.0\times 10^{-4}$ |

The per-class likelihoods $P(w \mid c)$ sum to $\sum_{c'} P(w \mid c') = (5.0 + 4.0 +
2.5 + 1.5 + 1.0)\times 10^{-4} = 1.4\times 10^{-3}$. The Potts score for each rating
is that class's share of the total:

$$
\PottsScore(\text{disappointing}, 1) = \frac{5.0\times 10^{-4}}{1.4\times 10^{-3}} = 0.357,
\qquad
\PottsScore(\text{disappointing}, 5) = \frac{1.0\times 10^{-4}}{1.4\times 10^{-3}} = 0.071,
$$

with the intermediate ratings at $0.286$, $0.179$, and $0.107$. The profile
$[0.357, 0.286, 0.179, 0.107, 0.071]$ falls monotonically from 1-star to 5-star — the
reverse-J of a negative word — and it sums to $1$ by construction, so it reads as a
distribution over ratings. Normalizing by class size is what makes this fair: without
dividing by the tokens-per-class, a rating level that simply has more reviews would
inflate every word's count there.

$$
% caption: The worked Potts profile for "disappointing": the normalized share of the
% word at each star rating, [0.357, 0.286, 0.179, 0.107, 0.071], falling from 1 to 5
% stars. The monotone descent is the reverse-J signature of a negative word.
\begin{tikzpicture}[>=stealth, font=\footnotesize]
  \definecolor{acc}{HTML}{2348F2}
  \definecolor{red}{HTML}{C0392B}
  \draw[->, black] (0,0) -- (0,2.7) node[above, black, font=\scriptsize] {Potts share};
  \draw[->, black] (0,0) -- (5.4,0) node[right, black, font=\scriptsize] {stars};
  % bars: heights scaled x6 from [.357,.286,.179,.107,.071]
  \foreach \c/\h/\lab in {1/2.14/0.357, 2/1.72/0.286, 3/1.07/0.179, 4/0.64/0.107, 5/0.43/0.071}{
    \fill[red!30] (\c*0.9-0.28,0) rectangle (\c*0.9+0.28,\h);
    \draw[red] (\c*0.9-0.28,0) rectangle (\c*0.9+0.28,\h);
    \node[black, anchor=north, font=\scriptsize] at (\c*0.9,-0.03) {\c};
    \node[red, anchor=south, font=\scriptsize] at (\c*0.9,\h) {\lab};
  }
  \node[red, anchor=west, font=\scriptsize] at (4.4,1.9) {reverse J};
\end{tikzpicture}
$$

Plot the profile across ratings and the _shape_ tells you the word's sentiment.
Strongly positive scalars (_excellent_) trace a rising **J**; strongly negative
scalars (_terrible_) trace a reverse **J**; weakly polarized words (_good_,
_disappointing_) make a hump, peaked just above or just below the middle.[^jm-potts]

$$
% caption: Potts-diagram shapes. Strongly positive words rise toward the top
% rating (a J); strongly negative words fall (a reverse J); weakly polar words peak
% in the middle (a hump). The shape is a typology of affective meaning.
\begin{tikzpicture}[>=stealth, font=\footnotesize]
  \definecolor{acc}{HTML}{2348F2}
  \definecolor{red}{HTML}{C0392B}
  % three mini-plots side by side
  \foreach \dx/\lab/\col in {0/{excellent (J)}/acc, 5/{terrible (rev J)}/red, 10/{good (hump)}/black}{
    \begin{scope}[xshift=\dx cm]
      \draw[->, black] (0,0) -- (0,2.0);
      \draw[->, black] (0,0) -- (3.4,0) node[anchor=west, font=\scriptsize] {rating};
    \end{scope}
  }
  % J for excellent
  \draw[acc, very thick] plot[smooth] coordinates {(0.3,0.25)(1.0,0.35)(1.7,0.55)(2.4,1.0)(3.0,1.75)};
  \node[acc, anchor=south, font=\scriptsize] at (1.6,1.85) {excellent (J)};
  % reverse J for terrible
  \draw[red, very thick] plot[smooth] coordinates {(5.3,1.75)(6.0,1.0)(6.7,0.55)(7.4,0.35)(8.0,0.25)};
  \node[red, anchor=south, font=\scriptsize] at (6.6,1.85) {terrible (rev J)};
  % hump for good
  \draw[black, very thick] plot[smooth] coordinates {(10.3,0.3)(11.0,0.9)(11.7,1.5)(12.4,0.9)(13.0,0.35)};
  \node[black, anchor=south, font=\scriptsize] at (11.6,1.85) {good (hump)};
\end{tikzpicture}
$$

When the goal is instead to find the words that most _distinguish_ two classes
(1-star versus 5-star, Democrat versus Republican), raw frequency differences
mislead: every difference looks big for frequent words and small for rare ones.
The **log-odds-ratio with an informative Dirichlet prior** fixes this by shrinking
counts toward a large background corpus and reporting a $z$-score, so the words it
surfaces are genuinely over-represented, not just common. Applied to a Yelp
corpus, it recovers the obvious sentiment words (1-star: _worst, awful_; 5-star:
_amazing, delicious_) but also subtler tells — 1-star reviews use logical
negation (_no, not_) and first-person plural (_we, us_), 5-star reviews use
emphatics (_very, highly, always_).

## Using a lexicon

Two uses, matching the two data regimes.[^jm-using] With **no training data**,
run a rule-based classifier: count positive-lexicon words and negative-lexicon
words in the document and pick the majority. If the lexicon carries weights
$\theta_w^+$ and $\theta_w^-$, sum those instead, and classify by the ratio
against a threshold $\lambda$:

$$
f^+ = \!\!\sum_{w \in L^+}\!\! \theta_w^+\,\count(w),
\qquad
f^- = \!\!\sum_{w \in L^-}\!\! \theta_w^-\,\count(w),
\qquad
\text{sentiment} =
\begin{cases}
+ & \text{if } f^+/f^- > \lambda,\\
- & \text{if } f^-/f^+ > \lambda,\\
0 & \text{otherwise.}
\end{cases}
$$

With **training data**, the lexicon becomes a small set of features feeding a
[logistic-regression](/natural-language-processing/classification/logistic-regression)
or SVM classifier, alongside the raw words. The simplest lexicon feature is an
indicator — 1 if the document contains any word from lexicon $L$ — or a count,
$f_L = \sum_{w \in L}\count(w)$, optionally weighted by
$\theta_w^L$. This is the affect-recognition recipe too: label a training set for
whatever affective category you want (emotion, personality), extract word,
bigram, and lexicon-count features, and train a standard classifier. When the
training set is large and matched to the test set, using all the words as
features is very hard to beat; lexicon features help when data is
sparse or the domains differ.

## Connotation frames

Every lexicon so far pins a word to a point in affect space — one score, or one
triple. A **connotation frame** records something structurally richer: the
sentiment a _predicate_ implies about each of its _arguments_.[^jm-conno] It
combines the affect lexicon with the frame-semantic idea that a verb has slots
(agent, theme) each playing a role.

Consider "Country A **violated** the sovereignty of Country B." The verb
_violate_ does more than express negativity; it takes a stance. It casts the
object (Country B) as a sympathetic victim, the subject (Country A) as the
antagonist, and signals the writer's sympathy with B and antagonism toward A —
all before you know anything about the countries. Contrast "the teenager
**survived** the bombing": _survive_ makes its subject the sympathetic party and
marks the event as a hardship. The connotation is part of the verb's meaning.

$$
% caption: Connotation frames for "survive" and "violate". Arrows carry the
% sentiment the writer implies toward each role. For survive the subject (Role1) is
% sympathetic; for violate that positive sentiment shifts to the object (Role2).
\begin{tikzpicture}[>=stealth, font=\scriptsize,
  r/.style={draw, minimum width=13mm, minimum height=6mm, align=center, font=\scriptsize}]
  \definecolor{acc}{HTML}{2348F2}
  \definecolor{red}{HTML}{C0392B}
  % --- survive (left) ---
  \node[font=\footnotesize] at (0,2.4) {Role1 survives Role2};
  \node[r] (w1) at (0,1.4) {writer};
  \node[r, draw=acc, text=acc] (a1) at (-1.4,0) {Role1};
  \node[r, draw=red, text=red]  (b1) at (1.4,0)  {Role2};
  \draw[->, acc] (w1) -- (a1) node[midway, anchor=south east, font=\scriptsize] {+};
  \draw[->, red] (w1) -- (b1) node[midway, anchor=south west, font=\scriptsize] {-};
  \draw[->, red] (a1) -- (b1) node[midway, anchor=north, font=\scriptsize] {-};
  \node[black, anchor=north, font=\scriptsize, align=center] at (-1.4,-0.45) {sympathetic};
  \node[black, anchor=north, font=\scriptsize, align=center] at (1.4,-0.45) {hardship};
  % --- violate (right) ---
  \begin{scope}[xshift=6.2cm]
    \node[font=\footnotesize] at (0,2.4) {Role1 violates Role2};
    \node[r] (w2) at (0,1.4) {writer};
    \node[r, draw=red, text=red]  (a2) at (-1.4,0) {Role1};
    \node[r, draw=acc, text=acc] (b2) at (1.4,0)  {Role2};
    \draw[->, red] (w2) -- (a2) node[midway, anchor=south east, font=\scriptsize] {-};
    \draw[->, acc] (w2) -- (b2) node[midway, anchor=south west, font=\scriptsize] {+};
    \draw[->, red] (a2) -- (b2) node[midway, anchor=north, font=\scriptsize] {-};
    \node[black, anchor=north, font=\scriptsize, align=center] at (-1.4,-0.45) {antagonist};
    \node[black, anchor=north, font=\scriptsize, align=center] at (1.4,-0.45) {victim};
  \end{scope}
\end{tikzpicture}
$$

The frame lexicons of Rashkin and Sap record several such relations per verb:
the writer's sentiment toward each role, the effect on each role (something bad
happened to it), its value, its mental state, the **power** differential (_implore_
implies the agent has _less_ power than the theme; _demand_ implies more), and
the **agency** of each argument (_waited_ is low-agency, _determined_ is high).
Trained on such a lexicon, an entity-centric analysis of a novel or a plot
summary can chart each character: run it on _The Dark Knight_ and Batman comes
out high-power, the Joker high-agency but low-sentiment, the love interest
low-power but high-sentiment — the affective structure of the story recovered
from the connotations of its verbs.

> **Definition (Connotation frame).** A lexicon entry for a predicate that
> records the affective relations it implies among its arguments — the writer's
> sentiment toward each role, and relations like power and agency between them —
> rather than a single sentiment score for the word in isolation.

## From lexicons to contextual sentiment

A lexicon assigns a word one fixed score regardless of context, and that assumption
is both its strength and its main limitation. Two lines of work past Jurafsky & Martin's core push
against it while keeping the lexicon useful.

The first refines rule-based counting so it works on real text. **VADER** (Hutto &
Gilbert, 2014, ICWSM) is a hand-tuned, valence-aware lexicon-and-rules system built
for social media.[^vader] It starts from a crowd-scored lexicon but adds rules that a
plain count ignores: punctuation and capitalization amplify intensity (`good` versus
`GOOD!!!`), degree modifiers scale it (`extremely` up, `marginally` down), a
contrastive _but_ shifts the weight to the second clause, and — the case pure counting
gets backwards — negation flips polarity, so `not good` is scored negative rather than
as a positive word plus a negative one. Each is a failure mode of the
threshold classifier this lesson opened with, here handled by rules rather than learning,
and VADER remains a strong zero-training-data baseline for short informal text.

The second line drops fixed scores entirely. A [transformer](/natural-language-processing/transformers/transformers-and-attention)
sentiment classifier — a pretrained encoder such as BERT (Devlin et al., 2019, NAACL)
fine-tuned on labeled reviews — represents each word _in context_, so the same word
takes different values in different sentences.[^devlin-sent] This resolves the case a
lexicon cannot: _sick_ is negative in "I feel sick" and positive in "that trick was
sick," and _unpredictable_ praises a plot but pans a car. A fixed lexicon must pick
one sign; a contextual model reads the sign off the sentence. On benchmark sentiment
tasks with adequate labeled data, these models are the accurate choice.

Lexicons remain useful for three reasons, each a limit of the
neural approach. They need no training data, which matters for a new language or
genre; they are interpretable, so you can read _why_ a document scored as it did,
which matters in the social-science settings LIWC and connotation frames were built
for; and they still supply useful features to a supervised model when labeled data is
sparse. The methods in this lesson complement representation learning rather than
compete with it — and the semi-supervised induction methods, which run on
the same embedding geometry the neural models are built from, are the bridge between
the two.

## Where this sits

Word-level affect is the layer beneath document sentiment. Naive Bayes and
logistic regression pool words into a document label; a lexicon says what those
words mean one at a time, and can feed that meaning back as features or, with no
training data at all, classify by counting. The semi-supervised induction
methods are the reason the next chapter matters: they run on
[embeddings](/natural-language-processing/semantics/vector-semantics-and-embeddings),
turning the geometry of vector space into the poles and neighborhoods that carry
sentiment from a handful of seeds to a lexicon of twenty thousand words.

[^jm-intro]: **Jurafsky & Martin**, _Speech and Language Processing_ (3rd ed.), Ch. 20 — Lexicons for Sentiment, Affect, and Connotation: affective meaning as the emotion, sentiment, opinion, and evaluation a word carries; sentiment lexicons as lists of the words that cue affect most strongly, presupposing that words have fixed affective connotations.
[^jm-emotion]: **Jurafsky & Martin**, §20.1 — Defining Emotion: the two NLP families of emotion theory — basic emotions (Ekman's six, Plutchik's eight in four opposing pairs) and dimensional models (valence, arousal, dominance), with sentiment as the valence axis.
[^jm-lexicons]: **Jurafsky & Martin**, §20.2 — Available Sentiment and Affect Lexicons: General Inquirer, MPQA Subjectivity, the Hu & Liu opinion lexicon, NRC VAD, NRC EmoLex, and LIWC, and the binary-vs.-scored, one-vs.-multi-dimensional axes along which they differ.
[^jm-human]: **Jurafsky & Martin**, §20.3 — Creating Affect Lexicons by Human Labeling: crowdsourced annotation, the EmoLex two-step sense-priming plus emotion-association scheme, and split-half reliability.
[^jm-bws]: **Jurafsky & Martin**, §20.3 — best-worst scaling: annotators pick only the most and least extreme of four items; a word's score is the fraction chosen best minus the fraction chosen worst, as used to build the NRC VAD lexicon.
[^jm-semisup]: **Jurafsky & Martin**, §20.4 — Semi-supervised Induction of Affect Lexicons: seed words at each pole plus a similarity metric, with genre-specific seed sets for general, Twitter, and financial text.
[^jm-axis]: **Jurafsky & Martin**, §20.4.1 — Semantic Axis Methods: the Turney & Littman / An et al. algorithm — pole centroids $V^+, V^-$, the axis $V_{\text{axis}} = V^+ - V^-$, and scoring by cosine of a word's embedding with the axis.
[^jm-sentprop]: **Jurafsky & Martin**, §20.4.2 — Label Propagation: the SentProp algorithm — a k-nearest-neighbor lexical graph, random walks from positive and negative seeds, combined polarity scores, and bootstrap confidence.
[^jm-pmi]: **Jurafsky & Martin**, §20.4.3 and Bibliographical Notes — Turney's PMI-based semantic orientation: SO-PMI as PMI with the seed "excellent" minus PMI with "poor", estimated from co-occurrence counts.
[^jm-super]: **Jurafsky & Martin**, §20.5 — Supervised Learning of Word Sentiment: review star ratings as free supervision, word sentiment as a distribution over rating classes, and the log-odds-ratio informative Dirichlet prior for finding class-distinguishing words.
[^jm-potts]: **Jurafsky & Martin**, §20.5 — the Potts score and Potts diagrams: normalized likelihood across rating categories, and the J / reverse-J / hump shapes that distinguish strongly from weakly polar words.
[^jm-using]: **Jurafsky & Martin**, §20.6–20.7 — Using Lexicons for Sentiment and Affect Recognition: rule-based counting with a threshold when unlabeled, and lexicon-count features (indicator, count, weighted) inside a supervised classifier when labeled.
[^jm-conno]: **Jurafsky & Martin**, §20.9 — Connotation Frames: predicates that imply sentiment, effect, value, mental state, power, and agency about their arguments, illustrated by _survive_ and _violate_ and the entity-centric analysis of characters.
[^vader]: **C. J. Hutto & E. Gilbert**, "VADER: A Parsimonious Rule-based Model for Sentiment Analysis of Social Media Text," _Proceedings of ICWSM_, 2014 — a valence-aware lexicon plus five heuristics (punctuation and capitalization amplification, degree modifiers, contrastive _but_, and negation flipping) that make rule-based counting competitive on short informal text without training data.
[^devlin-sent]: **J. Devlin, M.-W. Chang, K. Lee, K. Toutanova**, "BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding," _Proceedings of NAACL-HLT_, 2019 — a pretrained transformer fine-tuned for sentiment, giving each word a context-dependent representation so that a word's contribution can change sign with the sentence, unlike a fixed-score lexicon.
