Skip to main content

Sentiment Analysis - Foundations and Workflow

Sentiment analysis is a core task in Natural Language Processing (NLP) that enables machines to interpret subjective information at scale. Modern software can infer emotional tone from written language, powering features like customer feedback analysis, recommendation engines, and brand monitoring. Instead of simply matching keywords, these systems identify patterns that signal positive, neutral, or negative intent.

Why Sentiment Analysis Matters

Opinion mining is widely used across industries:

  • Streaming platforms analyze reactions to content and improve recommendations.
  • Marketing teams monitor perception of products in real time.
  • Financial institutions analyze news sentiment to anticipate market behavior.
  • Public policy analysts measure reactions to laws and social trends.

At scale, public opinion becomes structured data that organizations can act on.

What the Model Is Trying to Do

The goal is to classify text into emotional categories such as positive, neutral, or negative. Humans rely on context, tone, and cultural cues to make these distinctions. Machines, however, rely on labeled examples and statistical patterns - which is why ambiguous cases like sarcasm remain challenging.

from transformers import pipeline

classifier = pipeline(
"sentiment-analysis",
model="distilbert-base-uncased-finetuned-sst-2-english",
)

results = classifier([
"This product is absolutely fantastic!",
"The service was okay, nothing special.",
"I'm really disappointed with the quality.",
])

for r in results:
print(f"{r['label']}: {r['score']:.4f}")
# POSITIVE: 0.9999
# POSITIVE: 0.9367
# NEGATIVE: 0.9998

Language is inherently irregular, making it one of the hardest domains for machine learning.

Understanding the Dataset

A typical sentiment dataset contains:

  • Raw text - tweets, reviews, comments
  • Labels representing sentiment categories
  • Thousands of samples to train reliable models

The text serves as the input feature, while sentiment becomes the prediction target. Real-world datasets are rarely clean and often require extensive preparation.

import pandas as pd

data = {
"text": [
"Loved the new update, everything works smoothly!",
"Terrible experience, the app keeps crashing.",
"It's fine, does what it's supposed to do.",
"Worst customer support I've ever dealt with.",
"Great value for the price, highly recommend.",
],
"sentiment": ["positive", "negative", "neutral", "negative", "positive"],
}

df = pd.DataFrame(data)
print(df["sentiment"].value_counts())
# positive 2
# negative 2
# neutral 1

The Problem of Class Imbalance

If one category dominates the dataset, models may learn shortcuts instead of meaningful patterns. For example, if most samples are positive, a naive model predicting only "positive" might still appear accurate.

Common mitigation strategies:

  • Downsampling majority classes
  • Upsampling minority classes
  • Weighted loss functions that penalize misclassification of underrepresented categories
from sklearn.utils import resample

df_positive = df[df["sentiment"] == "positive"]
df_negative = df[df["sentiment"] == "negative"]
df_neutral = df[df["sentiment"] == "neutral"]

target_count = df["sentiment"].value_counts().max()

df_neutral_upsampled = resample(
df_neutral, replace=True, n_samples=target_count, random_state=42
)

df_balanced = pd.concat([df_positive, df_negative, df_neutral_upsampled])
print(df_balanced["sentiment"].value_counts())

Text Preprocessing

Raw text must be transformed before modeling. Preprocessing determines what information the model will prioritize.

Typical preprocessing steps:

  1. Removing URLs and symbols
  2. Lowercasing text
  3. Eliminating common stopwords (e.g., "the", "is")
  4. Stemming or lemmatization
import re
from typing import List

STOP_WORDS = {"the", "a", "an", "is", "in", "on", "at", "to", "for", "and", "of", "it"}

def preprocess(text: str) -> str:
text = re.sub(r"http\S+|www\.\S+", "", text)
text = re.sub(r"[^a-zA-Z\s]", "", text)
text = text.lower()
tokens = text.split()
tokens = [t for t in tokens if t not in STOP_WORDS]
return " ".join(tokens)

raw = "Check out https://example.com! This product is AMAZING & worth $50."
print(preprocess(raw))
# check out this product amazing worth

Why Counting Words Isn't Enough

Simply counting word frequency creates problems:

  • Very common words dominate the feature space
  • Important but rare words get ignored
  • Context is lost entirely

High-frequency words often provide little semantic value. The sentence "the the the" would score highly on word count but carries no meaning.

TF-IDF - Term Importance Weighting

TF-IDF (Term Frequency-Inverse Document Frequency) assigns importance scores to words based on two ideas:

  1. How often the word appears in a document (Term Frequency)
  2. How rare it is across the entire dataset (Inverse Document Frequency)

The intuition is straightforward: common words get low importance, while rare but meaningful words get high importance.

Simplified formula:

  • TF = frequency of a term within a document
  • IDF = log(total documents / documents containing the term)
  • TF-IDF = TF x IDF
from sklearn.feature_extraction.text import TfidfVectorizer

corpus = [
"the food was excellent and service was great",
"terrible food and horrible service",
"average experience nothing special",
"loved the ambiance but food was cold",
]

vectorizer = TfidfVectorizer(stop_words="english")
tfidf_matrix = vectorizer.fit_transform(corpus)

feature_names = vectorizer.get_feature_names_out()
for i, doc in enumerate(corpus):
scores = tfidf_matrix[i].toarray().flatten()
top_indices = scores.argsort()[-3:][::-1]
top_terms = [(feature_names[j], round(scores[j], 3)) for j in top_indices]
print(f"Doc {i}: {top_terms}")

Converting Text into Vectors

Machine learning models operate on numbers, not words. After weighting terms, each sentence becomes a numeric vector through a process called vectorization.

Each document is represented as a high-dimensional coordinate where:

  • Each dimension corresponds to a vocabulary term
  • Each value represents the importance score for that term

Measuring Similarity with Cosine Similarity

Once vectorized, text samples can be compared using cosine similarity, which measures the angle between two vectors.

Range:

  • 1 - identical direction (very similar)
  • 0 - unrelated (orthogonal)
  • -1 - opposite meaning
from sklearn.metrics.pairwise import cosine_similarity

query = vectorizer.transform(["the food was amazing"])
similarities = cosine_similarity(query, tfidf_matrix).flatten()

for i, score in enumerate(similarities):
print(f"Doc {i} similarity: {score:.4f}")

best_match = similarities.argmax()
print(f"\nBest match: '{corpus[best_match]}'")

This metric is widely used in semantic search, recommendation systems, and document clustering.

The Core Pipeline

A basic sentiment analysis workflow follows these steps:

  1. Collect text data
  2. Clean and preprocess text
  3. Convert text into weighted vectors (TF-IDF)
  4. Measure similarity or patterns
  5. Train a classifier
  6. Predict sentiment labels
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction.text import TfidfVectorizer

texts = [
"absolutely loved it", "great product", "highly recommend",
"terrible quality", "waste of money", "very disappointing",
"it was okay", "nothing remarkable", "does the job",
]
labels = [1, 1, 1, 0, 0, 0, 2, 2, 2] # 1=positive, 0=negative, 2=neutral

vectorizer = TfidfVectorizer(stop_words="english")
X = vectorizer.fit_transform(texts)

X_train, X_test, y_train, y_test = train_test_split(
X, labels, test_size=0.33, random_state=42
)

model = LogisticRegression(max_iter=200)
model.fit(X_train, y_train)

new_texts = ["this is wonderful", "really bad experience"]
predictions = model.predict(vectorizer.transform(new_texts))
label_map = {0: "negative", 1: "positive", 2: "neutral"}
for text, pred in zip(new_texts, predictions):
print(f"'{text}' -> {label_map[pred]}")

Classification and Decision Boundaries

Once vectors are created, a classifier draws a boundary separating sentiment groups. Common classifiers include:

  • Logistic Regression - fast and interpretable, works well with TF-IDF features
  • Support Vector Machines - effective in high-dimensional spaces
  • Naive Bayes - probabilistic approach that performs surprisingly well on text

Each new text is placed relative to these boundaries and assigned a sentiment category.

Evaluating Model Performance

Accuracy alone is insufficient, especially with imbalanced datasets. Important evaluation metrics include:

  • Precision - how many predicted positives were actually correct
  • Recall - how many real positives were detected
  • F1 Score - harmonic mean of precision and recall
from sklearn.metrics import classification_report

y_pred = model.predict(X_test)
print(classification_report(y_test, y_pred, target_names=["negative", "positive", "neutral"]))

These metrics provide a more realistic view of model quality than accuracy alone.

Limitations of Traditional Approaches

Classic TF-IDF pipelines have real constraints:

  • Weak understanding of context - word order is ignored entirely
  • Struggles with sarcasm and irony - "Oh great, another meeting" reads as positive
  • Limited handling of long text dependencies - relationships between distant words are lost

Despite these limitations, TF-IDF pipelines remain valuable for their simplicity, speed, and interpretability.

Modern Advances

Recent NLP breakthroughs have shifted sentiment analysis toward deep learning models built on the transformer architecture.

Transformer Models

Architectures like BERT and RoBERTa capture contextual meaning by analyzing word relationships dynamically. Key benefits include:

  • Context awareness - the same word gets different representations depending on surrounding text
  • Strong performance on nuanced language - better handling of negation, sarcasm, and complex phrasing
  • Transfer learning - models pretrained on massive corpora can be fine-tuned for specific sentiment tasks with relatively little labeled data
from transformers import pipeline

sentiment = pipeline(
"sentiment-analysis",
model="nlptown/bert-base-multilingual-uncased-sentiment",
)

reviews = [
"The camera quality is incredible, best phone I've owned.",
"Battery life is decent but nothing groundbreaking.",
"Phone overheats constantly, regret buying it.",
]

for review in reviews:
result = sentiment(review)[0]
print(f"{result['label']} ({result['score']:.3f}): {review}")

Future Directions

Emerging trends in sentiment analysis:

  • Aspect-based sentiment analysis - detecting sentiment toward specific features ("the battery is great but the screen is dim")
  • Real-time sentiment pipelines - streaming analysis of social media and live events
  • Multimodal sentiment analysis - combining text with audio, video, and images
  • Emotion detection beyond polarity - classifying into fine-grained emotions like joy, anger, fear, and surprise

Key Takeaways

  1. High-quality, balanced data is essential for reliable sentiment models
  2. Preprocessing strongly impacts downstream performance
  3. TF-IDF transforms language into meaningful numerical representations
  4. Cosine similarity enables effective text comparison in vector space
  5. Evaluation must go beyond accuracy - use precision, recall, and F1
  6. Traditional pipelines trade context understanding for speed and simplicity
  7. Transformer-based models provide deeper contextual understanding and are now the standard for production sentiment systems