{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "6ccef370",
   "metadata": {},
   "source": [
    "# ESIEE Paris — Data Engineering I — Assignment 3\n",
    "> Author : Badr TAJINI\n",
    "\n",
    "**Academic year:** 2025–2026  \n",
    "**Program:** Data & Applications - Engineering - (FD)   \n",
    "**Course:** Data Engineering I  \n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bd3c7a52",
   "metadata": {},
   "source": [
    "## Learning goals\n",
    "- Analyze with **SQL** and **DataFrames**.\n",
    "- Implement two **RDD means** variants.\n",
    "- Implement **RDD joins** (shuffle and hash).\n",
    "- Record and explain performance observations.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d42f7c31",
   "metadata": {},
   "source": [
    "## 1. Setup"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e76cdb5-698f-4369-8c1f-2526d7785b45",
   "metadata": {},
   "source": [
    "Download data files from the following URL:\n",
    "https://www.dropbox.com/scl/fi/7012u693u06dgj95mgq2a/retail_dw_20250826.tar.gz?rlkey=fxyozuoryn951gzwmli5xi2zd&dl=0\n",
    "\n",
    "Unpack somewhere and define the `data_path` accordingly:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "8073f5db",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Change to path on your local machine.\n",
    "data_path = \"/home/saraa/lab_assignment\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3a93421e-3ed4-4adc-8dd9-f605b53c8498",
   "metadata": {},
   "source": [
    "The following cell contains setup to measure wall clock time and memory usage. (Don't worry about the details, just run the cell)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "82905153",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: numpy in /home/saraa/miniconda3/envs/de1-env/lib/python3.10/site-packages (2.2.6)\n",
      "Requirement already satisfied: pandas in /home/saraa/miniconda3/envs/de1-env/lib/python3.10/site-packages (2.3.3)\n",
      "Collecting pyarrow\n",
      "  Downloading pyarrow-22.0.0-cp310-cp310-manylinux_2_28_x86_64.whl.metadata (3.2 kB)\n",
      "Collecting matplotlib\n",
      "  Downloading matplotlib-3.10.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (52 kB)\n",
      "Collecting scipy\n",
      "  Downloading scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (61 kB)\n",
      "Requirement already satisfied: python-dateutil>=2.8.2 in /home/saraa/miniconda3/envs/de1-env/lib/python3.10/site-packages (from pandas) (2.9.0.post0)\n",
      "Requirement already satisfied: pytz>=2020.1 in /home/saraa/miniconda3/envs/de1-env/lib/python3.10/site-packages (from pandas) (2025.2)\n",
      "Requirement already satisfied: tzdata>=2022.7 in /home/saraa/miniconda3/envs/de1-env/lib/python3.10/site-packages (from pandas) (2025.2)\n",
      "Collecting contourpy>=1.0.1 (from matplotlib)\n",
      "  Downloading contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (5.5 kB)\n",
      "Collecting cycler>=0.10 (from matplotlib)\n",
      "  Downloading cycler-0.12.1-py3-none-any.whl.metadata (3.8 kB)\n",
      "Collecting fonttools>=4.22.0 (from matplotlib)\n",
      "  Downloading fonttools-4.61.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (114 kB)\n",
      "Collecting kiwisolver>=1.3.1 (from matplotlib)\n",
      "  Downloading kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl.metadata (6.3 kB)\n",
      "Requirement already satisfied: packaging>=20.0 in /home/saraa/miniconda3/envs/de1-env/lib/python3.10/site-packages (from matplotlib) (25.0)\n",
      "Collecting pillow>=8 (from matplotlib)\n",
      "  Downloading pillow-12.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (8.8 kB)\n",
      "Collecting pyparsing>=3 (from matplotlib)\n",
      "  Downloading pyparsing-3.3.1-py3-none-any.whl.metadata (5.6 kB)\n",
      "Requirement already satisfied: six>=1.5 in /home/saraa/miniconda3/envs/de1-env/lib/python3.10/site-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)\n",
      "Downloading pyarrow-22.0.0-cp310-cp310-manylinux_2_28_x86_64.whl (47.6 MB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m47.6/47.6 MB\u001b[0m \u001b[31m44.5 MB/s\u001b[0m  \u001b[33m0:00:01\u001b[0mm0:00:01\u001b[0m00:01\u001b[0m\n",
      "\u001b[?25hDownloading matplotlib-3.10.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (8.7 MB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m8.7/8.7 MB\u001b[0m \u001b[31m46.7 MB/s\u001b[0m  \u001b[33m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (37.7 MB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m37.7/37.7 MB\u001b[0m \u001b[31m47.2 MB/s\u001b[0m  \u001b[33m0:00:00\u001b[0m eta \u001b[36m0:00:01\u001b[0m\n",
      "\u001b[?25hDownloading contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (325 kB)\n",
      "Downloading cycler-0.12.1-py3-none-any.whl (8.3 kB)\n",
      "Downloading fonttools-4.61.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (4.9 MB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m4.9/4.9 MB\u001b[0m \u001b[31m46.8 MB/s\u001b[0m  \u001b[33m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (1.6 MB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.6/1.6 MB\u001b[0m \u001b[31m32.6 MB/s\u001b[0m  \u001b[33m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading pillow-12.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (7.0 MB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m7.0/7.0 MB\u001b[0m \u001b[31m37.0 MB/s\u001b[0m  \u001b[33m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading pyparsing-3.3.1-py3-none-any.whl (121 kB)\n",
      "Installing collected packages: scipy, pyparsing, pyarrow, pillow, kiwisolver, fonttools, cycler, contourpy, matplotlib\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m9/9\u001b[0m [matplotlib]9\u001b[0m [matplotlib]\n",
      "\u001b[1A\u001b[2KSuccessfully installed contourpy-1.3.2 cycler-0.12.1 fonttools-4.61.1 kiwisolver-1.4.9 matplotlib-3.10.8 pillow-12.0.0 pyarrow-22.0.0 pyparsing-3.3.1 scipy-1.15.3\n",
      "psutil is installed.\n"
     ]
    }
   ],
   "source": [
    "!pip install -U numpy pandas pyarrow matplotlib scipy\n",
    "import sys, subprocess\n",
    "try:\n",
    "    import psutil  # noqa: F401\n",
    "except Exception:\n",
    "    subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"psutil\"])\n",
    "print(\"psutil is installed.\")\n",
    "\n",
    "\n",
    "from IPython.core.magic import register_cell_magic\n",
    "import time, os, platform\n",
    "\n",
    "# Try to import optional modules\n",
    "try:\n",
    "    import psutil\n",
    "except Exception:\n",
    "    psutil = None\n",
    "\n",
    "try:\n",
    "    import resource  # not available on Windows\n",
    "except Exception:\n",
    "    resource = None\n",
    "\n",
    "\n",
    "def _rss_bytes():\n",
    "    \"\"\"Resident Set Size in bytes (cross-platform via psutil if available).\"\"\"\n",
    "    if psutil is not None:\n",
    "        return psutil.Process(os.getpid()).memory_info().rss\n",
    "    # Fallback: unknown RSS → 0 \n",
    "    return 0\n",
    "\n",
    "\n",
    "def _peak_bytes():\n",
    "    \"\"\"\n",
    "    Best-effort peak memory in bytes.\n",
    "    - Windows: psutil peak working set (peak_wset)\n",
    "    - Linux:   resource.ru_maxrss (KB → bytes)\n",
    "    - macOS:   resource.ru_maxrss (bytes)\n",
    "    Fallback to current RSS if unavailable.\n",
    "    \"\"\"\n",
    "    sysname = platform.system()\n",
    "\n",
    "    # Windows path: use psutil peak_wset if present\n",
    "    if sysname == \"Windows\" and psutil is not None:\n",
    "        mi = psutil.Process(os.getpid()).memory_info()\n",
    "        peak = getattr(mi, \"peak_wset\", None)  # should be available on Windows\n",
    "        if peak is not None:\n",
    "            return int(peak)\n",
    "        return int(mi.rss)\n",
    "\n",
    "    # POSIX path: resource may be available\n",
    "    if resource is not None:\n",
    "        try:\n",
    "            ru = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss\n",
    "            # On Linux ru_maxrss is in kilobytes; on macOS/BSD it is bytes\n",
    "            if sysname == \"Linux\":\n",
    "                return int(ru) * 1024\n",
    "            else:\n",
    "                return int(ru)\n",
    "        except Exception:\n",
    "            pass\n",
    "\n",
    "    # Last resort\n",
    "    return _rss_bytes()\n",
    "\n",
    "\n",
    "@register_cell_magic\n",
    "def timemem(line, cell):\n",
    "    \"\"\"\n",
    "    Measure wall time and memory around the execution of this cell.\n",
    "\n",
    "        %%timemem\n",
    "        <your code>\n",
    "\n",
    "    Notes:\n",
    "    - RSS = resident memory after the cell.\n",
    "    - Peak is OS-dependent (see _peak_bytes docstring).\n",
    "    \"\"\"\n",
    "    ip = get_ipython()\n",
    "\n",
    "    rss_before  = _rss_bytes()\n",
    "    peak_before = _peak_bytes()\n",
    "    t0 = time.perf_counter()\n",
    "\n",
    "    # Execute the cell body\n",
    "    result = ip.run_cell(cell)\n",
    "\n",
    "    t1 = time.perf_counter()\n",
    "    rss_after  = _rss_bytes()\n",
    "    peak_after = _peak_bytes()\n",
    "\n",
    "    wall = t1 - t0\n",
    "    rss_delta_mb  = (rss_after  - rss_before)  / (1024 * 1024)\n",
    "    peak_delta_mb = (peak_after - peak_before) / (1024 * 1024)\n",
    "\n",
    "    print(\"======================================\")\n",
    "    print(f\"Wall time: {wall:.3f} s\")\n",
    "    print(f\"RSS Δ: {rss_delta_mb:+.2f} MB\")\n",
    "    print(f\"Peak memory Δ: {peak_delta_mb:+.2f} MB (OS-dependent)\")\n",
    "    print(\"======================================\")\n",
    "\n",
    "    return result"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55796a4b-ef95-4351-b065-5bb435deda6f",
   "metadata": {},
   "source": [
    "The following code snippet should \"just work\" to initialize Spark."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "39fbe369-33b0-478b-ab73-d23ebddaea77",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: findspark in /home/saraa/miniconda3/envs/de1-env/lib/python3.10/site-packages (2.0.1)\n"
     ]
    }
   ],
   "source": [
    "!pip install findspark\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "bf34eee1-079b-454a-9f73-5500c1d9c3ef",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/home/saraa/miniconda3/envs/de1-env/bin/python\n"
     ]
    }
   ],
   "source": [
    "import sys\n",
    "print(sys.executable)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "e8e75035-4490-4dbb-af40-69911b90356e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/home/saraa/miniconda3/envs/de1-env/lib/python3.10/site-packages/pyspark\n"
     ]
    }
   ],
   "source": [
    "import pyspark\n",
    "import os\n",
    "\n",
    "print(os.path.dirname(pyspark.__file__))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "76e415a0-05a2-411a-9d45-d852fd5d32a5",
   "metadata": {},
   "outputs": [],
   "source": [
    "import findspark, os\n",
    "os.environ[\"SPARK_HOME\"] = \"/home/saraa/miniconda3/envs/de1-env/lib/python3.10/site-packages/pyspark\"\n",
    "findspark.init()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "31bc0a5b",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING: Using incubator modules: jdk.incubator.vector\n",
      "Using Spark's default log4j profile: org/apache/spark/log4j2-defaults.properties\n",
      "25/12/25 18:10:56 WARN Utils: Your hostname, hpSARA, resolves to a loopback address: 127.0.1.1; using 10.255.255.254 instead (on interface lo)\n",
      "25/12/25 18:10:56 WARN Utils: Set SPARK_LOCAL_IP if you need to bind to another address\n",
      "Using Spark's default log4j profile: org/apache/spark/log4j2-defaults.properties\n",
      "Setting default log level to \"WARN\".\n",
      "To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).\n",
      "25/12/25 18:10:58 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Spark: 4.0.0\n"
     ]
    }
   ],
   "source": [
    "import findspark, os\n",
    "\n",
    "# Change to path on your local machine.\n",
    "os.environ[\"SPARK_HOME\"] = \"/home/saraa/miniconda3/envs/de1-env/lib/python3.10/site-packages/pyspark\"\n",
    "findspark.init()\n",
    "\n",
    "from pyspark.sql import SparkSession, functions as F, Window\n",
    "from pyspark.sql.functions import broadcast\n",
    "\n",
    "py = sys.executable  # the Python of this notebook (e.g., .../envs/yourenv/bin/python)\n",
    "os.environ[\"PYSPARK_DRIVER_PYTHON\"] = py\n",
    "os.environ[\"PYSPARK_PYTHON\"] = py\n",
    "\n",
    "spark = SparkSession.getActiveSession() or (\n",
    "    SparkSession.builder\n",
    "    .appName(\"A3\")\n",
    "    .master(\"local[*]\")\n",
    "    .config(\"spark.driver.memory\", \"8g\")           \n",
    "    .config(\"spark.sql.shuffle.partitions\",\"400\")\n",
    "    .config(\"spark.sql.adaptive.enabled\", \"true\")\n",
    "    .config(\"spark.pyspark.driver.python\", py)\n",
    "    .config(\"spark.pyspark.python\", py)\n",
    "    .config(\"spark.executorEnv.PYSPARK_PYTHON\", py)\n",
    "    .getOrCreate()\n",
    ")\n",
    "spark.sparkContext.setLogLevel(\"ERROR\")\n",
    "\n",
    "sc = spark.sparkContext\n",
    "print(\"Spark:\", spark.version)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d7e7a530",
   "metadata": {},
   "source": [
    "## 2. Loading DataFrames"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9745a229-813a-463b-beee-7b7284b5ed21",
   "metadata": {},
   "source": [
    "Let's load the DataFrames and print out their schemas:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "f31b3d2c-3dfc-4a63-ba32-f3f5dac21e65",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "root\n",
      " |-- date_key: integer (nullable = true)\n",
      " |-- user_key: integer (nullable = true)\n",
      " |-- age_key: integer (nullable = true)\n",
      " |-- product_key: integer (nullable = true)\n",
      " |-- brand_key: integer (nullable = true)\n",
      " |-- category_key: integer (nullable = true)\n",
      " |-- session_id: string (nullable = true)\n",
      " |-- event_time: timestamp (nullable = true)\n",
      " |-- event_type: string (nullable = true)\n",
      " |-- price: double (nullable = true)\n",
      "\n",
      "root\n",
      " |-- category_code: string (nullable = true)\n",
      " |-- brand_code: string (nullable = true)\n",
      " |-- product_id: integer (nullable = true)\n",
      " |-- product_name: string (nullable = true)\n",
      " |-- product_desc: string (nullable = true)\n",
      " |-- brand_key: integer (nullable = true)\n",
      " |-- category_key: integer (nullable = true)\n",
      " |-- product_key: integer (nullable = true)\n",
      "\n",
      "root\n",
      " |-- brand_code: string (nullable = true)\n",
      " |-- brand_desc: string (nullable = true)\n",
      " |-- brand_key: integer (nullable = true)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Note that you should have defined data_path above\n",
    "\n",
    "events_df   = spark.read.parquet(os.path.join(data_path, \"retail_dw_20250826_events\"))\n",
    "products_df = spark.read.parquet(os.path.join(data_path, \"retail_dw_20250826_products\"))\n",
    "brands_df   = spark.read.parquet(os.path.join(data_path, \"retail_dw_20250826_brands\"))\n",
    "\n",
    "events_df.printSchema()\n",
    "products_df.printSchema()\n",
    "brands_df.printSchema()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "95e9a33b-fc66-47da-8eb6-18554ff19bdf",
   "metadata": {},
   "source": [
    "How many rows are in each table?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "4b70c3a3-4da7-41e9-9e64-ae8f870c29b8",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Number of rows in events   table: 42351862\n",
      "Number of rows in products table: 166794\n",
      "Number of rows in brands   table: 3444\n"
     ]
    }
   ],
   "source": [
    "print(f\"Number of rows in events   table: {events_df.count()}\")\n",
    "print(f\"Number of rows in products table: {products_df.count()}\")\n",
    "print(f\"Number of rows in brands   table: {brands_df.count()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "62b16635-d1c3-4bf9-ba5a-49778d3f1530",
   "metadata": {},
   "source": [
    "We can register the DataFrames as tables and issue SQL queries:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "5bc244b9-b5d5-42f9-8efe-f7c4ea095499",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+--------+\n",
      "|count(1)|\n",
      "+--------+\n",
      "|42351862|\n",
      "+--------+\n",
      "\n",
      "+--------+\n",
      "|count(1)|\n",
      "+--------+\n",
      "|  166794|\n",
      "+--------+\n",
      "\n",
      "+--------+\n",
      "|count(1)|\n",
      "+--------+\n",
      "|    3444|\n",
      "+--------+\n",
      "\n"
     ]
    }
   ],
   "source": [
    "events_df.createOrReplaceTempView(\"events\")\n",
    "products_df.createOrReplaceTempView(\"products\")\n",
    "brands_df.createOrReplaceTempView(\"brands\")\n",
    "\n",
    "spark.sql('select count(*) from events').show()\n",
    "spark.sql('select count(*) from products').show()\n",
    "spark.sql('select count(*) from brands').show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e11d7aad-277f-495f-965f-831c8d247e4a",
   "metadata": {},
   "source": [
    "As a sanity check, the corresponding values should match: counting the rows in the DataFrame vs. issuing an SQL query to count the number of rows."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "70a37092",
   "metadata": {},
   "source": [
    "## 3. Data Science"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b93689a-e015-4007-9126-1e10e75d8dfa",
   "metadata": {},
   "source": [
    "Answer Q1 to Q7 below with SQL queries and DataFrame manipulations.\n",
    "\n",
    "**write some code here**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7cf85258",
   "metadata": {},
   "source": [
    "### 3.1 Q1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7393de9c-1479-480c-9948-881f368a19e3",
   "metadata": {},
   "source": [
    "For session_id `789d3699-028e-4367-b515-b82e2cb5225f`, what was the purchase price?\n",
    "\n",
    "**Hint:** We only care about purchase events.\n",
    "\n",
    "First, do it using SQL:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "794fe1d9-5eeb-4cbc-9fb2-5a44b0471da4",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[Stage 23:============================>                            (8 + 8) / 16]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+------+\n",
      "| price|\n",
      "+------+\n",
      "|100.39|\n",
      "+------+\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    }
   ],
   "source": [
    "\n",
    "# codecell_31a (keep this id for tracking purposes)\n",
    "\n",
    "# Write your SQL below\n",
    "sql_query = f\"\"\"\n",
    "SELECT price\n",
    "FROM events\n",
    "WHERE session_id = '789d3699-028e-4367-b515-b82e2cb5225f'\n",
    "  AND event_type = 'purchase'\n",
    "\"\"\"\n",
    "\n",
    "\n",
    "results = spark.sql(sql_query)\n",
    "\n",
    "results.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be5b441e-741c-47bd-afea-26b6e259c4ad",
   "metadata": {},
   "source": [
    "Next, do it with DataFrames:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "d74d4457-1f5d-4a90-8974-edc78417c5e5",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[Stage 26:=============================================>          (13 + 3) / 16]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+------+\n",
      "| price|\n",
      "+------+\n",
      "|100.39|\n",
      "+------+\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    }
   ],
   "source": [
    "\n",
    "# codecell_31b (keep this id for tracking purposes)\n",
    "\n",
    "# TODO: Write your code below, but do not remove any lines already in this cell.\n",
    "results_df = (\n",
    "    events_df\n",
    "    .filter(\n",
    "        (F.col(\"session_id\") == \"789d3699-028e-4367-b515-b82e2cb5225f\") &\n",
    "        (F.col(\"event_type\") == \"purchase\")\n",
    "    )\n",
    "    .select(\"price\")\n",
    ")\n",
    "\n",
    "\n",
    "results_df.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b87aa67",
   "metadata": {},
   "source": [
    "### 3.2 Q2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "31c4f80c-4d3e-4ed5-8a6a-065f7631739f",
   "metadata": {},
   "source": [
    "How many products are sold by the brand \"sokolov\"?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b86f5c8-4dd2-4d9f-9216-ff3a34337fc8",
   "metadata": {},
   "source": [
    "First, do it using SQL:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "6f8b9b4b-81de-4292-bbc8-34f66c3b2cbc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+------------+\n",
      "|num_products|\n",
      "+------------+\n",
      "|        1601|\n",
      "+------------+\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# codecell_32a (keep this id for tracking purposes)\n",
    "\n",
    "sql_query = f\"\"\"\n",
    "SELECT COUNT(*) AS num_products\n",
    "FROM products\n",
    "WHERE brand_code = 'sokolov'\n",
    "\"\"\"\n",
    "\n",
    "results = spark.sql(sql_query)\n",
    "results.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "53249ee1-99aa-41e5-a484-c75342147fd8",
   "metadata": {},
   "source": [
    "Next, do it with DataFrames:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "585b3df4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1601\n"
     ]
    }
   ],
   "source": [
    "\n",
    "# codecell_32b (keep this id for tracking purposes)\n",
    "\n",
    "# TODO: Write your code below, but do not remove any lines already in this cell.\n",
    "\n",
    "results_df = ( \n",
    "    products_df \n",
    "    .filter(F.col(\"brand_code\") == \"sokolov\") \n",
    "    .count() ) \n",
    "print(results_df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8cc4800c",
   "metadata": {},
   "source": [
    "### 3.3 Q3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20e553ce-aa6d-416e-a0f3-6d5b5fe160a0",
   "metadata": {},
   "source": [
    "What is the average purchase price of items purchased from the brand \"febest\"? (Report answer to two digits after the decimal point, i.e., XX.XX.)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a53d59f4-9d27-4b03-9935-aa3aaa797a8d",
   "metadata": {},
   "source": [
    "First, do it using SQL:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "19187c9f-06ce-4364-bf7b-998797ca6e09",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[Stage 34:=====================================================>  (20 + 1) / 21]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+------------------+\n",
      "|avg_purchase_price|\n",
      "+------------------+\n",
      "|             20.39|\n",
      "+------------------+\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    }
   ],
   "source": [
    "\n",
    "# codecell_33a (keep this id for tracking purposes)\n",
    "\n",
    "sql_query = f\"\"\"\n",
    "SELECT ROUND(AVG(e.price), 2) AS avg_purchase_price\n",
    "FROM events e\n",
    "JOIN products p\n",
    "    ON e.product_key = p.product_key\n",
    "WHERE e.event_type = 'purchase'\n",
    "  AND p.brand_code = 'febest'\n",
    "\"\"\"\n",
    "\n",
    "results = spark.sql(sql_query)\n",
    "results.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "695d1667-6646-4e1b-be7a-c18228861857",
   "metadata": {},
   "source": [
    "Next, do it with DataFrames:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "2eade2c0",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[Stage 38:========================================>               (15 + 6) / 21]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+------------------+\n",
      "|avg_purchase_price|\n",
      "+------------------+\n",
      "|             20.39|\n",
      "+------------------+\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    }
   ],
   "source": [
    "# codecell_33b (keep this id for tracking purposes)\n",
    "\n",
    "results_df = (\n",
    "    events_df.alias(\"e\")\n",
    "    .join(products_df.alias(\"p\"), F.col(\"e.product_key\") == F.col(\"p.product_key\"))\n",
    "    .filter(\n",
    "        (F.col(\"e.event_type\") == \"purchase\") &\n",
    "        (F.col(\"p.brand_code\") == \"febest\")\n",
    "    )\n",
    "    .select(F.round(F.avg(\"e.price\"), 2).alias(\"avg_purchase_price\"))\n",
    ")\n",
    "\n",
    "results_df.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1394c731",
   "metadata": {},
   "source": [
    "### 3.4 Q4"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "66317c46-94f7-459d-a99b-5e1bba29c6cf",
   "metadata": {},
   "source": [
    "What is the average number of events per user? (Report answer to two digits after the decimal point, i.e., XX.XX.)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c0888b2-4961-458f-8f58-c9bf29a360dc",
   "metadata": {},
   "source": [
    "First, do it using SQL:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "0dab447c-b909-4155-a925-d1b01e3420e2",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[Stage 43:==============>                                          (3 + 9) / 12]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+-------------------+\n",
      "|avg_events_per_user|\n",
      "+-------------------+\n",
      "|              14.02|\n",
      "+-------------------+\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    }
   ],
   "source": [
    "# codecell_34a (keep this id for tracking purposes)\n",
    "\n",
    "sql_query = f\"\"\"\n",
    "SELECT ROUND(AVG(event_count), 2) AS avg_events_per_user\n",
    "FROM (\n",
    "    SELECT user_key, COUNT(*) AS event_count\n",
    "    FROM events\n",
    "    GROUP BY user_key\n",
    ")\n",
    "\"\"\"\n",
    "\n",
    "results = spark.sql(sql_query)\n",
    "results.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "83ea6bc1-5ee3-4951-8c3c-0c326ebb3b2b",
   "metadata": {},
   "source": [
    "Next, do it with DataFrames:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "b9c5696b",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[Stage 47:==================================>                     (13 + 8) / 21]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+-------------------+\n",
      "|avg_events_per_user|\n",
      "+-------------------+\n",
      "|              14.02|\n",
      "+-------------------+\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    }
   ],
   "source": [
    "# codecell_34b (keep this id for tracking purposes)\n",
    "\n",
    "user_event_counts = (\n",
    "    events_df\n",
    "    .groupBy(\"user_key\")\n",
    "    .count()\n",
    "    .withColumnRenamed(\"count\", \"event_count\")\n",
    ")\n",
    "\n",
    "results_df = user_event_counts.select(\n",
    "    F.round(F.avg(\"event_count\"), 2).alias(\"avg_events_per_user\")\n",
    ")\n",
    "\n",
    "results_df.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ddcd30e9",
   "metadata": {},
   "source": [
    "### 3.5 Q5"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7e011a70-e6e3-46fc-8ff7-041a919dca2b",
   "metadata": {},
   "source": [
    "What are the top 10 (`product_name`, `brand_code`) pairs in terms of revenue? We want the answer rows sorted by revenue in descending order."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f18ca411-eef0-45cf-bd92-99ece1d89510",
   "metadata": {},
   "source": [
    "First, do it using SQL:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "b9d4e788-9576-4122-9d5a-8c1532feef36",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[Stage 54:================================================>       (18 + 3) / 21]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+------------+----------+--------------------+\n",
      "|product_name|brand_code|revenue             |\n",
      "+------------+----------+--------------------+\n",
      "|smartphone  |apple     |1.671134080299983E8 |\n",
      "|smartphone  |samsung   |9.54662750799999E7  |\n",
      "|smartphone  |xiaomi    |2.2549726339999955E7|\n",
      "|NULL        |NULL      |1.673241267000003E7 |\n",
      "|smartphone  |huawei    |1.363398708999999E7 |\n",
      "|video.tv    |samsung   |1.2209992470000006E7|\n",
      "|smartphone  |NULL      |1.1997126250000002E7|\n",
      "|NULL        |lucente   |9556989.319999998   |\n",
      "|notebook    |acer      |8963128.649999993   |\n",
      "|clocks      |apple     |8622900.639999997   |\n",
      "+------------+----------+--------------------+\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    }
   ],
   "source": [
    "# codecell_35a (keep this id for tracking purposes)\n",
    "\n",
    "sql_query = f\"\"\"\n",
    "SELECT \n",
    "    p.product_name,\n",
    "    p.brand_code,\n",
    "    SUM(e.price) AS revenue\n",
    "FROM events e\n",
    "JOIN products p\n",
    "    ON e.product_key = p.product_key\n",
    "WHERE e.event_type = 'purchase'\n",
    "GROUP BY p.product_name, p.brand_code\n",
    "ORDER BY revenue DESC\n",
    "LIMIT 10\n",
    "\"\"\"\n",
    "\n",
    "results = spark.sql(sql_query)\n",
    "results.show(truncate=False)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fbcfe9ba-cd8b-4482-9d9d-c5d0501940bf",
   "metadata": {},
   "source": [
    "Next, do it with DataFrames:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "1267c2a4",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[Stage 58:========================================>               (15 + 6) / 21]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+------------+----------+--------------------+\n",
      "|product_name|brand_code|revenue             |\n",
      "+------------+----------+--------------------+\n",
      "|smartphone  |apple     |1.671134080299983E8 |\n",
      "|smartphone  |samsung   |9.54662750799999E7  |\n",
      "|smartphone  |xiaomi    |2.2549726339999955E7|\n",
      "|NULL        |NULL      |1.673241267000003E7 |\n",
      "|smartphone  |huawei    |1.363398708999999E7 |\n",
      "|video.tv    |samsung   |1.2209992470000006E7|\n",
      "|smartphone  |NULL      |1.1997126250000002E7|\n",
      "|NULL        |lucente   |9556989.319999998   |\n",
      "|notebook    |acer      |8963128.649999993   |\n",
      "|clocks      |apple     |8622900.639999997   |\n",
      "+------------+----------+--------------------+\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    }
   ],
   "source": [
    "# codecell_35b (keep this id for tracking purposes)\n",
    "\n",
    "results_df = (\n",
    "    events_df.alias(\"e\")\n",
    "    .join(products_df.alias(\"p\"), F.col(\"e.product_key\") == F.col(\"p.product_key\"))\n",
    "    .filter(F.col(\"e.event_type\") == \"purchase\")\n",
    "    .groupBy(\"p.product_name\", \"p.brand_code\")\n",
    "    .agg(F.sum(\"e.price\").alias(\"revenue\"))\n",
    "    .orderBy(F.col(\"revenue\").desc())\n",
    "    .limit(10)\n",
    ")\n",
    "\n",
    "results_df.show(truncate=False)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ab771d9",
   "metadata": {},
   "source": [
    "### 3.6 Q6"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "457fcb44-d4a3-4e6e-8dc9-25cc1186b641",
   "metadata": {},
   "source": [
    "Tally up counts of events by hour.\n",
    "More precisely, we want a table with hours 0, 1, ... 23 with the counts of events in that hour."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "36a15e7a-fed4-4229-86a4-5e57d0390581",
   "metadata": {},
   "source": [
    "First, do it using SQL:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "b8436405-44fc-4852-9f7a-ad13e26623dc",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[Stage 61:==========================================>             (16 + 5) / 21]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+----+-------+\n",
      "|hour|  count|\n",
      "+----+-------+\n",
      "|   0| 263808|\n",
      "|   1| 223635|\n",
      "|   2| 353509|\n",
      "|   3| 623434|\n",
      "|   4|1137209|\n",
      "|   5|1605037|\n",
      "|   6|1955461|\n",
      "|   7|2131930|\n",
      "|   8|2269469|\n",
      "|   9|2332649|\n",
      "|  10|2380185|\n",
      "|  11|2335494|\n",
      "|  12|2282992|\n",
      "|  13|2181477|\n",
      "|  14|2171196|\n",
      "|  15|2407266|\n",
      "|  16|2717710|\n",
      "|  17|2988054|\n",
      "|  18|3008559|\n",
      "|  19|2631424|\n",
      "|  20|1999466|\n",
      "|  21|1244129|\n",
      "|  22| 694728|\n",
      "|  23| 413041|\n",
      "+----+-------+\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    }
   ],
   "source": [
    "# codecell_36a (keep this id for tracking purposes)\n",
    "\n",
    "sql_query = f\"\"\"\n",
    "SELECT \n",
    "    HOUR(event_time) AS hour,\n",
    "    COUNT(*) AS count\n",
    "FROM events\n",
    "GROUP BY HOUR(event_time)\n",
    "ORDER BY hour\n",
    "\"\"\"\n",
    "\n",
    "results = spark.sql(sql_query)\n",
    "results.show(24)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40a5385c-10b5-4768-a88d-d0733ec0d64e",
   "metadata": {},
   "source": [
    "Next, do it with DataFrames:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "36fc5060-a437-48f5-aa50-8602e9f1ef9c",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[Stage 64:========================================>               (15 + 6) / 21]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+----+-------+\n",
      "|hour|  count|\n",
      "+----+-------+\n",
      "|   0| 263808|\n",
      "|   1| 223635|\n",
      "|   2| 353509|\n",
      "|   3| 623434|\n",
      "|   4|1137209|\n",
      "|   5|1605037|\n",
      "|   6|1955461|\n",
      "|   7|2131930|\n",
      "|   8|2269469|\n",
      "|   9|2332649|\n",
      "|  10|2380185|\n",
      "|  11|2335494|\n",
      "|  12|2282992|\n",
      "|  13|2181477|\n",
      "|  14|2171196|\n",
      "|  15|2407266|\n",
      "|  16|2717710|\n",
      "|  17|2988054|\n",
      "|  18|3008559|\n",
      "|  19|2631424|\n",
      "|  20|1999466|\n",
      "|  21|1244129|\n",
      "|  22| 694728|\n",
      "|  23| 413041|\n",
      "+----+-------+\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    }
   ],
   "source": [
    "# codecell_36b (keep this id for tracking purposes)\n",
    "\n",
    "events_by_hour_df = (\n",
    "    events_df\n",
    "    .withColumn(\"hour\", F.hour(\"event_time\"))\n",
    "    .groupBy(\"hour\")\n",
    "    .count()\n",
    "    .orderBy(\"hour\")\n",
    ")\n",
    "\n",
    "events_by_hour_df.show(24)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7b294736-e759-4942-939d-5f8545f1ea89",
   "metadata": {},
   "source": [
    "When you run the cell above, `events_by_hour_df` should be something like:\n",
    "\n",
    "```\n",
    "+----+-------+\n",
    "|hour|  count|\n",
    "+----+-------+\n",
    "|   0|    ???|\n",
    "|   1|    ???|\n",
    "  ...\n",
    "|  23|    ???|\n",
    "+----+-------+\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "42738a71-361d-4508-89ef-a5c255f1d505",
   "metadata": {},
   "source": [
    "Now plot the above DataFrame using `matplotlib`.\n",
    "Here we want a line graph, with hour on the _x_ axis and count on the _y_ axis.\n",
    "\n",
    "**Hint:** use the code below to get started."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "b1bcafdc",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAA04AAAHWCAYAAABACtmGAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAfHZJREFUeJzt3Xlc1NX+x/HXzLCJbAKyqKi4i7hvkWWWe2XZaovX9nszvS223PrVTc17W263a3Ur2zUzS9v02oKSuWTu4ob7guLCIiCLIDDMzO8PhCJAQBm+LO/n48Htzne+M/MePYx8OOf7OSaHw+FAREREREREKmQ2OoCIiIiIiEhdp8JJRERERESkEiqcREREREREKqHCSUREREREpBIqnERERERERCqhwklERERERKQSKpxEREREREQqocJJRERERESkEiqcREREREREKqHCSUREREREpBIqnEREGqE5c+ZgMpkq/Fq/fr3REVm7di3Tpk0jIyPD6Cg1ymQyMXny5HLvK/572bx5cy2nEhGRyrgYHUBERIzzwgsvEB4eXuZ4hw4dDEhT2tq1a5k+fTp33303fn5+RscREZFGToWTiEgjNnr0aPr162d0jAYlLy8PNzc3zOb6s6jDbrdTUFCAh4eH0VFEROqs+vOpLiIitcpqteLv788999xT5r6srCw8PDx44oknSo7l5+czdepUOnTogLu7O2FhYTz11FPk5+eXemzxUrVFixYRGRmJu7s73bp1Izo6uuScadOm8eSTTwIQHh5esoTwyJEjAMTExHDZZZfh5+eHl5cXnTt35v/+7/8qfU/Fr/3ZZ5/RuXNnPDw86Nu3L6tXry5z7okTJ7j33nsJDg4uyfjxxx+XOmflypWYTCa++OILnnvuOVq2bImnpydZWVmVZqmOn3/+mcsvv5ymTZvi5+fH9ddfz549e0qdc/fdd9O2bdsyj502bRomk6nUsd//OXTr1g13d/dSf/4iIlKWZpxERBqxzMxMUlNTSx0zmUwEBATg6urKDTfcwDfffMN7772Hm5tbyTmLFi0iPz+f2267DSiasbjuuutYs2YNf/7zn+natSs7d+5k5syZ7N+/n0WLFpV6jTVr1vDNN9/w0EMP4e3tzZtvvslNN91EQkICAQEB3Hjjjezfv5/PP/+cmTNnEhgYCEDz5s3ZtWsX1157LT169OCFF17A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4PPz85k7d27JeR999BF9+/Zl37595V7MKyJSlzz//PPlHp89e3apwunSSy8lLCyMY8eOlZltgqJZqUWLFjFz5kzmzp3Lt99+i6enJ+3ateORRx4p81laFf3792fGjBm8++67REdHY7fbiY+P57rrruPIkSN8/PHHpKamEhgYyBVXXMH06dPx9fWt9HmvuOIKoqKimD59OgkJCURERDBnzpxS1y0FBwezceNGXnjhBb755hveeecdAgIC6NatG6+88kq138vFGDZsGNHR0UydOpXnn38eV1dXrrjiCl555ZVSy8MDAgL49ttvmTJlCk899RTh4eG89NJLHDhwQIWTiEgNMDmqciVuI2AymUp11VuwYAF33nknu3btKnOhsZeXFyEhIUydOpUXX3wRq9Vact/Zs2fx9PRk2bJlDB8+vDbfgoiIVMJkMjFp0iTeeusto6OIiEg9oxmnCvTu3RubzUZKSgqXX355uecMGjSIwsJCDh06VLIfyf79+4HfLugVEREREZH6r1EXTmfOnOHgwYMlt+Pj49m2bRv+/v506tSJO++8kwkTJvDaa6/Ru3dvTp06xfLly+nRowfXXHMNw4YNo0+fPtx77728/vrr2O12Jk2axPDhwy9oWYqIiIiIiNRNjbqr3ubNm+nduze9e/cGYMqUKfTu3btkzf/s2bOZMGECjz/+OJ07d2bs2LFs2rSpZONGs9nMkiVLCAwMZPDgwVxzzTV07dqVL774wrD3JCIiIiIiNU/XOImIiIiIiFSiUc84iYiIiIiIVIUKJxERERERkUo0uuYQdrudkydP4u3tjclkMjqOiIiIiIgYxOFwkJ2dTYsWLTCbzz+n1OgKp5MnTxIWFmZ0DBERERERqSOOHTtGq1atzntOoyucvL29gaI/HB8fH4PTgNVqZdmyZYwYMQJXV1ej40gDpDEmzqYxJs6mMSbOpjHWeGVlZREWFlZSI5xPoyucipfn+fj41JnCydPTEx8fH32jilNojImzaYyJs2mMibNpjElVLuFRcwgREREREZFKqHASERERERGphAonERERERGRSqhwEhERERERqYQKJxERERERkUqocBIREREREamECicREREREZFKqHASERERERGphAonERERERGRSrgYHUBERERE6gab3cHG+HRSsvMI8vZgQLg/FrPJ6FgidYIKJxEREREhOi6R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CRR9jrm5ueHn51fqXH2OSTEXowOISM0bPXp0yf/v0aMHAwcOpE2bNixcuJD77rvPwGQiIhfu90s+u3fvTo8ePWjfvj0rV65k6NChBiaT+mbSpEnExcXp+l+pFs04GSAwMBCLxVKmS0tycjIhISEGpZKGzM/Pj06dOnHw4EGjo0gDVfzZpc81qU3t2rUjMDBQn21SLZMnT+a7775jxYoVtGrVquR4SEgIBQUFZGRklDpfn2NSTIWTAdzc3Ojbty/Lly8vOWa321m+fDlRUVEGJpOG6syZMxw6dIjQ0FCjo0gDFR4eTkhISKnPtaysLDZs2KDPNXGa48ePk5aWps82qRKHw8HkyZP59ttv+fnnnwkPDy91f9++fXF1dS31ObZv3z4SEhL0OSaAluoZZsqUKdx1113069ePAQMG8Prrr5OTk8M999xjdDRpAJ544gnGjBlDmzZtOHnyJFOnTsVisXD77bcbHU3qsTNnzpT6zX58fDzbtm3D39+f1q1b8+ijj/KPf/yDjh07Eh4ezt///ndatGhRqiuayPmcb4z5+/szffp0brrpJkJCQjh06BBPPfUUHTp0YOTIkQamlvpi0qRJzJ8/n8WLF+Pt7V1y3ZKvry9NmjTB19eX++67jylTpuDv74+Pjw9//etfiYqKUkc9KeIQw/z3v/91tG7d2uHm5uYYMGCAY/369UZHkgZi3LhxjtDQUIebm5ujZcuWjnHjxjkOHjxodCyp51asWOEAynzdddddDofD4bDb7Y6///3vjuDgYIe7u7tj6NChjn379hkbWuqV842x3Nxcx4gRIxzNmzd3uLq6Otq0aeN44IEHHElJSUbHlnqivLEFOGbPnl1yztmzZx0PPfSQo1mzZg5PT0/HDTfc4EhMTDQutNQp2sdJRERERESkErrGSUREREREpBIqnERERERERCqhwklERERERKQSKpxEREREREQqocJJRERERESkEiqcREREREREKqHCSUREREREpBIqnERERERERCqhwklEROQP3n//fcLCwjCbzbz++utGxxERkTpAhZOIiDjF3XffzdixY8scX7lyJSaTiYyMjFrPVBVZWVlMnjyZv/3tb5w4cYI///nP5Z5nMplKvpo2bUrHjh25++672bJlSy0nFhGR2qDCSUREGiSr1XpBj0tISMBqtXLNNdcQGhqKp6dnhefOnj2bxMREdu3axdtvv82ZM2cYOHAgc+fOvdDYIiJSR6lwEhERw3399dd069YNd3d32rZty2uvvVbqfpPJxKJFi0od8/PzY86cOQAcOXIEk8nEggULuOKKK/Dw8OCzzz4r97USEhK4/vrr8fLywsfHh1tvvZXk5GQA5syZQ/fu3QFo164dJpOJI0eOVJjbz8+PkJAQ2rZty4gRI/jqq6+48847mTx5MqdPnwYgLS2N22+/nZYtW+Lp6Un37t35/PPPS55j7ty5BAQEkJ+fX+q5x44dy5/+9KdK/+xERKR2qHASERFDbdmyhVtvvZXbbruNnTt3Mm3aNP7+97+XFEXV8fTTT/PII4+wZ88eRo4cWeZ+u93O9ddfT3p6OqtWrSImJobDhw8zbtw4AMaNG8dPP/0EwMaNG0lMTCQsLKxaGR577DGys7OJiYkBIC8vj759+/L9998TFxfHn//8Z/70pz+xceNGAG655RZsNhv/+9//Sp4jJSWF77//nnvvvbfafwYiIuIcLkYHEBGRhuu7777Dy8ur1DGbzVbq9n/+8x+GDh3K3//+dwA6derE7t27efXVV7n77rur9XqPPvooN954Y4X3L1++nJ07dxIfH19SEM2dO5du3bqxadMm+vfvT0BAAADNmzcnJCSkWq8P0KVLF4CSmaqWLVvyxBNPlNz/17/+laVLl7Jw4UIGDBhAkyZNuOOOO5g9eza33HILAPPmzaN169YMGTKk2q8vIiLOoRknERFxmiuvvJJt27aV+vrwww9LnbNnzx4GDRpU6tigQYM4cOBAmSKrMv369Tvv/Xv27CEsLKzULFJERAR+fn7s2bOnWq9VEYfDARQtL4SiQnHGjBl0794df39/vLy8WLp0KQkJCSWPeeCBB1i2bBknTpwAipYM3n333SXPISIixtOMk4iIOE3Tpk3p0KFDqWPHjx+v9vOYTKaSgqRYec0fmjZtWu3nrmnFBVh4eDgAr776Km+88Qavv/463bt3p2nTpjz66KMUFBSUPKZ379707NmTuXPnMmLECHbt2sX3339vSH4RESmfCicRETFU165d+fXXX0sd+/XXX+nUqRMWiwUoWjaXmJhYcv+BAwfIzc29oNc6duwYx44dK5l12r17NxkZGURERFzEu/jN66+/jo+PD8OGDQOK3sv111/P+PHjgaLrrPbv31/m9e6//35ef/11Tpw4wbBhw6p9bZWIiDiXluqJiIihHn/8cZYvX86MGTPYv38/n3zyCW+99Vap64Kuuuoq3nrrLbZu3crmzZt58MEHcXV1rfZrDRs2jO7du3PnnXcSGxvLxo0bmTBhAldccUWly/zKk5GRQVJSEkePHiUmJoabb76Z+fPnM2vWLPz8/ADo2LEjMTExrF27lj179vCXv/ylpIvf791xxx0cP36cDz74QE0hRETqIBVOIiJiqD59+rBw4UK++OILIiMjef7553nhhRdKNYZ47bXXCAsL4/LLL+eOO+7giSeeOO/+ShUxmUwsXryYZs2aMXjwYIYNG0a7du1YsGDBBWW/5557CA0NpUuXLkycOBEvLy82btzIHXfcUXLOc889R58+fRg5ciRDhgwhJCSk3I2BfX19uemmm/Dy8ir3fhERMZbJ8cdF4yIiImKIoUOH0q1bN958802jo4iIyB+ocBIRETHY6dOnWblyJTfffDO7d++mc+fORkcSEZE/UHMIERERg/Xu3ZvTp0/zyiuvqGgSEamjNOMkIiIiIiJSCTWHEBERERERqYQKJxERERERkUqocBIREREREamECicREREREZFKqHASERERERGphAonERERERGRSqhwEhERERERqYQKJxERERERkUr8P2UICLIN23j2AAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# codecell_36c (keep this id for tracking purposes)\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "events_by_hour_pdf = events_by_hour_df.toPandas()\n",
    "\n",
    "plt.figure(figsize=(10,5))\n",
    "plt.plot(events_by_hour_pdf[\"hour\"], events_by_hour_pdf[\"count\"], marker=\"o\")\n",
    "plt.xlabel(\"Hour of Day\")\n",
    "plt.ylabel(\"Event Count\")\n",
    "plt.title(\"Events per Hour\")\n",
    "plt.grid(True)\n",
    "\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10445d63",
   "metadata": {},
   "source": [
    "### 3.7 Q7"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "498671fc-16a7-4da2-b9ea-2a18a0564d03",
   "metadata": {},
   "source": [
    "We are going to analyze the \"big\" brands. Find out the average purchase price by brand, and restrict to cases where the average is more than 10K.\n",
    "We want the results sorted by the average purchase price from the largest to smallest value.\n",
    "(Report answers to two digits after the decimal point, i.e., XX.XX, but it's okay if the output only contains one digit after the decimal point.)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ba375938-7e73-4512-8939-b289c78a89d3",
   "metadata": {},
   "source": [
    "First, do it using SQL:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "eec28857-1a72-4541-8260-d28bcfb28dad",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[Stage 76:==================================>                     (13 + 8) / 21]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+----------+---------+\n",
      "|brand_code|avg_price|\n",
      "+----------+---------+\n",
      "|      adam|  58946.0|\n",
      "|      kona|  43759.0|\n",
      "|  yuandong|  35329.0|\n",
      "|   bentley|  23164.0|\n",
      "|      otex| 18633.14|\n",
      "|    suunto| 10732.82|\n",
      "|     stark| 10400.25|\n",
      "+----------+---------+\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    }
   ],
   "source": [
    "# codecell_37a (keep this id for tracking purposes)\n",
    "\n",
    "sql_query = f\"\"\"\n",
    "SELECT \n",
    "    p.brand_code,\n",
    "    ROUND(AVG(e.price), 2) AS avg_price\n",
    "FROM events e\n",
    "JOIN products p\n",
    "    ON e.product_key = p.product_key\n",
    "WHERE e.event_type = 'purchase'\n",
    "GROUP BY p.brand_code\n",
    "HAVING AVG(e.price) > 10000\n",
    "ORDER BY avg_price DESC\n",
    "\"\"\"\n",
    "\n",
    "results = spark.sql(sql_query)\n",
    "results.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "03a9495a-d3f5-4ae8-9698-3e24499d2696",
   "metadata": {},
   "source": [
    "Next, do it with DataFrames:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "66223d77-75b5-4168-b656-149c32561e74",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[Stage 80:==================================>                     (13 + 8) / 21]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+----------+---------+\n",
      "|brand_code|avg_price|\n",
      "+----------+---------+\n",
      "|      adam|  58946.0|\n",
      "|      kona|  43759.0|\n",
      "|  yuandong|  35329.0|\n",
      "|   bentley|  23164.0|\n",
      "|      otex| 18633.14|\n",
      "|    suunto| 10732.82|\n",
      "|     stark| 10400.25|\n",
      "+----------+---------+\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    }
   ],
   "source": [
    "# codecell_37b (keep this id for tracking purposes)\n",
    "\n",
    "avg_price_by_brand_df = (\n",
    "    events_df.alias(\"e\")\n",
    "    .join(products_df.alias(\"p\"), F.col(\"e.product_key\") == F.col(\"p.product_key\"))\n",
    "    .filter(F.col(\"e.event_type\") == \"purchase\")\n",
    "    .groupBy(\"p.brand_code\")\n",
    "    .agg(F.avg(\"e.price\").alias(\"avg_price\"))\n",
    "    .filter(F.col(\"avg_price\") > 10000)\n",
    "    .select(\"brand_code\", F.round(\"avg_price\", 2).alias(\"avg_price\"))\n",
    "    .orderBy(F.col(\"avg_price\").desc())\n",
    ")\n",
    "\n",
    "avg_price_by_brand_df.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8cb9ef73-ac15-4895-a932-09a48f6b3e2d",
   "metadata": {},
   "source": [
    "When you run the cell above, `avg_price_by_brand_df` should be something like:\n",
    "\n",
    "```\n",
    "+----------+------------------+\n",
    "|brand_code|         avg_price|\n",
    "+----------+------------------+\n",
    "|       ???|               ???|\n",
    "        ...\n",
    "|       ???|               ???|\n",
    "+----------+------------------+\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d2546c5d-7c52-4910-8af6-bd6373e6b6c8",
   "metadata": {},
   "source": [
    "Now plot the above DataFrame using `matplotlib`.\n",
    "Here we want a bar chart, with each of the brands as a bar, and the average price on the _y_ axis.\n",
    "\n",
    "**Hint:** use the code below to get started."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "af0887f5",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "data": {
      "image/png": "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wcuXL1GvXj0sWrQIlSpVkvrEx8djwIAB2LNnDzQ0NODt7Y158+ahcOHCUp8rV67Az88P58+fR/HixTFw4ECMHDlSqZYtW7bg119/xb1791CxYkXMnDkTTZs2zdHz4LL3REREREQEqJYNZA9k+UVeC2TlAvbJXUK+cW+6l9wlEBEREdF3RJVsIOuURSIiIiIiooKMgYyIiIiIiEgmDGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQykTWQjR8/HgqFQulflSpVpO3v3r2Dn58fihUrhsKFC8Pb2xtxcXFK+7h//z68vLygr6+PEiVK4JdffkFaWppSn2PHjqFmzZrQ0dGBtbU1Vq5cmaWWhQsXoly5ctDV1YWTkxPOnTuXK8+ZiIiIiIgok+xHyKpWrYqYmBjp36lTp6RtQ4cOxZ49e7BlyxYcP34cjx8/RqtWraTt6enp8PLyQkpKCk6fPo1Vq1Zh5cqVGDt2rNQnOjoaXl5ecHNzQ3h4OIYMGYKePXvi4MGDUp9NmzbB398f48aNw8WLF1GjRg14enriyZMn3+ZFICIiIiKiAkkhhBByPfj48eOxc+dOhIeHZ9n26tUrmJqaYv369WjdujUAICoqCjY2NggNDYWzszMOHDiAZs2a4fHjxzAzMwMABAUFYeTIkXj69Cm0tbUxcuRI7Nu3D5GRkdK+fXx88PLlSwQHBwMAnJycULt2bSxYsAAAkJGRAUtLSwwcOBABAQE5ei4JCQkwNjbGq1evYGRk9DUvi1qUC9gndwn5xr3pXnKXQERERETfEVWyQaFvVNMn3bp1CyVLloSuri5cXFwwbdo0lClTBmFhYUhNTYW7u7vUt0qVKihTpowUyEJDQ2FnZyeFMQDw9PREv379cPXqVTg4OCA0NFRpH5l9hgwZAgBISUlBWFgYRo0aJW3X0NCAu7s7QkNDP1l3cnIykpOTpdsJCQkAgNTUVKSmpn7Va6IOOpqy5ex8Jy+MJxERERF9P1T5/ChrIHNycsLKlStRuXJlxMTEYMKECfjhhx8QGRmJ2NhYaGtrw8TEROk+ZmZmiI2NBQDExsYqhbHM7Znb/q1PQkICkpKS8OLFC6Snp2fbJyoq6pO1T5s2DRMmTMjSfujQIejr6+fsBchFM+vIXUH+sX//frlLICIiIqLvyNu3b3PcV9ZA9uOPP0r/r169OpycnFC2bFls3rwZenp6Mlb2eaNGjYK/v790OyEhAZaWlvDw8MgTUxarjT/4+U6UI5HjPeUugYiIiIi+I5mz53JC9imLHzIxMUGlSpVw+/ZtNG7cGCkpKXj58qXSUbK4uDiYm5sDAMzNzbOshpi5CuOHfT5emTEuLg5GRkbQ09ODpqYmNDU1s+2TuY/s6OjoQEdHJ0u7lpYWtLS0cv6kc0lyukLuEvKNvDCeRERERPT9UOXzo+yrLH4oMTERd+7cgYWFBWrVqgUtLS0cPnxY2n7jxg3cv38fLi4uAAAXFxdEREQorYYYEhICIyMj2NraSn0+3Edmn8x9aGtro1atWkp9MjIycPjwYakPERERERFRbpA1kA0fPhzHjx/HvXv3cPr0afz888/Q1NRE+/btYWxsjB49esDf3x9Hjx5FWFgYunXrBhcXFzg7OwMAPDw8YGtri86dO+Py5cs4ePAgfv31V/j5+UlHr/r27Yu7d+9ixIgRiIqKwqJFi7B582YMHTpUqsPf3x9Lly7FqlWrcP36dfTr1w9v3rxBt27dZHldiIiIiIioYJB1yuLDhw/Rvn17PH/+HKampqhXrx7OnDkDU1NTAMCcOXOgoaEBb29vJCcnw9PTE4sWLZLur6mpib1796Jfv35wcXGBgYEBfH19MXHiRKmPlZUV9u3bh6FDh2Lu3LkoXbo0li1bBk/P/50X1K5dOzx9+hRjx45FbGws7O3tERwcnGWhDyIiIiIiInWS9Tpk+QmvQ5Z/8TpkRERERKQKVbJBnjqHjIiIiIiIqCBhICMiIiIiIpIJAxkREREREZFMGMiIiIiIiIhkwkBGREREREQkEwYyIiIiIiIimTCQERERERERyYSBjIiIiIiISCYMZERERERERDJhICMiIiIiIpIJAxkREREREZFMGMiIiIiIiIhkwkBGREREREQkEwYyIiIiIiIimTCQERERERERyYSBjIiIiIiISCYMZERERERERDJhICMiIiIiIpIJAxkREREREZFMGMiIiIiIiIhkwkBGREREREQkEwYyIiIiIiIimTCQERERERERyYSBjIiIiIiISCYMZERERERERDJhICMiIiIiIpIJAxkREREREZFMGMiIiIiIiIhkwkBGREREREQkEwYyIiIiIiIimTCQERERERERyeSLA1lKSgpu3LiBtLQ0ddZDRERERERUYBRS9Q5v377FwIEDsWrVKgDAzZs3Ub58eQwcOBClSpVCQECA2oskym/KBeyTu4R84950L7lLICIiIvpiKh8hGzVqFC5fvoxjx45BV1dXand3d8emTZvUWhwREREREVF+pvIRsp07d2LTpk1wdnaGQqGQ2qtWrYo7d+6otTgiIiIiIqL8TOUjZE+fPkWJEiWytL9580YpoBEREREREdG/UzmQOTo6Yt++/53/khnCli1bBhcXF/VVRkRERERElM+pPGVx6tSp+PHHH3Ht2jWkpaVh7ty5uHbtGk6fPo3jx4/nRo1ERERERET5kspHyOrVq4fw8HCkpaXBzs4Ohw4dQokSJRAaGopatWrlRo1ERERERET5kspHyACgQoUKWLp0qbprISIiIiIiKlBUPkK2f/9+HDx4MEv7wYMHceDAAbUURUREREREVBCoHMgCAgKQnp6epV0IwYtCExERERERqUDlQHbr1i3Y2tpmaa9SpQpu376tlqKIiIiIiIgKApUDmbGxMe7evZul/fbt2zAwMFBLUURERERERAWByoGsRYsWGDJkCO7cuSO13b59G8OGDUPz5s3VWhwREREREVF+pnIgmzlzJgwMDFClShVYWVnBysoKNjY2KFasGH7/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5cuXSE9PBwCYmJggMDBQ3fURERERERHlWyoHsvnz52Pp0qUYPXo0NDU1pXZHR0dERESotTgiIiIiIqL8TOVAFh0dDQcHhyztOjo6ePPmjVqKIiIiIiIiKghUDmRWVlYIDw/P0h4cHAwbGxt11ERERERERFQgFFL1Dv7+/vDz88O7d+8ghMC5c+ewYcMGTJs2DcuWLcuNGomIiIiIiPIllQNZz549oaenh19//RVv375Fhw4dULJkScydOxc+Pj65USMREREREVG+pHIgA4COHTuiY8eOePv2LRITE1GiRAl110VERERERJTvqXwOWVJSEt6+fQsA0NfXR1JSEgIDA3Ho0CG1F0dERERERJSfqRzIWrRogdWrVwMAXr58iTp16mDWrFlo0aIFFi9erPYCiYiIiIiI8iuVA9nFixfxww8/AAC2bt0Kc3Nz/PPPP1i9ejXmzZun9gKJiIiIiIjyK5UD2du3b2FoaAgAOHToEFq1agUNDQ04Ozvjn3/+UXuBRERERERE+ZXKgcza2ho7d+7EgwcPcPDgQXh4eAAAnjx5AiMjI7UXSERERERElF+pHMjGjh2L4cOHo1y5cnBycoKLiwuA90fLHBwc1F4gERERERFRfqXysvetW7dGvXr1EBMTgxo1akjtjRo1ws8//6zW4oiI5FAuYJ/cJeQL96Z7yV0CERFRnvdF1yEzNzeHubm5UludOnXUUhAREREREVFB8UWB7MKFC9i8eTPu37+PlJQUpW3bt29XS2FERERERET5ncrnkG3cuBH/+c9/cP36dezYsQOpqam4evUqjhw5AmNj49yokYiIiIiIKF9SOZBNnToVc+bMwZ49e6CtrY25c+ciKioKbdu2RZkyZXKjRiIiIiIionxJ5UB2584deHm9P1FbW1sbb968gUKhwNChQ7FkyRK1F0hERERERJRfqRzIihQpgtevXwMASpUqhcjISADAy5cv8fbtW/VWR0RERERElI+pvKiHq6srQkJCYGdnhzZt2mDw4ME4cuQIQkJC0KhRo9yokYiIiIiIKF9SOZAtWLAA7969AwCMHj0aWlpaOH36NLy9vfHrr7+qvUAiIiIiIqL8SuVAVrRoUen/GhoaCAgIUGtBREREREREBcUXXYcsIyMDt2/fxpMnT5CRkaG0zdXVVS2FERERERER5XcqB7IzZ86gQ4cO+OeffyCE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jBgxAt27d8eRI0ewefNm7Nv3v1XU/P394evrC0dHR9SpUweBgYF48+YNunXrpurLQ0RERERElGMqBzJ7e3soFAp8vFq+s7Mzli9frtK+njx5gi5duiAmJgbGxsaoXr06Dh48iMaNGwMA5syZAw0NDXh7eyM5ORmenp5YtGiRdH9NTU3s3bsX/fr1g4uLCwwMDODr64uJEydKfaysrLBv3z4MHToUc+fORenSpbFs2TLpGmQA0K5dOzx9+hRjx45FbGws7O3tERwcnGWhDyIiIiIiInVS+Tpk//zzj9JtDQ0NmJqaQldXV62FfW94HbL8i9e5yts4PnkXr0NGREQFVa5dh+zevXsICQlBSkoKGjRogKpVq35VoURERERERAVZjgPZ0aNH0axZMyQlJb2/Y6FCWL58OTp16pRrxREREREREeVnOV5lccyYMWjcuDEePXqE58+fo1evXhgxYkRu1kZERERERJSv5TiQRUZGYurUqbCwsECRIkXw22+/4cmTJ3j+/Hlu1kdERERERJRv5TiQJSQkoHjx4tJtfX196Onp4dWrV7lSGBERERERUX6n0qIemdcHy5R5AeXIyEiprXnz5uqrjoiIiIiIKB9TKZD5+vpmaevTp4/0f4VCgfT09K+vioiIiIiIqADIcSDLyMjIzTqIiIiIiIgKnByfQ0ZERERERETqxUBGREREREQkEwYyIiIiIiIimTCQERERERERyYSBjIiIiIiISCZfFMhevnyJZcuWYdSoUYiPjwcAXLx4EY8ePVJrcURERERERPmZStchA4ArV67A3d0dxsbGuHfvHnr16oWiRYti+/btuH//PlavXp0bdRIREREREeU7Kh8h8/f3R9euXXHr1i3o6upK7U2bNsWJEyfUWhwREREREVF+pnIgO3/+PPr06ZOlvVSpUoiNjVVLUURERERERAWByoFMR0cHCQkJWdpv3rwJU1NTtRRFRERERERUEKgcyJo3b46JEyciNTUVAKBQKHD//n2MHDkS3t7eai+QiIiIiIgov1I5kM2aNQuJiYkoUaIEkpKSUL9+fVhbW8PQ0BBTpkzJjRqJiIiIiIjyJZVXWTQ2NkZISAhOnTqFK1euIDExETVr1oS7u3tu1EdERERERJRvqRzIMtWrVw/16tVTZy1EREREREQFisqBbN68edm2KxQK6OrqwtraGq6urtDU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kZycjJ07d0oLaBAREREREVHO5DiQ/fTTTzhx4gS8vLwQGBiIJk2aQFNTE0FBQblZHxEREX0nOKVUfTillKjgyHEgO3DgAAYNGoR+/fqhYsWKuVkTERERERFRgZDjc8hOnTqF169fo1atWnBycsKCBQvw7Nmz3KyNiIiIiIgoX8txIHN2dsbSpUsRExODPn36YOPGjShZsiQyMjIQEhKC169f52adRERERERE+Y7KqywaGBige/fuOHXqFCIiIjBs2DBMnz4dJUqUQPPmzXOjRiIiIiIionzpi5e9B4DKlStj5syZePjwITZs2KCumoiIiIiIiAqErwpkmTQ1NdGyZUvs3r1bHbsjIiIiIiIqENQSyIiIiIiIiEh1DGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQyKSR3AURERESUu8oF7JO7hHzj3nQvuUugfEbWI2QnTpzATz/9hJIlS0KhUGDnzp1K24UQGDt2LCwsLKCnpwd3d3fcunVLqU98fDw6duwIIyMjmJiYoEePHkhMTFTqc+XKFfzwww/Q1dWFpaUlZs6cmaWWLVu2oEqVKtDV1YWdnR3279+v9udLRERERET0IVmPkL158wY1atRA9+7d0apVqyzbZ86ciXnz5mHVqlWwsrLCmDFj4OnpiWvXrkFXVxcA0LFjR8TExCAkJASpqano1q0bevfujfXr1wMAEhIS4OHhAXd3dwQFBSEiIgLdu3eHiYkJevfuDQA4ffo02rdvj2nTpqFZs2ZYv349WrZsiYsXL6JatWrf7gUhIiIiogKHRzDV53s8gilrIPvxxx/x448/ZrtNCIHAwED8+uuvaNGiBQBg9erVMDMzw86dO+Hj44Pr168jODgY58+fh6OjIwBg/vz5aNq0KX7//XeULFkS69atQ0pKCpYvXw5tbW1UrVoV4eHhmD17thTI5s6diyZNmuCXX34BAEyaNAkhISFYsGABgoKCvsErQUREREREBVGePYcsOjoasbGxcHd3l9qMjY3h5OSE0NBQ+Pj4IDQ0FCYmJlIYAwB3d3doaGjg7Nmz+PnnnxEaGgpXV1doa2tLfTw9PTFjxgy8ePECRYoUQWhoKPz9/ZUe39PTM8sUyg8lJycjOTlZup2QkAAASE1NRWpq6tc+/a+moynkLiHfyI3x5PioD8cn7+LY5G0cn7xN3ePDsVEfvnfytrzwORxQrY48G8hiY2MBAGZmZkrtZmZm0rbY2FiUKFFCaXuhQoVQtGhRpT5WVlZZ9pG5rUiRIoiNjf3Xx8nOtGnTMGHChCzthw4dgr6+fk6eYq6aWUfuCvKP3DifkOOjPhyfvItjk7dxfPI2dY8Px0Z9+N7J2/LKOhBv377Ncd88G8jyulGjRikdVUtISIClpSU8PDxgZGQkY2XvVRt/UO4S8o3I8Z5q3yfHR304PnkXxyZv4/jkbeoeH46N+vC9k7flxvh8iczZczmRZwOZubk5ACAuLg4WFhZSe1xcHOzt7aU+T548UbpfWloa4uPjpfubm5sjLi5OqU/m7c/1ydyeHR0dHejo6GRp19LSgpaWVk6eYq5KTlfIXUK+kRvjyfFRH45P3sWxyds4PnmbuseHY6M+fO/kbXnhczigWh159sLQVlZWMDc3x+HDh6W2hIQEnD17Fi4uLgAAFxcXvHz5EmFhYVKfI0eOICMjA05OTlKfEydOKM3jDAkJQeXKlVGkSBGpz4ePk9kn83GIiIiIiIhyg6yBLDExEeHh4QgPDwfwfiGP8PBw3L9/HwqFAkOGDMHkyZOxe/duREREoEuXLihZsiRatmwJALCxsUGTJk3Qq1cvnDt3Dn///TcGDBgAHx8flCxZEgDQoUMHaGtro0ePHrh69So2bdqEuXPnKk03HDx4MIKDgzFr1ixERUVh/PjxuHDhAgYMGPCtXxIiIiIiIipAZJ2yeOHCBbi5uUm3M0OSr68vVq5ciREjRuDNmzfo3bs3Xr58iXr16iE4OFi6BhkArFu3DgMGDECjRo2goaEBb29vzJs3T9pubGyMQ4cOwc/PD7Vq1ULx4sUxduxYacl7APjPf/6D9evX49dff8V///tfVKxYETt37uQ1yIiIiIiIKFfJGsgaNGgAIT69zKdCocDEiRMxceLET/YpWrSodBHoT6levTpOnjz5r33atGmDNm3a/HvBREREREREapRnzyEjIiIiIiLK7xjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGMiIiIiIiIpkwkBEREREREcmEgYyIiIiIiEgmDGREREREREQyYSAjIiIiIiKSCQMZERERERGRTBjIiIiIiIiIZMJARkREREREJBMGso8sXLgQ5cqVg66uLpycnHDu3Dm5SyIiIiIionyKgewDmzZtgr+/P8aNG4eLFy+iRo0a8PT0xJMnT+QujYiIiIiI8iEGsg/Mnj0bvXr1Qrdu3WBra4ugoCDo6+tj+fLlcpdGRERERET5UCG5C8grUlJSEBYWhlGjRkltGhoacHd3R2hoaJb+ycnJSE5Olm6/evUKABAfH4/U1NTcL/gzCqW9kbuEfOP58+dq3yfHR304PnkXxyZv4/jkbeoeH46N+vC9k7flxvh8idevXwMAhBCf7asQOelVADx+/Bil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",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# codecell_37c (keep this id for tracking purposes)\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "avg_price_by_brand_pdf = avg_price_by_brand_df.toPandas()\n",
    "\n",
    "plt.figure(figsize=(10,5))\n",
    "plt.bar(avg_price_by_brand_pdf[\"brand_code\"], avg_price_by_brand_pdf[\"avg_price\"])\n",
    "plt.xlabel(\"Brand Code\")\n",
    "plt.ylabel(\"Average Purchase Price\")\n",
    "plt.title(\"Average Purchase Price by Brand (> 10K)\")\n",
    "plt.xticks(rotation=45)\n",
    "plt.grid(axis=\"y\")\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8740f635",
   "metadata": {},
   "source": [
    "## 4. Load RDDs"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b2c86140-8287-4d48-9eff-1b16281174fd",
   "metadata": {},
   "source": [
    "The remaining exercises focus on RDD manipulations.\n",
    "\n",
    "Let's start by loading the RDDs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "3411a973-b7cc-4f31-9e6b-cebaef3196ac",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Get RDDs directly from DataFrames (with required repartitions)\n",
    "# type: RDD[Row]\n",
    "events_rdd   = events_df.rdd.repartition(1000)\n",
    "products_rdd = products_df.rdd.repartition(100)\n",
    "brands_rdd   = brands_df.rdd.repartition(100)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ba1e1c0-de17-4e17-985c-3d4329d207b2",
   "metadata": {},
   "source": [
    "You'll need `Row`, so let's make sure we've imported it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "c291905f-5ec5-442a-b06b-d7fa03e9f07f",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyspark.sql import Row"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bd92b878-6f10-4bd6-90c2-624c3bedd41b",
   "metadata": {},
   "source": [
    "## 5. Implementations of Computing Averages"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "433b6453",
   "metadata": {},
   "source": [
    "In this next exercise, we're going to implement \"computing the mean\" (version 1) and (version 3) in Spark as described in the second lecture **Batch Processing I** (please use ctrl+f to reach the slide with the title : \"Computing the Mean: Version 1\" or \"Computing the Mean: Version 3\"."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "70ad895c-719c-4c09-b72a-27e890f78f1e",
   "metadata": {},
   "source": [
    "To make the problem more tractable (i.e., to reduce the running times), let's first do a bit of filtering of the `events` table.\n",
    "We'll do this using DataFrames, and then generate an RDD:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "4b0d9982-816f-49f7-9960-91835e2011a4",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Number of rows in events          table: 42351862\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Number of rows in filtered events table: 664885\n"
     ]
    }
   ],
   "source": [
    "filtered_events_df = (\n",
    "    events_df\n",
    "        .filter((F.col(\"event_type\") == \"purchase\") & F.col(\"price\").isNotNull())\n",
    "        .join(brands_df, on=\"brand_key\")\n",
    ")\n",
    "\n",
    "filtered_events_df.count()\n",
    "\n",
    "print(f\"Number of rows in events          table: {events_df.count()}\")\n",
    "print(f\"Number of rows in filtered events table: {filtered_events_df.count()}\")\n",
    "\n",
    "filtered_events_rdd = filtered_events_df.rdd"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72ffa9e2-e05d-45ab-9885-0a071a6156dd",
   "metadata": {},
   "source": [
    "You can confirm that we're working with a smaller dataset."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c9f80ad-13a2-49ca-bbef-495be02c15d5",
   "metadata": {},
   "source": [
    "Compute the average purchase price by brand. We want the results sorted by the average purchase price from the largest to smallest value. As before, round to two digits after the decimal point. This is similar to Q7 above, except _without_ the \"more than 10K\" condition.\n",
    "\n",
    "Implement using the naive **\"version 1\"** algorithm, as described in the lectures:\n",
    "\n",
    "+ You _must_ start with `filtered_events_rdd`.\n",
    "+ You _must_ use `groupByKey()`.\n",
    "+ Per \"version 1\", your implementation _must_ shuffle all values from the \"mappers\" to the \"reducers\".\n",
    "\n",
    "**write some code here**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "e73f28d7-f55b-454b-9273-3609a0d10f6a",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "data": {
      "text/plain": [
       "[('adam', 58946.0),\n",
       " ('kona', 43759.0),\n",
       " ('yuandong', 35329.0),\n",
       " ('bentley', 23164.0),\n",
       " ('otex', 18633.13),\n",
       " ('suunto', 10732.82),\n",
       " ('stark', 10400.25),\n",
       " ('zenmart', 9447.0),\n",
       " ('baltekstil', 8504.19),\n",
       " ('bugati', 8288.42)]"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\n",
    "# codecell_5x1 (keep this id for tracking purposes)\n",
    "\n",
    "# TODO: Write your code below, but do not remove any lines already in this cell.\n",
    "\n",
    "\n",
    "average_revenue_per_brand_v1 = (\n",
    "    filtered_events_rdd\n",
    "        .map(lambda row: (row.brand_code, row.price))          # (brand, price)\n",
    "        .groupByKey()                                          # (brand, iterable(prices))\n",
    "        .map(lambda kv: (kv[0], round(sum(kv[1]) / len(list(kv[1])), 2)))  \n",
    "        .sortBy(lambda x: x[1], ascending=False)               # sort by avg desc\n",
    ")\n",
    "average_revenue_per_brand_v1.take(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2221f4d7-4b39-4849-a9f2-d185a26a3209",
   "metadata": {},
   "source": [
    "Compute the average purchase price by brand. We want the results sorted by the average purchase price from the largest to smallest value. As before, round to two digits after the decimal point. This is similar to Q7 above, except _without_ the \"more than 10K\" condition.\n",
    "\n",
    "Implement using the improved **\"version 3\"** algorithm, as described in the lectures:\n",
    "\n",
    "+ You _must_ start with `filtered_events_rdd`.\n",
    "+ You _must_ use `reduceByKey()`.\n",
    "+ Per \"version 3\", your implementation _must_ emit `(sum, count)` pairs and take advantage opportunities to perform aggregations.\n",
    "\n",
    "**write some code here**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "024fbcb8-c5bc-4d3c-b22d-f2b99b9b40be",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "data": {
      "text/plain": [
       "[('adam', 58946.0),\n",
       " ('kona', 43759.0),\n",
       " ('yuandong', 35329.0),\n",
       " ('bentley', 23164.0),\n",
       " ('otex', 18633.13),\n",
       " ('suunto', 10732.82),\n",
       " ('stark', 10400.25),\n",
       " ('zenmart', 9447.0),\n",
       " ('baltekstil', 8504.19),\n",
       " ('bugati', 8288.42)]"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\n",
    "# codecell_5x2 (keep this id for tracking purposes)\n",
    "\n",
    "# TODO: Write your code below, but do not remove any lines already in this cell.\n",
    "\n",
    "average_revenue_per_brand_v3 = (\n",
    "    filtered_events_rdd\n",
    "        .map(lambda row: (row.brand_code, (row.price, 1)))     # (brand, (sum, count))\n",
    "        .reduceByKey(lambda a, b: (a[0] + b[0], a[1] + b[1]))   # combine sums & counts\n",
    "        .map(lambda kv: (kv[0], round(kv[1][0] / kv[1][1], 2))) # compute avg\n",
    "        .sortBy(lambda x: x[1], ascending=False)               # sort by avg desc\n",
    ")\n",
    "\n",
    "average_revenue_per_brand_v3.take(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3d0807e6-179c-4703-af49-a30b00df80fe",
   "metadata": {},
   "source": [
    "## 6. Implementations of Joins"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "021be923-60f5-4961-b6fd-f76e48ec759f",
   "metadata": {},
   "source": [
    "Next, we're going to implement joins."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "88579fa7-cdc4-43d3-a660-ffc09d82e6da",
   "metadata": {},
   "source": [
    "Our join implementations will be general, but we're going to check correctness using the following query:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "644786ea-5d01-4db6-a7e9-4f6bd59200ce",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+----------+--------------------+---------+-------------+----------+----------+------------+--------------------+---------+------------+-----------+\n",
      "|brand_code|          brand_desc|brand_key|category_code|brand_code|product_id|product_name|        product_desc|brand_key|category_key|product_key|\n",
      "+----------+--------------------+---------+-------------+----------+----------+------------+--------------------+---------+------------+-----------+\n",
      "| blaupunkt|\"Blaupunkt is a G...|      423|  electronics| blaupunkt|   1802099|    video.tv|The video.tv is a...|      423|           8|       4813|\n",
      "| blaupunkt|\"Blaupunkt is a G...|      423|  electronics| blaupunkt|   1802107|    video.tv|The video.tv is a...|      423|           8|       4821|\n",
      "+----------+--------------------+---------+-------------+----------+----------+------------+--------------------+---------+------------+-----------+\n",
      "\n"
     ]
    }
   ],
   "source": [
    "spark.sql(\"\"\"\n",
    "\n",
    "SELECT * FROM brands b\n",
    "JOIN products p ON p.brand_key = b.brand_key\n",
    "WHERE b.brand_key = '423'\n",
    "\n",
    "\"\"\").show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9ac59e86",
   "metadata": {},
   "source": [
    "### 6.1 Shuffle Join Implementation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6da28f25-363b-4aab-9ab5-8dd091a5f6ee",
   "metadata": {},
   "source": [
    "Here, we're going to implement a shuffle join, aka reduce-side join.\n",
    "\n",
    "Write the function `shuffle_join`, as follows:\n",
    "+ Takes in `R`, `S`, `keyR`, and `keyS`: `R` and `S` are the RDDs to be joined; `keyR` and `keyS` are the join keys in `R` and `S`, respectively (type string).\n",
    "+ The output is an RDD of `Row`s that corresponds to the inner join on the keys.\n",
    "\n",
    "The function should implement a shuffle join between the two RDDs (as discussed in lecture).\n",
    "Specifically:\n",
    "+ You _cannot_ use the `join` (or any related) transformation on RDDs, because that would defeat the point of the exercise.\n",
    "+ If you have any additional questions about allowed or disallowed transformations, ask!\n",
    "\n",
    "Note that in SQL, `keyR` and `keyS` are repeated in the joined output (i.e., you get duplicate columns).\n",
    "Here, you just want one copy.\n",
    "Hint: Concatenate the `Row`s but keep only one copy of the join key.\n",
    "\n",
    "**write some code here**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "496e5917",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "data": {
      "text/plain": [
       "115584"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# codecell_61a (keep this id for tracking purposes)\n",
    "\n",
    "from pyspark.sql import Row\n",
    "\n",
    "def shuffle_join(R, S, keyR, keyS):\n",
    "\n",
    "    # Step 1: map to (key, (\"R\", row)) and (key, (\"S\", row))\n",
    "    R_mapped = R.map(lambda row: (row[keyR], (\"R\", row)))\n",
    "    S_mapped = S.map(lambda row: (row[keyS], (\"S\", row)))\n",
    "\n",
    "    # Step 2: union and groupByKey (shuffle)\n",
    "    grouped = R_mapped.union(S_mapped).groupByKey()\n",
    "\n",
    "    # Step 3: for each key, join R rows with S rows\n",
    "    def join_rows(kv):\n",
    "        key, values = kv\n",
    "        R_rows = [v[1] for v in values if v[0] == \"R\"]\n",
    "        S_rows = [v[1] for v in values if v[0] == \"S\"]\n",
    "\n",
    "        results = []\n",
    "        for r in R_rows:\n",
    "            for s in S_rows:\n",
    "                # merge Row objects but keep only one copy of the join key\n",
    "                merged_dict = r.asDict().copy()\n",
    "                merged_dict.update(s.asDict())\n",
    "                results.append(Row(**merged_dict))\n",
    "        return results\n",
    "\n",
    "    return grouped.flatMap(join_rows)\n",
    "\n",
    "\n",
    "# Run the join\n",
    "shuffle_join_rdd = shuffle_join(brands_rdd, products_rdd, \"brand_key\", \"brand_key\")\n",
    "shuffle_join_rdd.count()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6852c41-859a-485f-8300-a37d1ce4b95d",
   "metadata": {},
   "source": [
    "Let's try to use it!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "62205616-23f1-46b9-b28e-ff45dfb1bd11",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "data": {
      "text/plain": [
       "115584"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\n",
    "\n",
    "shuffle_join_rdd = shuffle_join(brands_rdd, products_rdd, \"brand_key\", \"brand_key\")\n",
    "shuffle_join_rdd.count()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "baa4acd1-197a-4a4e-bdcd-89f7c06e0017",
   "metadata": {},
   "source": [
    "Add in the `WHERE` clause:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "3d17537e-7a50-48b6-a54d-ac3e1bce5b2d",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "shuffle_join_results_rdd = shuffle_join_rdd.filter(lambda row: row[\"brand_key\"] == 423)\n",
    "shuffle_join_results_rdd.count()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "00f1330b-930b-4c05-85df-60453d11b6cb",
   "metadata": {},
   "source": [
    "If you look at the results, they're a bit difficult to read... why don't we just use Spark DataFrames for prettification?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "727bcdc5-cae2-4a36-837d-2a51786d3e0e",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+----------+--------------------+---------+-------------+----------+------------+--------------------+------------+-----------+\n",
      "|brand_code|          brand_desc|brand_key|category_code|product_id|product_name|        product_desc|category_key|product_key|\n",
      "+----------+--------------------+---------+-------------+----------+------------+--------------------+------------+-----------+\n",
      "| blaupunkt|\"Blaupunkt is a G...|      423|  electronics|   1802099|    video.tv|The video.tv is a...|           8|       4813|\n",
      "| blaupunkt|\"Blaupunkt is a G...|      423|  electronics|   1802107|    video.tv|The video.tv is a...|           8|       4821|\n",
      "+----------+--------------------+---------+-------------+----------+------------+--------------------+------------+-----------+\n",
      "\n"
     ]
    }
   ],
   "source": [
    "df = spark.createDataFrame(shuffle_join_results_rdd.collect())\n",
    "df.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ef4bb60-8cd0-412b-9953-7805492337b9",
   "metadata": {},
   "source": [
    "Verify output against the SQL query."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0f28739c",
   "metadata": {},
   "source": [
    "### 6.2 Replicated Hash Join Implementation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43636b18-7cf2-438c-ade1-1b1a9b180a72",
   "metadata": {},
   "source": [
    "Here, we're going to implement a replicated hash join.\n",
    "\n",
    "Write the function `replicated_hash_join`, as follows:\n",
    "+ Takes in `R`, `S`, `keyR`, and `keyS`: `R` and `S` are the RDDs to be joined; `keyR` and `keyS` are the join keys in `R` and `S`, respectively (type string).\n",
    "+ The output is an RDD of `Row`s that corresponds to the inner join on the keys.\n",
    "\n",
    "The function should implement a hash join between the two RDDs (as discussed in lecture).\n",
    "Specifically:\n",
    "+ `R` is the dataset you load into memory and replicate.\n",
    "+ You _cannot_ use the `join` (or any related) transformation on RDDs, because that would defeat the point of the exercise.\n",
    "+ If you have any additional questions about allowed or disallowed transformations, ask!\n",
    "\n",
    "Note that in SQL, `keyR` and `keyS` are repeated in the joined output (i.e., you get duplicate columns).\n",
    "Here, you just want one copy.\n",
    "Hint: Concatenate the `Row`s but keep only one copy of the join key.\n",
    "\n",
    "**write some code here**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "cf816e60",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "data": {
      "text/plain": [
       "115584"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# codecell_62a (keep this id for tracking purposes)\n",
    "\n",
    "from pyspark.sql import Row\n",
    "\n",
    "def replicated_hash_join(R, S, keyR, keyS):\n",
    "\n",
    "    # Step 1: collect R into a hash table\n",
    "    R_hash = {}\n",
    "    for row in R.collect():\n",
    "        key = row[keyR]\n",
    "        if key not in R_hash:\n",
    "            R_hash[key] = []\n",
    "        R_hash[key].append(row)\n",
    "\n",
    "    # Step 2: for each row in S, probe the hash table\n",
    "    def probe(s_row):\n",
    "        key = s_row[keyS]\n",
    "        if key not in R_hash:\n",
    "            return []\n",
    "        results = []\n",
    "        for r_row in R_hash[key]:\n",
    "            merged = r_row.asDict().copy()\n",
    "            merged.update(s_row.asDict())\n",
    "            results.append(Row(**merged))\n",
    "        return results\n",
    "\n",
    "    return S.flatMap(probe)\n",
    "\n",
    "\n",
    "# Run the join\n",
    "replicated_hash_join_rdd = replicated_hash_join(brands_rdd, products_rdd, \"brand_key\", \"brand_key\")\n",
    "replicated_hash_join_rdd.count()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "34eb139d-b5f6-43cc-97ff-849542f31f71",
   "metadata": {},
   "source": [
    "Let's try to use it!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "f48c34d5",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "data": {
      "text/plain": [
       "115584"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\n",
    "replicated_hash_join_rdd = replicated_hash_join(brands_rdd, products_rdd, \"brand_key\", \"brand_key\")\n",
    "replicated_hash_join_rdd.count()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eaaf9916-f31a-4c23-a7e6-f2964c0d5608",
   "metadata": {},
   "source": [
    "Add in the `WHERE` clause:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "5d216906-91af-4661-bbff-a5d9de5f23bb",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "replicated_hash_join_results_rdd = replicated_hash_join_rdd.filter(lambda row: row[\"brand_key\"] == 423)\n",
    "replicated_hash_join_results_rdd.count()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2cd937bb-1a33-4538-a481-a3e75a47fcbd",
   "metadata": {},
   "source": [
    "If you look at the results, they're a bit difficult to read... why don't we just use Spark DataFrames for prettification?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "2c605d94-80a3-4eab-a459-fc3399127373",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "+----------+--------------------+---------+-------------+----------+------------+--------------------+------------+-----------+\n",
      "|brand_code|          brand_desc|brand_key|category_code|product_id|product_name|        product_desc|category_key|product_key|\n",
      "+----------+--------------------+---------+-------------+----------+------------+--------------------+------------+-----------+\n",
      "| blaupunkt|\"Blaupunkt is a G...|      423|  electronics|   1802099|    video.tv|The video.tv is a...|           8|       4813|\n",
      "| blaupunkt|\"Blaupunkt is a G...|      423|  electronics|   1802107|    video.tv|The video.tv is a...|           8|       4821|\n",
      "+----------+--------------------+---------+-------------+----------+------------+--------------------+------------+-----------+\n",
      "\n"
     ]
    }
   ],
   "source": [
    "df = spark.createDataFrame(replicated_hash_join_results_rdd.collect())\n",
    "df.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3fa82c76-7de4-442c-bb7d-5b75d28ebb7e",
   "metadata": {},
   "source": [
    "Verify output against the SQL query."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "03123495",
   "metadata": {},
   "source": [
    "## 7. Join Performance"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f96a587-5ce2-4050-a045-f249ce6bafeb",
   "metadata": {},
   "source": [
    "Now that we have two different implementations of joins, let's compare them, on the _same exact query_.\n",
    "The first two are repeated from above."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f482150-b30b-4431-90bb-ee28b85bfb33",
   "metadata": {},
   "source": [
    "Let's call this J1 below.\n",
    "(Run the cell, it should just work. If it doesn't you'll need to fix the implementation above.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "6a5a7ae0-2970-4dfb-87c3-3682e662d705",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\n",
    "\n",
    "shuffle_join_rdd = shuffle_join(brands_rdd, products_rdd, \"brand_key\", \"brand_key\").filter(lambda row: row[\"brand_key\"] == 423)\n",
    "shuffle_join_rdd.count()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3c9a8e93-9172-47bd-8fb6-9017086c18b9",
   "metadata": {},
   "source": [
    "Let's call this J2 below.\n",
    "(Run the cell, it should just work. If it doesn't you'll need to fix the implementation above.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "d5b76ec2-db1d-4547-a443-9c535bf57d97",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\n",
    "\n",
    "replicated_hash_join_rdd = replicated_hash_join(brands_rdd, products_rdd, \"brand_key\", \"brand_key\").filter(lambda row: row[\"brand_key\"] == 423)\n",
    "replicated_hash_join_rdd.count()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "436989b3-d611-48aa-864f-de8afe4500ec",
   "metadata": {},
   "source": [
    "Let's call this J3 below.\n",
    "(Run the cell, it should just work. If it doesn't you'll need to fix the implementation above.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "371d156d-9f44-4045-8079-17304d54a776",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                "
     ]
    },
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\n",
    "\n",
    "replicated_hash_join_rdd = replicated_hash_join(products_rdd, brands_rdd, \"brand_key\", \"brand_key\").filter(lambda row: row[\"brand_key\"] == 423)\n",
    "replicated_hash_join_rdd.count()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a79bcae4-2b82-45c3-b771-6aa66fa7ee90",
   "metadata": {},
   "source": [
    "J1, J2, and J3 should give you exactly the same results.\n",
    "After all, they're just different implementations of the same query.\n",
    "\n",
    "Answer the questions below."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2bb03c29-6571-4d38-b971-a953e63c1a65",
   "metadata": {},
   "source": [
    "**Put your answers below!**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4147d654-a741-45b9-bc2f-2d7270656ef1",
   "metadata": {},
   "source": [
    "// qcell_7x1290 (keep this id for tracking purposes)\n",
    "\n",
    "**What are the running times of J1, J2, and J3**?\n",
    "(You might want to run the cells a few times and take the average.)\n",
    "\n",
    "- **Running time of J1:** <font color=\"red\">X.X</font> seconds\n",
    "- **Running time of J2:** <font color=\"red\">X.X</font> seconds\n",
    "- **Running time of J3:** <font color=\"red\">X.X</font> seconds\n",
    "\n",
    "**Explain:**\n",
    "\n",
    "+ If the running times are what you expect, explain why X > Y > Z.\n",
    "+ If the running times are _not_ what you expect, explain what they _should_ be, and then explain why X > Y > Z.\n",
    "+ Specifically compare J2 and J3.\n",
    "\n",
    "**Your answer?**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5f5ea5ce-0a3c-4ad0-a699-334e14ed251d",
   "metadata": {},
   "source": [
    "# 8. Submission"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa8db0a8",
   "metadata": {},
   "source": [
    "Details about the Submission of this assignment are outlined in the helper."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "id": "1651e3d5",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "spark.stop()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10410d36",
   "metadata": {},
   "source": [
    "## Performance notes\n",
    "- Set and justify `spark.sql.shuffle.partitions` for local vs. cluster runs.\n",
    "- Prefer DataFrame built-ins over Python UDFs; push logic to Catalyst when possible.\n",
    "- Use **AQE** (adaptive query execution) to mitigate skew; consider salting for extreme keys.\n",
    "- Cache only when reuse exists; unpersist when no longer needed.\n",
    "- Use **broadcast join** only when the small side fits in memory; verify with `explain`.\n",
    "- Capture `df.explain(mode='formatted')` for at least one analysis query and one join.\n",
    "- A3 note: Python RDDs cross the Python/JVM boundary; slower runtimes are expected for the RDD parts."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eb6b6bf9",
   "metadata": {},
   "source": [
    "## Self-check (toy data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e73ea2b1",
   "metadata": {},
   "outputs": [],
   "source": [
    "if spark is not None:\n",
    "    a = spark.sparkContext.parallelize([1,2,3,4])\n",
    "    # write some code here to exercise your rdd_mean functions\n",
    "    left = spark.sparkContext.parallelize([(1,'A'), (2,'B'), (3,'C')])\n",
    "    right = spark.sparkContext.parallelize([(1,10), (2,20)])\n",
    "    # write some code here to exercise your join functions\n",
    "    pass\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4a98307a",
   "metadata": {},
   "source": [
    "## Reproducibility checklist\n",
    "- Record Python, Java, and Spark versions.\n",
    "- Fix timezone to UTC and log run timestamp.\n",
    "- Pin random seeds where randomness is used.\n",
    "- Save configs: `spark.sql.shuffle.partitions`, AQE flags, broadcast thresholds if changed.\n",
    "- Provide exact run commands and input/output paths.\n",
    "- Export a minimal environment file (`environment.yml` or `requirements.txt`).\n",
    "- Keep data paths relative to project root; avoid user-specific absolute paths.\n",
    "- Include small sample outputs for verification.\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python (de1-env)",
   "language": "python",
   "name": "de1-env"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
