Downloads · 30 days
0
ShubhamOza/sentiment-headline-lexicon
sentiment-headline-lexicon is a text classification model from ShubhamOza. Use it when you need a label for a piece of text. The card lists the license as mit.
This is a sentiment lexicon I built from about 85k stock news headlines for a homework assignment (lexicon based sentiment analysis, so no ML model here, just word weights). Instead of using a hand built dictionary li…
Downloads · 30 days
0
Access
Public
Updated Sep 10, 2026
Repo size
39.1 MB
Likes
0
Public
Click a slice to open those files.
.csv39.1 MB · 93%
From the Hugging Face model README
This is a sentiment lexicon I built from about 85k stock news headlines for a homework assignment (lexicon based sentiment analysis, so no ML model here, just word weights). Instead of using a hand built dictionary like TextBlob or VADER, this one is learned straight from the labeled headlines: every word gets scored by how often it showed up next to an "up" move vs a "down" move in the training data.
For every word that showed up in at least 5 training headlines, I compute a smoothed log odds score:
weight(word) = log( P(word | next return up) / P(word | next return down) )
using only the training slice of the data (chronologically 2019-08-01 to 2020-10-01), counted by how many headlines a word appears in rather than raw word counts, so one headline repeating a word a bunch of times can't skew things.
Tokenizing for this specific upload used a plain regex clean plus sklearn's stopword list. The course notebook itself uses spaCy lemmatization instead (that's what the assignment asked for), and gets basically the same result, 0.5968 there vs 0.5988 here, so either one reproduces the lexicon fine.
import json
from huggingface_hub import hf_hub_download
path = hf_hub_download(repo_id="ShubhamOza/sentiment-headline-lexicon", filename="lexicon.json")
lexicon = json.load(open(path))
def score(tokens):
weights = [lexicon[t] for t in tokens if t in lexicon]
return sum(weights) / len(weights) if weights else 0.0
score(["downgrade", "cut", "target", "price"]) # negative
score(["beat", "estimate", "record", "revenue"]) # positive
Score >= 0 means predict "up", below 0 means "down".
Same test used for TextBlob / VADER / Loughran-McDonald in the course notebook: threshold 0, full 91,851 headline corpus.
TextBlob 0.5212, VADER 0.5225, Loughran-McDonald 0.5207, just guessing "up" every time 0.5324, this lexicon 0.5988.
That last number is a little unfair though, since about 92% of that corpus is the same data the lexicon trained on. Split out by time it looks more like this:
| partition | n | accuracy | majority baseline |
|---|---|---|---|
| train | 84,643 | 0.6081 | 0.5316 |
| test (before the training window) | 4,811 | 0.5049 | 0.5053 |
| inference (after training, Oct 2020) | 2,397 | 0.4589 | 0.6154 |
So out of sample it's close to a coin flip on test, and actually worse than baseline on inference. That tracks with market efficiency, if headline word choice reliably predicted next day stock moves out of sample, that edge wouldn't stick around for long. It still beats the other lexicons fair and square under their own evaluation though, just don't take it as a trading signal.
Built for a lexicon based sentiment homework, extending the "Build our own lexicon" part of an NLP course notebook that already covers TextBlob, VADER and Loughran-McDonald as baselines. Companion dataset: ShubhamOza/sentiment-headline-scores