{ "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": 2, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "f48c8f8c", "outputId": "5143dab8-2a4f-400a-9c82-82ba5f1be700", "collapsed": true }, "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": 3, "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": 4, "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": 6, "metadata": { "id": "l5FkkNhUYTHh", "colab": { "base_uri": "https://localhost:8080/", "height": 424 }, "outputId": "efbaaa0b-8e25-48bc-db84-64e30cf2dbda" }, "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\n", ".. ... ... ...\n", "995 Alice in Wonderland (Alice's Adventures in Won... 55.53 One\n", "996 Ajin: Demi-Human, Volume 1 (Ajin: Demi-Human #1) 57.06 Four\n", "997 A Spy's Devotion (The Regency Spies of London #1) 16.97 Five\n", "998 1st to Die (Women's Murder Club #1) 53.98 One\n", "999 1,000 Places to See Before You Die 26.08 Five\n", "\n", "[1000 rows x 3 columns]" ], "text/html": [ "\n", "
\n", "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
TitlePrice (Ā£)Rating
0A Light in the Attic51.77Three
1Tipping the Velvet53.74One
2Soumission50.10One
3Sharp Objects47.82Four
4Sapiens: A Brief History of Humankind54.23Five
............
995Alice in Wonderland (Alice's Adventures in Won...55.53One
996Ajin: Demi-Human, Volume 1 (Ajin: Demi-Human #1)57.06Four
997A Spy's Devotion (The Regency Spies of London #1)16.97Five
9981st to Die (Women's Murder Club #1)53.98One
9991,000 Places to See Before You Die26.08Five
\n", "

1000 rows Ɨ 3 columns

\n", "
\n", "
\n", "\n", "
\n", " \n", "\n", " \n", "\n", " \n", "
\n", "\n", "\n", "
\n", " \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 (\\u00a3)\",\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": 6 } ], "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 = []\n", "prices = []\n", "ratings = []\n", "\n", "# 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\n", "\n", "# Create DataFrame\n", "df_books = pd.DataFrame({\n", " \"Title\": titles,\n", " \"Price (Ā£)\": prices,\n", " \"Rating\": ratings\n", "})\n", "\n", "# Display nicely (for Jupyter / Colab)\n", "df_books" ] }, { "cell_type": "markdown", "metadata": { "id": "duI5dv3CZYvF" }, "source": [ "### *d. Save web-scraped dataframe either as a CSV or Excel file*" ] }, { "cell_type": "code", "execution_count": 7, "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": 8, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 206 }, "id": "O_wIvTxYZqCK", "outputId": "a4b4b3c8-7a49-4878-c0fe-d6c7f0447705" }, "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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
TitlePrice (Ā£)Rating
0A Light in the Attic51.77Three
1Tipping the Velvet53.74One
2Soumission50.10One
3Sharp Objects47.82Four
4Sapiens: A Brief History of Humankind54.23Five
\n", "
\n", "
\n", "\n", "
\n", " \n", "\n", " \n", "\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 (\\u00a3)\",\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": 8 } ], "source": [ "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": 13, "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": 15, "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": 16, "metadata": { "id": "V-G3OCUCgR07", "colab": { "base_uri": "https://localhost:8080/", "height": 206 }, "outputId": "b5ab1912-fc6e-499a-eb94-af2cd4d6b651" }, "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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
TitlePrice (Ā£)Ratingpopularity_score
0A Light in the Attic51.77Three3
1Tipping the Velvet53.74One2
2Soumission50.10One2
3Sharp Objects47.82Four4
4Sapiens: A Brief History of Humankind54.23Five3
\n", "
\n", "
\n", "\n", "
\n", " \n", "\n", " \n", "\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 (\\u00a3)\",\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": 16 } ], "source": [ "# Create popularity_score column\n", "df_books[\"popularity_score\"] = df_books[\"Rating\"].apply(generate_popularity_score)\n", "\n", "# Display result\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": 17, "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": 18, "metadata": { "id": "tafQj8_7gYCG", "colab": { "base_uri": "https://localhost:8080/", "height": 206 }, "outputId": "ff52f20c-6741-4cab-c606-aac5dac55707" }, "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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
TitlePrice (Ā£)Ratingpopularity_scoresentiment_label
0A Light in the Attic51.77Three3neutral
1Tipping the Velvet53.74One2negative
2Soumission50.10One2negative
3Sharp Objects47.82Four4positive
4Sapiens: A Brief History of Humankind54.23Five3neutral
\n", "
\n", "
\n", "\n", "
\n", " \n", "\n", " \n", "\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 (\\u00a3)\",\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": 18 } ], "source": [ "# Create sentiment_label column\n", "df_books[\"sentiment_label\"] = df_books[\"popularity_score\"].apply(get_sentiment)\n", "\n", "# Display result\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": 33, "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": 37, "metadata": { "id": "SlJ24AUafoDB" }, "outputs": [], "source": [ "sales_data = []\n", "\n", "for _, row in df_books.iterrows():\n", " records = generate_sales_profile(row[\"sentiment_label\"])\n", "\n", " for month, units in records:\n", " sales_data.append({\n", " \"title\": row[\"Title\"], # fixed here\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": 42, "metadata": { "id": "wcN6gtiZg-ws", "colab": { "base_uri": "https://localhost:8080/", "height": 206 }, "outputId": "f6b74d1c-3a60-405b-da04-d0f2f1643dd5" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ " title month units_sold sentiment_label\n", "0 A Light in the Attic 2024-09 123 neutral\n", "1 A Light in the Attic 2024-10 122 neutral\n", "2 A Light in the Attic 2024-11 127 neutral\n", "3 A Light in the Attic 2024-12 127 neutral\n", "4 A Light in the Attic 2025-01 127 neutral" ], "text/html": [ "\n", "
\n", "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
titlemonthunits_soldsentiment_label
0A Light in the Attic2024-09123neutral
1A Light in the Attic2024-10122neutral
2A Light in the Attic2024-11127neutral
3A Light in the Attic2024-12127neutral
4A Light in the Attic2025-01127neutral
\n", "
\n", "
\n", "\n", "
\n", " \n", "\n", " \n", "\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\": 100,\n \"min\": 0,\n \"max\": 365,\n \"num_unique_values\": 354,\n \"samples\": [\n 237,\n 275,\n 228\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": 42 } ], "source": [ "# Create df_sales DataFrame\n", "df_sales = pd.DataFrame(sales_data)\n", "\n", "# Display result\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": 43, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "MzbZvLcAhGaH", "outputId": "cf18e473-d633-4448-9f98-51e18f7e4f27" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ " title month units_sold sentiment_label\n", "0 A Light in the Attic 2024-09 123 neutral\n", "1 A Light in the Attic 2024-10 122 neutral\n", "2 A Light in the Attic 2024-11 127 neutral\n", "3 A Light in the Attic 2024-12 127 neutral\n", "4 A Light in the Attic 2025-01 127 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": 44, "metadata": { "id": "b3cd2a50" }, "outputs": [], "source": [ "# 50 generic review texts per sentiment label\n", "synthetic_reviews_by_sentiment = {\n", " \"positive\": [\n", " \"A captivating story with memorable characters and a satisfying ending.\",\n", " \"Beautifully written and emotionally engaging from start to finish.\",\n", " \"A page-turner that kept me hooked throughout.\",\n", " \"Thought-provoking and well-paced, with strong storytelling.\",\n", " \"An uplifting read that left me feeling inspired.\",\n", " \"The writing style was smooth and the plot flowed nicely.\",\n", " \"A brilliant book — I couldn’t put it down.\",\n", " \"Rich world-building and characters that felt truly alive.\",\n", " \"A charming and heartfelt story that really resonated with me.\",\n", " \"An impressive read with excellent pacing and strong themes.\",\n", " \"Clever plot twists and a great sense of atmosphere.\",\n", " \"A standout novel with depth, warmth, and originality.\",\n", " \"Engrossing from the first chapter to the last.\",\n", " \"The characters were compelling and the dialogue felt natural.\",\n", " \"A wonderfully immersive book with a strong emotional core.\",\n", " \"Smart, entertaining, and beautifully crafted.\",\n", " \"I loved the tone, the message, and the overall journey.\",\n", " \"A highly enjoyable read that exceeded my expectations.\",\n", " \"Powerful storytelling with moments that truly moved me.\",\n", " \"An excellent blend of drama, humor, and heart.\",\n", " \"The plot was engaging and the ending was very satisfying.\",\n", " \"A thoughtful book that made me reflect long after finishing.\",\n", " \"Great character development and a vivid sense of place.\",\n", " \"A delightful read — warm, engaging, and well written.\",\n", " \"A masterfully told story with a strong narrative voice.\",\n", " \"A fresh and original take on a familiar theme.\",\n", " \"An absorbing story that kept me invested throughout.\",\n", " \"Wonderful pacing and a strong emotional payoff.\",\n", " \"The writing was polished and the story was immersive.\",\n", " \"A meaningful and inspiring book with memorable moments.\",\n", " \"Highly recommended — engaging, smart, and heartfelt.\",\n", " \"A compelling plot with characters I genuinely cared about.\",\n", " \"A beautifully structured story with a satisfying arc.\",\n", " \"A fun and rewarding read with great momentum.\",\n", " \"The author’s voice is strong and the story is gripping.\",\n", " \"A powerful book that balanced emotion and plot perfectly.\",\n", " \"A wonderful experience — engaging, moving, and memorable.\",\n", " \"Excellent storytelling with a unique perspective.\",\n", " \"A charming book that left me smiling.\",\n", " \"A well-crafted novel with a strong sense of direction.\",\n", " \"An emotional and immersive journey that felt authentic.\",\n", " \"Great writing and a plot that stayed interesting throughout.\",\n", " \"A top-tier read with depth and great character work.\",\n", " \"The story was engaging and the themes were handled well.\",\n", " \"A delightful page-turner with strong character dynamics.\",\n", " \"A rewarding read that I’d happily reread.\",\n", " \"A strong and satisfying story with great pacing.\",\n", " \"An impressive book — creative, engaging, and thoughtful.\",\n", " \"A moving and memorable read with a great ending.\",\n", " \"A fantastic read that delivered on every level.\"\n", " ],\n", " \"neutral\": [\n", " \"An okay read — enjoyable in parts but not especially memorable.\",\n", " \"It had some strong moments, though the pacing felt uneven.\",\n", " \"A decent book overall, but it didn’t fully grab me.\",\n", " \"The story was fine, but I didn’t feel strongly about it.\",\n", " \"Interesting premise, but the execution was mixed for me.\",\n", " \"Some chapters were engaging, others dragged a little.\",\n", " \"Not bad, but it didn’t stand out compared to similar books.\",\n", " \"A solid read, though it lacked a bit of spark.\",\n", " \"The characters were acceptable, but I wanted more depth.\",\n", " \"It was entertaining enough, but not something I’d reread.\",\n", " \"A middle-of-the-road book with both strengths and weaknesses.\",\n", " \"The writing was fine, but the plot felt predictable at times.\",\n", " \"I liked parts of it, but overall it was just average.\",\n", " \"It held my attention, but didn’t leave a lasting impression.\",\n", " \"Decent storytelling, though the ending felt a bit rushed.\",\n", " \"A pleasant read, but nothing particularly special.\",\n", " \"Some good ideas, but the story didn’t fully deliver for me.\",\n", " \"The plot was okay, but the pacing could have been tighter.\",\n", " \"It was fine — not disappointing, not amazing.\",\n", " \"A fair book with a few standout scenes.\",\n", " \"Readable and straightforward, but not very impactful.\",\n", " \"It started strong, then felt a bit repetitive in the middle.\",\n", " \"A decent effort, though some parts felt underdeveloped.\",\n", " \"It was enjoyable enough, but I didn’t connect deeply with it.\",\n", " \"The writing style was clear, though sometimes a bit plain.\",\n", " \"Not a bad read, but I expected a little more.\",\n", " \"It had potential, but didn’t fully reach it for me.\",\n", " \"Some characters were interesting, others felt a bit flat.\",\n", " \"An average book that’s okay for passing the time.\",\n", " \"It was fine overall, though it lacked tension in places.\",\n", " \"A solid story, but the plot didn’t surprise me much.\",\n", " \"Mixed feelings — good moments, but also slower sections.\",\n", " \"I didn’t love it, but I didn’t dislike it either.\",\n", " \"An acceptable read with a familiar storyline.\",\n", " \"It was okay, though the ending didn’t fully satisfy me.\",\n", " \"The concept was interesting, but it felt unevenly executed.\",\n", " \"Not terrible, but not particularly compelling for me.\",\n", " \"A reasonable read, though it didn’t stand out.\",\n", " \"There were some nice scenes, but it felt forgettable overall.\",\n", " \"It was decent — a light read without much depth.\",\n", " \"Some parts worked well, others didn’t land for me.\",\n", " \"A good attempt, but it felt a bit safe and predictable.\",\n", " \"I enjoyed it in the moment, but it didn’t stick with me.\",\n", " \"The plot was fine, but I wanted more character development.\",\n", " \"It was readable, but I wouldn’t strongly recommend it.\",\n", " \"A straightforward story that was neither great nor bad.\",\n", " \"An okay book with a few engaging moments.\",\n", " \"It had its moments, but overall it was just alright.\",\n", " \"A passable read that didn’t leave a big impact.\",\n", " \"Decent enough, though I’m not sure I’d recommend it widely.\"\n", " ],\n", " \"negative\": [\n", " \"I struggled to stay interested — it felt slow and repetitive.\",\n", " \"The story didn’t really come together, and I lost interest quickly.\",\n", " \"Disappointing overall — I expected more from the premise.\",\n", " \"The pacing dragged and the plot didn’t engage me.\",\n", " \"The characters felt flat and I couldn’t connect with them.\",\n", " \"It was hard to finish — the writing didn’t hold my attention.\",\n", " \"The plot was confusing and not very satisfying.\",\n", " \"I found it frustrating — too many loose ends and little payoff.\",\n", " \"The story felt predictable and lacked tension.\",\n", " \"I didn’t enjoy the writing style; it felt clunky to me.\",\n", " \"The characters’ decisions didn’t make much sense.\",\n", " \"The book felt overly long without enough happening.\",\n", " \"I wanted to like it, but it just didn’t work for me.\",\n", " \"The dialogue felt unnatural and pulled me out of the story.\",\n", " \"The plot had potential, but the execution fell short.\",\n", " \"It never really grabbed me, even after several chapters.\",\n", " \"The ending was disappointing and didn’t feel earned.\",\n", " \"I found the story messy and hard to follow.\",\n", " \"It felt repetitive and didn’t offer anything new.\",\n", " \"The pacing was uneven and the middle dragged a lot.\",\n", " \"The characters felt underdeveloped and uninteresting.\",\n", " \"I didn’t connect with the story and it felt forgettable.\",\n", " \"The writing was dull and the plot lacked momentum.\",\n", " \"It didn’t live up to its premise — very underwhelming.\",\n", " \"I found it difficult to care about what was happening.\",\n", " \"Too many clichĆ©s and not enough originality.\",\n", " \"The story felt unfocused and scattered.\",\n", " \"It was a chore to read — I kept losing interest.\",\n", " \"The plot twists were predictable and didn’t add much.\",\n", " \"The tone felt inconsistent and the pacing was poor.\",\n", " \"I finished it, but it wasn’t enjoyable for me.\",\n", " \"The characters were unlikeable and lacked depth.\",\n", " \"The storyline didn’t make much sense in the end.\",\n", " \"It dragged on and didn’t deliver a satisfying payoff.\",\n", " \"The writing felt repetitive and the plot went nowhere.\",\n", " \"I didn’t find the story engaging or well-structured.\",\n", " \"The ending left me frustrated rather than satisfied.\",\n", " \"It felt rushed in places and slow in others — not balanced.\",\n", " \"The book didn’t meet my expectations at all.\",\n", " \"The plot felt thin and the characters didn’t feel real.\",\n", " \"I had trouble following the story and staying invested.\",\n", " \"The writing lacked clarity and the pacing was off.\",\n", " \"It didn’t draw me in — I was bored for much of it.\",\n", " \"The story felt stretched out without enough substance.\",\n", " \"I couldn’t connect with the characters or the plot.\",\n", " \"The narrative felt repetitive and lacked direction.\",\n", " \"Not my favorite — I found it dull and unsatisfying.\",\n", " \"It promised a lot but delivered very little.\",\n", " \"I wouldn’t recommend it — it didn’t work for me.\",\n", " \"Overall, a disappointing read with little payoff.\"\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": 47, "metadata": { "id": "l2SRc3PjuTGM" }, "outputs": [], "source": [ "review_rows = []\n", "\n", "for _, row in df_books.iterrows():\n", " title = row['Title'] # Capital T\n", " sentiment_label = row['sentiment_label']\n", " review_pool = synthetic_reviews_by_sentiment[sentiment_label]\n", "\n", " sampled_reviews = random.sample(review_pool, 10)\n", "\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'], # Capital R\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": 52, "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": 51, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "3946e521", "outputId": "d893283f-d4ab-454a-eabb-928c8726986a" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "āœ… Wrote synthetic_title_level_features.csv\n", "āœ… Wrote synthetic_monthly_revenue_series.csv\n" ] } ], "source": [ "import pandas as pd\n", "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", "# Map text ratings to numbers (BooksToScrape style)\n", "RATING_MAP = {\"One\": 1, \"Two\": 2, \"Three\": 3, \"Four\": 4, \"Five\": 5}\n", "\n", "# ---------- Standardize df_books columns ----------\n", "df_books_r = df_books.copy()\n", "\n", "# Handle different column name variants\n", "rename_books = {}\n", "if \"Title\" in df_books_r.columns: rename_books[\"Title\"] = \"title\"\n", "if \"Price (Ā£)\" in df_books_r.columns: rename_books[\"Price (Ā£)\"] = \"price\"\n", "if \"Rating\" in df_books_r.columns: rename_books[\"Rating\"] = \"rating\"\n", "df_books_r = df_books_r.rename(columns=rename_books)\n", "\n", "# Ensure required cols exist\n", "if \"title\" not in df_books_r.columns:\n", " raise KeyError(f\"df_books is missing a title column. Columns: {df_books_r.columns.tolist()}\")\n", "\n", "df_books_r[\"title\"] = df_books_r[\"title\"].astype(str).str.strip()\n", "\n", "# Clean price\n", "if \"price\" in df_books_r.columns:\n", " df_books_r[\"price\"] = _safe_num(df_books_r[\"price\"])\n", "\n", "# Clean rating: text -> numeric if needed\n", "if \"rating\" in df_books_r.columns:\n", " df_books_r[\"rating\"] = df_books_r[\"rating\"].replace(RATING_MAP)\n", " df_books_r[\"rating\"] = pd.to_numeric(df_books_r[\"rating\"], errors=\"coerce\")\n", "\n", "# ---------- Clean df_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 df_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", "\n", "if \"rating\" in df_reviews_r.columns:\n", " df_reviews_r[\"rating\"] = df_reviews_r[\"rating\"].replace(RATING_MAP)\n", " df_reviews_r[\"rating\"] = pd.to_numeric(df_reviews_r[\"rating\"], errors=\"coerce\")\n", "\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 ----------\n", "sent_counts = (\n", " df_reviews_r.groupby([\"title\", \"sentiment_label\"])\n", " .size()\n", " .unstack(fill_value=0)\n", ")\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 ----------\n", "df_title = (\n", " sales_by_title\n", " .merge(df_books_r[[\"title\", \"price\", \"rating\"]], on=\"title\", how=\"left\")\n", " .merge(\n", " sent_counts[[\"title\", \"share_positive\", \"share_neutral\", \"share_negative\", \"total_reviews\"]],\n", " on=\"title\", how=\"left\"\n", " )\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 ----------\n", "monthly_rev = df_sales_r.merge(df_books_r[[\"title\", \"price\"]], on=\"title\", how=\"left\")\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", "\n", "# fallback if price missing everywhere\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\")" ] }, { "cell_type": "markdown", "metadata": { "id": "RYvGyVfXuo54" }, "source": [ "### *d. āœ‹šŸ»šŸ›‘ā›”ļø View the first few lines*" ] }, { "cell_type": "code", "execution_count": 53, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 206 }, "id": "xfE8NMqOurKo", "outputId": "89b1d9bf-04a5-4f6f-a8c9-9cda2e7424ed" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ " title sentiment_label \\\n", "0 A Light in the Attic neutral \n", "1 A Light in the Attic neutral \n", "2 A Light in the Attic neutral \n", "3 A Light in the Attic neutral \n", "4 A Light in the Attic neutral \n", "\n", " review_text rating popularity_score \n", "0 Decent storytelling, though the ending felt a ... Three 3 \n", "1 A straightforward story that was neither great... Three 3 \n", "2 It was readable, but I wouldn’t strongly recom... Three 3 \n", "3 Not terrible, but not particularly compelling ... Three 3 \n", "4 Interesting premise, but the execution was mix... Three 3 " ], "text/html": [ "\n", "
\n", "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
titlesentiment_labelreview_textratingpopularity_score
0A Light in the AtticneutralDecent storytelling, though the ending felt a ...Three3
1A Light in the AtticneutralA straightforward story that was neither great...Three3
2A Light in the AtticneutralIt was readable, but I wouldn’t strongly recom...Three3
3A Light in the AtticneutralNot terrible, but not particularly compelling ...Three3
4A Light in the AtticneutralInteresting premise, but the execution was mix...Three3
\n", "
\n", "
\n", "\n", "
\n", " \n", "\n", " \n", "\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 \"The tone felt inconsistent and the pacing was poor.\",\n \"I had trouble following the story and staying invested.\",\n \"It had some strong moments, though the pacing felt uneven.\"\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": 53 } ], "source": [ "df_reviews.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 }