{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "4ba6aba8"
},
"source": [
"# ๐ค **Data Collection, Creation, Storage, and Processing**\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "jpASMyIQMaAq"
},
"source": [
"## **1.** ๐ฆ Install required packages"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "f48c8f8c",
"outputId": "b374af14-0adf-4e0b-82a5-6781cd0019e0"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Requirement already satisfied: beautifulsoup4 in /usr/local/lib/python3.12/dist-packages (4.13.5)\n",
"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",
"Requirement already satisfied: soupsieve>1.2 in /usr/local/lib/python3.12/dist-packages (from beautifulsoup4) (2.8.3)\n",
"Requirement already satisfied: typing-extensions>=4.0.0 in /usr/local/lib/python3.12/dist-packages (from beautifulsoup4) (4.15.0)\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: 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: regex>=2021.8.3 in /usr/local/lib/python3.12/dist-packages (from nltk>=3.9->textblob) (2025.11.3)\n",
"Requirement already satisfied: tqdm in /usr/local/lib/python3.12/dist-packages (from nltk>=3.9->textblob) (4.67.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"
]
}
],
"source": [
"!pip install beautifulsoup4 pandas matplotlib seaborn numpy textblob"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "lquNYCbfL9IM"
},
"source": [
"## **2.** โ Web-scrape all book titles, prices, and ratings from books.toscrape.com"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "0IWuNpxxYDJF"
},
"source": [
"### *a. Initial setup*\n",
"Define the base url of the website you will scrape as well as how and what you will scrape"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "91d52125"
},
"outputs": [],
"source": [
"import requests\n",
"from bs4 import BeautifulSoup\n",
"import pandas as pd\n",
"import time\n",
"\n",
"base_url = \"https://books.toscrape.com/catalogue/page-{}.html\"\n",
"headers = {\"User-Agent\": \"Mozilla/5.0\"}\n",
"\n",
"titles, prices, ratings = [], [], []"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "oCdTsin2Yfp3"
},
"source": [
"### *b. Fill titles, prices, and ratings from the web pages*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "xqO5Y3dnYhxt"
},
"outputs": [],
"source": [
"# Loop through all 50 pages\n",
"for page in range(1, 51):\n",
" url = base_url.format(page)\n",
" response = requests.get(url, headers=headers)\n",
" soup = BeautifulSoup(response.content, \"html.parser\")\n",
" books = soup.find_all(\"article\", class_=\"product_pod\")\n",
"\n",
" for book in books:\n",
" titles.append(book.h3.a[\"title\"])\n",
" prices.append(float(book.find(\"p\", class_=\"price_color\").text[1:]))\n",
" ratings.append(book.p.get(\"class\")[1])\n",
"\n",
" time.sleep(0.5) # polite scraping delay"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "T0TOeRC4Yrnn"
},
"source": [
"### *c. โ๐ป๐โ๏ธ Create a dataframe df_books that contains the now complete \"title\", \"price\", and \"rating\" objects*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "l5FkkNhUYTHh",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 206
},
"outputId": "ef12cc81-77b3-49c4-8f2d-43163e10b3bf"
},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" title price rating\n",
"0 A Light in the Attic 51.77 Three\n",
"1 Tipping the Velvet 53.74 One\n",
"2 Soumission 50.10 One\n",
"3 Sharp Objects 47.82 Four\n",
"4 Sapiens: A Brief History of Humankind 54.23 Five"
],
"text/html": [
"\n",
"
\n",
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" title | \n",
" price | \n",
" rating | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" A Light in the Attic | \n",
" 51.77 | \n",
" Three | \n",
"
\n",
" \n",
" | 1 | \n",
" Tipping the Velvet | \n",
" 53.74 | \n",
" One | \n",
"
\n",
" \n",
" | 2 | \n",
" Soumission | \n",
" 50.10 | \n",
" One | \n",
"
\n",
" \n",
" | 3 | \n",
" Sharp Objects | \n",
" 47.82 | \n",
" Four | \n",
"
\n",
" \n",
" | 4 | \n",
" Sapiens: A Brief History of Humankind | \n",
" 54.23 | \n",
" Five | \n",
"
\n",
" \n",
"
\n",
"
\n",
"
\n",
"
\n"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "dataframe",
"variable_name": "df_books",
"summary": "{\n \"name\": \"df_books\",\n \"rows\": 1000,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\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\": \"price\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 14.446689669952772,\n \"min\": 10.0,\n \"max\": 59.99,\n \"num_unique_values\": 903,\n \"samples\": [\n 19.73,\n 55.65,\n 46.31\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}"
}
},
"metadata": {},
"execution_count": 4
}
],
"source": [
"import pandas as pd\n",
"\n",
"# Create dataframe\n",
"df_books = pd.DataFrame({\n",
" \"title\": titles,\n",
" \"price\": prices,\n",
" \"rating\": ratings\n",
"})\n",
"\n",
"# Display first few rows\n",
"df_books.head()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "duI5dv3CZYvF"
},
"source": [
"### *d. Save web-scraped dataframe either as a CSV or Excel file*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "lC1U_YHtZifh"
},
"outputs": [],
"source": [
"# ๐พ Save to CSV\n",
"df_books.to_csv(\"books_data.csv\", index=False)\n",
"\n",
"# ๐พ Or save to Excel\n",
"# df_books.to_excel(\"books_data.xlsx\", index=False)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "qMjRKMBQZlJi"
},
"source": [
"### *e. โ๐ป๐โ๏ธ View first fiew lines*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "O_wIvTxYZqCK",
"outputId": "05dcfc08-ab3d-4e81-de9c-5c5582be2397"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
" title price rating\n",
"0 A Light in the Attic 51.77 Three\n",
"1 Tipping the Velvet 53.74 One\n",
"2 Soumission 50.10 One\n",
"3 Sharp Objects 47.82 Four\n",
"4 Sapiens: A Brief History of Humankind 54.23 Five\n"
]
}
],
"source": [
"# View first 5 rows\n",
"print(df_books.head())"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "p-1Pr2szaqLk"
},
"source": [
"## **3.** ๐งฉ Create a meaningful connection between real & synthetic datasets"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "SIaJUGIpaH4V"
},
"source": [
"### *a. Initial setup*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "-gPXGcRPuV_9"
},
"outputs": [],
"source": [
"import numpy as np\n",
"import random\n",
"from datetime import datetime\n",
"import warnings\n",
"\n",
"warnings.filterwarnings(\"ignore\")\n",
"random.seed(2025)\n",
"np.random.seed(2025)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "pY4yCoIuaQqp"
},
"source": [
"### *b. Generate popularity scores based on rating (with some randomness) with a generate_popularity_score function*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "mnd5hdAbaNjz"
},
"outputs": [],
"source": [
"def generate_popularity_score(rating):\n",
" base = {\"One\": 2, \"Two\": 3, \"Three\": 3, \"Four\": 4, \"Five\": 4}.get(rating, 3)\n",
" trend_factor = random.choices([-1, 0, 1], weights=[1, 3, 2])[0]\n",
" return int(np.clip(base + trend_factor, 1, 5))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "n4-TaNTFgPak"
},
"source": [
"### *c. โ๐ป๐โ๏ธ Run the function to create a \"popularity_score\" column from \"rating\"*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "V-G3OCUCgR07",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 206
},
"outputId": "d112ed12-c279-416d-feb7-79f75cfbab1f"
},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" title price rating popularity_score\n",
"0 A Light in the Attic 51.77 Three 3\n",
"1 Tipping the Velvet 53.74 One 2\n",
"2 Soumission 50.10 One 2\n",
"3 Sharp Objects 47.82 Four 4\n",
"4 Sapiens: A Brief History of Humankind 54.23 Five 3"
],
"text/html": [
"\n",
" \n",
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" title | \n",
" price | \n",
" rating | \n",
" popularity_score | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" A Light in the Attic | \n",
" 51.77 | \n",
" Three | \n",
" 3 | \n",
"
\n",
" \n",
" | 1 | \n",
" Tipping the Velvet | \n",
" 53.74 | \n",
" One | \n",
" 2 | \n",
"
\n",
" \n",
" | 2 | \n",
" Soumission | \n",
" 50.10 | \n",
" One | \n",
" 2 | \n",
"
\n",
" \n",
" | 3 | \n",
" Sharp Objects | \n",
" 47.82 | \n",
" Four | \n",
" 4 | \n",
"
\n",
" \n",
" | 4 | \n",
" Sapiens: A Brief History of Humankind | \n",
" 54.23 | \n",
" Five | \n",
" 3 | \n",
"
\n",
" \n",
"
\n",
"
\n",
"
\n",
"
\n"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "dataframe",
"variable_name": "df_books",
"summary": "{\n \"name\": \"df_books\",\n \"rows\": 1000,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\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\": \"price\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 14.446689669952772,\n \"min\": 10.0,\n \"max\": 59.99,\n \"num_unique_values\": 903,\n \"samples\": [\n 19.73,\n 55.65,\n 46.31\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}"
}
},
"metadata": {},
"execution_count": 10
}
],
"source": [
"# Create popularity_score column\n",
"df_books[\"popularity_score\"] = df_books[\"rating\"].apply(generate_popularity_score)\n",
"\n",
"# View first few rows to confirm\n",
"df_books.head()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "HnngRNTgacYt"
},
"source": [
"### *d. Decide on the sentiment_label based on the popularity score with a get_sentiment function*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "kUtWmr8maZLZ"
},
"outputs": [],
"source": [
"def get_sentiment(popularity_score):\n",
" if popularity_score <= 2:\n",
" return \"negative\"\n",
" elif popularity_score == 3:\n",
" return \"neutral\"\n",
" else:\n",
" return \"positive\""
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "HF9F9HIzgT7Z"
},
"source": [
"### *e. โ๐ป๐โ๏ธ Run the function to create a \"sentiment_label\" column from \"popularity_score\"*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "tafQj8_7gYCG",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 206
},
"outputId": "fbdd0e3c-03e7-41dc-94ea-1fa2d99e5487"
},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
" title price rating popularity_score \\\n",
"0 A Light in the Attic 51.77 Three 3 \n",
"1 Tipping the Velvet 53.74 One 2 \n",
"2 Soumission 50.10 One 2 \n",
"3 Sharp Objects 47.82 Four 4 \n",
"4 Sapiens: A Brief History of Humankind 54.23 Five 3 \n",
"\n",
" sentiment_label \n",
"0 neutral \n",
"1 negative \n",
"2 negative \n",
"3 positive \n",
"4 neutral "
],
"text/html": [
"\n",
" \n",
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" title | \n",
" price | \n",
" rating | \n",
" popularity_score | \n",
" sentiment_label | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" A Light in the Attic | \n",
" 51.77 | \n",
" Three | \n",
" 3 | \n",
" neutral | \n",
"
\n",
" \n",
" | 1 | \n",
" Tipping the Velvet | \n",
" 53.74 | \n",
" One | \n",
" 2 | \n",
" negative | \n",
"
\n",
" \n",
" | 2 | \n",
" Soumission | \n",
" 50.10 | \n",
" One | \n",
" 2 | \n",
" negative | \n",
"
\n",
" \n",
" | 3 | \n",
" Sharp Objects | \n",
" 47.82 | \n",
" Four | \n",
" 4 | \n",
" positive | \n",
"
\n",
" \n",
" | 4 | \n",
" Sapiens: A Brief History of Humankind | \n",
" 54.23 | \n",
" Five | \n",
" 3 | \n",
" neutral | \n",
"
\n",
" \n",
"
\n",
"
\n",
"
\n",
"
\n"
],
"application/vnd.google.colaboratory.intrinsic+json": {
"type": "dataframe",
"variable_name": "df_books",
"summary": "{\n \"name\": \"df_books\",\n \"rows\": 1000,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\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\": \"price\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 14.446689669952772,\n \"min\": 10.0,\n \"max\": 59.99,\n \"num_unique_values\": 903,\n \"samples\": [\n 19.73,\n 55.65,\n 46.31\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\": \"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}"
}
},
"metadata": {},
"execution_count": 13
}
],
"source": [
"# Create sentiment_label column\n",
"df_books[\"sentiment_label\"] = df_books[\"popularity_score\"].apply(get_sentiment)\n",
"\n",
"# View first few rows to confirm\n",
"df_books.head()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "T8AdKkmASq9a"
},
"source": [
"## **4.** ๐ Generate synthetic book sales data of 18 months"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "OhXbdGD5fH0c"
},
"source": [
"### *a. Create a generate_sales_profit function that would generate sales patterns based on sentiment_label (with some randomness)*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "qkVhYPXGbgEn"
},
"outputs": [],
"source": [
"def generate_sales_profile(sentiment):\n",
" months = pd.date_range(end=datetime.today(), periods=18, freq=\"M\")\n",
"\n",
" if sentiment == \"positive\":\n",
" base = random.randint(200, 300)\n",
" trend = np.linspace(base, base + random.randint(20, 60), len(months))\n",
" elif sentiment == \"negative\":\n",
" base = random.randint(20, 80)\n",
" trend = np.linspace(base, base - random.randint(10, 30), len(months))\n",
" else: # neutral\n",
" base = random.randint(80, 160)\n",
" trend = np.full(len(months), base + random.randint(-10, 10))\n",
"\n",
" seasonality = 10 * np.sin(np.linspace(0, 3 * np.pi, len(months)))\n",
" noise = np.random.normal(0, 5, len(months))\n",
" monthly_sales = np.clip(trend + seasonality + noise, a_min=0, a_max=None).astype(int)\n",
"\n",
" return list(zip(months.strftime(\"%Y-%m\"), monthly_sales))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "L2ak1HlcgoTe"
},
"source": [
"### *b. Run the function as part of building sales_data*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "SlJ24AUafoDB"
},
"outputs": [],
"source": [
"sales_data = []\n",
"for _, row in df_books.iterrows():\n",
" records = generate_sales_profile(row[\"sentiment_label\"])\n",
" for month, units in records:\n",
" sales_data.append({\n",
" \"title\": row[\"title\"],\n",
" \"month\": month,\n",
" \"units_sold\": units,\n",
" \"sentiment_label\": row[\"sentiment_label\"]\n",
" })"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "4IXZKcCSgxnq"
},
"source": [
"### *c. โ๐ป๐โ๏ธ Create a df_sales DataFrame from sales_data*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "wcN6gtiZg-ws",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 206
},
"outputId": "cbc2c960-2e9d-4943-9a6f-e53815d11b13"
},
"outputs": [
{
"output_type": "execute_result",
"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",
"variable_name": "df_sales",
"summary": "{\n \"name\": \"df_sales\",\n \"rows\": 18000,\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\": \"month\",\n \"properties\": {\n \"dtype\": \"object\",\n \"num_unique_values\": 18,\n \"samples\": [\n \"2024-09\",\n \"2024-10\",\n \"2025-05\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"units_sold\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 98,\n \"min\": 0,\n \"max\": 362,\n \"num_unique_values\": 354,\n \"samples\": [\n 214,\n 289,\n 205\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}"
}
},
"metadata": {},
"execution_count": 16
}
],
"source": [
"# Create df_sales DataFrame\n",
"df_sales = pd.DataFrame(sales_data)\n",
"\n",
"# View first few rows\n",
"df_sales.head()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "EhIjz9WohAmZ"
},
"source": [
"### *d. Save df_sales as synthetic_sales_data.csv & view first few lines*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "MzbZvLcAhGaH",
"outputId": "bba3099f-6bbb-4c96-a7f7-a05bb542fb38"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
" 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\n"
]
}
],
"source": [
"df_sales.to_csv(\"synthetic_sales_data.csv\", index=False)\n",
"\n",
"print(df_sales.head())"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "7g9gqBgQMtJn"
},
"source": [
"## **5.** ๐ฏ Generate synthetic customer reviews"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Gi4y9M9KuDWx"
},
"source": [
"### *a. โ๐ป๐โ๏ธ Ask ChatGPT to create a list of 50 distinct generic book review texts for the sentiment labels \"positive\", \"neutral\", and \"negative\" called synthetic_reviews_by_sentiment*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "b3cd2a50"
},
"outputs": [],
"source": [
"synthetic_reviews_by_sentiment = {\n",
" \"positive\": [\n",
" \"A compelling and heartwarming read that stayed with me long after I finished.\",\n",
" \"Brilliantly written! The characters were unforgettable and the plot was engaging.\",\n",
" \"One of the best books I've read this year โ inspiring and emotionally rich.\",\n",
" \"Absolutely captivating from start to finish.\",\n",
" \"A beautifully crafted story with powerful themes.\",\n",
" \"The storytelling was immersive and deeply satisfying.\",\n",
" \"An outstanding novel that exceeded my expectations.\",\n",
" \"I couldnโt put it down โ truly addictive.\",\n",
" \"An emotionally resonant and uplifting story.\",\n",
" \"Superb pacing and exceptional character development.\",\n",
" \"A masterpiece of modern storytelling.\",\n",
" \"The authorโs voice was fresh and compelling.\",\n",
" \"A thoroughly enjoyable and rewarding read.\",\n",
" \"Heartfelt, engaging, and wonderfully written.\",\n",
" \"An inspiring journey that left me smiling.\",\n",
" \"Creative, thoughtful, and beautifully executed.\",\n",
" \"The plot twists were clever and satisfying.\",\n",
" \"A fantastic book that I would highly recommend.\",\n",
" \"Rich in detail and emotionally powerful.\",\n",
" \"An unforgettable reading experience.\",\n",
" \"Deeply moving and expertly told.\",\n",
" \"A truly magical and immersive book.\",\n",
" \"Smart, engaging, and emotionally gripping.\",\n",
" \"An excellent balance of drama and heart.\",\n",
" \"A story that completely drew me in.\",\n",
" \"Brilliant character arcs and strong writing.\",\n",
" \"A refreshing and exciting read.\",\n",
" \"Full of warmth and meaningful moments.\",\n",
" \"A top-tier novel with lasting impact.\",\n",
" \"The writing style was elegant and captivating.\",\n",
" \"An absolute joy to read.\",\n",
" \"Compelling themes handled with great care.\",\n",
" \"A remarkable and satisfying conclusion.\",\n",
" \"An inspiring and beautifully told tale.\",\n",
" \"Gripping from the very first chapter.\",\n",
" \"A standout book in its genre.\",\n",
" \"Thought-provoking and emotionally rich.\",\n",
" \"An expertly woven narrative.\",\n",
" \"Highly entertaining and deeply engaging.\",\n",
" \"A book Iโll definitely reread.\",\n",
" \"Exceptional storytelling and vivid imagery.\",\n",
" \"A powerful and uplifting experience.\",\n",
" \"Engaging, heartfelt, and masterfully written.\",\n",
" \"A delightful surprise from beginning to end.\",\n",
" \"A beautifully layered and nuanced story.\",\n",
" \"Strong characters and an engaging plot.\",\n",
" \"A wonderfully satisfying literary journey.\",\n",
" \"An impressive and memorable book.\",\n",
" \"Emotionally compelling and well-paced.\",\n",
" \"A thoroughly impressive work of fiction.\"\n",
" ],\n",
" \"neutral\": [\n",
" \"An average book โ not great, but not bad either.\",\n",
" \"Some parts really stood out, others felt a bit flat.\",\n",
" \"It was okay overall. A decent way to pass the time.\",\n",
" \"A fairly standard story with predictable moments.\",\n",
" \"Not particularly memorable, but not disappointing.\",\n",
" \"An easy and straightforward read.\",\n",
" \"The plot was fine, though not very surprising.\",\n",
" \"A balanced mix of strengths and weaknesses.\",\n",
" \"It held my attention, but didnโt wow me.\",\n",
" \"A decent book for a quiet weekend.\",\n",
" \"Some chapters were stronger than others.\",\n",
" \"It had potential, though execution was uneven.\",\n",
" \"The characters were interesting at times.\",\n",
" \"A moderately engaging storyline.\",\n",
" \"Entertaining enough, but nothing groundbreaking.\",\n",
" \"An acceptable read with a few highlights.\",\n",
" \"The writing was solid but not remarkable.\",\n",
" \"A serviceable book that met expectations.\",\n",
" \"Not bad, just not particularly exciting.\",\n",
" \"It had its moments, though inconsistent.\",\n",
" \"An ordinary but readable story.\",\n",
" \"Fairly engaging, though somewhat predictable.\",\n",
" \"A reasonable effort with mixed results.\",\n",
" \"The pacing was steady but not gripping.\",\n",
" \"It was fine โ neither impressive nor poor.\",\n",
" \"A mildly interesting narrative.\",\n",
" \"Somewhat enjoyable but easily forgettable.\",\n",
" \"A passable book overall.\",\n",
" \"There were interesting ideas, though unevenly explored.\",\n",
" \"A typical example of its genre.\",\n",
" \"Pleasant enough, but not standout.\",\n",
" \"Competently written but lacking spark.\",\n",
" \"A steady, uncomplicated read.\",\n",
" \"Enjoyable in parts, average overall.\",\n",
" \"Nothing particularly special, but solid.\",\n",
" \"An adequate and balanced read.\",\n",
" \"The themes were clear, though lightly developed.\",\n",
" \"A middle-of-the-road reading experience.\",\n",
" \"Readable, though not captivating.\",\n",
" \"A simple and straightforward narrative.\",\n",
" \"Some engaging scenes, others less so.\",\n",
" \"It delivered what it promised.\",\n",
" \"A fair effort with room for improvement.\",\n",
" \"A neutral reading experience overall.\",\n",
" \"Moderately satisfying but not memorable.\",\n",
" \"The story was coherent but predictable.\",\n",
" \"A calm and easygoing read.\",\n",
" \"An acceptable but unremarkable novel.\",\n",
" \"It met expectations without exceeding them.\",\n",
" \"Neither thrilling nor disappointing.\"\n",
" ],\n",
" \"negative\": [\n",
" \"I struggled to get through this one โ it just didnโt grab me.\",\n",
" \"The plot was confusing and the characters felt underdeveloped.\",\n",
" \"Disappointing. I had high hopes, but they weren't met.\",\n",
" \"The pacing was slow and hard to follow.\",\n",
" \"It failed to keep my interest.\",\n",
" \"The story felt flat and uninspired.\",\n",
" \"I found it difficult to connect with the characters.\",\n",
" \"The writing style didnโt work for me.\",\n",
" \"Predictable and lacking excitement.\",\n",
" \"The ending was unsatisfying.\",\n",
" \"It felt longer than it needed to be.\",\n",
" \"The dialogue seemed unrealistic.\",\n",
" \"A frustrating reading experience overall.\",\n",
" \"The plot lacked cohesion.\",\n",
" \"Not engaging enough to recommend.\",\n",
" \"I nearly gave up halfway through.\",\n",
" \"The characters lacked depth.\",\n",
" \"The themes were poorly developed.\",\n",
" \"A forgettable and disappointing read.\",\n",
" \"The story felt repetitive.\",\n",
" \"Too many clichรฉs throughout.\",\n",
" \"The pacing dragged significantly.\",\n",
" \"The narrative was difficult to follow.\",\n",
" \"I expected much more from this book.\",\n",
" \"It lacked originality and excitement.\",\n",
" \"The writing felt rushed and uneven.\",\n",
" \"Hard to stay interested in the storyline.\",\n",
" \"The book failed to deliver on its premise.\",\n",
" \"Underwhelming and forgettable.\",\n",
" \"It simply didnโt resonate with me.\",\n",
" \"The characters were unconvincing.\",\n",
" \"A dull and uninspired narrative.\",\n",
" \"The plot twists were predictable.\",\n",
" \"It struggled to maintain coherence.\",\n",
" \"An unsatisfying reading experience.\",\n",
" \"The emotional depth was missing.\",\n",
" \"The story felt disconnected.\",\n",
" \"Too slow and lacking momentum.\",\n",
" \"A disappointing addition to the genre.\",\n",
" \"The execution fell flat.\",\n",
" \"I wouldnโt read it again.\",\n",
" \"The writing felt overly simplistic.\",\n",
" \"The storyline lacked tension.\",\n",
" \"It didnโt live up to the description.\",\n",
" \"The development felt shallow.\",\n",
" \"Not worth the time investment.\",\n",
" \"The book lacked focus.\",\n",
" \"An overall weak narrative.\",\n",
" \"I was left unimpressed.\",\n",
" \"A frustrating and underwhelming read.\"\n",
" ]\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "fQhfVaDmuULT"
},
"source": [
"### *b. Generate 10 reviews per book using random sampling from the corresponding 50*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "l2SRc3PjuTGM"
},
"outputs": [],
"source": [
"review_rows = []\n",
"for _, row in df_books.iterrows():\n",
" title = row['title']\n",
" sentiment_label = row['sentiment_label']\n",
" review_pool = synthetic_reviews_by_sentiment[sentiment_label]\n",
" sampled_reviews = random.sample(review_pool, 10)\n",
" for review_text in sampled_reviews:\n",
" review_rows.append({\n",
" \"title\": title,\n",
" \"sentiment_label\": sentiment_label,\n",
" \"review_text\": review_text,\n",
" \"rating\": row['rating'],\n",
" \"popularity_score\": row['popularity_score']\n",
" })"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "bmJMXF-Bukdm"
},
"source": [
"### *c. Create the final dataframe df_reviews & save it as synthetic_book_reviews.csv*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "ZUKUqZsuumsp"
},
"outputs": [],
"source": [
"df_reviews = pd.DataFrame(review_rows)\n",
"df_reviews.to_csv(\"synthetic_book_reviews.csv\", index=False)"
]
},
{
"cell_type": "markdown",
"source": [
"### *c. inputs for R*"
],
"metadata": {
"id": "_602pYUS3gY5"
}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "3946e521",
"outputId": "7c5c5b9c-a4ad-4756-f28b-9d53c71687df"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"โ
Wrote synthetic_title_level_features.csv\n",
"โ
Wrote synthetic_monthly_revenue_series.csv\n"
]
}
],
"source": [
"import numpy as np\n",
"\n",
"def _safe_num(s):\n",
" return pd.to_numeric(\n",
" pd.Series(s).astype(str).str.replace(r\"[^0-9.]\", \"\", regex=True),\n",
" errors=\"coerce\"\n",
" )\n",
"\n",
"# --- Clean book metadata (price/rating) ---\n",
"df_books_r = df_books.copy()\n",
"if \"price\" in df_books_r.columns:\n",
" df_books_r[\"price\"] = _safe_num(df_books_r[\"price\"])\n",
"if \"rating\" in df_books_r.columns:\n",
" df_books_r[\"rating\"] = _safe_num(df_books_r[\"rating\"])\n",
"\n",
"df_books_r[\"title\"] = df_books_r[\"title\"].astype(str).str.strip()\n",
"\n",
"# --- Clean sales ---\n",
"df_sales_r = df_sales.copy()\n",
"df_sales_r[\"title\"] = df_sales_r[\"title\"].astype(str).str.strip()\n",
"df_sales_r[\"month\"] = pd.to_datetime(df_sales_r[\"month\"], errors=\"coerce\")\n",
"df_sales_r[\"units_sold\"] = _safe_num(df_sales_r[\"units_sold\"])\n",
"\n",
"# --- Clean reviews ---\n",
"df_reviews_r = df_reviews.copy()\n",
"df_reviews_r[\"title\"] = df_reviews_r[\"title\"].astype(str).str.strip()\n",
"df_reviews_r[\"sentiment_label\"] = df_reviews_r[\"sentiment_label\"].astype(str).str.lower().str.strip()\n",
"if \"rating\" in df_reviews_r.columns:\n",
" df_reviews_r[\"rating\"] = _safe_num(df_reviews_r[\"rating\"])\n",
"if \"popularity_score\" in df_reviews_r.columns:\n",
" df_reviews_r[\"popularity_score\"] = _safe_num(df_reviews_r[\"popularity_score\"])\n",
"\n",
"# --- Sentiment shares per title (from reviews) ---\n",
"sent_counts = (\n",
" df_reviews_r.groupby([\"title\", \"sentiment_label\"])\n",
" .size()\n",
" .unstack(fill_value=0)\n",
")\n",
"for lab in [\"positive\", \"neutral\", \"negative\"]:\n",
" if lab not in sent_counts.columns:\n",
" sent_counts[lab] = 0\n",
"\n",
"sent_counts[\"total_reviews\"] = sent_counts[[\"positive\", \"neutral\", \"negative\"]].sum(axis=1)\n",
"den = sent_counts[\"total_reviews\"].replace(0, np.nan)\n",
"sent_counts[\"share_positive\"] = sent_counts[\"positive\"] / den\n",
"sent_counts[\"share_neutral\"] = sent_counts[\"neutral\"] / den\n",
"sent_counts[\"share_negative\"] = sent_counts[\"negative\"] / den\n",
"sent_counts = sent_counts.reset_index()\n",
"\n",
"# --- Sales aggregation per title ---\n",
"sales_by_title = (\n",
" df_sales_r.dropna(subset=[\"title\"])\n",
" .groupby(\"title\", as_index=False)\n",
" .agg(\n",
" months_observed=(\"month\", \"nunique\"),\n",
" avg_units_sold=(\"units_sold\", \"mean\"),\n",
" total_units_sold=(\"units_sold\", \"sum\"),\n",
" )\n",
")\n",
"\n",
"# --- Title-level features (join sales + books + sentiment) ---\n",
"df_title = (\n",
" sales_by_title\n",
" .merge(df_books_r[[\"title\", \"price\", \"rating\"]], on=\"title\", how=\"left\")\n",
" .merge(sent_counts[[\"title\", \"share_positive\", \"share_neutral\", \"share_negative\", \"total_reviews\"]],\n",
" on=\"title\", how=\"left\")\n",
")\n",
"\n",
"df_title[\"avg_revenue\"] = df_title[\"avg_units_sold\"] * df_title[\"price\"]\n",
"df_title[\"total_revenue\"] = df_title[\"total_units_sold\"] * df_title[\"price\"]\n",
"\n",
"df_title.to_csv(\"synthetic_title_level_features.csv\", index=False)\n",
"print(\"โ
Wrote synthetic_title_level_features.csv\")\n",
"\n",
"# --- Monthly revenue series (proxy: units_sold * price) ---\n",
"monthly_rev = (\n",
" df_sales_r.merge(df_books_r[[\"title\", \"price\"]], on=\"title\", how=\"left\")\n",
")\n",
"monthly_rev[\"revenue\"] = monthly_rev[\"units_sold\"] * monthly_rev[\"price\"]\n",
"\n",
"df_monthly = (\n",
" monthly_rev.dropna(subset=[\"month\"])\n",
" .groupby(\"month\", as_index=False)[\"revenue\"]\n",
" .sum()\n",
" .rename(columns={\"revenue\": \"total_revenue\"})\n",
" .sort_values(\"month\")\n",
")\n",
"# if revenue is all NA (e.g., missing price), fallback to units_sold as a teaching proxy\n",
"if df_monthly[\"total_revenue\"].notna().sum() == 0:\n",
" df_monthly = (\n",
" df_sales_r.dropna(subset=[\"month\"])\n",
" .groupby(\"month\", as_index=False)[\"units_sold\"]\n",
" .sum()\n",
" .rename(columns={\"units_sold\": \"total_revenue\"})\n",
" .sort_values(\"month\")\n",
" )\n",
"\n",
"df_monthly[\"month\"] = pd.to_datetime(df_monthly[\"month\"], errors=\"coerce\").dt.strftime(\"%Y-%m-%d\")\n",
"df_monthly.to_csv(\"synthetic_monthly_revenue_series.csv\", index=False)\n",
"print(\"โ
Wrote synthetic_monthly_revenue_series.csv\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "RYvGyVfXuo54"
},
"source": [
"### *d. โ๐ป๐โ๏ธ View the first few lines*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "xfE8NMqOurKo",
"outputId": "69daad99-74df-428f-e6b1-9fda5dd124dd"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
" title months_observed \\\n",
"0 \"Most Blessed of the Patriarchs\": Thomas Jeffe... 18 \n",
"1 #GIRLBOSS 18 \n",
"2 #HigherSelfie: Wake Up Your Life. Free Your So... 18 \n",
"3 'Salem's Lot 18 \n",
"4 (Un)Qualified: How God Uses Broken People to D... 18 \n",
"\n",
" avg_units_sold total_units_sold price rating share_positive \\\n",
"0 285.555556 5140 44.48 NaN 1.0 \n",
"1 47.944444 863 50.96 NaN 0.0 \n",
"2 226.777778 4082 23.11 NaN 1.0 \n",
"3 246.055556 4429 49.56 NaN 1.0 \n",
"4 294.444444 5300 54.00 NaN 1.0 \n",
"\n",
" share_neutral share_negative total_reviews avg_revenue total_revenue \n",
"0 0.0 0.0 10 12701.511111 228627.20 \n",
"1 0.0 1.0 10 2443.248889 43978.48 \n",
"2 0.0 0.0 10 5240.834444 94335.02 \n",
"3 0.0 0.0 10 12194.513333 219501.24 \n",
"4 0.0 0.0 10 15900.000000 286200.00 \n",
" month total_revenue\n",
"0 2024-09-01 5631956.77\n",
"1 2024-10-01 5856653.68\n",
"2 2024-11-01 6006876.26\n",
"3 2024-12-01 6061519.85\n",
"4 2025-01-01 6014276.79\n"
]
}
],
"source": [
"# View first few rows of title-level features\n",
"print(df_title.head())\n",
"\n",
"# View first few rows of monthly revenue series\n",
"print(df_monthly.head())"
]
}
],
"metadata": {
"colab": {
"collapsed_sections": [
"jpASMyIQMaAq",
"lquNYCbfL9IM",
"0IWuNpxxYDJF",
"oCdTsin2Yfp3",
"T0TOeRC4Yrnn",
"duI5dv3CZYvF",
"qMjRKMBQZlJi",
"p-1Pr2szaqLk",
"SIaJUGIpaH4V",
"pY4yCoIuaQqp",
"n4-TaNTFgPak",
"HnngRNTgacYt",
"HF9F9HIzgT7Z",
"T8AdKkmASq9a",
"OhXbdGD5fH0c",
"L2ak1HlcgoTe",
"4IXZKcCSgxnq",
"EhIjz9WohAmZ",
"Gi4y9M9KuDWx",
"fQhfVaDmuULT",
"bmJMXF-Bukdm",
"RYvGyVfXuo54"
],
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}