Created by Nathan Kelber for JSTOR Labs under Creative Commons CC BY License
Sentiment Analysis#
Description: This notebook describes Sentiment Analysis and demonstrates basic applications using:
VADER (Valence Aware Dictionary for sEntiment Reasoning), a rule-based algorithm
Hugging Face’s transformers library
Knowledge Required:
Python Basics Series (Start Python Basics I)
Methods for Sentiment Analysis#
Sentiment analysis can help an analyst discover whether feedback is positive, negative, or mixed. For example, a large company like Amazon or Walmart could use sentiment analysis on user reviews to determine whether a featured product should be promoted or discontinued. Sentiment analysis generally falls into two categories:
Rule-based algorithms
Machine Learning models
Rule-Based Algorithms#
Rule-based algorithms assign sentiment scores to particular words or multi-word constructions. Simple algorithms may simply assess each word individually in a feedback document and add up an overall score. More complex algorithms may assess multi-word (or n-gram) constructions and have special rules for addressing issues such as negation, emojis, and emoticons. They can detect the difference between “bad”, “not bad”, and “bad ass”. Some algorithms also support emojis and emoticons, such as “=)” and “😁”.
Machine Learning Models#
Machine learning models rely on feedback data that has already been assessed by humans to have a particular sentiment. Each piece of feedback is labeled by a human reader who may place the feedback into a particular category. The categories could be as simple as positive, negative, or neutral. As long as there exists labeled data, a machine learning model can often identify complex concepts. For example, a car manufacturer may desire to classify the sentiment of feedback from past buyers as: “budget-conscious”, “eco-conscious”, “tech-enthusiastic”, “luxury-driven”, “performance-driven”, etc. Assuming there is an adequately labeled training data for each of these categories, a machine learning model could assign a score for each category. This could help analysts understand the brand better, answering questions about what consumers do or do not like about a particular vehicle.
In the humanities, sentiment analysis could be used to track emerging trends on social media. For example, we might ask: “How are Twitter or Reddit users responding to a particular government policy or public event?” We could look at a hashtag like “#blm” and get a sense of national sentiment on the Black Lives Matter movement. The project On the Books: Jim Crow and Algorithms of Resistance is using machine classification to detect racist laws based on the pioneering work of Pauli Murray and Safiya Noble’s concept of “algorithmic oppression”.
VADER#
This notebook uses a rule-based algorithm named VADER (Valence Aware Dictionary and sEntiment Reasoner). VADER is a rule-based algorithm that is “specifically attuned to sentiments expressed in social media.” It relies on a specialized lexicon of words, phrases, and emojis. Each token in the lexicon is assigned a “mean-sentiment rating” between -4 (extremely negative) to 4 (extremely positive). Here are a few examples:
Token |
Mean-Sentiment Rating |
---|---|
(: |
2.2 |
/: |
-1.3 |
):< |
-1.9 |
rotflmao |
2.8 |
aghast |
-1.9 |
awesome |
3.1 |
awful |
-2.0 |
There are over 7500 tokens listed in VADER lexicon. (You can also add your own if you like.) VADER also considers grammatical and syntactical rules to measure intensity based on word order and sensitive relationships between terms. For example, it increases or decreases a sentiment based on degree modifers such as: “The product is good” versus “the product is very good” versus “the product is marginally good.” To read more about VADER, including how it works and to see its code, visit the github page.
Applying the VADER Algorithm#
First, we need to import the SentimentIntensityAnalyzer. Here we assign the VADER lexicon object to a variable sa
.
# Import the SentimentIntensityAnalyzer
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
# Creat the variable sa to hold the VADER lexicon object
sa = SentimentIntensityAnalyzer()
We can preview the contents of the lexicon by using sa.lexicon
. This will return a dictionary, where each key is a token and each value is a sentiment rating.
# Preview the lexicon contents
# There are over 7500 tokens in the lexicon
sa.lexicon
{'$:': -1.5,
'%)': -0.4,
'%-)': -1.5,
'&-:': -0.4,
'&:': -0.7,
"( '}{' )": 1.6,
'(%': -0.9,
"('-:": 2.2,
"(':": 2.3,
'((-:': 2.1,
'(*': 1.1,
'(-%': -0.7,
'(-*': 1.3,
'(-:': 1.6,
'(-:0': 2.8,
'(-:<': -0.4,
'(-:o': 1.5,
'(-:O': 1.5,
'(-:{': -0.1,
'(-:|>*': 1.9,
'(-;': 1.3,
'(-;|': 2.1,
'(8': 2.6,
'(:': 2.2,
'(:0': 2.4,
'(:<': -0.2,
'(:o': 2.5,
'(:O': 2.5,
'(;': 1.1,
'(;<': 0.3,
'(=': 2.2,
'(?:': 2.1,
'(^:': 1.5,
'(^;': 1.5,
'(^;0': 2.0,
'(^;o': 1.9,
'(o:': 1.6,
")':": -2.0,
")-':": -2.1,
')-:': -2.1,
')-:<': -2.2,
')-:{': -2.1,
'):': -1.8,
'):<': -1.9,
'):{': -2.3,
');<': -2.6,
'*)': 0.6,
'*-)': 0.3,
'*-:': 2.1,
'*-;': 2.4,
'*:': 1.9,
'*<|:-)': 1.6,
'*\\0/*': 2.3,
'*^:': 1.6,
',-:': 1.2,
"---'-;-{@": 2.3,
'--<--<@': 2.2,
'.-:': -1.2,
'..###-:': -1.7,
'..###:': -1.9,
'/-:': -1.3,
'/:': -1.3,
'/:<': -1.4,
'/=': -0.9,
'/^:': -1.0,
'/o:': -1.4,
'0-8': 0.1,
'0-|': -1.2,
'0:)': 1.9,
'0:-)': 1.4,
'0:-3': 1.5,
'0:03': 1.9,
'0;^)': 1.6,
'0_o': -0.3,
'10q': 2.1,
'1337': 2.1,
'143': 3.2,
'1432': 2.6,
'14aa41': 2.4,
'182': -2.9,
'187': -3.1,
'2g2b4g': 2.8,
'2g2bt': -0.1,
'2qt': 2.1,
'3:(': -2.2,
'3:)': 0.5,
'3:-(': -2.3,
'3:-)': -1.4,
'4col': -2.2,
'4q': -3.1,
'5fs': 1.5,
'8)': 1.9,
'8-d': 1.7,
'8-o': -0.3,
'86': -1.6,
'8d': 2.9,
':###..': -2.4,
':$': -0.2,
':&': -0.6,
":'(": -2.2,
":')": 2.3,
":'-(": -2.4,
":'-)": 2.7,
':(': -1.9,
':)': 2.0,
':*': 2.5,
':-###..': -2.5,
':-&': -0.5,
':-(': -1.5,
':-)': 1.3,
':-))': 2.8,
':-*': 1.7,
':-,': 1.1,
':-.': -0.9,
':-/': -1.2,
':-<': -1.5,
':-d': 2.3,
':-D': 2.3,
':-o': 0.1,
':-p': 1.5,
':-[': -1.6,
':-\\': -0.9,
':-c': -1.3,
':-|': -0.7,
':-||': -2.5,
':-Þ': 0.9,
':/': -1.4,
':3': 2.3,
':<': -2.1,
':>': 2.1,
':?)': 1.3,
':?c': -1.6,
':@': -2.5,
':d': 2.3,
':D': 2.3,
':l': -1.7,
':o': -0.4,
':p': 1.0,
':s': -1.2,
':[': -2.0,
':\\': -1.3,
':]': 2.2,
':^)': 2.1,
':^*': 2.6,
':^/': -1.2,
':^\\': -1.0,
':^|': -1.0,
':c': -2.1,
':c)': 2.0,
':o)': 2.1,
':o/': -1.4,
':o\\': -1.1,
':o|': -0.6,
':P': 1.4,
':{': -1.9,
':|': -0.4,
':}': 2.1,
':Þ': 1.1,
';)': 0.9,
';-)': 1.0,
';-*': 2.2,
';-]': 0.7,
';d': 0.8,
';D': 0.8,
';]': 0.6,
';^)': 1.4,
'</3': -3.0,
'<3': 1.9,
'<:': 2.1,
'<:-|': -1.4,
'=)': 2.2,
'=-3': 2.0,
'=-d': 2.4,
'=-D': 2.4,
'=/': -1.4,
'=3': 2.1,
'=d': 2.3,
'=D': 2.3,
'=l': -1.2,
'=\\': -1.2,
'=]': 1.6,
'=p': 1.3,
'=|': -0.8,
'>-:': -2.0,
'>.<': -1.3,
'>:': -2.1,
'>:(': -2.7,
'>:)': 0.4,
'>:-(': -2.7,
'>:-)': -0.4,
'>:/': -1.6,
'>:o': -1.2,
'>:p': 1.0,
'>:[': -2.1,
'>:\\': -1.7,
'>;(': -2.9,
'>;)': 0.1,
'>_>^': 2.1,
'@:': -2.1,
'@>-->--': 2.1,
"@}-;-'---": 2.2,
'aas': 2.5,
'aayf': 2.7,
'afu': -2.9,
'alol': 2.8,
'ambw': 2.9,
'aml': 3.4,
'atab': -1.9,
'awol': -1.3,
'ayc': 0.2,
'ayor': -1.2,
'aug-00': 0.3,
'bfd': -2.7,
'bfe': -2.6,
'bff': 2.9,
'bffn': 1.0,
'bl': 2.3,
'bsod': -2.2,
'btd': -2.1,
'btdt': -0.1,
'bz': 0.4,
'b^d': 2.6,
'cwot': -2.3,
"d-':": -2.5,
'd8': -3.2,
'd:': 1.2,
'd:<': -3.2,
'd;': -2.9,
'd=': 1.5,
'doa': -2.3,
'dx': -3.0,
'ez': 1.5,
'fav': 2.0,
'fcol': -1.8,
'ff': 1.8,
'ffs': -2.8,
'fkm': -2.4,
'foaf': 1.8,
'ftw': 2.0,
'fu': -3.7,
'fubar': -3.0,
'fwb': 2.5,
'fyi': 0.8,
'fysa': 0.4,
'g1': 1.4,
'gg': 1.2,
'gga': 1.7,
'gigo': -0.6,
'gj': 2.0,
'gl': 1.3,
'gla': 2.5,
'gn': 1.2,
'gr8': 2.7,
'grrr': -0.4,
'gt': 1.1,
'h&k': 2.3,
'hagd': 2.2,
'hagn': 2.2,
'hago': 1.2,
'hak': 1.9,
'hand': 2.2,
'heart': 3.2,
'hearts': 3.3,
'hho1/2k': 1.4,
'hhoj': 2.0,
'hhok': 0.9,
'hugz': 2.0,
'hi5': 1.9,
'idk': -0.4,
'ijs': 0.7,
'ilu': 3.4,
'iluaaf': 2.7,
'ily': 3.4,
'ily2': 2.6,
'iou': 0.7,
'iyq': 2.3,
'j/j': 2.0,
'j/k': 1.6,
'j/p': 1.4,
'j/t': -0.2,
'j/w': 1.0,
'j4f': 1.4,
'j4g': 1.7,
'jho': 0.8,
'jhomf': 1.0,
'jj': 1.0,
'jk': 0.9,
'jp': 0.8,
'jt': 0.9,
'jw': 1.6,
'jealz': -1.2,
'k4y': 2.3,
'kfy': 2.3,
'kia': -3.2,
'kk': 1.5,
'kmuf': 2.2,
'l': 2.0,
'l&r': 2.2,
'laoj': 1.3,
'lmao': 2.9,
'lmbao': 1.8,
'lmfao': 2.5,
'lmso': 2.7,
'lol': 1.8,
'lolz': 2.7,
'lts': 1.6,
'ly': 2.6,
'ly4e': 2.7,
'lya': 3.3,
'lyb': 3.0,
'lyl': 3.1,
'lylab': 2.7,
'lylas': 2.6,
'lylb': 1.6,
'm8': 1.4,
'mia': -1.2,
'mml': 2.0,
'mofo': -2.4,
'muah': 2.3,
'mubar': -1.0,
'musm': 0.9,
'mwah': 2.5,
'n1': 1.9,
'nbd': 1.3,
'nbif': -0.5,
'nfc': -2.7,
'nfw': -2.4,
'nh': 2.2,
'nimby': -0.8,
'nimjd': -0.7,
'nimq': -0.2,
'nimy': -1.4,
'nitl': -1.5,
'nme': -2.1,
'noyb': -0.7,
'np': 1.4,
'ntmu': 1.4,
'o-8': -0.5,
'o-:': -0.3,
'o-|': -1.1,
'o.o': -0.8,
'O.o': -0.6,
'o.O': -0.6,
'o:': -0.2,
'o:)': 1.5,
'o:-)': 2.0,
'o:-3': 2.2,
'o:3': 2.3,
'o:<': -0.3,
'o;^)': 1.6,
'ok': 1.2,
'o_o': -0.5,
'O_o': -0.5,
'o_O': -0.5,
'pita': -2.4,
'pls': 0.3,
'plz': 0.3,
'pmbi': 0.8,
'pmfji': 0.3,
'pmji': 0.7,
'po': -2.6,
'ptl': 2.6,
'pu': -1.1,
'qq': -2.2,
'qt': 1.8,
'r&r': 2.4,
'rofl': 2.7,
'roflmao': 2.5,
'rotfl': 2.6,
'rotflmao': 2.8,
'rotflmfao': 2.5,
'rotflol': 3.0,
'rotgl': 2.9,
'rotglmao': 1.8,
's:': -1.1,
'sapfu': -1.1,
'sete': 2.8,
'sfete': 2.7,
'sgtm': 2.4,
'slap': 0.6,
'slaw': 2.1,
'smh': -1.3,
'snafu': -2.5,
'sob': -1.0,
'swak': 2.3,
'tgif': 2.3,
'thks': 1.4,
'thx': 1.5,
'tia': 2.3,
'tmi': -0.3,
'tnx': 1.1,
'true': 1.8,
'tx': 1.5,
'txs': 1.1,
'ty': 1.6,
'tyvm': 2.5,
'urw': 1.9,
'vbg': 2.1,
'vbs': 3.1,
'vip': 2.3,
'vwd': 2.6,
'vwp': 2.1,
'wag': -0.2,
'wd': 2.7,
'wilco': 0.9,
'wp': 1.0,
'wtf': -2.8,
'wtg': 2.1,
'wth': -2.4,
'x-d': 2.6,
'x-p': 1.7,
'xd': 2.8,
'xlnt': 3.0,
'xoxo': 3.0,
'xoxozzz': 2.3,
'xp': 1.6,
'xqzt': 1.6,
'xtc': 0.8,
'yolo': 1.1,
'yoyo': 0.4,
'yvw': 1.6,
'yw': 1.8,
'ywia': 2.5,
'zzz': -1.2,
'[-;': 0.5,
'[:': 1.3,
'[;': 1.0,
'[=': 1.7,
'\\-:': -1.0,
'\\:': -1.0,
'\\:<': -1.7,
'\\=': -1.1,
'\\^:': -1.3,
'\\o/': 2.2,
'\\o:': -1.2,
']-:': -2.1,
']:': -1.6,
']:<': -2.5,
'^<_<': 1.4,
'^urs': -2.8,
'abandon': -1.9,
'abandoned': -2.0,
'abandoner': -1.9,
'abandoners': -1.9,
'abandoning': -1.6,
'abandonment': -2.4,
'abandonments': -1.7,
'abandons': -1.3,
'abducted': -2.3,
'abduction': -2.8,
'abductions': -2.0,
'abhor': -2.0,
'abhorred': -2.4,
'abhorrent': -3.1,
'abhors': -2.9,
'abilities': 1.0,
'ability': 1.3,
'aboard': 0.1,
'absentee': -1.1,
'absentees': -0.8,
'absolve': 1.2,
'absolved': 1.5,
'absolves': 1.3,
'absolving': 1.6,
'abuse': -3.2,
'abused': -2.3,
'abuser': -2.6,
'abusers': -2.6,
'abuses': -2.6,
'abusing': -2.0,
'abusive': -3.2,
'abusively': -2.8,
'abusiveness': -2.5,
'abusivenesses': -3.0,
'accept': 1.6,
'acceptabilities': 1.6,
'acceptability': 1.1,
'acceptable': 1.3,
'acceptableness': 1.3,
'acceptably': 1.5,
'acceptance': 2.0,
'acceptances': 1.7,
'acceptant': 1.6,
'acceptation': 1.3,
'acceptations': 0.9,
'accepted': 1.1,
'accepting': 1.6,
'accepts': 1.3,
'accident': -2.1,
'accidental': -0.3,
'accidentally': -1.4,
'accidents': -1.3,
'accomplish': 1.8,
'accomplished': 1.9,
'accomplishes': 1.7,
'accusation': -1.0,
'accusations': -1.3,
'accuse': -0.8,
'accused': -1.2,
'accuses': -1.4,
'accusing': -0.7,
'ache': -1.6,
'ached': -1.6,
'aches': -1.0,
'achievable': 1.3,
'aching': -2.2,
'acquit': 0.8,
'acquits': 0.1,
'acquitted': 1.0,
'acquitting': 1.3,
'acrimonious': -1.7,
'active': 1.7,
'actively': 1.3,
'activeness': 0.6,
'activenesses': 0.8,
'actives': 1.1,
'adequate': 0.9,
'admirability': 2.4,
'admirable': 2.6,
'admirableness': 2.2,
'admirably': 2.5,
'admiral': 1.3,
'admirals': 1.5,
'admiralties': 1.6,
'admiralty': 1.2,
'admiration': 2.5,
'admirations': 1.6,
'admire': 2.1,
'admired': 2.3,
'admirer': 1.8,
'admirers': 1.7,
'admires': 1.5,
'admiring': 1.6,
'admiringly': 2.3,
'admit': 0.8,
'admits': 1.2,
'admitted': 0.4,
'admonished': -1.9,
'adopt': 0.7,
'adopts': 0.7,
'adorability': 2.2,
'adorable': 2.2,
'adorableness': 2.5,
'adorably': 2.1,
'adoration': 2.9,
'adorations': 2.2,
'adore': 2.6,
'adored': 1.8,
'adorer': 1.7,
'adorers': 2.1,
'adores': 1.6,
'adoring': 2.6,
'adoringly': 2.4,
'adorn': 0.9,
'adorned': 0.8,
'adorner': 1.3,
'adorners': 0.9,
'adorning': 1.0,
'adornment': 1.3,
'adornments': 0.8,
'adorns': 0.5,
'advanced': 1.0,
'advantage': 1.0,
'advantaged': 1.4,
'advantageous': 1.5,
'advantageously': 1.9,
'advantageousness': 1.6,
'advantages': 1.5,
'advantaging': 1.6,
'adventure': 1.3,
'adventured': 1.3,
'adventurer': 1.2,
'adventurers': 0.9,
'adventures': 1.4,
'adventuresome': 1.7,
'adventuresomeness': 1.3,
'adventuress': 0.8,
'adventuresses': 1.4,
'adventuring': 2.3,
'adventurism': 1.5,
'adventurist': 1.4,
'adventuristic': 1.7,
'adventurists': 1.2,
'adventurous': 1.4,
'adventurously': 1.3,
'adventurousness': 1.8,
'adversarial': -1.5,
'adversaries': -1.0,
'adversary': -0.8,
'adversative': -1.2,
'adversatively': -0.1,
'adversatives': -1.0,
'adverse': -1.5,
'adversely': -0.8,
'adverseness': -0.6,
'adversities': -1.5,
'adversity': -1.8,
'affected': -0.6,
'affection': 2.4,
'affectional': 1.9,
'affectionally': 1.5,
'affectionate': 1.9,
'affectionately': 2.2,
'affectioned': 1.8,
'affectionless': -2.0,
'affections': 1.5,
'afflicted': -1.5,
'affronted': 0.2,
'aggravate': -2.5,
'aggravated': -1.9,
'aggravates': -1.9,
'aggravating': -1.2,
'aggress': -1.3,
'aggressed': -1.4,
'aggresses': -0.5,
'aggressing': -0.6,
'aggression': -1.2,
'aggressions': -1.3,
'aggressive': -0.6,
'aggressively': -1.3,
'aggressiveness': -1.8,
'aggressivities': -1.4,
'aggressivity': -0.6,
'aggressor': -0.8,
'aggressors': -0.9,
'aghast': -1.9,
'agitate': -1.7,
'agitated': -2.0,
'agitatedly': -1.6,
'agitates': -1.4,
'agitating': -1.8,
'agitation': -1.0,
'agitational': -1.2,
'agitations': -1.3,
'agitative': -1.3,
'agitato': -0.1,
'agitator': -1.4,
'agitators': -2.1,
'agog': 1.9,
'agonise': -2.1,
'agonised': -2.3,
'agonises': -2.4,
'agonising': -1.5,
'agonize': -2.3,
'agonized': -2.2,
'agonizes': -2.3,
'agonizing': -2.7,
'agonizingly': -2.3,
'agony': -1.8,
'agree': 1.5,
'agreeability': 1.9,
'agreeable': 1.8,
'agreeableness': 1.8,
'agreeablenesses': 1.3,
'agreeably': 1.6,
'agreed': 1.1,
'agreeing': 1.4,
'agreement': 2.2,
'agreements': 1.1,
'agrees': 0.8,
'alarm': -1.4,
'alarmed': -1.4,
'alarming': -0.5,
'alarmingly': -2.6,
'alarmism': -0.3,
'alarmists': -1.1,
'alarms': -1.1,
'alas': -1.1,
'alert': 1.2,
'alienation': -1.1,
'alive': 1.6,
'allergic': -1.2,
'allow': 0.9,
'alone': -1.0,
'alright': 1.0,
'amaze': 2.5,
'amazed': 2.2,
'amazedly': 2.1,
'amazement': 2.5,
'amazements': 2.2,
'amazes': 2.2,
'amazing': 2.8,
'amazon': 0.7,
'amazonite': 0.2,
'amazons': -0.1,
'amazonstone': 1.0,
'amazonstones': 0.2,
'ambitious': 2.1,
'ambivalent': 0.5,
'amor': 3.0,
'amoral': -1.6,
'amoralism': -0.7,
'amoralisms': -0.7,
'amoralities': -1.2,
'amorality': -1.5,
'amorally': -1.0,
'amoretti': 0.2,
'amoretto': 0.6,
'amorettos': 0.3,
'amorino': 1.2,
'amorist': 1.6,
'amoristic': 1.0,
'amorists': 0.1,
'amoroso': 2.3,
'amorous': 1.8,
'amorously': 2.3,
'amorousness': 2.0,
'amorphous': -0.2,
'amorphously': 0.1,
'amorphousness': 0.3,
'amort': -2.1,
'amortise': 0.5,
'amortised': -0.2,
'amortises': 0.1,
'amortizable': 0.5,
'amortization': 0.6,
'amortizations': 0.2,
'amortize': -0.1,
'amortized': 0.8,
'amortizes': 0.6,
'amortizing': 0.8,
'amusable': 0.7,
'amuse': 1.7,
'amused': 1.8,
'amusedly': 2.2,
'amusement': 1.5,
'amusements': 1.5,
'amuser': 1.1,
'amusers': 1.3,
'amuses': 1.7,
'amusia': 0.3,
'amusias': -0.4,
'amusing': 1.6,
'amusingly': 0.8,
'amusingness': 1.8,
'amusive': 1.7,
'anger': -2.7,
'angered': -2.3,
'angering': -2.2,
'angerly': -1.9,
'angers': -2.3,
'angrier': -2.3,
'angriest': -3.1,
'angrily': -1.8,
'angriness': -1.7,
'angry': -2.3,
'anguish': -2.9,
'anguished': -1.8,
'anguishes': -2.1,
'anguishing': -2.7,
'animosity': -1.9,
'annoy': -1.9,
'annoyance': -1.3,
'annoyances': -1.8,
'annoyed': -1.6,
'annoyer': -2.2,
'annoyers': -1.5,
'annoying': -1.7,
'annoys': -1.8,
'antagonism': -1.9,
'antagonisms': -1.2,
'antagonist': -1.9,
'antagonistic': -1.7,
'antagonistically': -2.2,
'antagonists': -1.7,
'antagonize': -2.0,
'antagonized': -1.4,
'antagonizes': -0.5,
'antagonizing': -2.7,
'anti': -1.3,
'anticipation': 0.4,
'anxieties': -0.6,
'anxiety': -0.7,
'anxious': -1.0,
'anxiously': -0.9,
'anxiousness': -1.0,
'aok': 2.0,
'apathetic': -1.2,
'apathetically': -0.4,
'apathies': -0.6,
'apathy': -1.2,
'apeshit': -0.9,
'apocalyptic': -3.4,
'apologise': 1.6,
'apologised': 0.4,
'apologises': 0.8,
'apologising': 0.2,
'apologize': 0.4,
'apologized': 1.3,
'apologizes': 1.5,
'apologizing': -0.3,
'apology': 0.2,
'appall': -2.4,
'appalled': -2.0,
'appalling': -1.5,
'appallingly': -2.0,
'appalls': -1.9,
'appease': 1.1,
'appeased': 0.9,
'appeases': 0.9,
'appeasing': 1.0,
'applaud': 2.0,
'applauded': 1.5,
'applauding': 2.1,
'applauds': 1.4,
'applause': 1.8,
'appreciate': 1.7,
'appreciated': 2.3,
'appreciates': 2.3,
'appreciating': 1.9,
'appreciation': 2.3,
'appreciations': 1.7,
'appreciative': 2.6,
'appreciatively': 1.8,
'appreciativeness': 1.6,
'appreciator': 2.6,
'appreciators': 1.5,
'appreciatory': 1.7,
'apprehensible': 1.1,
'apprehensibly': -0.2,
'apprehension': -2.1,
'apprehensions': -0.9,
'apprehensively': -0.3,
'apprehensiveness': -0.7,
'approval': 2.1,
'approved': 1.8,
'approves': 1.7,
'ardent': 2.1,
'arguable': -1.0,
'arguably': -1.0,
'argue': -1.4,
'argued': -1.5,
'arguer': -1.6,
'arguers': -1.4,
'argues': -1.6,
'arguing': -2.0,
'argument': -1.5,
'argumentative': -1.5,
'argumentatively': -1.8,
'argumentive': -1.5,
'arguments': -1.7,
'arrest': -1.4,
'arrested': -2.1,
'arrests': -1.9,
'arrogance': -2.4,
'arrogances': -1.9,
'arrogant': -2.2,
'arrogantly': -1.8,
'ashamed': -2.1,
'ashamedly': -1.7,
'ass': -2.5,
'assassination': -2.9,
'assassinations': -2.7,
'assault': -2.8,
'assaulted': -2.4,
'assaulting': -2.3,
'assaultive': -2.8,
'assaults': -2.5,
'asset': 1.5,
'assets': 0.7,
'assfucking': -2.5,
'assholes': -2.8,
'assurance': 1.4,
'assurances': 1.4,
'assure': 1.4,
'assured': 1.5,
'assuredly': 1.6,
'assuredness': 1.4,
'assurer': 0.9,
'assurers': 1.1,
'assures': 1.3,
'assurgent': 1.3,
'assuring': 1.6,
'assuror': 0.5,
'assurors': 0.7,
'astonished': 1.6,
'astound': 1.7,
'astounded': 1.8,
'astounding': 1.8,
'astoundingly': 2.1,
'astounds': 2.1,
'attachment': 1.2,
'attachments': 1.1,
'attack': -2.1,
'attacked': -2.0,
'attacker': -2.7,
'attackers': -2.7,
'attacking': -2.0,
'attacks': -1.9,
'attract': 1.5,
'attractancy': 0.9,
'attractant': 1.3,
'attractants': 1.4,
'attracted': 1.8,
'attracting': 2.1,
'attraction': 2.0,
'attractions': 1.8,
'attractive': 1.9,
'attractively': 2.2,
'attractiveness': 1.8,
'attractivenesses': 2.1,
'attractor': 1.2,
'attractors': 1.2,
'attracts': 1.7,
'audacious': 0.9,
'authority': 0.3,
'aversion': -1.9,
'aversions': -1.1,
'aversive': -1.6,
'aversively': -0.8,
'avert': -0.7,
'averted': -0.3,
'averts': -0.4,
'avid': 1.2,
'avoid': -1.2,
'avoidance': -1.7,
'avoidances': -1.1,
'avoided': -1.4,
'avoider': -1.8,
'avoiders': -1.4,
'avoiding': -1.4,
'avoids': -0.7,
'await': 0.4,
'awaited': -0.1,
'awaits': 0.3,
'award': 2.5,
'awardable': 2.4,
'awarded': 1.7,
'awardee': 1.8,
'awardees': 1.2,
'awarder': 0.9,
'awarders': 1.3,
'awarding': 1.9,
'awards': 2.0,
'awesome': 3.1,
'awful': -2.0,
'awkward': -0.6,
'awkwardly': -1.3,
'awkwardness': -0.7,
'axe': -0.4,
'axed': -1.3,
'backed': 0.1,
'backing': 0.1,
'backs': -0.2,
'bad': -2.5,
'badass': 1.4,
'badly': -2.1,
'bailout': -0.4,
'bamboozle': -1.5,
'bamboozled': -1.5,
'bamboozles': -1.5,
'ban': -2.6,
'banish': -1.9,
'bankrupt': -2.6,
'bankster': -2.1,
'banned': -2.0,
'bargain': 0.8,
'barrier': -0.5,
'bashful': -0.1,
'bashfully': 0.2,
'bashfulness': -0.8,
'bastard': -2.5,
'bastardies': -1.8,
'bastardise': -2.1,
'bastardised': -2.3,
'bastardises': -2.3,
'bastardising': -2.6,
'bastardization': -2.4,
'bastardizations': -2.1,
'bastardize': -2.4,
'bastardized': -2.0,
'bastardizes': -1.8,
'bastardizing': -2.3,
'bastardly': -2.7,
'bastards': -3.0,
'bastardy': -2.7,
'battle': -1.6,
'battled': -1.2,
'battlefield': -1.6,
'battlefields': -0.9,
'battlefront': -1.2,
'battlefronts': -0.8,
'battleground': -1.7,
'battlegrounds': -0.6,
'battlement': -0.4,
'battlements': -0.4,
'battler': -0.8,
'battlers': -0.2,
'battles': -1.6,
'battleship': -0.1,
'battleships': -0.5,
'battlewagon': -0.3,
'battlewagons': -0.5,
'battling': -1.1,
'beaten': -1.8,
'beatific': 1.8,
'beating': -2.0,
'beaut': 1.6,
'beauteous': 2.5,
'beauteously': 2.6,
...}
# Check if a word is in the lexicon
test_word = 'sweet' # The word to check for
# Get the word's score or print a message for missing words
sa.lexicon.get(test_word, 'No score for that word')
2.0
In order to do our analysis, we will use a very small sample of 8 user reviews. Each review is a simple text string inside a list variable called product_reviews
.
# Define a list of product reviews
product_reviews = [
'I love this product. It helps me get so much work done. I tell everyone about what a great thing it is.',
'This product is defective. I feel like it is broken because it does not do what it promises. Do not buy this.',
'Do yourself a favor and buy this product as soon as possible. I recommend it to everyone I know. It has saved me so much time!',
'This product is overpriced and useless. It was a waste of money and it made all my hair fall out.',
'Works like a dream and it is a bargain! It solves my problems with ease. I bought two!',
'Do not buy! This product is a ripoff. I wish it was better, but it fails constantly. What a mistake!',
'This thing is garbage. Do yourself a favor and save the money. Mine is a dumpster fire and fell apart.',
'I adore this product. =) It makes my life so much easier. And it is a deal!'
]
Now we will analyze each product and assign it a “normalized, weighted composite score” based on summing the valence scores of each word in the lexicon (with some adjustments based on word order and other rules). VADER measures the proportion of text that falls into positive, negative, and neutral sentiment. The result is a sentiment score that falls between -1 (the most negative) and +1 (the most positive). (This is different from the lexicon scores that fall between -4 to +4!)
# For each review in our `product_reviews` list
# Store a polarity score in `scores`
# Then print the score followed by the review
for review in product_reviews:
scores = sa.polarity_scores(review)
print(scores['compound'], review)
0.8979 I love this product. It helps me get so much work done. I tell everyone about what a great thing it is.
-0.2263 This product is defective. I feel like it is broken because it does not do what it promises. Do not buy this.
0.807 Do yourself a favor and buy this product as soon as possible. I recommend it to everyone I know. It has saved me so much time!
-0.6808 This product is overpriced and useless. It was a waste of money and it made all my hair fall out.
0.7772 Works like a dream and it is a bargain! It solves my problems with ease. I bought two!
-0.6792 Do not buy! This product is a ripoff. I wish it was better, but it fails constantly. What a mistake!
0.4404 This thing is garbage. Do yourself a favor and save the money. Mine is a dumpster fire and fell apart.
0.8799 I adore this product. =) It makes my life so much easier. And it is a deal!
Our simple analysis does a fairly good job of assessing positive and negative sentiment. Notice that our second to last review was not very accurate though:
0.5423 This thing is garbage. Do yourself a favor and save the money. Mine started on fire and fell apart.
The VADER lexicon contains the following entries:
Token |
Mean-Sentiment Rating |
---|---|
favor |
1.7 |
fire |
-1.4 |
VADER assigns a value of -1.4 for “fire” but “fire” can also have a positive connotation, such as “straight fire.” However, words like “garbage” and “dumpster,” as in “dumpster fire,” are less ambiguous. If a specific token is not found in the VADER lexicon, it is considered to be neutral. Like any other statistical approach, the process benefits from having more data. In this case, the sentences are very short and several significant words do not happen to exist in our lexicon.
Adding Tokens to the VADER Lexicon#
The sa.lexicon
is a simple dictionary, so we can add words that we want included. There are some guidelines for best scoring practices included in the academic paper linked on VADER’s github repository. (Remember that lexicon tokens are scored from -4 to +4.)
# Adding the dictionary of `new_words`
# to sa.lexicon
new_words = {
'garbage': -2.0,
'dumpster': -3.1,
}
sa.lexicon.update(new_words)
Let’s try our analysis again with the new lexicon.
# For each review in our `product_reviews` list
# Store a polarity score in `scores`
# Then print the score followed by the review
for review in product_reviews:
scores = sa.polarity_scores(review)
print(scores['compound'], review)
0.8979 I love this product. It helps me get so much work done. I tell everyone about what a great thing it is.
-0.2263 This product is defective. I feel like it is broken because it does not do what it promises. Do not buy this.
0.807 Do yourself a favor and buy this product as soon as possible. I recommend it to everyone I know. It has saved me so much time!
-0.6808 This product is overpriced and useless. It was a waste of money and it made all my hair fall out.
0.7772 Works like a dream and it is a bargain! It solves my problems with ease. I bought two!
-0.6792 Do not buy! This product is a ripoff. I wish it was better, but it fails constantly. What a mistake!
-0.5574 This thing is garbage. Do yourself a favor and save the money. Mine is a dumpster fire and fell apart.
0.8799 I adore this product. =) It makes my life so much easier. And it is a deal!
Sentiment analysis with machine learning#
The primary advantage of using a machine learning classifier for sentiment analysis is there is no need to maintain a lexicon, assign sentiment scores to particular words, develop linguistic rules based on grammatical structures (negation, intensifiers), or keep track of novel expressions (slang, emoticons, etc.).
The very best models for sentiment analysis will be trained or tuned on the type of data you are analyzing. We always recommend trying existing models first though, since training a model from scratch takes significant resources, both on the computational side and on the labor side for data quality assurance. If you are interested in training your own models, then you may want to invest in high-end hardware, especially a computer with a powerful graphics processing unit (GPU). If your data or model requires significant resources, consider purchasing cloud-computing resources.
There are many existing models that are a great place to start with sentiment analysis. Let’s try using an existing model with the popular transfomers library from HuggingFace. We will use a dataset of Amazon game reviews created by Logan Kells.
# Install the transformers package
%pip install -q transformers
# Install TensorFlow, a popular library for machine learning
%pip install TensorFlow
# Install the tf_keras package
%pip install tf_keras
# Download the dataset
from pathlib import Path
import urllib.request
# The file URL
url = 'https://huggingface.co/datasets/LoganKells/amazon_product_reviews_video_games/resolve/main/data.csv'
# Check if a data folder exists. If not, create it.
data_folder = Path('../data/')
data_folder.mkdir(exist_ok=True)
# Download the file
path_url = Path(url)
urllib.request.urlretrieve(url, f'{data_folder.as_posix()}/{path_url.name}')
## Success message
print('Data downloaded.')
Data downloaded.
# Import Pandas and Read Data CSV file
import pandas as pd
df = pd.read_csv('../data/data.csv')
# Check the size of the dataframe
df.shape
(50000, 10)
#Preview the dataframe
df.head()
Unnamed: 0 | reviewerID | asin | reviewerName | helpful | reviewText | overall | summary | unixReviewTime | reviewTime | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | A2HD75EMZR8QLN | 0700099867 | 123 | [8, 12] | Installing the game was a struggle (because of... | 0.0 | Pay to unlock content? I don't think so. | 1341792000 | 07 9, 2012 |
1 | 1 | A3UR8NLLY1ZHCX | 0700099867 | Alejandro Henao "Electronic Junky" | [0, 0] | If you like rally cars get this game you will ... | 3.0 | Good rally game | 1372550400 | 06 30, 2013 |
2 | 2 | A1INA0F5CWW3J4 | 0700099867 | Amazon Shopper "Mr.Repsol" | [0, 0] | 1st shipment received a book instead of the ga... | 0.0 | Wrong key | 1403913600 | 06 28, 2014 |
3 | 4 | A361M14PU2GUEG | 0700099867 | Angry Ryan "Ryan A. Forrest" | [2, 2] | I had Dirt 2 on Xbox 360 and it was an okay ga... | 3.0 | DIRT 3 | 1308009600 | 06 14, 2011 |
4 | 5 | A2UTRVO4FDCBH6 | 0700099867 | A.R.G. | [0, 0] | Overall this is a well done racing game, with ... | 3.0 | Good racing game, terrible Windows Live Requir... | 1368230400 | 05 11, 2013 |
# Create a list of the first 100 review texts
review_texts = df['reviewText'].tolist()[:100]
# Using pipeline class to make predictions from models available in the Hub in an easy way
from transformers import pipeline
sentiment_pipeline = pipeline("sentiment-analysis")
sentiment_scores = sentiment_pipeline(review_texts)
No model was supplied, defaulted to distilbert/distilbert-base-uncased-finetuned-sst-2-english and revision 714eb0f (https://huggingface.co/distilbert/distilbert-base-uncased-finetuned-sst-2-english).
Using a pipeline without specifying a model name and revision in production is not recommended.
Device set to use mps:0
# Examine each review to see how the model did
review = 0
# Print Sentiment
print(sentiment_scores[review])
# Print Review
print(review_texts[review])
{'label': 'NEGATIVE', 'score': 0.9992536902427673}
Installing the game was a struggle (because of games for windows live bugs).Some championship races and cars can only be "unlocked" by buying them as an addon to the game. I paid nearly 30 dollars when the game was new. I don't like the idea that I have to keep paying to keep playing.I noticed no improvement in the physics or graphics compared to Dirt 2.I tossed it in the garbage and vowed never to buy another codemasters game. I'm really tired of arcade style rally/racing games anyway.I'll continue to get my fix from Richard Burns Rally, and you should to. :)http://www.amazon.com/Richard-Burns-Rally-PC/dp/B000C97156/ref=sr_1_1?ie=UTF8&qid;=1341886844&sr;=8-1&keywords;=richard+burns+rallyThank you for reading my review! If you enjoyed it, be sure to rate it as helpful.
# Show reviews with lower certainty scores
for review_number, sentiment_score in enumerate(sentiment_scores):
if sentiment_score['score'] < .9:
print(sentiment_score, review_texts[review_number], '\n')
{'label': 'NEGATIVE', 'score': 0.8348784446716309} I have been playing car racing games since their early beginning on PC. I currently have a logitech G25 force feedback wheel to play with.I have played most of the Colin Mc Rae, Need for speed, Grid and Dirt2 games before. I also tried my hands at more simulation oriented games like GTR, GTR2, GP Legends...Dirt2 came in as a nice and pleasant surprise. Dirt3 tops it off.As I am sure many will detail everything about this game, I will limit myself to the most important points for me.Pros:- Amazing graphics- Amazing physics- Challenging but entertaining racesCons:- Gymkhana- InterfaceWhat went through the conceptors mind about gymkhana? Racing is not easy for most but gymkhana is really a pain in the neck.I saw the videos of Ken Block on Youtube and indeed he is really impressive. Am I even dreaming of doing the same? No!Racing require good racing skills to start but Gymkhana requires a perfect control of the vehicles and most of us will never achieve such level. So I find the attemps slightly pointless and extremely frustrating.Now, what about the interface? Simply put, it does not remotely compare with Dirt2's interface. It is messy, I had to search online to figure out where to find my best times. There are not enough info about the cars, the races, the sponsors... It leaves a lot to desire. A complete miss.Conclusion:As far as rallye racing. This game is the best out there today.If you are one who needs crazy challenges, try the gymkhana and good luck!In spite of my criticism, I definitely do not regreat this purchase.
{'label': 'NEGATIVE', 'score': 0.8938910365104675} I enjoyed the first Hawx game. However they left out some key things in Hawx 21) No coop mode - I really enjoyed that2) Can't choose your own plane right off the bat. You have to use whatever they tell you.Those two things will make me not play this game anymore!
{'label': 'NEGATIVE', 'score': 0.5815169215202332} Have had this skin for almost 2 months and so far there are no signs of wear or tear which is good. Its sticky just enough to be grippy but not overly so. It protects the controller fairly well. Keeps it from getting dirty as fast especially if you have a white controller.Overall it feels good and is comfortable even in long play periods. The only problem I have is there is annoying little piece up by the triggers on both sides. Its right in between the trigger and the bumper buttons and there is no tension on the piece so when you switch between the bumper and triggers it can sometimes flip up and your finger will catch it. Not really a deal breaker or a big deal or anything since I plan on cutting it off anyways. Plan on buy another so far after this one falls apart. Will update my review if it doesnt last long.