Exercise 8: Big data and text analytics

In this notebook, we implement and compare different text analytics methods:

Exercise part Time (min)
Part 1: Case and dataset 10
Part 2: Sparse text representations 20
Part 3: Dense representations 25
Part 4: Classification 20
Part 5: Edge cases and reflection 10
Wrap-up 5
Overall 90

Resume your Codespace at analytics-and-big-data-notebooks.

Case: Understanding movie reviews

You are a data analyst working for ReviewSense, a fictional analytics team that supports a streaming platform. The platform collects many user reviews for movies and series. Management wants to understand whether text analytics can help to classify reviews as positive or negative and to identify semantically similar reviews.

We use a small public sample of the IMDB movie review sentiment dataset. The dataset contains movie reviews with binary sentiment labels. For this exercise, the course repository should contain a prepared subset (data/imdb_reviews_sample.csv) with the following columns:

Variable Meaning Type
review review text text
sentiment sentiment label: positive or negative categorical
NoteDataset note

Use a local course copy of the IMDB sample rather than downloading the full dataset during class. This makes the exercise more reliable and keeps the focus on representation, similarity, visualization, and prediction. A small sample of 1,000–3,000 reviews is sufficient for this exercise.

Part 1: Case and dataset

We begin by loading the dataset and inspecting text as a form of data. Unlike the structured datasets from earlier exercises, the relevant information is inside a free-text column.

Task 1.1 — Load and inspect the dataset

Load the dataset and inspect the first rows. Also run statistics on the sentiment column. Then answer the questions below.

import pandas as pd

# Load the prepared IMDB sample
# df = ...

# Inspect the data
# ...

Task 1.2 — Prepare text, labels, and a train-test split

Create a lightly cleaned text column (use regex replacement to remove html <br> tags, and multiple spaces), create a numeric target variable, and split the data into training and test data.

from sklearn.model_selection import train_test_split

# Light text cleaning
# df["clean_review"] = ...

# Convert sentiment labels into 0/1 values
# df["label"] = ...

# Define X and y
# X = ...
# y = ...

# Split into training and test data
# X_train, X_test, y_train, y_test = train_test_split(...)

Part 2: Sparse text representations

Sparse representations transform text into a large matrix. Each row represents a document and each column represents a word or phrase. Most cells are zero because each review uses only a small share of the full vocabulary.

In this part, we compare two classical approaches:

  • Bag-of-Words: counts how often a word appears.
  • TF-IDF: gives more weight to words that are characteristic for a document and less weight to words that occur everywhere.

Task 2.1 — Create Bag-of-Words and TF-IDF matrices

Create a Bag-of-Words matrix and a TF-IDF matrix. Inspect the matrix shapes and calculate the sparsity of the TF-IDF matrix.

from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer

# Create Bag-of-Words features
# count_vectorizer = CountVectorizer(...)
# X_train_bow = ...
# X_test_bow = ...

# Create TF-IDF features
# tfidf_vectorizer = TfidfVectorizer(...)
# X_train_tfidf = ...
# X_test_tfidf = ...

# Inspect shape and sparsity
# print(...)

Questions:

  1. How many documents are represented in the training matrix?
  2. How many features are created?
  3. What does a high sparsity value mean?







Task 2.2 — Inspect sparse features for one review

Select one review from the training data and inspect the highest TF-IDF features.

# Select one review
# review_id = 0

# Retrieve feature names
# feature_names = ...

# Retrieve TF-IDF values for this review
# row = ...

# Show the strongest features
# ...

Question:

  • Do the strongest features seem meaningful for understanding the review?







Task 2.3 — Find similar reviews using TF-IDF

Use cosine similarity to find the reviews most similar to a selected query review.

from sklearn.metrics.pairwise import cosine_similarity

# Choose a query review from the training data
# query_id = 0

# Compute similarity between this review and all other training reviews
# similarities = ...

# Identify the top 5 most similar reviews, excluding the query itself
# top_ids = ...

# Create an overview table with review text, sentiment, and similarity
# ...

Questions:

  1. Are the most similar reviews similar in meaning or mainly similar in wording?
  2. Do the retrieved reviews have the same sentiment label as the query review?
  3. What does this tell us about sparse similarity?









Part 3: Dense representations

Dense representations transform each review into a shorter vector of continuous numbers. In contrast to sparse TF-IDF vectors, dense vectors do not correspond directly to individual words. They are learned representations intended to capture semantic relationships.

We use a pretrained sentence embedding model. The first run may take a moment because the model must be available in the environment.

Task 3.1 — Create sentence embeddings

Create dense embeddings for the training and test reviews. Use a small and efficient sentence-transformer model.

# If needed, install once in the Codespace terminal:
# pip install sentence-transformers

from sentence_transformers import SentenceTransformer

# Load the embedding model
# embedding_model = SentenceTransformer("all-MiniLM-L6-v2")

# Encode training and test texts
# X_train_emb = ...
# X_test_emb = ...

# Inspect the embedding matrix
# print(...)

Questions:

  1. How many dimensions does each dense vector have?
  2. Why is this representation called dense?
  3. Why are the dimensions less directly interpretable than TF-IDF features?







Task 3.2 — Find similar reviews using dense embeddings

Repeat the similarity search from Task 2.3, but now use dense embeddings.

from sklearn.metrics.pairwise import cosine_similarity

# query_id = 0
# similarities_dense = ...
# top_ids_dense = ...
# similar_reviews_dense = ...

Questions:

  1. Compare the dense results with the TF-IDF results.
  2. Which representation retrieves reviews that are more similar in meaning?
  3. Do dense embeddings always retrieve reviews with the same sentiment?









Task 3.3 — Visualize dense embeddings

Reduce the dense embeddings to two dimensions and create a scatter plot. Color the points by sentiment.

Use a sample if the dataset is large.

from sklearn.decomposition import PCA
import matplotlib.pyplot as plt

# Select a sample for visualization
# sample_size = ...
# sample_df = ...

# Create embeddings for the sample
# sample_embeddings = ...

# Reduce to two dimensions
# pca = ...
# coords = ...

# Plot the result
# ...

Questions:

  1. Do positive and negative reviews form clearly separated clusters?
  2. What kinds of patterns do you see?
  3. Why should a two-dimensional plot be interpreted carefully?









Part 4: Classification

We now compare whether sparse and dense representations are useful for predicting sentiment. The model is intentionally simple: logistic regression. This keeps the focus on the representation rather than on complex modeling choices.

Task 4.1 — Train a classifier on TF-IDF features

Train a logistic regression model using TF-IDF features. Report accuracy, the classification report, and training time.

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import time

# Start timer
# ...

# Fit model
# tfidf_clf = ...

# Predict on test set
# y_pred_tfidf = ...

# Stop timer
# ...

# Evaluate
# ...

Questions:

  1. How well does the model perform?
  2. Which errors are more common: false positives or false negatives?
  3. Why is this model relatively interpretable?







Task 4.2 — Train a classifier on dense embeddings

Train the same type of classifier using dense embeddings. Report the same metrics.

# Start timer
# ...

# Fit model on dense embeddings
# dense_clf = ...

# Predict on test embeddings
# y_pred_dense = ...

# Stop timer and evaluate
# ...

Questions:

  1. Does the dense model perform better, worse, or similarly?
  2. Is the dense model easier or harder to interpret?
  3. Which representation was more expensive to create?







Task 4.3 — Compare sparse and dense models

Create a small comparison table.

# results = pd.DataFrame(...)
# results

Then complete the interpretation table below.

Criterion TF-IDF + logistic regression Dense embeddings + logistic regression
Accuracy
Training time
Feature interpretability
Captures exact words
Captures semantic similarity







Part 5: Edge cases and reflection

We now test how the models behave on short, manually created edge cases. These cases are not meant to be a representative dataset. They are a stress test that helps us discuss robustness, ambiguity, and generalization.

Create a small synthetic test set with ambiguous or difficult examples. Apply both models and compare predictions.

edge_cases = pd.DataFrame({
    "review": [
        "This movie was sick in the best possible way.",
        "This movie made me sick.",
        "Not bad at all.",
        "A masterpiece? Not really.",
        "The acting was fine, but the story went nowhere.",
        "I expected more.",
        "The plot was unpredictable and surprisingly moving.",
        "I would not say it is a bad movie.",
    ],
    "expected_sentiment": [
        "positive",
        "negative",
        "positive",
        "negative",
        "negative",
        "negative",
        "positive",
        "positive_or_ambiguous",
    ],
})

# Predict with TF-IDF model
# ...

# Predict with dense model
# ...

# Display results
# edge_cases

Questions:

  1. Which examples are difficult for the models?
  2. Which representation seems more robust to wording differences?
  3. What types of meaning are still difficult for both models?







Wrap-up

In this exercise, we moved from manually transforming text into sparse matrices to using learned dense representations. The key lesson is not that one representation is always better. Rather, each representation supports a different analytical perspective:

Representation Main idea Strength Limitation
Bag-of-Words Count words Simple and transparent Ignores meaning and order
TF-IDF Weight characteristic words Strong interpretable baseline Still mostly lexical
Dense embeddings Encode semantic relationships Useful for similarity and generalization Less transparent and more resource-intensive
TipSession 8 survey

Before you wrap up, please complete the Session 8 survey here: ?meta:surveys.session_08.url. Thank you 🙏