{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "kz8lLSv6mVQo"
},
"source": [
"# **🤖 Data Analysis & Visualization**"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "jpASMyIQMaAq"
},
"source": [
"## **1.** 📦 Install required packages"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "f48c8f8c",
"outputId": "4aa2e63e-f7ce-4b6b-9fde-5ee3c36f8c3e"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Requirement already satisfied: pandas in /usr/local/lib/python3.12/dist-packages (2.2.2)\n",
"Requirement already satisfied: matplotlib in /usr/local/lib/python3.12/dist-packages (3.10.0)\n",
"Requirement already satisfied: seaborn in /usr/local/lib/python3.12/dist-packages (0.13.2)\n",
"Requirement already satisfied: numpy in /usr/local/lib/python3.12/dist-packages (2.0.2)\n",
"Requirement already satisfied: textblob in /usr/local/lib/python3.12/dist-packages (0.19.0)\n",
"Collecting faker\n",
" Downloading faker-40.5.1-py3-none-any.whl.metadata (16 kB)\n",
"Requirement already satisfied: transformers in /usr/local/lib/python3.12/dist-packages (5.0.0)\n",
"Collecting vaderSentiment\n",
" Downloading vaderSentiment-3.3.2-py2.py3-none-any.whl.metadata (572 bytes)\n",
"Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.12/dist-packages (from pandas) (2.9.0.post0)\n",
"Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.12/dist-packages (from pandas) (2025.2)\n",
"Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.12/dist-packages (from pandas) (2025.3)\n",
"Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (1.3.3)\n",
"Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (0.12.1)\n",
"Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (4.61.1)\n",
"Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (1.4.9)\n",
"Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (26.0)\n",
"Requirement already satisfied: pillow>=8 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (11.3.0)\n",
"Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (3.3.2)\n",
"Requirement already satisfied: nltk>=3.9 in /usr/local/lib/python3.12/dist-packages (from textblob) (3.9.1)\n",
"Requirement already satisfied: filelock in /usr/local/lib/python3.12/dist-packages (from transformers) (3.24.3)\n",
"Requirement already satisfied: huggingface-hub<2.0,>=1.3.0 in /usr/local/lib/python3.12/dist-packages (from transformers) (1.4.1)\n",
"Requirement already satisfied: pyyaml>=5.1 in /usr/local/lib/python3.12/dist-packages (from transformers) (6.0.3)\n",
"Requirement already satisfied: regex!=2019.12.17 in /usr/local/lib/python3.12/dist-packages (from transformers) (2025.11.3)\n",
"Requirement already satisfied: tokenizers<=0.23.0,>=0.22.0 in /usr/local/lib/python3.12/dist-packages (from transformers) (0.22.2)\n",
"Requirement already satisfied: typer-slim in /usr/local/lib/python3.12/dist-packages (from transformers) (0.24.0)\n",
"Requirement already satisfied: safetensors>=0.4.3 in /usr/local/lib/python3.12/dist-packages (from transformers) (0.7.0)\n",
"Requirement already satisfied: tqdm>=4.27 in /usr/local/lib/python3.12/dist-packages (from transformers) (4.67.3)\n",
"Requirement already satisfied: requests in /usr/local/lib/python3.12/dist-packages (from vaderSentiment) (2.32.4)\n",
"Requirement already satisfied: fsspec>=2023.5.0 in /usr/local/lib/python3.12/dist-packages (from huggingface-hub<2.0,>=1.3.0->transformers) (2025.3.0)\n",
"Requirement already satisfied: hf-xet<2.0.0,>=1.2.0 in /usr/local/lib/python3.12/dist-packages (from huggingface-hub<2.0,>=1.3.0->transformers) (1.3.0)\n",
"Requirement already satisfied: httpx<1,>=0.23.0 in /usr/local/lib/python3.12/dist-packages (from huggingface-hub<2.0,>=1.3.0->transformers) (0.28.1)\n",
"Requirement already satisfied: shellingham in /usr/local/lib/python3.12/dist-packages (from huggingface-hub<2.0,>=1.3.0->transformers) (1.5.4)\n",
"Requirement already satisfied: typing-extensions>=4.1.0 in /usr/local/lib/python3.12/dist-packages (from huggingface-hub<2.0,>=1.3.0->transformers) (4.15.0)\n",
"Requirement already satisfied: click in /usr/local/lib/python3.12/dist-packages (from nltk>=3.9->textblob) (8.3.1)\n",
"Requirement already satisfied: joblib in /usr/local/lib/python3.12/dist-packages (from nltk>=3.9->textblob) (1.5.3)\n",
"Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)\n",
"Requirement already satisfied: charset_normalizer<4,>=2 in /usr/local/lib/python3.12/dist-packages (from requests->vaderSentiment) (3.4.4)\n",
"Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.12/dist-packages (from requests->vaderSentiment) (3.11)\n",
"Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.12/dist-packages (from requests->vaderSentiment) (2.5.0)\n",
"Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.12/dist-packages (from requests->vaderSentiment) (2026.1.4)\n",
"Requirement already satisfied: typer>=0.24.0 in /usr/local/lib/python3.12/dist-packages (from typer-slim->transformers) (0.24.1)\n",
"Requirement already satisfied: anyio in /usr/local/lib/python3.12/dist-packages (from httpx<1,>=0.23.0->huggingface-hub<2.0,>=1.3.0->transformers) (4.12.1)\n",
"Requirement already satisfied: httpcore==1.* in /usr/local/lib/python3.12/dist-packages (from httpx<1,>=0.23.0->huggingface-hub<2.0,>=1.3.0->transformers) (1.0.9)\n",
"Requirement already satisfied: h11>=0.16 in /usr/local/lib/python3.12/dist-packages (from httpcore==1.*->httpx<1,>=0.23.0->huggingface-hub<2.0,>=1.3.0->transformers) (0.16.0)\n",
"Requirement already satisfied: rich>=12.3.0 in /usr/local/lib/python3.12/dist-packages (from typer>=0.24.0->typer-slim->transformers) (13.9.4)\n",
"Requirement already satisfied: annotated-doc>=0.0.2 in /usr/local/lib/python3.12/dist-packages (from typer>=0.24.0->typer-slim->transformers) (0.0.4)\n",
"Requirement already satisfied: markdown-it-py>=2.2.0 in /usr/local/lib/python3.12/dist-packages (from rich>=12.3.0->typer>=0.24.0->typer-slim->transformers) (4.0.0)\n",
"Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /usr/local/lib/python3.12/dist-packages (from rich>=12.3.0->typer>=0.24.0->typer-slim->transformers) (2.19.2)\n",
"Requirement already satisfied: mdurl~=0.1 in /usr/local/lib/python3.12/dist-packages (from markdown-it-py>=2.2.0->rich>=12.3.0->typer>=0.24.0->typer-slim->transformers) (0.1.2)\n",
"Downloading faker-40.5.1-py3-none-any.whl (2.0 MB)\n",
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.0/2.0 MB\u001b[0m \u001b[31m47.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
"\u001b[?25hDownloading vaderSentiment-3.3.2-py2.py3-none-any.whl (125 kB)\n",
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m126.0/126.0 kB\u001b[0m \u001b[31m10.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
"\u001b[?25hInstalling collected packages: faker, vaderSentiment\n",
"Successfully installed faker-40.5.1 vaderSentiment-3.3.2\n"
]
}
],
"source": [
"!pip install pandas matplotlib seaborn numpy textblob faker transformers vaderSentiment\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "NZd99NpKkKyp"
},
"source": [
"## **2.** ✅️ Load & inspect input datasets"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "_JBLmm508Uq2"
},
"source": [
"### *a. Initial setup*"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"id": "eBDXPQz18Xrs"
},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"import random"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "IL8lZbMm8m3k"
},
"source": [
"### *b. ✋🏻🛑⛔️ Create the df_reviews dataframe from the synthetic_book_reviews.csv file*"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"id": "fdgjghfO8uuq"
},
"outputs": [],
"source": [
"import pandas as pd\n",
"\n",
"df_reviews = pd.read_csv(\"synthetic_book_reviews.csv\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "N-Dl37J0HLhU"
},
"source": [
"### *c. ✋🏻🛑⛔️ Create the df_sales dataframe from the synthetic_sales_data.csv file*"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"id": "6XZs3P7fHgQe",
"colab": {
"base_uri": "https://localhost:8080/"
},
"outputId": "8e653027-8140-4d4d-9349-a65540ddaba0"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"File caricato correttamente ✅\n"
]
}
],
"source": [
"import os\n",
"import pandas as pd\n",
"\n",
"if \"synthetic_sales_data.csv\" in os.listdir():\n",
" df_sales = pd.read_csv(\"synthetic_sales_data.csv\")\n",
" print(\"File caricato correttamente ✅\")\n",
"else:\n",
" print(\"Il file synthetic_sales_data.csv non esiste ❌\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "MUI3SkmyrGQo"
},
"source": [
"### *d. ✋🏻🛑⛔️ Visualize the first few lines of the two final datasets: df_reviews and df_sales*"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 449
},
"id": "p8FdQFXErOqE",
"outputId": "539e81d6-114e-458e-a24e-8fff57717de0"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"df_reviews:\n"
]
},
{
"output_type": "display_data",
"data": {
"text/plain": [
" title sentiment_label review_text \\\n",
"0 A Light in the Attic neutral It didn’t leave a strong impression. \n",
"1 A Light in the Attic neutral A mild and easy read. \n",
"2 A Light in the Attic neutral The writing style was standard. \n",
"3 A Light in the Attic neutral I found it moderately interesting. \n",
"4 A Light in the Attic neutral A modest and simple read. \n",
"\n",
" rating popularity_score \n",
"0 Three 3 \n",
"1 Three 3 \n",
"2 Three 3 \n",
"3 Three 3 \n",
"4 Three 3 "
],
"text/html": [
"\n",
"
\n",
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" title | \n",
" sentiment_label | \n",
" review_text | \n",
" rating | \n",
" popularity_score | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" A Light in the Attic | \n",
" neutral | \n",
" It didn’t leave a strong impression. | \n",
" Three | \n",
" 3 | \n",
"
\n",
" \n",
" | 1 | \n",
" A Light in the Attic | \n",
" neutral | \n",
" A mild and easy read. | \n",
" Three | \n",
" 3 | \n",
"
\n",
" \n",
" | 2 | \n",
" A Light in the Attic | \n",
" neutral | \n",
" The writing style was standard. | \n",
" Three | \n",
" 3 | \n",
"
\n",
" \n",
" | 3 | \n",
" A Light in the Attic | \n",
" neutral | \n",
" I found it moderately interesting. | \n",
" Three | \n",
" 3 | \n",
"
\n",
" \n",
" | 4 | \n",
" A Light in the Attic | \n",
" neutral | \n",
" A modest and simple read. | \n",
" Three | \n",
" 3 | \n",
"
\n",
" \n",
"
\n",
"
\n",
"
\n",
"
\n"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "dataframe",
"summary": "{\n \"name\": \"display(df_sales\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"A Light in the Attic\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"sentiment_label\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"neutral\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"review_text\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"A mild and easy read.\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"rating\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"Three\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"popularity_score\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0,\n \"min\": 3,\n \"max\": 3,\n \"num_unique_values\": 1,\n \"samples\": [\n 3\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
}
},
"metadata": {}
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"df_sales:\n"
]
},
{
"output_type": "display_data",
"data": {
"text/plain": [
" title month units_sold sentiment_label\n",
"0 A Light in the Attic 2024-09 100 neutral\n",
"1 A Light in the Attic 2024-10 109 neutral\n",
"2 A Light in the Attic 2024-11 102 neutral\n",
"3 A Light in the Attic 2024-12 107 neutral\n",
"4 A Light in the Attic 2025-01 108 neutral"
],
"text/html": [
"\n",
" \n",
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" title | \n",
" month | \n",
" units_sold | \n",
" sentiment_label | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" A Light in the Attic | \n",
" 2024-09 | \n",
" 100 | \n",
" neutral | \n",
"
\n",
" \n",
" | 1 | \n",
" A Light in the Attic | \n",
" 2024-10 | \n",
" 109 | \n",
" neutral | \n",
"
\n",
" \n",
" | 2 | \n",
" A Light in the Attic | \n",
" 2024-11 | \n",
" 102 | \n",
" neutral | \n",
"
\n",
" \n",
" | 3 | \n",
" A Light in the Attic | \n",
" 2024-12 | \n",
" 107 | \n",
" neutral | \n",
"
\n",
" \n",
" | 4 | \n",
" A Light in the Attic | \n",
" 2025-01 | \n",
" 108 | \n",
" neutral | \n",
"
\n",
" \n",
"
\n",
"
\n",
"
\n",
"
\n"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "dataframe",
"summary": "{\n \"name\": \"display(df_sales\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"A Light in the Attic\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"month\",\n \"properties\": {\n \"dtype\": \"object\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"2024-10\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"units_sold\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3,\n \"min\": 100,\n \"max\": 109,\n \"num_unique_values\": 5,\n \"samples\": [\n 109\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"sentiment_label\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"neutral\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
}
},
"metadata": {}
}
],
"source": [
"print(\"df_reviews:\")\n",
"display(df_reviews.head())\n",
"\n",
"print(\"\\ndf_sales:\")\n",
"display(df_sales.head())"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Y3oqGHsmrQzx"
},
"source": [
"### *d. Run a quality check on the datasets*"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 1000
},
"id": "VArQGPoKrfLm",
"outputId": "d0b2b3ca-15ef-4d04-ae17-4e860d11d2cc"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"🔍 Quality Check Report for: df_reviews\n",
"===================================\n",
"\n",
"📏 Shape: (10000, 5)\n",
"\n",
"🔠 Column Types:\n",
"title object\n",
"sentiment_label object\n",
"review_text object\n",
"rating object\n",
"popularity_score int64\n",
"dtype: object\n",
"\n",
"❓ Missing Values:\n",
"title 0\n",
"sentiment_label 0\n",
"review_text 0\n",
"rating 0\n",
"popularity_score 0\n",
"dtype: int64\n",
"\n",
"📋 Duplicate Rows: 0\n",
"\n",
"📊 Summary Statistics:\n"
]
},
{
"output_type": "display_data",
"data": {
"text/plain": [
" count unique top freq \\\n",
"title 10000 999 The Star-Touched Queen 20 \n",
"sentiment_label 10000 3 positive 4380 \n",
"review_text 10000 150 A wonderfully constructed narrative. 104 \n",
"rating 10000 5 One 2260 \n",
"popularity_score 10000.0 NaN NaN NaN \n",
"\n",
" mean std min 25% 50% 75% max \n",
"title NaN NaN NaN NaN NaN NaN NaN \n",
"sentiment_label NaN NaN NaN NaN NaN NaN NaN \n",
"review_text NaN NaN NaN NaN NaN NaN NaN \n",
"rating NaN NaN NaN NaN NaN NaN NaN \n",
"popularity_score 3.282 1.028874 1.0 3.0 3.0 4.0 5.0 "
],
"text/html": [
"\n",
" \n",
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" count | \n",
" unique | \n",
" top | \n",
" freq | \n",
" mean | \n",
" std | \n",
" min | \n",
" 25% | \n",
" 50% | \n",
" 75% | \n",
" max | \n",
"
\n",
" \n",
" \n",
" \n",
" | title | \n",
" 10000 | \n",
" 999 | \n",
" The Star-Touched Queen | \n",
" 20 | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
"
\n",
" \n",
" | sentiment_label | \n",
" 10000 | \n",
" 3 | \n",
" positive | \n",
" 4380 | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
"
\n",
" \n",
" | review_text | \n",
" 10000 | \n",
" 150 | \n",
" A wonderfully constructed narrative. | \n",
" 104 | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
"
\n",
" \n",
" | rating | \n",
" 10000 | \n",
" 5 | \n",
" One | \n",
" 2260 | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
"
\n",
" \n",
" | popularity_score | \n",
" 10000.0 | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" 3.282 | \n",
" 1.028874 | \n",
" 1.0 | \n",
" 3.0 | \n",
" 3.0 | \n",
" 4.0 | \n",
" 5.0 | \n",
"
\n",
" \n",
"
\n",
"
\n",
"
\n",
"
\n"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "dataframe",
"summary": "{\n \"name\": \"quality_check(df_sales, \\\"df_sales\\\")\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"count\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"10000\",\n \"max\": \"10000\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"10000\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"unique\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 3,\n \"max\": 999,\n \"num_unique_values\": 4,\n \"samples\": [\n 3\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"top\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 4,\n \"samples\": [\n \"positive\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"freq\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"20\",\n \"max\": \"4380\",\n \"num_unique_values\": 4,\n \"samples\": [\n \"4380\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"mean\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 3.282,\n \"max\": 3.282,\n \"num_unique_values\": 1,\n \"samples\": [\n 3.282\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"std\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 1.0288740730458223,\n \"max\": 1.0288740730458223,\n \"num_unique_values\": 1,\n \"samples\": [\n 1.0288740730458223\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"min\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 1.0,\n \"max\": 1.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 1.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"25%\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 3.0,\n \"max\": 3.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 3.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"50%\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 3.0,\n \"max\": 3.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 3.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"75%\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 4.0,\n \"max\": 4.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 4.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"max\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 5.0,\n \"max\": 5.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 5.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
}
},
"metadata": {}
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"👀 Sample Rows:\n"
]
},
{
"output_type": "display_data",
"data": {
"text/plain": [
" title sentiment_label \\\n",
"9933 Bleach, Vol. 1: Strawberry and the Soul Reaper... positive \n",
"3098 El Deafo positive \n",
"6703 It neutral \n",
"7036 Vegan Vegetarian Omnivore: Dinner for Everyone... neutral \n",
"621 The Most Perfect Thing: Inside (and Outside) a... positive \n",
"\n",
" review_text rating popularity_score \n",
"9933 A masterfully written story. Five 4 \n",
"3098 A captivating and heartfelt read. Five 5 \n",
"6703 An average reading experience. Three 3 \n",
"7036 An adequate storytelling effort. Two 3 \n",
"621 A refreshing and engaging perspective. Four 4 "
],
"text/html": [
"\n",
" \n",
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" title | \n",
" sentiment_label | \n",
" review_text | \n",
" rating | \n",
" popularity_score | \n",
"
\n",
" \n",
" \n",
" \n",
" | 9933 | \n",
" Bleach, Vol. 1: Strawberry and the Soul Reaper... | \n",
" positive | \n",
" A masterfully written story. | \n",
" Five | \n",
" 4 | \n",
"
\n",
" \n",
" | 3098 | \n",
" El Deafo | \n",
" positive | \n",
" A captivating and heartfelt read. | \n",
" Five | \n",
" 5 | \n",
"
\n",
" \n",
" | 6703 | \n",
" It | \n",
" neutral | \n",
" An average reading experience. | \n",
" Three | \n",
" 3 | \n",
"
\n",
" \n",
" | 7036 | \n",
" Vegan Vegetarian Omnivore: Dinner for Everyone... | \n",
" neutral | \n",
" An adequate storytelling effort. | \n",
" Two | \n",
" 3 | \n",
"
\n",
" \n",
" | 621 | \n",
" The Most Perfect Thing: Inside (and Outside) a... | \n",
" positive | \n",
" A refreshing and engaging perspective. | \n",
" Four | \n",
" 4 | \n",
"
\n",
" \n",
"
\n",
"
\n",
"
\n",
"
\n"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "dataframe",
"summary": "{\n \"name\": \"quality_check(df_sales, \\\"df_sales\\\")\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"El Deafo\",\n \"The Most Perfect Thing: Inside (and Outside) a Bird's Egg\",\n \"It\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"sentiment_label\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"neutral\",\n \"positive\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"review_text\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"A captivating and heartfelt read.\",\n \"A refreshing and engaging perspective.\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"rating\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 4,\n \"samples\": [\n \"Three\",\n \"Four\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"popularity_score\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0,\n \"min\": 3,\n \"max\": 5,\n \"num_unique_values\": 3,\n \"samples\": [\n 4,\n 5\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
}
},
"metadata": {}
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"🔍 Quality Check Report for: df_sales\n",
"=================================\n",
"\n",
"📏 Shape: (18000, 4)\n",
"\n",
"🔠 Column Types:\n",
"title object\n",
"month object\n",
"units_sold int64\n",
"sentiment_label object\n",
"dtype: object\n",
"\n",
"❓ Missing Values:\n",
"title 0\n",
"month 0\n",
"units_sold 0\n",
"sentiment_label 0\n",
"dtype: int64\n",
"\n",
"📋 Duplicate Rows: 0\n",
"\n",
"📊 Summary Statistics:\n"
]
},
{
"output_type": "display_data",
"data": {
"text/plain": [
" count unique top freq mean \\\n",
"title 18000 999 The Star-Touched Queen 36 NaN \n",
"month 18000 18 2024-09 1000 NaN \n",
"units_sold 18000.0 NaN NaN NaN 168.024167 \n",
"sentiment_label 18000 3 positive 7884 NaN \n",
"\n",
" std min 25% 50% 75% max \n",
"title NaN NaN NaN NaN NaN NaN \n",
"month NaN NaN NaN NaN NaN NaN \n",
"units_sold 98.656354 0.0 84.0 148.0 262.0 362.0 \n",
"sentiment_label NaN NaN NaN NaN NaN NaN "
],
"text/html": [
"\n",
" \n",
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" count | \n",
" unique | \n",
" top | \n",
" freq | \n",
" mean | \n",
" std | \n",
" min | \n",
" 25% | \n",
" 50% | \n",
" 75% | \n",
" max | \n",
"
\n",
" \n",
" \n",
" \n",
" | title | \n",
" 18000 | \n",
" 999 | \n",
" The Star-Touched Queen | \n",
" 36 | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
"
\n",
" \n",
" | month | \n",
" 18000 | \n",
" 18 | \n",
" 2024-09 | \n",
" 1000 | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
"
\n",
" \n",
" | units_sold | \n",
" 18000.0 | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" 168.024167 | \n",
" 98.656354 | \n",
" 0.0 | \n",
" 84.0 | \n",
" 148.0 | \n",
" 262.0 | \n",
" 362.0 | \n",
"
\n",
" \n",
" | sentiment_label | \n",
" 18000 | \n",
" 3 | \n",
" positive | \n",
" 7884 | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
" NaN | \n",
"
\n",
" \n",
"
\n",
"
\n",
"
\n",
"
\n"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "dataframe",
"summary": "{\n \"name\": \"quality_check(df_sales, \\\"df_sales\\\")\",\n \"rows\": 4,\n \"fields\": [\n {\n \"column\": \"count\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"18000\",\n \"max\": \"18000\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"18000\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"unique\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 3,\n \"max\": 999,\n \"num_unique_values\": 3,\n \"samples\": [\n 999\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"top\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"The Star-Touched Queen\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"freq\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": \"36\",\n \"max\": \"7884\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"36\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"mean\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 168.02416666666667,\n \"max\": 168.02416666666667,\n \"num_unique_values\": 1,\n \"samples\": [\n 168.02416666666667\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"std\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 98.65635350480927,\n \"max\": 98.65635350480927,\n \"num_unique_values\": 1,\n \"samples\": [\n 98.65635350480927\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"min\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 0.0,\n \"max\": 0.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 0.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"25%\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 84.0,\n \"max\": 84.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 84.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"50%\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 148.0,\n \"max\": 148.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 148.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"75%\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 262.0,\n \"max\": 262.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 262.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"max\",\n \"properties\": {\n \"dtype\": \"date\",\n \"min\": 362.0,\n \"max\": 362.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 362.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
}
},
"metadata": {}
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"👀 Sample Rows:\n"
]
},
{
"output_type": "display_data",
"data": {
"text/plain": [
" title month units_sold \\\n",
"7751 The Art and Science of Low Carbohydrate Living 2025-08 289 \n",
"4780 Someone Like You (The Harrisons #2) 2025-07 315 \n",
"602 The Bear and the Piano 2025-05 27 \n",
"13477 The Goldfinch 2025-10 39 \n",
"6612 The Children 2025-03 94 \n",
"\n",
" sentiment_label \n",
"7751 positive \n",
"4780 positive \n",
"602 negative \n",
"13477 negative \n",
"6612 neutral "
],
"text/html": [
"\n",
" \n",
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" title | \n",
" month | \n",
" units_sold | \n",
" sentiment_label | \n",
"
\n",
" \n",
" \n",
" \n",
" | 7751 | \n",
" The Art and Science of Low Carbohydrate Living | \n",
" 2025-08 | \n",
" 289 | \n",
" positive | \n",
"
\n",
" \n",
" | 4780 | \n",
" Someone Like You (The Harrisons #2) | \n",
" 2025-07 | \n",
" 315 | \n",
" positive | \n",
"
\n",
" \n",
" | 602 | \n",
" The Bear and the Piano | \n",
" 2025-05 | \n",
" 27 | \n",
" negative | \n",
"
\n",
" \n",
" | 13477 | \n",
" The Goldfinch | \n",
" 2025-10 | \n",
" 39 | \n",
" negative | \n",
"
\n",
" \n",
" | 6612 | \n",
" The Children | \n",
" 2025-03 | \n",
" 94 | \n",
" neutral | \n",
"
\n",
" \n",
"
\n",
"
\n",
"
\n",
"
\n"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "dataframe",
"repr_error": "0"
}
},
"metadata": {}
}
],
"source": [
"def quality_check(df, name=\"DataFrame\"):\n",
" print(f\"\\n🔍 Quality Check Report for: {name}\")\n",
" print(\"=\" * (25 + len(name)))\n",
"\n",
" # Basic info\n",
" print(f\"\\n📏 Shape: {df.shape}\")\n",
" print(\"\\n🔠 Column Types:\")\n",
" print(df.dtypes)\n",
"\n",
" # Missing values\n",
" print(\"\\n❓ Missing Values:\")\n",
" print(df.isnull().sum())\n",
"\n",
" # Duplicates\n",
" duplicate_count = df.duplicated().sum()\n",
" print(f\"\\n📋 Duplicate Rows: {duplicate_count}\")\n",
"\n",
" # Summary stats\n",
" print(\"\\n📊 Summary Statistics:\")\n",
" display(df.describe(include='all').transpose())\n",
"\n",
" # Sample rows\n",
" print(\"\\n👀 Sample Rows:\")\n",
" display(df.sample(5))\n",
"\n",
"# Run checks\n",
"quality_check(df_reviews, \"df_reviews\")\n",
"quality_check(df_sales, \"df_sales\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "TTxUKDYINPxV"
},
"source": [
"## **3.** 🎭 Perform sentiment analysis using VADER"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "OqhYU8rDxQRT"
},
"source": [
"### *a. Initial setup*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "DNk5w8mNxSZ6"
},
"outputs": [],
"source": [
"from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer\n",
"\n",
"# 🤖 Initialize VADER analyzer\n",
"analyzer = SentimentIntensityAnalyzer()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "P123TwSWxVAr"
},
"source": [
"### *b. Create a function get_sentiment_label that will return the label negative, neutral, or positive based on the VADER analyzer's scoring of the text*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "89809e6f"
},
"outputs": [],
"source": [
"def get_sentiment_label(text):\n",
" score = analyzer.polarity_scores(text)[\"compound\"]\n",
" if score >= 0.05:\n",
" return \"positive\"\n",
" elif score <= -0.05:\n",
" return \"negative\"\n",
" else:\n",
" return \"neutral\""
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "DS9eCZ95yQn3"
},
"source": [
"### *c. ✋🏻🛑⛔️ Apply get_sentiment_label to df_reviews column named review_text to get sentiment_label column*"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"id": "SpXzFaDfyM7I",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 206
},
"outputId": "bf1b14f6-f3d5-44c8-b92b-15865bb89cad"
},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" title sentiment_label review_text \\\n",
"0 A Light in the Attic neutral It didn’t leave a strong impression. \n",
"1 A Light in the Attic neutral A mild and easy read. \n",
"2 A Light in the Attic neutral The writing style was standard. \n",
"3 A Light in the Attic neutral I found it moderately interesting. \n",
"4 A Light in the Attic neutral A modest and simple read. \n",
"\n",
" rating popularity_score vader_sentiment \n",
"0 Three 3 positive \n",
"1 Three 3 positive \n",
"2 Three 3 neutral \n",
"3 Three 3 positive \n",
"4 Three 3 neutral "
],
"text/html": [
"\n",
" \n",
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" title | \n",
" sentiment_label | \n",
" review_text | \n",
" rating | \n",
" popularity_score | \n",
" vader_sentiment | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" A Light in the Attic | \n",
" neutral | \n",
" It didn’t leave a strong impression. | \n",
" Three | \n",
" 3 | \n",
" positive | \n",
"
\n",
" \n",
" | 1 | \n",
" A Light in the Attic | \n",
" neutral | \n",
" A mild and easy read. | \n",
" Three | \n",
" 3 | \n",
" positive | \n",
"
\n",
" \n",
" | 2 | \n",
" A Light in the Attic | \n",
" neutral | \n",
" The writing style was standard. | \n",
" Three | \n",
" 3 | \n",
" neutral | \n",
"
\n",
" \n",
" | 3 | \n",
" A Light in the Attic | \n",
" neutral | \n",
" I found it moderately interesting. | \n",
" Three | \n",
" 3 | \n",
" positive | \n",
"
\n",
" \n",
" | 4 | \n",
" A Light in the Attic | \n",
" neutral | \n",
" A modest and simple read. | \n",
" Three | \n",
" 3 | \n",
" neutral | \n",
"
\n",
" \n",
"
\n",
"
\n",
"
\n",
"
\n"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "dataframe",
"variable_name": "df_reviews",
"summary": "{\n \"name\": \"df_reviews\",\n \"rows\": 10000,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 999,\n \"samples\": [\n \"The Grownup\",\n \"Persepolis: The Story of a Childhood (Persepolis #1-2)\",\n \"Ayumi's Violin\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"sentiment_label\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"neutral\",\n \"negative\",\n \"positive\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"review_text\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 150,\n \"samples\": [\n \"A brilliant blend of emotion and storytelling.\",\n \"The ending was unsatisfying.\",\n \"The dialogue felt natural and authentic.\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"rating\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"One\",\n \"Two\",\n \"Four\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"popularity_score\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1,\n \"min\": 1,\n \"max\": 5,\n \"num_unique_values\": 5,\n \"samples\": [\n 2,\n 5,\n 4\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vader_sentiment\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"positive\",\n \"neutral\",\n \"negative\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
}
},
"metadata": {},
"execution_count": 16
}
],
"source": [
"from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer\n",
"\n",
"# Initialize VADER\n",
"analyzer = SentimentIntensityAnalyzer()\n",
"\n",
"# Define function\n",
"def get_sentiment_label(text):\n",
" score = analyzer.polarity_scores(text)[\"compound\"]\n",
"\n",
" if score >= 0.05:\n",
" return \"positive\"\n",
" elif score <= -0.05:\n",
" return \"negative\"\n",
" else:\n",
" return \"neutral\"\n",
"\n",
"# Apply VADER to review_text\n",
"df_reviews[\"vader_sentiment\"] = df_reviews[\"review_text\"].apply(get_sentiment_label)\n",
"\n",
"# View first rows\n",
"df_reviews.head()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "5cnPCFFnyXN6"
},
"source": [
"### *d. ✋🏻🛑⛔️ View the first few lines of the resulting table df_reviews*"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 206
},
"id": "ODGyfjBSyZEO",
"outputId": "437e991c-5343-4892-ae45-1e72f6dde135"
},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" review_text sentiment_label vader_sentiment\n",
"0 It didn’t leave a strong impression. neutral positive\n",
"1 A mild and easy read. neutral positive\n",
"2 The writing style was standard. neutral neutral\n",
"3 I found it moderately interesting. neutral positive\n",
"4 A modest and simple read. neutral neutral"
],
"text/html": [
"\n",
" \n",
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" review_text | \n",
" sentiment_label | \n",
" vader_sentiment | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" It didn’t leave a strong impression. | \n",
" neutral | \n",
" positive | \n",
"
\n",
" \n",
" | 1 | \n",
" A mild and easy read. | \n",
" neutral | \n",
" positive | \n",
"
\n",
" \n",
" | 2 | \n",
" The writing style was standard. | \n",
" neutral | \n",
" neutral | \n",
"
\n",
" \n",
" | 3 | \n",
" I found it moderately interesting. | \n",
" neutral | \n",
" positive | \n",
"
\n",
" \n",
" | 4 | \n",
" A modest and simple read. | \n",
" neutral | \n",
" neutral | \n",
"
\n",
" \n",
"
\n",
"
\n",
"
\n",
"
\n"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "dataframe",
"summary": "{\n \"name\": \"df_reviews[[\\\"review_text\\\", \\\"sentiment_label\\\", \\\"vader_sentiment\\\"]]\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"review_text\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"A mild and easy read.\",\n \"A modest and simple read.\",\n \"The writing style was standard.\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"sentiment_label\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 1,\n \"samples\": [\n \"neutral\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"vader_sentiment\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"neutral\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}"
}
},
"metadata": {},
"execution_count": 17
}
],
"source": [
"df_reviews[[\"review_text\", \"sentiment_label\", \"vader_sentiment\"]].head()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Qy3Hqm-FojvT"
},
"source": [
"## **4.** 📊 Use the following data visualization code snippets"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "lcjGSw2bzqtZ"
},
"source": [
"### *a. Initial setup*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "p5LV2o1rzsiC"
},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"import matplotlib.dates as mdates"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "tvaBtswpGS__"
},
"outputs": [],
"source": [
"# ----------------------------\n",
"# Outputs (for Hugging Face app)\n",
"# ----------------------------\n",
"# In the notebook: you still SEE interactive tables/plots inline.\n",
"# For the Space dashboard: we also SAVE the same outputs as files.\n",
"\n",
"from pathlib import Path\n",
"\n",
"ART_DIR = Path(\"artifacts\")\n",
"PY_FIG = ART_DIR / \"py\" / \"figures\"\n",
"PY_TAB = ART_DIR / \"py\" / \"tables\"\n",
"\n",
"for p in [PY_FIG, PY_TAB]:\n",
" p.mkdir(parents=True, exist_ok=True)\n",
"\n",
"print(\"✅ Output folders:\")\n",
"print(\" -\", PY_FIG.resolve())\n",
"print(\" -\", PY_TAB.resolve())\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "b9T1rkBe0AJU"
},
"source": [
"### *b. Sample of 5 books for each popularity level for visualizations*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "sLdFmGqXqo_t"
},
"outputs": [],
"source": [
"sampled_titles = []\n",
"for pop_score in sorted(df_reviews[\"popularity_score\"].dropna().unique()):\n",
" all_titles = df_reviews[df_reviews[\"popularity_score\"] == pop_score][\"title\"].unique()\n",
" sampled = random.sample(list(all_titles), min(5, len(all_titles)))\n",
" sampled_titles.extend(sampled)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "xq7-C8m70mMH"
},
"source": [
"### *c. Copy relevant sales, reviews, and book names*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "laDdMece0qrq"
},
"outputs": [],
"source": [
"sampled_sales = df_sales[df_sales[\"title\"].isin(sampled_titles)].copy()\n",
"sampled_reviews = df_reviews[df_reviews[\"title\"].isin(sampled_titles)].copy()\n",
"sampled_books = df_reviews[df_reviews[\"title\"].isin(sampled_titles)].copy()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "8YtfkG_A0wTy"
},
"source": [
"### *d. Plot sales trends over time for the sampled books*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "1iTVzflW0Rkw"
},
"outputs": [],
"source": [
"# 🕒 Ensure datetime format\n",
"df_sales[\"month\"] = pd.to_datetime(df_sales[\"month\"])\n",
"# 🎨 Color mapping\n",
"popularity_colors = {\n",
" 1: \"darkred\", 2: \"orangered\", 3: \"gold\", 4: \"mediumseagreen\", 5: \"royalblue\"\n",
"}\n",
"\n",
"# 📈 Plot 1: Sales trends\n",
"plt.figure(figsize=(20, 8))\n",
"for title in sampled_titles:\n",
" row = sampled_books[sampled_books[\"title\"] == title].iloc[0]\n",
" color = popularity_colors.get(row[\"popularity_score\"], \"gray\")\n",
" subset = sampled_sales[sampled_sales[\"title\"] == title]\n",
" plt.plot(subset[\"month\"], subset[\"units_sold\"], label=f\"{title} (Pop. {row['popularity_score']})\", color=color)\n",
"\n",
"plt.title(\"📈 Sales Trends Over Time (5 per Popularity Level)\")\n",
"plt.xlabel(\"Month\")\n",
"plt.ylabel(\"Units Sold\")\n",
"plt.xticks(rotation=45)\n",
"plt.legend(loc='center left', bbox_to_anchor=(1, 0.5), fontsize='small')\n",
"plt.grid(True)\n",
"plt.tight_layout()\n",
"plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n",
"plt.savefig(PY_FIG / 'sales_trends_sampled_titles.png', dpi=150)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "lDpMkjDP1K6j"
},
"source": [
"### *e. Plot sentiment_label distribution per book*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "dn1Jgd5R1KLu"
},
"outputs": [],
"source": [
"# 🎨 Give a new name to each book that includes the rating together with the title\n",
"sampled_reviews[\"grouped_title\"] = sampled_reviews[\"rating\"].astype(str) + \"★ | \" + sampled_reviews[\"title\"]\n",
"\n",
"# 📊 Aggregate sentiment counts\n",
"sentiment_counts = (\n",
" sampled_reviews.groupby([\"grouped_title\", \"sentiment_label\"])\n",
" .size()\n",
" .unstack(fill_value=0)[[\"negative\", \"neutral\", \"positive\"]] # consistent order\n",
")\n",
"\n",
"# 💾 Save table for HF dashboard\n",
"sentiment_counts.reset_index().to_csv(PY_TAB / 'sentiment_counts_sampled.csv', index=False)\n",
"\n",
"\n",
"# ✅ Plot stacked horizontal bars\n",
"fig, ax = plt.subplots(figsize=(12, 14))\n",
"sentiment_counts.plot.barh(\n",
" stacked=True,\n",
" ax=ax,\n",
" color={\"negative\": \"royalblue\", \"neutral\": \"lightgray\", \"positive\": \"crimson\"}\n",
")\n",
"\n",
"plt.title(\"💬 Sentiment Distribution in Reviews (5 Books per Popularity Level)\", fontsize=14)\n",
"plt.xlabel(\"Number of Reviews\")\n",
"plt.ylabel(\"Book Title (Grouped by Popularity Score)\")\n",
"plt.legend(title=\"Sentiment\", loc=\"lower right\")\n",
"plt.grid(axis=\"x\", linestyle=\"--\", alpha=0.6)\n",
"plt.tight_layout()\n",
"plt.savefig(PY_FIG / 'sentiment_distribution_sampled_titles.png', dpi=150)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "rmgylC1ENCHy"
},
"source": [
"## **5.** 🔮 Forecast book sales with the following ARIMA code"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "jFV4JE1R3FKH"
},
"source": [
"### *a. Initial setup*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "Mh8Alha03H22"
},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"import matplotlib.dates as mdates\n",
"import statsmodels.api as sm\n",
"from itertools import product\n",
"import matplotlib.cm as cm\n",
"import warnings"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "gHucD8OW3U0w"
},
"source": [
"### *b. Define function find_best_arima to try different ARIMA parameter values and return the best combination for each book's price forecast*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "477fa43f"
},
"outputs": [],
"source": [
"def find_best_arima(series, p_range=(0, 5), d_range=(0, 2), q_range=(0, 1)):\n",
" best_aic = float(\"inf\")\n",
" best_order = None\n",
" best_model = None\n",
"\n",
" for p, d, q in product(range(p_range[0], p_range[1] + 1),\n",
" range(d_range[0], d_range[1] + 1),\n",
" range(q_range[0], q_range[1] + 1)):\n",
" try:\n",
" model = sm.tsa.ARIMA(series, order=(p, d, q))\n",
" results = model.fit()\n",
" if results.aic < best_aic:\n",
" best_aic = results.aic\n",
" best_order = (p, d, q)\n",
" best_model = results\n",
" except:\n",
" continue\n",
"\n",
" return best_order, best_model"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Rq5t1Hey3jkD"
},
"source": [
"### *c. Plot the figure*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "DmxGdvLE3dHQ"
},
"outputs": [],
"source": [
"# 🎨 Generate 25 highly distinct colors using HUSL (hue-saturation-lightness)\n",
"colors = sns.color_palette(\"tab10\", len(sampled_titles))\n",
"\n",
"plt.figure(figsize=(16, 10))\n",
"\n",
"for i, title in enumerate(sampled_titles):\n",
" book_sales = sampled_sales[sampled_sales[\"title\"] == title].copy()\n",
" book_sales[\"month\"] = pd.to_datetime(book_sales[\"month\"])\n",
" book_sales = book_sales.sort_values(\"month\").set_index(\"month\")\n",
"\n",
" with warnings.catch_warnings():\n",
" warnings.simplefilter(\"ignore\")\n",
" best_order, best_model = find_best_arima(book_sales[\"units_sold\"])\n",
" if best_model is not None:\n",
" forecast = best_model.get_forecast(steps=6)\n",
" forecast_index = pd.date_range(start=book_sales.index[-1] + pd.DateOffset(months=1), periods=6, freq='MS')\n",
"\n",
" # 🟦 Plot observed sales (solid line)\n",
" plt.plot(book_sales.index, book_sales[\"units_sold\"], color=colors[i], label=title, linewidth=2)\n",
"\n",
" # 🟠 Plot forecast (dotted line, same color)\n",
" plt.plot(forecast_index, forecast.predicted_mean, linestyle=\"--\", color=colors[i], linewidth=2)\n",
"\n",
"# 📈 Final formatting\n",
"plt.title(\"📈 ARIMA Forecasts for Sampled Books (1 per Popularity Level)\", fontsize=14)\n",
"plt.xlabel(\"Month\")\n",
"plt.ylabel(\"Units Sold\")\n",
"plt.xticks(rotation=45)\n",
"plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n",
"plt.grid(True)\n",
"plt.legend(loc=\"center left\", bbox_to_anchor=(1, 0.5), fontsize=\"small\")\n",
"plt.tight_layout()\n",
"plt.savefig(PY_FIG / 'arima_forecasts_sampled_titles.png', dpi=150)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "SKBcx3fyCFly"
},
"source": [
"## **6.** 🏷️ Decide on price changes with a rule-based approach based on sentiment and future revenue"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "nY-vV2JJDZqu"
},
"source": [
"### *a. Calculate average sales per book*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "nbDT_RHaDD2R"
},
"outputs": [],
"source": [
"avg_sales = df_sales.groupby(\"title\")[\"units_sold\"].mean().reset_index()\n",
"avg_sales.columns = [\"title\", \"avg_units_sold\"]"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "94wi-RvkDf2z"
},
"source": [
"### *b. Calculate sentiment distribution per book*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "fWjQ9IOXDk-M"
},
"outputs": [],
"source": [
"sentiment_counts = df_reviews.groupby([\"title\", \"sentiment_label\"]).size().unstack(fill_value=0)\n",
"sentiment_counts[\"total\"] = sentiment_counts.sum(axis=1)\n",
"sentiment_counts[\"positive_ratio\"] = sentiment_counts.get(\"positive\") / sentiment_counts[\"total\"]\n",
"sentiment_counts[\"negative_ratio\"] = sentiment_counts.get(\"negative\") / sentiment_counts[\"total\"]"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Vm10ym_iDtEW"
},
"source": [
"### *c. Merge the calculated sales and sentiment characteristics*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "T-zlh6rBDpxg"
},
"outputs": [],
"source": [
"df_decision = avg_sales.merge(sentiment_counts, on=\"title\", how=\"left\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "1WIWDojyD7fK"
},
"source": [
"### *d. ✋🏻🛑⛔️ Create the pricing_decision function as a basic rule-based pricing decider based on sentiment and revenue*\n",
"\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "b5qJCb46Dxfb"
},
"source": [
"* If there are 120 or more average units sold and 0.6 or higher positive ratio, the decision should be to increase price.\n",
"* If there are 60 or less average units sold and 0.4 or higher negative ratio, the decision should be to decrease price.\n",
"* Otherwise, the price should be kept the same."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "XBzozedwD6yx"
},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "xmLEdF14EPAA"
},
"source": [
"### *e. ✋🏻🛑⛔️ Run the pricing_decision function and check out the first few decisions*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "TZ0ZhgHrEQJB"
},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "WTkP2_-EApev"
},
"source": [
"\n",
"## **7.** 💾 Save Python outputs for the Hugging Face dashboard"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "3EIjfnokGpJv"
},
"source": [
"\n",
"This section exports **HF-ready artifacts** into a consistent folder structure:\n",
"\n",
"- `(root folder)py/figures/` (Python-generated visuals)\n",
"- `(root folder)py/tables/` (tables/metrics)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "ZJJ4PMgIApev"
},
"outputs": [],
"source": [
"\n",
"import json\n",
"\n",
"# -------------------------\n",
"# 1) Dashboard table (monthly) — reuse if already built\n",
"# -------------------------\n",
"if \"df_monthly\" in globals() and df_monthly is not None:\n",
" df_dashboard = df_monthly.copy()\n",
"else:\n",
" # fallback: monthly units sold only\n",
" df_dashboard = (\n",
" df_sales.groupby(\"month\", as_index=False)\n",
" .agg(total_units_sold=(\"units_sold\", \"sum\"))\n",
" .sort_values(\"month\")\n",
" )\n",
"\n",
"# Save the single overview dashboard table\n",
"df_dashboard.to_csv(PY_TAB / \"df_dashboard.csv\", index=False)\n",
"\n",
"# -------------------------\n",
"# 2) KPI summary (small json) — computed from raw df_sales + df_dashboard\n",
"# -------------------------\n",
"kpis = {\n",
" \"n_titles\": int(df_sales[\"title\"].nunique()),\n",
" \"n_months\": int(df_dashboard[\"month\"].nunique()),\n",
" \"total_units_sold\": float(df_sales[\"units_sold\"].sum()),\n",
"}\n",
"\n",
"# Only include revenue KPIs if df_dashboard contains it (since you said monthly revenue already exists)\n",
"if \"total_revenue\" in df_dashboard.columns and df_dashboard[\"total_revenue\"].notna().any():\n",
" kpis[\"total_revenue\"] = float(df_dashboard[\"total_revenue\"].sum())\n",
"\n",
"with open(PY_FIG / \"kpis.json\", \"w\", encoding=\"utf-8\") as f:\n",
" json.dump(kpis, f, indent=2)\n",
"\n",
"# -------------------------\n",
"# 3) Python tables (title-level quick inspection)\n",
"# -------------------------\n",
"df_by_title_units = (\n",
" df_sales.groupby(\"title\", as_index=False)\n",
" .agg(total_units_sold=(\"units_sold\", \"sum\"))\n",
" .sort_values(\"total_units_sold\", ascending=False)\n",
")\n",
"df_by_title_units.head(10).to_csv(PY_TAB / \"top_titles_by_units_sold.csv\", index=False)\n",
"\n",
"# Optional: title-level revenue table ONLY if df_sales already has per-row revenue\n",
"if \"revenue\" in df_sales.columns and df_sales[\"revenue\"].notna().any():\n",
" df_by_title_rev = (\n",
" df_sales.groupby(\"title\", as_index=False)\n",
" .agg(total_revenue=(\"revenue\", \"sum\"))\n",
" .sort_values(\"total_revenue\", ascending=False)\n",
" )\n",
" df_by_title_rev.head(10).to_csv(PY_TAB / \"top_titles_by_revenue.csv\", index=False)\n",
"\n",
"print(\"✅ Exports written to artifacts/:\")\n",
"print(\" - common/: df_dashboard.csv, kpis.json\")\n",
"print(\" - py/tables/: top_titles_by_units_sold.csv (+ optional top_titles_by_revenue.csv)\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "0b4e76d3"
},
"source": [
"✅ **Extra outputs for the R notebook**: `(root folder)common/r_input_title_level.csv` and `(root folder)common/r_input_monthly_revenue.csv` (these are the only two files the R portion needs)."
]
}
],
"metadata": {
"colab": {
"collapsed_sections": [
"jpASMyIQMaAq",
"NZd99NpKkKyp",
"TTxUKDYINPxV",
"Qy3Hqm-FojvT",
"rmgylC1ENCHy",
"SKBcx3fyCFly",
"WTkP2_-EApev"
],
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}