Last active
October 2, 2024 10:53
-
-
Save ThaiDat/81c3662801aa8410a65b94f3c993c377 to your computer and use it in GitHub Desktop.
Notebook used for the tutorial. Read full article about - basic PySpark operations at http://note.datengineer.dev/posts/a-practical-pyspark-tutorial-for-beginners-in-jupyter-notebook/ - PySpark UDF at https://note.datengineer.dev/posts/pyspark-udfs-a-comprehensive-guide-to-unlock-pyspark-potential/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"id": "26357b1e", | |
"metadata": { | |
"papermill": { | |
"duration": 0.125431, | |
"end_time": "2022-04-18T16:05:28.535018", | |
"exception": false, | |
"start_time": "2022-04-18T16:05:28.409587", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"# PySpark Demo" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "527fc531", | |
"metadata": { | |
"papermill": { | |
"duration": 0.12404, | |
"end_time": "2022-04-18T16:05:28.789185", | |
"exception": false, | |
"start_time": "2022-04-18T16:05:28.665145", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"## Installation" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"id": "b2bd8328", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:05:29.043014Z", | |
"iopub.status.busy": "2022-04-18T16:05:29.037694Z", | |
"iopub.status.idle": "2022-04-18T16:06:14.896025Z", | |
"shell.execute_reply": "2022-04-18T16:06:14.895187Z", | |
"shell.execute_reply.started": "2022-04-18T14:25:04.978612Z" | |
}, | |
"papermill": { | |
"duration": 45.983882, | |
"end_time": "2022-04-18T16:06:14.896217", | |
"exception": false, | |
"start_time": "2022-04-18T16:05:28.912335", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Collecting pyspark\r\n", | |
" Downloading pyspark-3.2.1.tar.gz (281.4 MB)\r\n", | |
" |████████████████████████████████| 281.4 MB 31 kB/s \r\n", | |
"\u001b[?25h Preparing metadata (setup.py) ... \u001b[?25l-\b \b\\\b \bdone\r\n", | |
"\u001b[?25hRequirement already satisfied: py4j==0.10.9.3 in /opt/conda/lib/python3.7/site-packages (from pyspark) (0.10.9.3)\r\n", | |
"Building wheels for collected packages: pyspark\r\n", | |
" Building wheel for pyspark (setup.py) ... \u001b[?25l-\b \b\\\b \b|\b \b/\b \b-\b \b\\\b \b|\b \b/\b \b-\b \b\\\b \b|\b \b/\b \b-\b \b\\\b \b|\b \b/\b \b-\b \b\\\b \b|\b \b/\b \b-\b \b\\\b \b|\b \b/\b \b-\b \b\\\b \b|\b \b/\b \b-\b \b\\\b \b|\b \b/\b \b-\b \b\\\b \b|\b \b/\b \b-\b \b\\\b \b|\b \b/\b \b-\b \b\\\b \b|\b \b/\b \b-\b \b\\\b \b|\b \b/\b \b-\b \b\\\b \b|\b \b/\b \b-\b \b\\\b \b|\b \b/\b \b-\b \b\\\b \b|\b \b/\b \b-\b \b\\\b \bdone\r\n", | |
"\u001b[?25h Created wheel for pyspark: filename=pyspark-3.2.1-py2.py3-none-any.whl size=281853642 sha256=35e0e2a73e1607e6ad7d7747f4ac7772d708c8d326820dc3d3ac2f8700284971\r\n", | |
" Stored in directory: /root/.cache/pip/wheels/9f/f5/07/7cd8017084dce4e93e84e92efd1e1d5334db05f2e83bcef74f\r\n", | |
"Successfully built pyspark\r\n", | |
"Installing collected packages: pyspark\r\n", | |
"Successfully installed pyspark-3.2.1\r\n", | |
"\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\r\n" | |
] | |
} | |
], | |
"source": [ | |
"!pip install pyspark" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "8097f07f", | |
"metadata": { | |
"papermill": { | |
"duration": 0.249348, | |
"end_time": "2022-04-18T16:06:15.396520", | |
"exception": false, | |
"start_time": "2022-04-18T16:06:15.147172", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"`SparkContext` served as an entry point before version 2.0. Spark 2.0 introduced the `SparkSession` class, which is a centralised class that contains all of the contexts that existed before the 2.0 update (SQLContext and Hive Context etc.). Note that `SparkContext` has not been completely replaced by SparkSession; certain functions are still present and are used in Spark 2.0 and later." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"id": "932a9587", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:06:15.895836Z", | |
"iopub.status.busy": "2022-04-18T16:06:15.895188Z", | |
"iopub.status.idle": "2022-04-18T16:06:22.067809Z", | |
"shell.execute_reply": "2022-04-18T16:06:22.068653Z", | |
"shell.execute_reply.started": "2022-04-18T14:26:05.605435Z" | |
}, | |
"papermill": { | |
"duration": 6.426796, | |
"end_time": "2022-04-18T16:06:22.068967", | |
"exception": false, | |
"start_time": "2022-04-18T16:06:15.642171", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"WARNING: An illegal reflective access operation has occurred\n", | |
"WARNING: Illegal reflective access by org.apache.spark.unsafe.Platform (file:/opt/conda/lib/python3.7/site-packages/pyspark/jars/spark-unsafe_2.12-3.2.1.jar) to constructor java.nio.DirectByteBuffer(long,int)\n", | |
"WARNING: Please consider reporting this to the maintainers of org.apache.spark.unsafe.Platform\n", | |
"WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations\n", | |
"WARNING: All illegal access operations will be denied in a future release\n", | |
"Using Spark's default log4j profile: org/apache/spark/log4j-defaults.properties\n", | |
"Setting default log level to \"WARN\".\n", | |
"To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).\n", | |
"22/04/18 16:06:18 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable\n" | |
] | |
}, | |
{ | |
"data": { | |
"text/html": [ | |
"\n", | |
" <div>\n", | |
" <p><b>SparkSession - in-memory</b></p>\n", | |
" \n", | |
" <div>\n", | |
" <p><b>SparkContext</b></p>\n", | |
"\n", | |
" <p><a href=\"http://2f64637d1284:4040\">Spark UI</a></p>\n", | |
"\n", | |
" <dl>\n", | |
" <dt>Version</dt>\n", | |
" <dd><code>v3.2.1</code></dd>\n", | |
" <dt>Master</dt>\n", | |
" <dd><code>local[*]</code></dd>\n", | |
" <dt>AppName</dt>\n", | |
" <dd><code>Spark Demo</code></dd>\n", | |
" </dl>\n", | |
" </div>\n", | |
" \n", | |
" </div>\n", | |
" " | |
], | |
"text/plain": [ | |
"<pyspark.sql.session.SparkSession at 0x7fbe894c2b90>" | |
] | |
}, | |
"execution_count": 2, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"from pyspark.sql import SparkSession\n", | |
"\n", | |
"\n", | |
"spark = SparkSession.builder.appName('Spark Demo').master('local[*]').getOrCreate()\n", | |
"spark" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "b93cc0e4", | |
"metadata": { | |
"papermill": { | |
"duration": 0.247276, | |
"end_time": "2022-04-18T16:06:22.569197", | |
"exception": false, | |
"start_time": "2022-04-18T16:06:22.321921", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"## Load" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "d7e07b61", | |
"metadata": { | |
"papermill": { | |
"duration": 0.248472, | |
"end_time": "2022-04-18T16:06:23.066790", | |
"exception": false, | |
"start_time": "2022-04-18T16:06:22.818318", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"`SparkSession` object provides `read` as a property that returns a `DataFrameReader` that can be used to read data in as a `DataFrame`." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"id": "055074cb", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:06:23.567222Z", | |
"iopub.status.busy": "2022-04-18T16:06:23.566214Z", | |
"iopub.status.idle": "2022-04-18T16:06:41.698827Z", | |
"shell.execute_reply": "2022-04-18T16:06:41.699644Z", | |
"shell.execute_reply.started": "2022-04-18T14:26:11.566310Z" | |
}, | |
"papermill": { | |
"duration": 18.385275, | |
"end_time": "2022-04-18T16:06:41.699910", | |
"exception": false, | |
"start_time": "2022-04-18T16:06:23.314635", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 1:> (0 + 4) / 4]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"root\n", | |
" |-- step: integer (nullable = true)\n", | |
" |-- type: string (nullable = true)\n", | |
" |-- amount: double (nullable = true)\n", | |
" |-- nameOrig: string (nullable = true)\n", | |
" |-- oldbalanceOrg: double (nullable = true)\n", | |
" |-- newbalanceOrig: double (nullable = true)\n", | |
" |-- nameDest: string (nullable = true)\n", | |
" |-- oldbalanceDest: double (nullable = true)\n", | |
" |-- newbalanceDest: double (nullable = true)\n", | |
" |-- isFraud: integer (nullable = true)\n", | |
" |-- isFlaggedFraud: integer (nullable = true)\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"data_path = '../input/fraudulent-transactions-data/Fraud.csv'\n", | |
"df = spark.read.csv(data_path, header=True, inferSchema=True)\n", | |
"df.printSchema()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "c80cc782", | |
"metadata": { | |
"papermill": { | |
"duration": 0.246857, | |
"end_time": "2022-04-18T16:06:42.202205", | |
"exception": false, | |
"start_time": "2022-04-18T16:06:41.955348", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"`inferSchema` requires a pre-read of the data just to infer the schema. We can define the schema by ourselves to achive better performance." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"id": "37127ed9", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:06:42.704055Z", | |
"iopub.status.busy": "2022-04-18T16:06:42.701230Z", | |
"iopub.status.idle": "2022-04-18T16:06:42.846874Z", | |
"shell.execute_reply": "2022-04-18T16:06:42.846278Z", | |
"shell.execute_reply.started": "2022-04-18T14:26:26.542279Z" | |
}, | |
"papermill": { | |
"duration": 0.401303, | |
"end_time": "2022-04-18T16:06:42.847040", | |
"exception": false, | |
"start_time": "2022-04-18T16:06:42.445737", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"root\n", | |
" |-- step: integer (nullable = true)\n", | |
" |-- type: string (nullable = true)\n", | |
" |-- amount: double (nullable = true)\n", | |
" |-- nameOrig: string (nullable = true)\n", | |
" |-- oldBalanceOrig: double (nullable = true)\n", | |
" |-- newBalanceOrig: double (nullable = true)\n", | |
" |-- nameDest: string (nullable = true)\n", | |
" |-- oldBalanceDest: double (nullable = true)\n", | |
" |-- newBalanceDest: double (nullable = true)\n", | |
" |-- isFraud: integer (nullable = true)\n", | |
" |-- isFlaggedFraud: integer (nullable = true)\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"from pyspark.sql import types as T\n", | |
"\n", | |
"predefined_schema = T.StructType([\n", | |
" T.StructField('step', T.IntegerType()),\n", | |
" T.StructField('type', T.StringType()),\n", | |
" T.StructField('amount', T.DoubleType()),\n", | |
" T.StructField('nameOrig', T.StringType()),\n", | |
" T.StructField('oldbalanceOrg', T.DoubleType()),\n", | |
" T.StructField('newbalanceOrig', T.DoubleType()), \n", | |
" T.StructField('nameDest', T.StringType()),\n", | |
" T.StructField('oldbalanceDest', T.DoubleType()),\n", | |
" T.StructField('newbalanceDest', T.DoubleType()), \n", | |
" T.StructField('isFraud', T.IntegerType()),\n", | |
" T.StructField('isFlaggedFraud', T.IntegerType())\n", | |
"])\n", | |
"\n", | |
"df = spark.read.csv(data_path, schema=predefined_schema, header=True)\n", | |
"\n", | |
"corrected_cols = {'oldbalanceOrg': 'oldBalanceOrig', 'newbalanceOrig': 'newBalanceOrig', \n", | |
" 'oldbalanceDest': 'oldBalanceDest', 'newbalanceDest': 'newBalanceDest'}\n", | |
"for old_col, new_col in corrected_cols.items():\n", | |
" df = df.withColumnRenamed(old_col, new_col)\n", | |
"\n", | |
"df.printSchema()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "9598135d", | |
"metadata": { | |
"papermill": { | |
"duration": 0.246272, | |
"end_time": "2022-04-18T16:06:43.342538", | |
"exception": false, | |
"start_time": "2022-04-18T16:06:43.096266", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"## Overview" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"id": "6b477790", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:06:43.866558Z", | |
"iopub.status.busy": "2022-04-18T16:06:43.865857Z", | |
"iopub.status.idle": "2022-04-18T16:06:44.314351Z", | |
"shell.execute_reply": "2022-04-18T16:06:44.313470Z", | |
"shell.execute_reply.started": "2022-04-18T14:26:26.666579Z" | |
}, | |
"papermill": { | |
"duration": 0.726734, | |
"end_time": "2022-04-18T16:06:44.314550", | |
"exception": false, | |
"start_time": "2022-04-18T16:06:43.587816", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+----+--------+--------+-----------+--------------+--------------+-----------+--------------+--------------+-------+--------------+\n", | |
"|step| type| amount| nameOrig|oldBalanceOrig|newBalanceOrig| nameDest|oldBalanceDest|newBalanceDest|isFraud|isFlaggedFraud|\n", | |
"+----+--------+--------+-----------+--------------+--------------+-----------+--------------+--------------+-------+--------------+\n", | |
"| 1| PAYMENT| 9839.64|C1231006815| 170136.0| 160296.36|M1979787155| 0.0| 0.0| 0| 0|\n", | |
"| 1| PAYMENT| 1864.28|C1666544295| 21249.0| 19384.72|M2044282225| 0.0| 0.0| 0| 0|\n", | |
"| 1|TRANSFER| 181.0|C1305486145| 181.0| 0.0| C553264065| 0.0| 0.0| 1| 0|\n", | |
"| 1|CASH_OUT| 181.0| C840083671| 181.0| 0.0| C38997010| 21182.0| 0.0| 1| 0|\n", | |
"| 1| PAYMENT|11668.14|C2048537720| 41554.0| 29885.86|M1230701703| 0.0| 0.0| 0| 0|\n", | |
"| 1| PAYMENT| 7817.71| C90045638| 53860.0| 46042.29| M573487274| 0.0| 0.0| 0| 0|\n", | |
"| 1| PAYMENT| 7107.77| C154988899| 183195.0| 176087.23| M408069119| 0.0| 0.0| 0| 0|\n", | |
"| 1| PAYMENT| 7861.64|C1912850431| 176087.23| 168225.59| M633326333| 0.0| 0.0| 0| 0|\n", | |
"| 1| PAYMENT| 4024.36|C1265012928| 2671.0| 0.0|M1176932104| 0.0| 0.0| 0| 0|\n", | |
"| 1| DEBIT| 5337.77| C712410124| 41720.0| 36382.23| C195600860| 41898.0| 40348.79| 0| 0|\n", | |
"+----+--------+--------+-----------+--------------+--------------+-----------+--------------+--------------+-------+--------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"df.show(10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "35fb6e82", | |
"metadata": { | |
"papermill": { | |
"duration": 0.262869, | |
"end_time": "2022-04-18T16:06:44.869220", | |
"exception": false, | |
"start_time": "2022-04-18T16:06:44.606351", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"Sometimes, the dataframe will not fit the screen. We can adjust the parameters of `show` function or split the dataframe to show." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"id": "c536f73e", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:06:45.376296Z", | |
"iopub.status.busy": "2022-04-18T16:06:45.375621Z", | |
"iopub.status.idle": "2022-04-18T16:06:46.094423Z", | |
"shell.execute_reply": "2022-04-18T16:06:46.093542Z", | |
"shell.execute_reply.started": "2022-04-18T14:26:27.080815Z" | |
}, | |
"papermill": { | |
"duration": 0.97618, | |
"end_time": "2022-04-18T16:06:46.094623", | |
"exception": false, | |
"start_time": "2022-04-18T16:06:45.118443", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+----+--------+--------+-----------+\n", | |
"|step| type| amount| nameOrig|\n", | |
"+----+--------+--------+-----------+\n", | |
"| 1| PAYMENT| 9839.64|C1231006815|\n", | |
"| 1| PAYMENT| 1864.28|C1666544295|\n", | |
"| 1|TRANSFER| 181.0|C1305486145|\n", | |
"| 1|CASH_OUT| 181.0| C840083671|\n", | |
"| 1| PAYMENT|11668.14|C2048537720|\n", | |
"| 1| PAYMENT| 7817.71| C90045638|\n", | |
"| 1| PAYMENT| 7107.77| C154988899|\n", | |
"| 1| PAYMENT| 7861.64|C1912850431|\n", | |
"| 1| PAYMENT| 4024.36|C1265012928|\n", | |
"| 1| DEBIT| 5337.77| C712410124|\n", | |
"+----+--------+--------+-----------+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+--------------+--------------+-----------+--------------+\n", | |
"|oldBalanceOrig|newBalanceOrig| nameDest|oldBalanceDest|\n", | |
"+--------------+--------------+-----------+--------------+\n", | |
"| 170136.0| 160296.36|M1979787155| 0.0|\n", | |
"| 21249.0| 19384.72|M2044282225| 0.0|\n", | |
"| 181.0| 0.0| C553264065| 0.0|\n", | |
"| 181.0| 0.0| C38997010| 21182.0|\n", | |
"| 41554.0| 29885.86|M1230701703| 0.0|\n", | |
"| 53860.0| 46042.29| M573487274| 0.0|\n", | |
"| 183195.0| 176087.23| M408069119| 0.0|\n", | |
"| 176087.23| 168225.59| M633326333| 0.0|\n", | |
"| 2671.0| 0.0|M1176932104| 0.0|\n", | |
"| 41720.0| 36382.23| C195600860| 41898.0|\n", | |
"+--------------+--------------+-----------+--------------+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+--------------+-------+--------------+\n", | |
"|newBalanceDest|isFraud|isFlaggedFraud|\n", | |
"+--------------+-------+--------------+\n", | |
"| 0.0| 0| 0|\n", | |
"| 0.0| 0| 0|\n", | |
"| 0.0| 1| 0|\n", | |
"| 0.0| 1| 0|\n", | |
"| 0.0| 0| 0|\n", | |
"| 0.0| 0| 0|\n", | |
"| 0.0| 0| 0|\n", | |
"| 0.0| 0| 0|\n", | |
"| 0.0| 0| 0|\n", | |
"| 40348.79| 0| 0|\n", | |
"+--------------+-------+--------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"import numpy as np\n", | |
"\n", | |
"\n", | |
"def show_split(df, split=-1, n_samples=10):\n", | |
" n_cols = len(df.columns)\n", | |
" if split <= 0:\n", | |
" split = n_cols\n", | |
" i = 0\n", | |
" j = i + split\n", | |
" while i < n_cols:\n", | |
" df.select(*df.columns[i:j]).show(n_samples)\n", | |
" i = j\n", | |
" j = i + split\n", | |
" \n", | |
"show_split(df, 4, 10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "d2dcb89e", | |
"metadata": { | |
"papermill": { | |
"duration": 0.25169, | |
"end_time": "2022-04-18T16:06:46.712129", | |
"exception": false, | |
"start_time": "2022-04-18T16:06:46.460439", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"When working with numerical data, looking at a long column of values isn’t very useful. We’re often more concerned about some key information, which may include count, mean, standard deviation, minimum, and maximum" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"id": "d3f108ba", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:06:47.217691Z", | |
"iopub.status.busy": "2022-04-18T16:06:47.217017Z", | |
"iopub.status.idle": "2022-04-18T16:06:54.615194Z", | |
"shell.execute_reply": "2022-04-18T16:06:54.614521Z", | |
"shell.execute_reply.started": "2022-04-18T14:26:27.760536Z" | |
}, | |
"papermill": { | |
"duration": 7.652204, | |
"end_time": "2022-04-18T16:06:54.615348", | |
"exception": false, | |
"start_time": "2022-04-18T16:06:46.963144", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 6:==============> (1 + 3) / 4]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+-------+------------------+------------------+\n", | |
"|summary| step| amount|\n", | |
"+-------+------------------+------------------+\n", | |
"| count| 6362620| 6362620|\n", | |
"| mean|243.39724563151657|179861.90354913412|\n", | |
"| stddev|142.33197104912588| 603858.2314629498|\n", | |
"| min| 1| 0.0|\n", | |
"| max| 743| 9.244551664E7|\n", | |
"+-------+------------------+------------------+\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"# describe take columns as params\n", | |
"df.describe('step', 'amount').show()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"id": "796149f9", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:06:55.210204Z", | |
"iopub.status.busy": "2022-04-18T16:06:55.209232Z", | |
"iopub.status.idle": "2022-04-18T16:07:14.195945Z", | |
"shell.execute_reply": "2022-04-18T16:07:14.196698Z", | |
"shell.execute_reply.started": "2022-04-18T14:26:34.014826Z" | |
}, | |
"papermill": { | |
"duration": 19.244938, | |
"end_time": "2022-04-18T16:07:14.196963", | |
"exception": false, | |
"start_time": "2022-04-18T16:06:54.952025", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 9:============================================> (3 + 1) / 4]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+-------+-----------------+-----------------+------------------+------------------+\n", | |
"|summary| oldBalanceOrig| newBalanceOrig| oldBalanceDest| newBalanceDest|\n", | |
"+-------+-----------------+-----------------+------------------+------------------+\n", | |
"| count| 6362620| 6362620| 6362620| 6362620|\n", | |
"| min| 0.0| 0.0| 0.0| 0.0|\n", | |
"| max| 5.958504037E7| 4.958504037E7| 3.5601588935E8| 3.5617927892E8|\n", | |
"| mean|833883.1040744719|855113.6685785714|1100701.6665196654|1224996.3982019408|\n", | |
"| 50%| 14211.23| 0.0| 132612.49| 214605.81|\n", | |
"+-------+-----------------+-----------------+------------------+------------------+\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"# summary take statistics as params\n", | |
"df.select('oldBalanceOrig', 'newBalanceOrig', 'oldBalanceDest', 'newBalanceDest').summary('count', 'min', 'max', 'mean', '50%').show()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "2cecec1b", | |
"metadata": { | |
"papermill": { | |
"duration": 0.263635, | |
"end_time": "2022-04-18T16:07:14.762155", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:14.498520", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"## Explore" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 9, | |
"id": "4515be3f", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:07:15.279378Z", | |
"iopub.status.busy": "2022-04-18T16:07:15.278723Z", | |
"iopub.status.idle": "2022-04-18T16:07:15.282736Z", | |
"shell.execute_reply": "2022-04-18T16:07:15.283317Z", | |
"shell.execute_reply.started": "2022-04-18T14:26:50.226832Z" | |
}, | |
"papermill": { | |
"duration": 0.269074, | |
"end_time": "2022-04-18T16:07:15.283489", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:15.014415", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"from pyspark.sql import functions as F" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "1058119e", | |
"metadata": { | |
"papermill": { | |
"duration": 0.258828, | |
"end_time": "2022-04-18T16:07:15.800147", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:15.541319", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"### Querying columns and rows" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "d0338ba7", | |
"metadata": { | |
"papermill": { | |
"duration": 0.250465, | |
"end_time": "2022-04-18T16:07:16.303802", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:16.053337", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"PySpark borrowed a lot of vocabulary from the SQL world. But it do not need to follow the strict SQL framework (select *what* from *where* where *condition met* ...). Each step of pyspark syntax will return a `DataFrame` or `GroupedData` which we can continue to work with flawlessly." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 10, | |
"id": "e049e111", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:07:16.825664Z", | |
"iopub.status.busy": "2022-04-18T16:07:16.824998Z", | |
"iopub.status.idle": "2022-04-18T16:07:17.146094Z", | |
"shell.execute_reply": "2022-04-18T16:07:17.145027Z", | |
"shell.execute_reply.started": "2022-04-18T14:26:50.246607Z" | |
}, | |
"papermill": { | |
"duration": 0.593217, | |
"end_time": "2022-04-18T16:07:17.146320", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:16.553103", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+--------+---------+\n", | |
"| type| amount|\n", | |
"+--------+---------+\n", | |
"|CASH_OUT| 181.0|\n", | |
"|CASH_OUT|229133.94|\n", | |
"|CASH_OUT|110414.71|\n", | |
"|CASH_OUT| 56953.9|\n", | |
"|CASH_OUT| 5346.89|\n", | |
"|CASH_OUT| 23261.3|\n", | |
"|CASH_OUT| 82940.31|\n", | |
"|CASH_OUT| 47458.86|\n", | |
"|CASH_OUT|136872.92|\n", | |
"|CASH_OUT| 94253.33|\n", | |
"+--------+---------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"df.where(df['type']=='CASH_OUT').select(df.type, F.col('amount')).show(10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "00d18fca", | |
"metadata": { | |
"papermill": { | |
"duration": 0.261561, | |
"end_time": "2022-04-18T16:07:17.696166", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:17.434605", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"Above code show us three different ways to access to pyspark columns, `df['tpye']`, `df.type`, and `F.col('type')`" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "6a72deab", | |
"metadata": { | |
"papermill": { | |
"duration": 0.25219, | |
"end_time": "2022-04-18T16:07:18.204287", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:17.952097", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"We also can use SQL code inside pyspark. But we must first register the `DataFrame` with spark sql environment" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 11, | |
"id": "603ae074", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:07:18.711105Z", | |
"iopub.status.busy": "2022-04-18T16:07:18.710431Z", | |
"iopub.status.idle": "2022-04-18T16:07:19.010606Z", | |
"shell.execute_reply": "2022-04-18T16:07:19.009745Z", | |
"shell.execute_reply.started": "2022-04-18T14:26:50.708957Z" | |
}, | |
"papermill": { | |
"duration": 0.554706, | |
"end_time": "2022-04-18T16:07:19.010754", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:18.456048", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+--------+---------+\n", | |
"| type| amount|\n", | |
"+--------+---------+\n", | |
"|CASH_OUT| 181.0|\n", | |
"|CASH_OUT|229133.94|\n", | |
"|CASH_OUT|110414.71|\n", | |
"|CASH_OUT| 56953.9|\n", | |
"|CASH_OUT| 5346.89|\n", | |
"|CASH_OUT| 23261.3|\n", | |
"|CASH_OUT| 82940.31|\n", | |
"|CASH_OUT| 47458.86|\n", | |
"|CASH_OUT|136872.92|\n", | |
"|CASH_OUT| 94253.33|\n", | |
"+--------+---------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"df.createOrReplaceTempView('df')\n", | |
"spark.sql('''\n", | |
" SELECT type, amount FROM df\n", | |
" WHERE type = \"CASH_OUT\" \n", | |
"''').show(10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "dad22034", | |
"metadata": { | |
"papermill": { | |
"duration": 0.262527, | |
"end_time": "2022-04-18T16:07:19.546975", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:19.284448", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"While I prefer to use pyspark's way. But there is some case writing SQL will be beneficial as string is more readable than a lot of boilerplate code." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "6b08f3fa", | |
"metadata": { | |
"papermill": { | |
"duration": 0.251924, | |
"end_time": "2022-04-18T16:07:20.058978", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:19.807054", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"### Grouping" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "0f7b4353", | |
"metadata": { | |
"papermill": { | |
"duration": 0.262019, | |
"end_time": "2022-04-18T16:07:20.572548", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:20.310529", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"pyspark provides `Column.alias` method to change the name of the column in the result." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 12, | |
"id": "1f39409e", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:07:21.085222Z", | |
"iopub.status.busy": "2022-04-18T16:07:21.084165Z", | |
"iopub.status.idle": "2022-04-18T16:07:29.237917Z", | |
"shell.execute_reply": "2022-04-18T16:07:29.238837Z", | |
"shell.execute_reply.started": "2022-04-18T14:26:51.004866Z" | |
}, | |
"papermill": { | |
"duration": 8.414507, | |
"end_time": "2022-04-18T16:07:29.239133", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:20.824626", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 14:==============> (1 + 3) / 4]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+--------+------------------+\n", | |
"| type| avgAmount|\n", | |
"+--------+------------------+\n", | |
"| DEBIT| 5483.665313767128|\n", | |
"| PAYMENT|13057.604660187604|\n", | |
"| CASH_IN| 168920.2420040954|\n", | |
"|CASH_OUT|176273.96434613998|\n", | |
"|TRANSFER| 910647.0096454868|\n", | |
"+--------+------------------+\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"#Sometimes we can pass column name directly to pyspark function\n", | |
"df.select('type', 'amount').groupBy('type').agg(F.mean('amount').alias('avgAmount')).orderBy('avgAmount').show(10)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 13, | |
"id": "754ecaf9", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:07:29.897379Z", | |
"iopub.status.busy": "2022-04-18T16:07:29.896184Z", | |
"iopub.status.idle": "2022-04-18T16:07:38.222453Z", | |
"shell.execute_reply": "2022-04-18T16:07:38.221443Z", | |
"shell.execute_reply.started": "2022-04-18T14:26:57.324339Z" | |
}, | |
"papermill": { | |
"duration": 8.589937, | |
"end_time": "2022-04-18T16:07:38.222670", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:29.632733", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 17:===========================================> (3 + 1) / 4]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+--------+------------------+\n", | |
"| type| avgAmount|\n", | |
"+--------+------------------+\n", | |
"| DEBIT| 5483.665313767128|\n", | |
"| PAYMENT|13057.604660187604|\n", | |
"| CASH_IN| 168920.2420040954|\n", | |
"|CASH_OUT|176273.96434613998|\n", | |
"|TRANSFER| 910647.0096454868|\n", | |
"+--------+------------------+\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"spark.sql('''\n", | |
" SELECT type, AVG(amount) avgAmount FROM df\n", | |
" GROUP BY type\n", | |
" ORDER BY 2\n", | |
"''').show(10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "68027a44", | |
"metadata": { | |
"papermill": { | |
"duration": 0.279305, | |
"end_time": "2022-04-18T16:07:38.815925", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:38.536620", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"To filter result after grouping, we can just simply apply `where` or `filter` to result `DataFrame` object or follow SQL framework with `having` keyword." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 14, | |
"id": "c9dd68d3", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:07:39.337683Z", | |
"iopub.status.busy": "2022-04-18T16:07:39.336980Z", | |
"iopub.status.idle": "2022-04-18T16:07:49.235799Z", | |
"shell.execute_reply": "2022-04-18T16:07:49.236575Z", | |
"shell.execute_reply.started": "2022-04-18T14:27:03.222215Z" | |
}, | |
"papermill": { | |
"duration": 10.161799, | |
"end_time": "2022-04-18T16:07:49.236769", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:39.074970", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 20:> (0 + 4) / 4]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+-----------+---------+\n", | |
"| nameOrig|sumAmount|\n", | |
"+-----------+---------+\n", | |
"| C551314014|301050.58|\n", | |
"| C661668091|323789.56|\n", | |
"| C228994633|517946.01|\n", | |
"|C1591008292|558254.22|\n", | |
"|C2100435651|357988.09|\n", | |
"| C624052656|476735.47|\n", | |
"| C948681098|353759.28|\n", | |
"| C50682517|386128.82|\n", | |
"|C1579521009|684561.18|\n", | |
"|C1871922377|394317.12|\n", | |
"+-----------+---------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"(\n", | |
" df.where(df['type']=='CASH_OUT')\n", | |
" .groupBy('nameOrig')\n", | |
" .agg(F.sum('amount').alias('sumAmount'))\n", | |
" .where(F.col('sumAmount') > 300000)\n", | |
" .show(10)\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 15, | |
"id": "70ebd597", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:07:49.792716Z", | |
"iopub.status.busy": "2022-04-18T16:07:49.791618Z", | |
"iopub.status.idle": "2022-04-18T16:07:58.216999Z", | |
"shell.execute_reply": "2022-04-18T16:07:58.216427Z", | |
"shell.execute_reply.started": "2022-04-18T14:27:11.428928Z" | |
}, | |
"papermill": { | |
"duration": 8.71375, | |
"end_time": "2022-04-18T16:07:58.217148", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:49.503398", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 23:==============> (1 + 3) / 4]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+-----------+---------+\n", | |
"| nameOrig|sumAmount|\n", | |
"+-----------+---------+\n", | |
"| C551314014|301050.58|\n", | |
"| C661668091|323789.56|\n", | |
"| C228994633|517946.01|\n", | |
"|C1591008292|558254.22|\n", | |
"|C2100435651|357988.09|\n", | |
"| C624052656|476735.47|\n", | |
"| C948681098|353759.28|\n", | |
"| C50682517|386128.82|\n", | |
"|C1579521009|684561.18|\n", | |
"|C1871922377|394317.12|\n", | |
"+-----------+---------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"spark.sql('''\n", | |
" SELECT nameOrig, SUM(amount) sumAmount FROM df\n", | |
" WHERE type = \"CASH_OUT\"\n", | |
" GROUP BY 1\n", | |
" HAVING sumAmount > 300000\n", | |
"''').show(10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "e231781f", | |
"metadata": { | |
"papermill": { | |
"duration": 0.255761, | |
"end_time": "2022-04-18T16:07:58.752136", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:58.496375", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"### Create table/views" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 16, | |
"id": "816647f1", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:07:59.276355Z", | |
"iopub.status.busy": "2022-04-18T16:07:59.275696Z", | |
"iopub.status.idle": "2022-04-18T16:07:59.278299Z", | |
"shell.execute_reply": "2022-04-18T16:07:59.278817Z", | |
"shell.execute_reply.started": "2022-04-18T14:27:18.299459Z" | |
}, | |
"papermill": { | |
"duration": 0.26778, | |
"end_time": "2022-04-18T16:07:59.279039", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:59.011259", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"# CREATE OR REPLACE TEMP VIEW name_of_view AS SELECT ... FROM ..." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "7a1b1ba7", | |
"metadata": { | |
"papermill": { | |
"duration": 0.258798, | |
"end_time": "2022-04-18T16:07:59.793576", | |
"exception": false, | |
"start_time": "2022-04-18T16:07:59.534778", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"### Uinon and Intersect" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 17, | |
"id": "cd97ddd1", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:08:00.317960Z", | |
"iopub.status.busy": "2022-04-18T16:08:00.317283Z", | |
"iopub.status.idle": "2022-04-18T16:08:04.926473Z", | |
"shell.execute_reply": "2022-04-18T16:08:04.927303Z", | |
"shell.execute_reply.started": "2022-04-18T14:27:18.305533Z" | |
}, | |
"papermill": { | |
"duration": 4.873564, | |
"end_time": "2022-04-18T16:08:04.927557", | |
"exception": false, | |
"start_time": "2022-04-18T16:08:00.053993", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
}, | |
{ | |
"data": { | |
"text/plain": [ | |
"12725240" | |
] | |
}, | |
"execution_count": 17, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"df.select('nameOrig').union(df.select('nameDest')).count()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 18, | |
"id": "23e6f44c", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:08:05.655396Z", | |
"iopub.status.busy": "2022-04-18T16:08:05.654333Z", | |
"iopub.status.idle": "2022-04-18T16:08:33.968916Z", | |
"shell.execute_reply": "2022-04-18T16:08:33.967981Z", | |
"shell.execute_reply.started": "2022-04-18T14:27:21.586558Z" | |
}, | |
"papermill": { | |
"duration": 28.602295, | |
"end_time": "2022-04-18T16:08:33.969166", | |
"exception": false, | |
"start_time": "2022-04-18T16:08:05.366871", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
}, | |
{ | |
"data": { | |
"text/plain": [ | |
"9073900" | |
] | |
}, | |
"execution_count": 18, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"spark.sql('''\n", | |
" SELECT nameOrig from df\n", | |
" UNION\n", | |
" SELECT nameDest from df\n", | |
"''').count()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "31da8ad8", | |
"metadata": { | |
"papermill": { | |
"duration": 0.260411, | |
"end_time": "2022-04-18T16:08:34.520400", | |
"exception": false, | |
"start_time": "2022-04-18T16:08:34.259989", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"We can see the difference here. The reason is `union()` function in pyspark will keep the duplicate samples from two sets. It is equivalent to `UNION ALL` in SQL. Remove duplication is a costly process." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 19, | |
"id": "0fa9cb8b", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:08:35.100012Z", | |
"iopub.status.busy": "2022-04-18T16:08:35.098953Z", | |
"iopub.status.idle": "2022-04-18T16:09:01.432422Z", | |
"shell.execute_reply": "2022-04-18T16:09:01.431381Z", | |
"shell.execute_reply.started": "2022-04-18T14:27:46.539315Z" | |
}, | |
"papermill": { | |
"duration": 26.645472, | |
"end_time": "2022-04-18T16:09:01.432650", | |
"exception": false, | |
"start_time": "2022-04-18T16:08:34.787178", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
}, | |
{ | |
"data": { | |
"text/plain": [ | |
"9073900" | |
] | |
}, | |
"execution_count": 19, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"df.select('nameOrig').union(df.select('nameDest')).dropDuplicates().count()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "257172c2", | |
"metadata": { | |
"papermill": { | |
"duration": 0.266071, | |
"end_time": "2022-04-18T16:09:01.964831", | |
"exception": false, | |
"start_time": "2022-04-18T16:09:01.698760", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"Unioning can be beneficial when we read data from multiple files. We can read them one by one and union them." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "5259772c", | |
"metadata": { | |
"papermill": { | |
"duration": 0.264503, | |
"end_time": "2022-04-18T16:09:02.500225", | |
"exception": false, | |
"start_time": "2022-04-18T16:09:02.235722", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"### Joining" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "587a310e", | |
"metadata": { | |
"papermill": { | |
"duration": 0.264936, | |
"end_time": "2022-04-18T16:09:03.030265", | |
"exception": false, | |
"start_time": "2022-04-18T16:09:02.765329", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"Sometimes, we can mix SQL code with pyspark to gain readability. Functions support: `selectExpr`, `where`, `filter`, `expr`,..." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 20, | |
"id": "85943200", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:09:03.569786Z", | |
"iopub.status.busy": "2022-04-18T16:09:03.568718Z", | |
"iopub.status.idle": "2022-04-18T16:09:36.071105Z", | |
"shell.execute_reply": "2022-04-18T16:09:36.070352Z", | |
"shell.execute_reply.started": "2022-04-18T14:28:08.367603Z" | |
}, | |
"papermill": { | |
"duration": 32.773863, | |
"end_time": "2022-04-18T16:09:36.071370", | |
"exception": false, | |
"start_time": "2022-04-18T16:09:03.297507", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 45:> (0 + 4) / 5]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+-----------+---+------------------+------------------+\n", | |
"| name|occ| avgChangeOrig| avgChangeDest|\n", | |
"+-----------+---+------------------+------------------+\n", | |
"|C1552859894| 43|193711.30000000005| 763241.1652380949|\n", | |
"|C1819271729| 37| 278937.79|283626.17805555544|\n", | |
"|C1692434834| 37|177369.73000000045| 438853.7616666666|\n", | |
"| C889762313| 32| 132731.31|211437.18741935486|\n", | |
"|C1868986147| 32| 120594.03|249840.37709677417|\n", | |
"| C55305556| 28|319860.45999999903|225565.42111111112|\n", | |
"| C636092700| 26|217273.86000000004|201888.05279999998|\n", | |
"|C1713505653| 25| 278622.8400000003|186625.34916666665|\n", | |
"|C2029542508| 24| 235760.1200000001|231022.98217391354|\n", | |
"| C699906968| 23| 177813.3799999999| 183054.3072727272|\n", | |
"+-----------+---+------------------+------------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"(\n", | |
" df.where('type = \"CASH_IN\" OR type = \"CASH_OUT\"')\n", | |
" .selectExpr('nameOrig', 'ABS(newBalanceOrig - oldBalanceOrig) changeOrig')\n", | |
" .groupBy('nameOrig')\n", | |
" .agg(\n", | |
" F.mean(F.col('changeOrig')).alias('avgChangeOrig'),\n", | |
" F.count('*').alias('occOrig')\n", | |
" )\n", | |
" .where('avgChangeOrig > 100000')\n", | |
" .join((\n", | |
" df.where('type = \"CASH_IN\" OR type = \"CASH_OUT\"')\n", | |
" .selectExpr('nameDest', 'ABS(newBalanceDest - oldBalanceDest) changeDest')\n", | |
" .groupBy('nameDest')\n", | |
" .agg(\n", | |
" F.mean(F.col('changeDest')).alias('avgChangeDest'),\n", | |
" F.count('*').alias('occDest')\n", | |
" )\n", | |
" .where('avgChangeDest > 100000')\n", | |
" ), on=F.col('nameOrig')==F.col('nameDest'), how='inner')\n", | |
" .selectExpr('nameOrig name', 'occOrig + occDest occ', 'avgChangeOrig', 'avgChangeDest')\n", | |
" .orderBy('occ', ascending=False)\n", | |
").show(10)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 21, | |
"id": "3ce7bdc8", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:09:36.650919Z", | |
"iopub.status.busy": "2022-04-18T16:09:36.650223Z", | |
"iopub.status.idle": "2022-04-18T16:09:59.622409Z", | |
"shell.execute_reply": "2022-04-18T16:09:59.623244Z", | |
"shell.execute_reply.started": "2022-04-18T14:28:27.953111Z" | |
}, | |
"papermill": { | |
"duration": 23.265558, | |
"end_time": "2022-04-18T16:09:59.623496", | |
"exception": false, | |
"start_time": "2022-04-18T16:09:36.357938", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 50:> (0 + 4) / 5]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+-----------+---+------------------+------------------+\n", | |
"| name|occ| avgChangeOrig| avgChangeDest|\n", | |
"+-----------+---+------------------+------------------+\n", | |
"|C1552859894| 43|193711.30000000005| 763241.1652380949|\n", | |
"|C1819271729| 37| 278937.79|283626.17805555544|\n", | |
"|C1692434834| 37|177369.73000000045| 438853.7616666666|\n", | |
"| C889762313| 32| 132731.31|211437.18741935486|\n", | |
"|C1868986147| 32| 120594.03|249840.37709677417|\n", | |
"| C55305556| 28|319860.45999999903|225565.42111111112|\n", | |
"| C636092700| 26|217273.86000000004|201888.05279999998|\n", | |
"|C1713505653| 25| 278622.8400000003|186625.34916666665|\n", | |
"|C2029542508| 24| 235760.1200000001|231022.98217391354|\n", | |
"| C699906968| 23| 177813.3799999999| 183054.3072727272|\n", | |
"+-----------+---+------------------+------------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"spark.sql('''\n", | |
" SELECT nameOrig name, occOrig + occDest occ, avgChangeOrig, avgChangeDest\n", | |
" FROM\n", | |
" (\n", | |
" SELECT nameOrig, AVG(ABS(newBalanceOrig - oldBalanceOrig)) avgChangeOrig, COUNT(*) occOrig\n", | |
" FROM df\n", | |
" WHERE type = \"CASH_IN\" OR type = \"CASH_OUT\"\n", | |
" GROUP BY nameOrig\n", | |
" HAVING avgChangeOrig > 100000\n", | |
" )\n", | |
" INNER JOIN\n", | |
" (\n", | |
" SELECT nameDest, AVG(ABS(newBalanceDest - oldBalanceDest)) avgChangeDest, COUNT(*) occDest\n", | |
" FROM df\n", | |
" WHERE type = \"CASH_IN\" OR type = \"CASH_OUT\"\n", | |
" GROUP BY nameDest\n", | |
" HAVING avgChangeDest > 100000\n", | |
" )\n", | |
" ON nameOrig = nameDest\n", | |
" ORDER BY occ DESC\n", | |
"''').show(10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "0f089874", | |
"metadata": { | |
"papermill": { | |
"duration": 0.279977, | |
"end_time": "2022-04-18T16:10:00.207625", | |
"exception": false, | |
"start_time": "2022-04-18T16:09:59.927648", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"There are several join method: `inner`, `left`, `right`, `cross`, `outer`, `left_outer`, `right_outer`, `left_semi`, `left_anti`, `right_semi`, `right_anti`, ..." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "783cd337", | |
"metadata": { | |
"papermill": { | |
"duration": 0.281242, | |
"end_time": "2022-04-18T16:10:00.762668", | |
"exception": false, | |
"start_time": "2022-04-18T16:10:00.481426", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"### Subqueries" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "2c783ed6", | |
"metadata": { | |
"papermill": { | |
"duration": 0.275711, | |
"end_time": "2022-04-18T16:10:01.312404", | |
"exception": false, | |
"start_time": "2022-04-18T16:10:01.036693", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"In a nutshell, subqueries allow you to create tables local to your query. In Python, this is similar to using the `with` statement or using a function block to limit the scope of a query. It is very helpful in keeping our code clean" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 22, | |
"id": "60b58296", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:10:01.902252Z", | |
"iopub.status.busy": "2022-04-18T16:10:01.901503Z", | |
"iopub.status.idle": "2022-04-18T16:10:24.597708Z", | |
"shell.execute_reply": "2022-04-18T16:10:24.596710Z", | |
"shell.execute_reply.started": "2022-04-18T14:28:45.130235Z" | |
}, | |
"papermill": { | |
"duration": 23.009323, | |
"end_time": "2022-04-18T16:10:24.597962", | |
"exception": false, | |
"start_time": "2022-04-18T16:10:01.588639", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 55:> (0 + 4) / 5]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+-----------+---+------------------+------------------+\n", | |
"| name|occ| avgChangeOrig| avgChangeDest|\n", | |
"+-----------+---+------------------+------------------+\n", | |
"|C1552859894| 43|193711.30000000005| 763241.1652380949|\n", | |
"|C1819271729| 37| 278937.79|283626.17805555544|\n", | |
"|C1692434834| 37|177369.73000000045| 438853.7616666666|\n", | |
"| C889762313| 32| 132731.31|211437.18741935486|\n", | |
"|C1868986147| 32| 120594.03|249840.37709677417|\n", | |
"| C55305556| 28|319860.45999999903|225565.42111111112|\n", | |
"| C636092700| 26|217273.86000000004|201888.05279999998|\n", | |
"|C1713505653| 25| 278622.8400000003|186625.34916666665|\n", | |
"|C2029542508| 24| 235760.1200000001|231022.98217391354|\n", | |
"| C699906968| 23| 177813.3799999999| 183054.3072727272|\n", | |
"+-----------+---+------------------+------------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"def temp_var_not_avail_outside_function():\n", | |
" orig_df = (\n", | |
" df.where((df.type=='CASH_IN') | (df.type=='CASH_OUT'))\n", | |
" .select('nameOrig', F.abs(df.newBalanceOrig - df.oldBalanceOrig).alias('changeOrig'))\n", | |
" .groupBy('nameOrig')\n", | |
" .agg(\n", | |
" F.mean(F.col('changeOrig')).alias('avgChangeOrig'),\n", | |
" F.count('*').alias('occOrig')\n", | |
" )\n", | |
" .where(F.col('avgChangeOrig') > 100000)\n", | |
" )\n", | |
" dest_df = (\n", | |
" df.where((df.type=='CASH_IN') | (df.type=='CASH_OUT'))\n", | |
" .select('nameDest', F.abs(df.newBalanceDest - df.oldBalanceDest).alias('changeDest'))\n", | |
" .groupBy('nameDest')\n", | |
" .agg(\n", | |
" F.mean(F.col('changeDest')).alias('avgChangeDest'),\n", | |
" F.count('*').alias('occDest')\n", | |
" )\n", | |
" .where(F.col('avgChangeDest') > 100000)\n", | |
" )\n", | |
" # Main query\n", | |
" (\n", | |
" orig_df.join(dest_df, on=orig_df.nameOrig==dest_df.nameDest, how='inner')\n", | |
" .select(\n", | |
" F.col('nameOrig').alias('name'), \n", | |
" (F.col('occOrig') + F.col('occDest')).alias('occ'),\n", | |
" 'avgChangeOrig', 'avgChangeDest' \n", | |
" )\n", | |
" .orderBy('occ', ascending=False)\n", | |
" .show(10)\n", | |
" )\n", | |
" \n", | |
"\n", | |
"temp_var_not_avail_outside_function()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 23, | |
"id": "e0a79ad8", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:10:25.177415Z", | |
"iopub.status.busy": "2022-04-18T16:10:25.176240Z", | |
"iopub.status.idle": "2022-04-18T16:10:48.027842Z", | |
"shell.execute_reply": "2022-04-18T16:10:48.026869Z", | |
"shell.execute_reply.started": "2022-04-18T14:29:01.985654Z" | |
}, | |
"papermill": { | |
"duration": 23.131135, | |
"end_time": "2022-04-18T16:10:48.028078", | |
"exception": false, | |
"start_time": "2022-04-18T16:10:24.896943", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 60:> (0 + 4) / 5]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+-----------+---+------------------+------------------+\n", | |
"| name|occ| avgChangeOrig| avgChangeDest|\n", | |
"+-----------+---+------------------+------------------+\n", | |
"|C1552859894| 43|193711.30000000005| 763241.1652380949|\n", | |
"|C1692434834| 37|177369.73000000045| 438853.7616666666|\n", | |
"|C1819271729| 37| 278937.79|283626.17805555544|\n", | |
"| C889762313| 32| 132731.31|211437.18741935486|\n", | |
"|C1868986147| 32| 120594.03|249840.37709677417|\n", | |
"| C55305556| 28|319860.45999999903|225565.42111111112|\n", | |
"| C636092700| 26|217273.86000000004|201888.05279999998|\n", | |
"|C1713505653| 25| 278622.8400000003|186625.34916666665|\n", | |
"|C2029542508| 24| 235760.1200000001|231022.98217391354|\n", | |
"| C699906968| 23| 177813.3799999999| 183054.3072727272|\n", | |
"+-----------+---+------------------+------------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"spark.sql('''\n", | |
" WITH \n", | |
" origDf as (\n", | |
" SELECT nameOrig, AVG(ABS(newBalanceOrig - oldBalanceOrig)) avgChangeOrig, COUNT(*) occOrig\n", | |
" FROM df\n", | |
" WHERE type = \"CASH_IN\" OR type = \"CASH_OUT\"\n", | |
" GROUP BY nameOrig\n", | |
" HAVING avgChangeOrig > 100000\n", | |
" ),\n", | |
" destDf as (\n", | |
" SELECT nameDest, AVG(ABS(newBalanceDest - oldBalanceDest)) avgChangeDest, COUNT(*) occDest\n", | |
" FROM df\n", | |
" WHERE type = \"CASH_IN\" OR type = \"CASH_OUT\"\n", | |
" GROUP BY nameDest\n", | |
" HAVING avgChangeDest > 100000\n", | |
" )\n", | |
" SELECT nameOrig name, occOrig + occDest occ, avgChangeOrig, avgChangeDest\n", | |
" FROM origDf INNER JOIN destDf ON nameOrig = nameDest\n", | |
" ORDER BY occ DESC\n", | |
"''').show(10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "1caa0b73", | |
"metadata": { | |
"papermill": { | |
"duration": 0.275635, | |
"end_time": "2022-04-18T16:10:48.585268", | |
"exception": false, | |
"start_time": "2022-04-18T16:10:48.309633", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"## UDF" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "aed6ecff", | |
"metadata": { | |
"papermill": { | |
"duration": 0.277879, | |
"end_time": "2022-04-18T16:10:49.139463", | |
"exception": false, | |
"start_time": "2022-04-18T16:10:48.861584", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"What comes in is a regular Python function, and what goes out is a function promoted to work on PySpark columns.\n" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "5ed788bc", | |
"metadata": { | |
"papermill": { | |
"duration": 0.307955, | |
"end_time": "2022-04-18T16:10:49.727618", | |
"exception": false, | |
"start_time": "2022-04-18T16:10:49.419663", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"PySpark provides a udf() function in the pyspark.sql.functions module to promote Python functions to their UDF equivalents. The function takes two parameters:\n", | |
"- The function you want to promote\n", | |
"- The return type of the generated UDF\n", | |
"\n", | |
"In order to make sure the input and output types are compatible. It is recommended to explicitly specify input-output types for python functions." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 24, | |
"id": "752b4e2c", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:10:50.362023Z", | |
"iopub.status.busy": "2022-04-18T16:10:50.361031Z", | |
"iopub.status.idle": "2022-04-18T16:10:50.365537Z", | |
"shell.execute_reply": "2022-04-18T16:10:50.364624Z", | |
"shell.execute_reply.started": "2022-04-18T14:29:17.932933Z" | |
}, | |
"papermill": { | |
"duration": 0.299517, | |
"end_time": "2022-04-18T16:10:50.365732", | |
"exception": false, | |
"start_time": "2022-04-18T16:10:50.066215", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"root\n", | |
" |-- step: integer (nullable = true)\n", | |
" |-- type: string (nullable = true)\n", | |
" |-- amount: double (nullable = true)\n", | |
" |-- nameOrig: string (nullable = true)\n", | |
" |-- oldBalanceOrig: double (nullable = true)\n", | |
" |-- newBalanceOrig: double (nullable = true)\n", | |
" |-- nameDest: string (nullable = true)\n", | |
" |-- oldBalanceDest: double (nullable = true)\n", | |
" |-- newBalanceDest: double (nullable = true)\n", | |
" |-- isFraud: integer (nullable = true)\n", | |
" |-- isFlaggedFraud: integer (nullable = true)\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"df.printSchema()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 25, | |
"id": "699a6a65", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:10:50.938677Z", | |
"iopub.status.busy": "2022-04-18T16:10:50.937948Z", | |
"iopub.status.idle": "2022-04-18T16:10:50.939680Z", | |
"shell.execute_reply": "2022-04-18T16:10:50.940267Z", | |
"shell.execute_reply.started": "2022-04-18T14:29:17.943460Z" | |
}, | |
"papermill": { | |
"duration": 0.285892, | |
"end_time": "2022-04-18T16:10:50.940446", | |
"exception": false, | |
"start_time": "2022-04-18T16:10:50.654554", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"def classify_tier(amount:float) -> int:\n", | |
" if amount < 500:\n", | |
" return 0\n", | |
" if amount < 10000:\n", | |
" return 1\n", | |
" if amount < 100000:\n", | |
" return 2\n", | |
" if amount < 1000000:\n", | |
" return 3\n", | |
" return 4\n", | |
"\n", | |
"\n", | |
"classifyTier = F.udf(classify_tier, T.ByteType())" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 26, | |
"id": "fcd36e57", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:10:51.499152Z", | |
"iopub.status.busy": "2022-04-18T16:10:51.498463Z", | |
"iopub.status.idle": "2022-04-18T16:11:11.741140Z", | |
"shell.execute_reply": "2022-04-18T16:11:11.741655Z", | |
"shell.execute_reply.started": "2022-04-18T14:29:17.956293Z" | |
}, | |
"papermill": { | |
"duration": 20.522851, | |
"end_time": "2022-04-18T16:11:11.741825", | |
"exception": false, | |
"start_time": "2022-04-18T16:10:51.218974", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 61:===========================================> (3 + 1) / 4]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+-----------+----+\n", | |
"| nameOrig|tier|\n", | |
"+-----------+----+\n", | |
"|C1495608502| 4|\n", | |
"|C1321115948| 4|\n", | |
"| C476579021| 4|\n", | |
"|C1520267010| 4|\n", | |
"| C106297322| 4|\n", | |
"|C1464177809| 4|\n", | |
"| C355885103| 4|\n", | |
"|C1057507014| 4|\n", | |
"|C1419332030| 4|\n", | |
"|C2007599722| 4|\n", | |
"+-----------+----+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"df.select('nameOrig', classifyTier(df.amount).alias('tier')).orderBy('tier', ascending=False).show(10)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 27, | |
"id": "e08fdb53", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:11:12.409479Z", | |
"iopub.status.busy": "2022-04-18T16:11:12.408716Z", | |
"iopub.status.idle": "2022-04-18T16:11:31.526739Z", | |
"shell.execute_reply": "2022-04-18T16:11:31.527275Z", | |
"shell.execute_reply.started": "2022-04-18T14:29:33.564186Z" | |
}, | |
"papermill": { | |
"duration": 19.406816, | |
"end_time": "2022-04-18T16:11:31.527451", | |
"exception": false, | |
"start_time": "2022-04-18T16:11:12.120635", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 62:==============> (1 + 3) / 4]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+-----------+----+\n", | |
"| nameOrig|tier|\n", | |
"+-----------+----+\n", | |
"| C263860433| 4|\n", | |
"| C306269750| 4|\n", | |
"|C1611915976| 4|\n", | |
"|C1387188921| 4|\n", | |
"| C300262358| 4|\n", | |
"| C389879985| 4|\n", | |
"|C1907016309| 4|\n", | |
"|C1046638041| 4|\n", | |
"|C1543404166| 4|\n", | |
"|C1155108056| 4|\n", | |
"+-----------+----+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"spark.udf.register('classifyTier', classify_tier)\n", | |
"spark.sql('''\n", | |
" SELECT nameOrig, classifyTier(amount) tier\n", | |
" FROM df\n", | |
" ORDER BY tier DESC \n", | |
"''').show(10)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 28, | |
"id": "83739696", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:11:32.109699Z", | |
"iopub.status.busy": "2022-04-18T16:11:32.109009Z", | |
"iopub.status.idle": "2022-04-18T16:11:49.432511Z", | |
"shell.execute_reply": "2022-04-18T16:11:49.431650Z", | |
"shell.execute_reply.started": "2022-04-18T14:29:46.658573Z" | |
}, | |
"papermill": { | |
"duration": 17.618618, | |
"end_time": "2022-04-18T16:11:49.432749", | |
"exception": false, | |
"start_time": "2022-04-18T16:11:31.814131", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 63:=============================> (2 + 2) / 4]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+--------+------------------+\n", | |
"| type| trueRatio|\n", | |
"+--------+------------------+\n", | |
"|TRANSFER|0.9923420321293129|\n", | |
"| CASH_IN| 1.0|\n", | |
"|CASH_OUT|0.9981604469273743|\n", | |
"| PAYMENT| 1.0|\n", | |
"| DEBIT| 1.0|\n", | |
"+--------+------------------+\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"@F.udf(T.BooleanType())\n", | |
"def isTrueFlag(predicted:int, truth:int) -> bool:\n", | |
" return predicted == truth\n", | |
"\n", | |
"(\n", | |
" df.withColumn('trueFlag', isTrueFlag(df.isFraud, df.isFlaggedFraud))\n", | |
" .groupBy('type')\n", | |
" .agg(\n", | |
" F.mean(F.col('trueFlag').cast('int')).alias('trueRatio')\n", | |
" )\n", | |
" .show(10)\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 29, | |
"id": "66d19152", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:11:50.047986Z", | |
"iopub.status.busy": "2022-04-18T16:11:50.046951Z", | |
"iopub.status.idle": "2022-04-18T16:12:07.657097Z", | |
"shell.execute_reply": "2022-04-18T16:12:07.656303Z", | |
"shell.execute_reply.started": "2022-04-18T14:29:59.737086Z" | |
}, | |
"papermill": { | |
"duration": 17.902481, | |
"end_time": "2022-04-18T16:12:07.657305", | |
"exception": false, | |
"start_time": "2022-04-18T16:11:49.754824", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 66:==============> (1 + 3) / 4]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+--------+------------------+\n", | |
"| type| mean(trueFlag)|\n", | |
"+--------+------------------+\n", | |
"|TRANSFER|0.9923420321293129|\n", | |
"| CASH_IN| 1.0|\n", | |
"|CASH_OUT|0.9981604469273743|\n", | |
"| PAYMENT| 1.0|\n", | |
"| DEBIT| 1.0|\n", | |
"+--------+------------------+\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"spark.udf.register('isTrueFlag', isTrueFlag)\n", | |
"spark.sql('''\n", | |
" WITH trueDf as (\n", | |
" SELECT type, isTrueFlag(isFraud, isFlaggedFraud) trueFlag FROM df\n", | |
" )\n", | |
" SELECT type, MEAN(INT(trueFlag)) FROM trueDf\n", | |
" GROUP BY type\n", | |
"''').show(10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "d87b9414", | |
"metadata": { | |
"papermill": { | |
"duration": 0.290615, | |
"end_time": "2022-04-18T16:12:08.292062", | |
"exception": false, | |
"start_time": "2022-04-18T16:12:08.001447", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"## Pandas UDF" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "2c972974", | |
"metadata": { | |
"papermill": { | |
"duration": 0.286788, | |
"end_time": "2022-04-18T16:12:08.861746", | |
"exception": false, | |
"start_time": "2022-04-18T16:12:08.574958", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"### Series to Series" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "f88e2275", | |
"metadata": { | |
"papermill": { | |
"duration": 0.285137, | |
"end_time": "2022-04-18T16:12:09.435603", | |
"exception": false, | |
"start_time": "2022-04-18T16:12:09.150466", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"In a Python UDF, when you pass column objects to your UDF, PySpark will unpack each value, perform the computation, and then return the value for each record. In a Scalar UDF, PySpark will serialize (through a library called PyArrow) each partitioned column into a pandas `Series` object. You then perform the operations on the `Series` object directly,returning a Series of the same dimension from your UDF. From an end user perspective, they are the same functionally. Because pandas is optimized for rapid data manipulation, it is preferable to use a Series to Series UDF when you can instead of using a regular Python UDF, as it’ll be much faster." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 30, | |
"id": "bb803e93", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:12:10.013394Z", | |
"iopub.status.busy": "2022-04-18T16:12:10.012348Z", | |
"iopub.status.idle": "2022-04-18T16:12:10.014476Z", | |
"shell.execute_reply": "2022-04-18T16:12:10.014997Z", | |
"shell.execute_reply.started": "2022-04-18T14:30:12.264830Z" | |
}, | |
"papermill": { | |
"duration": 0.29371, | |
"end_time": "2022-04-18T16:12:10.015174", | |
"exception": false, | |
"start_time": "2022-04-18T16:12:09.721464", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"import pandas as pd\n", | |
"\n", | |
"\n", | |
"# We can also promote the function to pandas as GetUserType = F.pandas_udf(get_user_type, T.StringType())\n", | |
"@F.pandas_udf(T.StringType())\n", | |
"def getUserType(name: pd.Series) -> pd.Series:\n", | |
" return name.str[0]\n", | |
"\n", | |
"# Unfortunately, we currently can't use pandas_udf with pure SQL" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 31, | |
"id": "c044d112", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:12:10.594652Z", | |
"iopub.status.busy": "2022-04-18T16:12:10.593472Z", | |
"iopub.status.idle": "2022-04-18T16:12:25.631865Z", | |
"shell.execute_reply": "2022-04-18T16:12:25.632636Z", | |
"shell.execute_reply.started": "2022-04-18T14:30:12.273390Z" | |
}, | |
"papermill": { | |
"duration": 15.331301, | |
"end_time": "2022-04-18T16:12:25.632905", | |
"exception": false, | |
"start_time": "2022-04-18T16:12:10.301604", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 69:=============================> (2 + 2) / 4]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+------------+------------------+-------+\n", | |
"|userTypeDest| avgAmount| n|\n", | |
"+------------+------------------+-------+\n", | |
"| C| 265083.4571810173|4211125|\n", | |
"| M|13057.604660187604|2151495|\n", | |
"+------------+------------------+-------+\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"(\n", | |
" df.select(getUserType(df.nameDest).alias('userTypeDest'), df.amount)\n", | |
" .groupBy('userTypeDest')\n", | |
" .agg(\n", | |
" F.mean('amount').alias('avgAmount'),\n", | |
" F.count('*').alias('n')\n", | |
" )\n", | |
" .orderBy('avgAmount', ascending=False)\n", | |
" .show(10)\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 32, | |
"id": "ee3dd896", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:12:26.272722Z", | |
"iopub.status.busy": "2022-04-18T16:12:26.272024Z", | |
"iopub.status.idle": "2022-04-18T16:12:26.275335Z", | |
"shell.execute_reply": "2022-04-18T16:12:26.274829Z", | |
"shell.execute_reply.started": "2022-04-18T14:30:23.272323Z" | |
}, | |
"papermill": { | |
"duration": 0.298723, | |
"end_time": "2022-04-18T16:12:26.275543", | |
"exception": false, | |
"start_time": "2022-04-18T16:12:25.976820", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"def get_change(old: pd.Series, new: pd.Series) -> pd.Series:\n", | |
" return (new - old).abs()\n", | |
"\n", | |
"getChange = F.pandas_udf(get_change, T.DoubleType())" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 33, | |
"id": "e5a0e701", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:12:26.869531Z", | |
"iopub.status.busy": "2022-04-18T16:12:26.868606Z", | |
"iopub.status.idle": "2022-04-18T16:12:39.649569Z", | |
"shell.execute_reply": "2022-04-18T16:12:39.648729Z", | |
"shell.execute_reply.started": "2022-04-18T14:30:23.279933Z" | |
}, | |
"papermill": { | |
"duration": 13.08578, | |
"end_time": "2022-04-18T16:12:39.649788", | |
"exception": false, | |
"start_time": "2022-04-18T16:12:26.564008", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 72:===========================================> (3 + 1) / 4]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+--------+--------------------+\n", | |
"| type| equalRatio|\n", | |
"+--------+--------------------+\n", | |
"|TRANSFER|0.010253157668570056|\n", | |
"| CASH_IN| 0.11754011337226754|\n", | |
"|CASH_OUT|0.030618994413407822|\n", | |
"| PAYMENT| 0.3598637226672616|\n", | |
"| DEBIT| 0.08978567290982815|\n", | |
"+--------+--------------------+\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"(\n", | |
" df.select(df.type, \n", | |
" getChange(df.oldBalanceOrig, df.newBalanceOrig).alias('changeOrig'),\n", | |
" getChange(df.oldBalanceDest, df.newBalanceDest).alias('changeDest'))\n", | |
" .withColumn('equal', F.col('changeOrig') == F.col('changeDest'))\n", | |
" .groupBy('type')\n", | |
" .agg(\n", | |
" F.mean(F.col('equal').cast('int')).alias('equalRatio')\n", | |
" )\n", | |
" .show(10)\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "87f9a87e", | |
"metadata": { | |
"papermill": { | |
"duration": 0.286999, | |
"end_time": "2022-04-18T16:12:40.287154", | |
"exception": false, | |
"start_time": "2022-04-18T16:12:40.000155", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"### Iterator of (mutiple) Series to Iterator of Series" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "d9b1ee57", | |
"metadata": { | |
"papermill": { | |
"duration": 0.292316, | |
"end_time": "2022-04-18T16:12:40.868711", | |
"exception": false, | |
"start_time": "2022-04-18T16:12:40.576395", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"Due to the distributed nature of Spark, the whole `Series` won't be fed to the udf at once, but instead, each cluster will call the udf on its own batch of data, and then aggregate the result. Iterator of Series UDFs are very useful when we have an expensive *cold start* operation you need to perform once at the beginning of the processing step." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 34, | |
"id": "858bbfe5", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:12:41.458627Z", | |
"iopub.status.busy": "2022-04-18T16:12:41.454453Z", | |
"iopub.status.idle": "2022-04-18T16:12:41.460172Z", | |
"shell.execute_reply": "2022-04-18T16:12:41.460763Z", | |
"shell.execute_reply.started": "2022-04-18T14:30:33.103250Z" | |
}, | |
"papermill": { | |
"duration": 0.298345, | |
"end_time": "2022-04-18T16:12:41.460951", | |
"exception": false, | |
"start_time": "2022-04-18T16:12:41.162606", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"from time import sleep\n", | |
"from typing import Iterator, Tuple\n", | |
"\n", | |
"\n", | |
"@F.pandas_udf(T.ByteType())\n", | |
"def getNameIdLength(name: Iterator[pd.Series]) -> Iterator[pd.Series]:\n", | |
" # Heavy task\n", | |
" # sleep(5)\n", | |
" \n", | |
" for name_batch in name:\n", | |
" name_len = name_batch.str.len()\n", | |
" name_len[~name_batch.str[0].str.isnumeric()] -= 1\n", | |
" yield name_len " | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 35, | |
"id": "ffda19f7", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:12:42.045251Z", | |
"iopub.status.busy": "2022-04-18T16:12:42.044566Z", | |
"iopub.status.idle": "2022-04-18T16:12:57.737153Z", | |
"shell.execute_reply": "2022-04-18T16:12:57.736340Z", | |
"shell.execute_reply.started": "2022-04-18T14:30:33.114190Z" | |
}, | |
"papermill": { | |
"duration": 15.986047, | |
"end_time": "2022-04-18T16:12:57.737350", | |
"exception": false, | |
"start_time": "2022-04-18T16:12:41.751303", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 75:=============================> (2 + 2) / 4]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+-----+------------------+\n", | |
"|idLen| avgAmount|\n", | |
"+-----+------------------+\n", | |
"| 4|155070.73742857145|\n", | |
"| 7|177477.50726081585|\n", | |
"| 10| 179702.4408980949|\n", | |
"| 9|179898.05510125632|\n", | |
"| 8| 181572.2097899971|\n", | |
"| 6|197756.81529433408|\n", | |
"| 5|199594.79368029739|\n", | |
"+-----+------------------+\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"(\n", | |
" df.select(getNameIdLength(df.nameOrig).alias('idLen'), 'amount')\n", | |
" .groupBy('idLen')\n", | |
" .agg(F.mean('amount').alias('avgAmount'))\n", | |
" .orderBy('avgAmount')\n", | |
" .show(10)\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 36, | |
"id": "7fa6b3c1", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:12:58.376889Z", | |
"iopub.status.busy": "2022-04-18T16:12:58.376106Z", | |
"iopub.status.idle": "2022-04-18T16:12:58.378866Z", | |
"shell.execute_reply": "2022-04-18T16:12:58.378359Z", | |
"shell.execute_reply.started": "2022-04-18T14:30:44.671010Z" | |
}, | |
"papermill": { | |
"duration": 0.303693, | |
"end_time": "2022-04-18T16:12:58.379031", | |
"exception": false, | |
"start_time": "2022-04-18T16:12:58.075338", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"def amount_mismatch(values: Iterator[Tuple[pd.Series, pd.Series, pd.Series, pd.Series]]) -> Iterator[pd.Series]:\n", | |
" # Heavy task\n", | |
" # ...\n", | |
" \n", | |
" for oldOrig, newOrig, oldDest, newDest in values:\n", | |
" yield abs(abs(newOrig - oldOrig) - abs(newDest - oldDest))\n", | |
" \n", | |
"amountMismatch = F.pandas_udf(amount_mismatch, T.DoubleType())" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 37, | |
"id": "c3634ea2", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:12:58.968598Z", | |
"iopub.status.busy": "2022-04-18T16:12:58.965657Z", | |
"iopub.status.idle": "2022-04-18T16:13:11.699122Z", | |
"shell.execute_reply": "2022-04-18T16:13:11.698473Z", | |
"shell.execute_reply.started": "2022-04-18T14:30:44.680669Z" | |
}, | |
"papermill": { | |
"duration": 13.03029, | |
"end_time": "2022-04-18T16:13:11.699285", | |
"exception": false, | |
"start_time": "2022-04-18T16:12:58.668995", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 78:===========================================> (3 + 1) / 4]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+--------+------------------+\n", | |
"| type| avgMismatch|\n", | |
"+--------+------------------+\n", | |
"|TRANSFER| 968056.4538892006|\n", | |
"|CASH_OUT|170539.39652580014|\n", | |
"| CASH_IN| 50038.95466155722|\n", | |
"| DEBIT| 25567.53969902471|\n", | |
"| PAYMENT| 6378.936662041953|\n", | |
"+--------+------------------+\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"(\n", | |
" df.select(\n", | |
" df.type,\n", | |
" amountMismatch(df.oldBalanceOrig, df.newBalanceOrig, df.oldBalanceDest, df.newBalanceDest).alias('mismatch')\n", | |
" )\n", | |
" .groupBy('type')\n", | |
" .agg(\n", | |
" F.mean('mismatch').alias('avgMismatch')\n", | |
" )\n", | |
" .orderBy('avgMismatch', ascending=False)\n", | |
" .show(10)\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "eda2c20a", | |
"metadata": { | |
"papermill": { | |
"duration": 0.292788, | |
"end_time": "2022-04-18T16:13:12.314441", | |
"exception": false, | |
"start_time": "2022-04-18T16:13:12.021653", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"### Group aggregate UDF" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "eec16af6", | |
"metadata": { | |
"papermill": { | |
"duration": 0.296477, | |
"end_time": "2022-04-18T16:13:12.906654", | |
"exception": false, | |
"start_time": "2022-04-18T16:13:12.610177", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"Group aggregate UDF, also known as the Series to Scalar UDF, distills the `Series` received as input to a single value." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 38, | |
"id": "1163b66b", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:13:13.499975Z", | |
"iopub.status.busy": "2022-04-18T16:13:13.499285Z", | |
"iopub.status.idle": "2022-04-18T16:13:13.502097Z", | |
"shell.execute_reply": "2022-04-18T16:13:13.501495Z", | |
"shell.execute_reply.started": "2022-04-18T14:30:53.777697Z" | |
}, | |
"papermill": { | |
"duration": 0.302711, | |
"end_time": "2022-04-18T16:13:13.502251", | |
"exception": false, | |
"start_time": "2022-04-18T16:13:13.199540", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"@F.pandas_udf(T.DoubleType())\n", | |
"def GetStdDeviation(series: pd.Series) -> float:\n", | |
" return series.std()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 39, | |
"id": "085b9c8a", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:13:14.087578Z", | |
"iopub.status.busy": "2022-04-18T16:13:14.086917Z", | |
"iopub.status.idle": "2022-04-18T16:13:25.748590Z", | |
"shell.execute_reply": "2022-04-18T16:13:25.747643Z", | |
"shell.execute_reply.started": "2022-04-18T14:30:53.784736Z" | |
}, | |
"papermill": { | |
"duration": 11.956113, | |
"end_time": "2022-04-18T16:13:25.748814", | |
"exception": false, | |
"start_time": "2022-04-18T16:13:13.792701", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 83:=============================> (2 + 2) / 4]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+--------+------------------+\n", | |
"| type| var|\n", | |
"+--------+------------------+\n", | |
"|TRANSFER|1879573.5289080725|\n", | |
"|CASH_OUT|175329.74448347004|\n", | |
"| CASH_IN|126508.25527180695|\n", | |
"| DEBIT|13318.535518284714|\n", | |
"| PAYMENT|12556.450185716356|\n", | |
"+--------+------------------+\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"(\n", | |
" df.groupBy('type')\n", | |
" .agg(\n", | |
" GetStdDeviation(df.amount).alias('var')\n", | |
" )\n", | |
" .orderBy('var', ascending=False)\n", | |
" .show(10)\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "c8590d78", | |
"metadata": { | |
"papermill": { | |
"duration": 0.297739, | |
"end_time": "2022-04-18T16:13:26.350083", | |
"exception": false, | |
"start_time": "2022-04-18T16:13:26.052344", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"### Group map UDF" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "c515f735", | |
"metadata": { | |
"papermill": { | |
"duration": 0.29299, | |
"end_time": "2022-04-18T16:13:26.938589", | |
"exception": false, | |
"start_time": "2022-04-18T16:13:26.645599", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"Unlike the group aggregate UDF, which returns a scalar value as a result over a batch, the grouped map UDF maps over each batch and returns a (pandas) data frame that gets combined back into a single (Spark) data frame\n", | |
"\n", | |
"Just like with the group aggregate UDF, we use `groupby()` to split a data frame into manageable batches but then pass our function to the `applyInPandas()` method. The method takes a function as a first argument and a schema as a second." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 40, | |
"id": "86a98259", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:13:27.543480Z", | |
"iopub.status.busy": "2022-04-18T16:13:27.542394Z", | |
"iopub.status.idle": "2022-04-18T16:13:43.921433Z", | |
"shell.execute_reply": "2022-04-18T16:13:43.920512Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:02.877682Z" | |
}, | |
"papermill": { | |
"duration": 16.681494, | |
"end_time": "2022-04-18T16:13:43.921681", | |
"exception": false, | |
"start_time": "2022-04-18T16:13:27.240187", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"[Stage 86:> (0 + 1) / 1]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+--------+---------+--------------------+\n", | |
"| type| amount| amountNorm|\n", | |
"+--------+---------+--------------------+\n", | |
"|TRANSFER| 181.0|1.929785364412691...|\n", | |
"|TRANSFER| 215310.3| 0.00232902269229461|\n", | |
"|TRANSFER|311685.89|0.003371535041334062|\n", | |
"|TRANSFER| 62610.8|6.772443276469881E-4|\n", | |
"|TRANSFER| 42712.39|4.619995945019032E-4|\n", | |
"|TRANSFER| 77957.68|8.432543299642404E-4|\n", | |
"|TRANSFER| 17231.46|1.863677235062513...|\n", | |
"|TRANSFER| 78766.03|8.519983994671721E-4|\n", | |
"|TRANSFER|224606.64|0.002429582898990...|\n", | |
"|TRANSFER|125872.53|0.001361558008596...|\n", | |
"+--------+---------+--------------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"def normalize_by_type(data: pd.DataFrame) -> pd.DataFrame:\n", | |
" result = data[['type', 'amount']].copy()\n", | |
" maxVal = result['amount'].max()\n", | |
" minVal = result['amount'].min()\n", | |
" if maxVal == minVal:\n", | |
" result['amountNorm'] = 0.5\n", | |
" else:\n", | |
" result['amountNorm'] = (result['amount'] - minVal) / (maxVal - minVal)\n", | |
" return result\n", | |
"\n", | |
"# We can use the SQL version of schema: \n", | |
"# schema = 'type string, amount double, amountNorm double'\n", | |
"schema = T.StructType([\n", | |
" T.StructField('type', T.StringType()),\n", | |
" T.StructField('amount', T.DoubleType()),\n", | |
" T.StructField('amountNorm', T.DoubleType())\n", | |
"])\n", | |
"\n", | |
"(\n", | |
" df.groupBy('type')\n", | |
" .applyInPandas(normalize_by_type, schema)\n", | |
" .show(10)\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "c58c06b5", | |
"metadata": { | |
"papermill": { | |
"duration": 0.300063, | |
"end_time": "2022-04-18T16:13:44.529623", | |
"exception": false, | |
"start_time": "2022-04-18T16:13:44.229560", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"We don't need to promote our function as an UDF explicitly. PySpark will still run smoothly when we call `applyInPandas` function.`applyInPandas` was newly introduced in PySpark 3. In older versions, we uses the `pandas_udf()` and passes the return schema as an argument, and we would use the `apply()` method here." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 41, | |
"id": "8134b7df", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:13:45.144998Z", | |
"iopub.status.busy": "2022-04-18T16:13:45.144310Z", | |
"iopub.status.idle": "2022-04-18T16:14:00.270053Z", | |
"shell.execute_reply": "2022-04-18T16:14:00.269144Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:15.984659Z" | |
}, | |
"papermill": { | |
"duration": 15.437474, | |
"end_time": "2022-04-18T16:14:00.270271", | |
"exception": false, | |
"start_time": "2022-04-18T16:13:44.832797", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"/opt/conda/lib/python3.7/site-packages/pyspark/sql/pandas/group_ops.py:84: UserWarning: It is preferred to use 'applyInPandas' over this API. This API will be deprecated in the future releases. See SPARK-28264 for more details.\n", | |
" \"more details.\", UserWarning)\n", | |
"[Stage 89:> (0 + 1) / 1]\r" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+--------+---------+--------------------+\n", | |
"| type| amount| amountNorm|\n", | |
"+--------+---------+--------------------+\n", | |
"|TRANSFER| 181.0|1.929785364412691...|\n", | |
"|TRANSFER| 215310.3| 0.00232902269229461|\n", | |
"|TRANSFER|311685.89|0.003371535041334062|\n", | |
"|TRANSFER| 62610.8|6.772443276469881E-4|\n", | |
"|TRANSFER| 42712.39|4.619995945019032E-4|\n", | |
"|TRANSFER| 77957.68|8.432543299642404E-4|\n", | |
"|TRANSFER| 17231.46|1.863677235062513...|\n", | |
"|TRANSFER| 78766.03|8.519983994671721E-4|\n", | |
"|TRANSFER|224606.64|0.002429582898990...|\n", | |
"|TRANSFER|125872.53|0.001361558008596...|\n", | |
"+--------+---------+--------------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"# It is preferred to use 'applyInPandas' over this API (in Spark 3). \n", | |
"# This API will be deprecated in the future releases. See SPARK-28264 for more details.\n", | |
"# As will be deprecated soon, type hint inference í not supported. So, we have to specify PandasUDFType explicitly\n", | |
"NormalizeByType = F.pandas_udf(normalize_by_type, schema, F.PandasUDFType.GROUPED_MAP)\n", | |
"(\n", | |
" df.groupBy('type')\n", | |
" .apply(NormalizeByType)\n", | |
" .show(10)\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "869cfd60", | |
"metadata": { | |
"papermill": { | |
"duration": 0.299906, | |
"end_time": "2022-04-18T16:14:00.878261", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:00.578355", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"### Iterator of DataFrame to Iterator of DataFrame" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "22c743b5", | |
"metadata": { | |
"papermill": { | |
"duration": 0.30191, | |
"end_time": "2022-04-18T16:14:01.483172", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:01.181262", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"A combination of Iterator of multiple Series to Iterator of Series UDF and Group map UDF. This is useful when we want to process the whole `DataFrame` instead of just some columns." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 42, | |
"id": "0aea46bb", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:02.099006Z", | |
"iopub.status.busy": "2022-04-18T16:14:02.098042Z", | |
"iopub.status.idle": "2022-04-18T16:14:02.474912Z", | |
"shell.execute_reply": "2022-04-18T16:14:02.475667Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:28.201972Z" | |
}, | |
"papermill": { | |
"duration": 0.691102, | |
"end_time": "2022-04-18T16:14:02.475904", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:01.784802", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+-----------+-----------+--------+--------+------------------+\n", | |
"| nameOrig| nameDest| type| amount| diff|\n", | |
"+-----------+-----------+--------+--------+------------------+\n", | |
"|C1231006815|M1979787155| PAYMENT| 9839.64| 9839.640000000014|\n", | |
"|C1666544295|M2044282225| PAYMENT| 1864.28|1864.2799999999988|\n", | |
"|C1305486145| C553264065|TRANSFER| 181.0| 181.0|\n", | |
"| C840083671| C38997010|CASH_OUT| 181.0| 21001.0|\n", | |
"|C2048537720|M1230701703| PAYMENT|11668.14| 11668.14|\n", | |
"| C90045638| M573487274| PAYMENT| 7817.71| 7817.709999999999|\n", | |
"| C154988899| M408069119| PAYMENT| 7107.77|7107.7699999999895|\n", | |
"|C1912850431| M633326333| PAYMENT| 7861.64| 7861.640000000014|\n", | |
"|C1265012928|M1176932104| PAYMENT| 4024.36| 2671.0|\n", | |
"| C712410124| C195600860| DEBIT| 5337.77|3788.5599999999977|\n", | |
"+-----------+-----------+--------+--------+------------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"def process_data(batches:Iterator[pd.DataFrame]) -> Iterator[pd.DataFrame]:\n", | |
" for data in batches:\n", | |
" data['changeOrig'] = (data['newBalanceOrig'] - data['oldBalanceOrig']).abs()\n", | |
" data['changeDest'] = (data['newBalanceDest'] - data['oldBalanceDest']).abs()\n", | |
" data['diff'] = (data['changeOrig'] - data['changeDest']).abs()\n", | |
" yield data[['nameOrig', 'nameDest', 'type', 'amount', 'diff']]\n", | |
" \n", | |
"schema = 'nameOrig STRING, nameDest STRING, type STRING, amount DOUBLE, diff DOUBLE'\n", | |
"# Just like applyInPandas, mapInPandas don't require us to promote the function explicitly\n", | |
"df.mapInPandas(process_data, schema).show(10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "17a1c9a3", | |
"metadata": { | |
"papermill": { | |
"duration": 0.304045, | |
"end_time": "2022-04-18T16:14:03.091864", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:02.787819", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"### Co-grouped map UDF" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"id": "ee9ec5e1", | |
"metadata": { | |
"papermill": { | |
"duration": 0.332712, | |
"end_time": "2022-04-18T16:14:03.740655", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:03.407943", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "c3d69218", | |
"metadata": { | |
"papermill": { | |
"duration": 0.317087, | |
"end_time": "2022-04-18T16:14:04.382574", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:04.065487", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"## Window function" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "7e873583", | |
"metadata": { | |
"papermill": { | |
"duration": 0.316281, | |
"end_time": "2022-04-18T16:14:05.027445", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:04.711164", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"Window functions enable users to perform calculations against partitions. Unlike traditional aggregation functions, which return only a single value for each group defined in the query, window functions return a single value for each input row." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "227b12ad", | |
"metadata": { | |
"papermill": { | |
"duration": 0.330329, | |
"end_time": "2022-04-18T16:14:05.677856", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:05.347527", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"To demonstrate the use of Window functions, we will use another dataset which is time-series here." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "82ff9ff2", | |
"metadata": { | |
"papermill": { | |
"duration": 0.30554, | |
"end_time": "2022-04-18T16:14:06.308747", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:06.003207", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"### Window functions basic" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 43, | |
"id": "21e09a94", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:06.981709Z", | |
"iopub.status.busy": "2022-04-18T16:14:06.980549Z", | |
"iopub.status.idle": "2022-04-18T16:14:07.050796Z", | |
"shell.execute_reply": "2022-04-18T16:14:07.051581Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:28.650341Z" | |
}, | |
"papermill": { | |
"duration": 0.429987, | |
"end_time": "2022-04-18T16:14:07.051830", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:06.621843", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"root\n", | |
" |-- date: string (nullable = true)\n", | |
" |-- open: double (nullable = true)\n", | |
" |-- high: double (nullable = true)\n", | |
" |-- low: double (nullable = true)\n", | |
" |-- close: double (nullable = true)\n", | |
" |-- adj_close: double (nullable = true)\n", | |
" |-- volume: double (nullable = true)\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"apple_data_path = '../input/apple-stock-data-updated-till-22jun2021/AAPL.csv'\n", | |
"apple_schema = T.StructType([\n", | |
" T.StructField('Date', T.StringType()),\n", | |
" T.StructField('Open', T.DoubleType()),\n", | |
" T.StructField('High', T.DoubleType()),\n", | |
" T.StructField('Low', T.DoubleType()),\n", | |
" T.StructField('Close', T.DoubleType()),\n", | |
" T.StructField('Adj Close', T.DoubleType()),\n", | |
" T.StructField('Volume', T.DoubleType()),\n", | |
"])\n", | |
"apple_df = spark.read.csv(apple_data_path, header=True, schema=apple_schema)\n", | |
"\n", | |
"for col in apple_df.columns:\n", | |
" new_col = col.lower().replace(' ', '_')\n", | |
" apple_df = apple_df.withColumnRenamed(col, new_col)\n", | |
"\n", | |
"apple_df.createOrReplaceTempView('apple_df')\n", | |
"apple_df.printSchema()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "39d90f53", | |
"metadata": { | |
"papermill": { | |
"duration": 0.306095, | |
"end_time": "2022-04-18T16:14:07.687517", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:07.381422", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"For example, we want to get the highest adjusted close price each year along with relevant information in the same day. Without window functions, it is essential to group data and use a self-join.\n", | |
"\n", | |
"While it’s not technically wrong, it can be slow and make the code look more complex than it needs to be. It also looks a little odd. Joining tables make sense when you want to link data contained into two or more tables. Joining a table with itself feels redundant." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 44, | |
"id": "402b085b", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:08.304313Z", | |
"iopub.status.busy": "2022-04-18T16:14:08.303514Z", | |
"iopub.status.idle": "2022-04-18T16:14:08.994379Z", | |
"shell.execute_reply": "2022-04-18T16:14:08.993678Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:28.726232Z" | |
}, | |
"papermill": { | |
"duration": 1.003199, | |
"end_time": "2022-04-18T16:14:08.994536", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:07.991337", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+----------+--------+--------+--------+--------+---------+\n", | |
"| date| open| close| high| low|adj_close|\n", | |
"+----------+--------+--------+--------+--------+---------+\n", | |
"|1980-12-29|0.160714|0.160714|0.161272|0.160714| 0.125622|\n", | |
"|1981-01-02|0.154018|0.154018|0.155134|0.154018| 0.120388|\n", | |
"|1982-12-07|0.149554|0.151228|0.154576|0.146205| 0.118207|\n", | |
"|1983-06-06|0.273996|0.280134|0.280134|0.273996| 0.218966|\n", | |
"|1984-05-01|0.141741|0.148438|0.148438|0.141741| 0.116026|\n", | |
"|1985-01-14|0.136719|0.136719|0.137835|0.136719| 0.106866|\n", | |
"|1986-12-05| 0.19029|0.195313|0.195313|0.189732| 0.152666|\n", | |
"|1987-10-05|0.522321|0.529018|0.533482|0.515625| 0.414671|\n", | |
"|1988-07-05|0.415179|0.421875|0.421875| 0.41183| 0.332719|\n", | |
"|1989-06-14| 0.4375| 0.44308|0.448661|0.430804| 0.352764|\n", | |
"+----------+--------+--------+--------+--------+---------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"tdf = apple_df.withColumn('year', F.substring(apple_df.date, 0, 4).cast(T.IntegerType()))\n", | |
"\n", | |
"(\n", | |
" tdf\n", | |
" .groupBy('year')\n", | |
" .agg(\n", | |
" F.max(tdf.adj_close).alias('max_adj_close'),\n", | |
" )\n", | |
" .alias('left')\n", | |
" .join(tdf.alias('right'), \n", | |
" on=(F.col('max_adj_close')==tdf.adj_close)&(F.col('left.year')==F.col('right.year')),\n", | |
" how='left')\n", | |
" .orderBy(F.col('left.year'))\n", | |
" .select('date', 'open', 'close', 'high', 'low', 'adj_close')\n", | |
" .show(10)\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 45, | |
"id": "215b2757", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:09.625316Z", | |
"iopub.status.busy": "2022-04-18T16:14:09.624204Z", | |
"iopub.status.idle": "2022-04-18T16:14:10.121768Z", | |
"shell.execute_reply": "2022-04-18T16:14:10.120865Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:29.397026Z" | |
}, | |
"papermill": { | |
"duration": 0.813748, | |
"end_time": "2022-04-18T16:14:10.122009", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:09.308261", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+----------+--------+--------+--------+--------+---------+\n", | |
"| date| open| close| high| low|adj_close|\n", | |
"+----------+--------+--------+--------+--------+---------+\n", | |
"|1980-12-29|0.160714|0.160714|0.161272|0.160714| 0.125622|\n", | |
"|1981-01-02|0.154018|0.154018|0.155134|0.154018| 0.120388|\n", | |
"|1982-12-07|0.149554|0.151228|0.154576|0.146205| 0.118207|\n", | |
"|1983-06-06|0.273996|0.280134|0.280134|0.273996| 0.218966|\n", | |
"|1984-05-01|0.141741|0.148438|0.148438|0.141741| 0.116026|\n", | |
"|1985-01-14|0.136719|0.136719|0.137835|0.136719| 0.106866|\n", | |
"|1986-12-05| 0.19029|0.195313|0.195313|0.189732| 0.152666|\n", | |
"|1987-10-05|0.522321|0.529018|0.533482|0.515625| 0.414671|\n", | |
"|1988-07-05|0.415179|0.421875|0.421875| 0.41183| 0.332719|\n", | |
"|1989-06-14| 0.4375| 0.44308|0.448661|0.430804| 0.352764|\n", | |
"+----------+--------+--------+--------+--------+---------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"spark.sql('''\n", | |
" WITH\n", | |
" tdf AS (\n", | |
" SELECT *, CAST(SUBSTRING(date, 0, 4) AS INTEGER) year\n", | |
" FROM apple_df\n", | |
" ),\n", | |
" max_df AS (\n", | |
" SELECT year, MAX(adj_close) max_adj_close\n", | |
" FROM tdf\n", | |
" GROUP BY year\n", | |
" )\n", | |
" SELECT date, open, close, high, low, adj_close \n", | |
" FROM \n", | |
" max_df LEFT JOIN tdf ON tdf.year = max_df.year AND tdf.adj_close = max_df.max_adj_close\n", | |
" ORDER BY date\n", | |
"''').show(10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "58d2b151", | |
"metadata": { | |
"papermill": { | |
"duration": 0.304513, | |
"end_time": "2022-04-18T16:14:10.806151", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:10.501638", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"Window functions also do the calculation on parts of date, not the whole dataset. But unlike traditional `group by` which return only one value for each group, window functions return one value for each input rows." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 46, | |
"id": "526dc2ee", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:11.505496Z", | |
"iopub.status.busy": "2022-04-18T16:14:11.504549Z", | |
"iopub.status.idle": "2022-04-18T16:14:12.067137Z", | |
"shell.execute_reply": "2022-04-18T16:14:12.068034Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:29.835649Z" | |
}, | |
"papermill": { | |
"duration": 0.951556, | |
"end_time": "2022-04-18T16:14:12.068395", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:11.116839", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+----------+--------+--------+--------+--------+---------+\n", | |
"| date| open| close| high| low|adj_close|\n", | |
"+----------+--------+--------+--------+--------+---------+\n", | |
"|1980-12-29|0.160714|0.160714|0.161272|0.160714| 0.125622|\n", | |
"|1981-01-02|0.154018|0.154018|0.155134|0.154018| 0.120388|\n", | |
"|1982-12-07|0.149554|0.151228|0.154576|0.146205| 0.118207|\n", | |
"|1983-06-06|0.273996|0.280134|0.280134|0.273996| 0.218966|\n", | |
"|1984-05-01|0.141741|0.148438|0.148438|0.141741| 0.116026|\n", | |
"|1985-01-14|0.136719|0.136719|0.137835|0.136719| 0.106866|\n", | |
"|1986-12-05| 0.19029|0.195313|0.195313|0.189732| 0.152666|\n", | |
"|1987-10-05|0.522321|0.529018|0.533482|0.515625| 0.414671|\n", | |
"|1988-07-05|0.415179|0.421875|0.421875| 0.41183| 0.332719|\n", | |
"|1989-06-14| 0.4375| 0.44308|0.448661|0.430804| 0.352764|\n", | |
"+----------+--------+--------+--------+--------+---------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"from pyspark.sql.window import Window\n", | |
"\n", | |
"\n", | |
"annual_window = Window.partitionBy('year')\n", | |
"\n", | |
"(\n", | |
" apple_df\n", | |
" .withColumn('year', F.substring(apple_df.date, 0, 4).cast(T.IntegerType()))\n", | |
" .withColumn('max_adj_close', F.max(apple_df.adj_close).over(annual_window))\n", | |
" .where(F.col('adj_close')==F.col('max_adj_close'))\n", | |
" .select('date', 'open', 'close', 'high', 'low', 'adj_close')\n", | |
").show(10)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 47, | |
"id": "fdadbbc7", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:12.737788Z", | |
"iopub.status.busy": "2022-04-18T16:14:12.736716Z", | |
"iopub.status.idle": "2022-04-18T16:14:13.038105Z", | |
"shell.execute_reply": "2022-04-18T16:14:13.037430Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:30.330854Z" | |
}, | |
"papermill": { | |
"duration": 0.63397, | |
"end_time": "2022-04-18T16:14:13.038301", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:12.404331", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+----------+--------+--------+--------+--------+---------+\n", | |
"| date| open| close| high| low|adj_close|\n", | |
"+----------+--------+--------+--------+--------+---------+\n", | |
"|1980-12-29|0.160714|0.160714|0.161272|0.160714| 0.125622|\n", | |
"|1981-01-02|0.154018|0.154018|0.155134|0.154018| 0.120388|\n", | |
"|1982-12-07|0.149554|0.151228|0.154576|0.146205| 0.118207|\n", | |
"|1983-06-06|0.273996|0.280134|0.280134|0.273996| 0.218966|\n", | |
"|1984-05-01|0.141741|0.148438|0.148438|0.141741| 0.116026|\n", | |
"|1985-01-14|0.136719|0.136719|0.137835|0.136719| 0.106866|\n", | |
"|1986-12-05| 0.19029|0.195313|0.195313|0.189732| 0.152666|\n", | |
"|1987-10-05|0.522321|0.529018|0.533482|0.515625| 0.414671|\n", | |
"|1988-07-05|0.415179|0.421875|0.421875| 0.41183| 0.332719|\n", | |
"|1989-06-14| 0.4375| 0.44308|0.448661|0.430804| 0.352764|\n", | |
"+----------+--------+--------+--------+--------+---------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"spark.sql('''\n", | |
" WITH \n", | |
" tdf as (\n", | |
" SELECT date, open, close, high, low, adj_close,\n", | |
" MAX(adj_close) OVER (PARTITION BY CAST(SUBSTRING(date, 0, 4) AS INTEGER)) max_adj_close\n", | |
" FROM apple_df\n", | |
" )\n", | |
" SELECT date, open, close, high, low, adj_close\n", | |
" FROM tdf\n", | |
" WHERE adj_close = max_adj_close \n", | |
"''').show(10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "a02d70af", | |
"metadata": { | |
"papermill": { | |
"duration": 0.301766, | |
"end_time": "2022-04-18T16:14:13.650341", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:13.348575", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"Taking the `max` (or `min`, '`sum`, `mean`, ...) don't require windows to be ordered. There are functions that are sensitive to the intra-order of windows. They are ranking functions (`rank`, `dense_rank`, `percent_rank`, `ntile`, `row_number`, ...) or analytic functions (`lag`, `lead`, `cume_dist`, ...)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 48, | |
"id": "6db165c8", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:14.257765Z", | |
"iopub.status.busy": "2022-04-18T16:14:14.256948Z", | |
"iopub.status.idle": "2022-04-18T16:14:14.745804Z", | |
"shell.execute_reply": "2022-04-18T16:14:14.745095Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:30.611435Z" | |
}, | |
"papermill": { | |
"duration": 0.798852, | |
"end_time": "2022-04-18T16:14:14.746032", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:13.947180", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+----------+---------+----+----------+--------+--------+\n", | |
"| date|adj_close|rank|dense_rank| lag| lead|\n", | |
"+----------+---------+----+----------+--------+--------+\n", | |
"|1980-12-12| 0.100323| 1| 1| null|0.095089|\n", | |
"|1980-12-15| 0.095089| 2| 2|0.100323| 0.08811|\n", | |
"|1980-12-16| 0.08811| 3| 3|0.095089|0.090291|\n", | |
"|1980-12-17| 0.090291| 4| 4| 0.08811|0.092908|\n", | |
"|1980-12-18| 0.092908| 5| 5|0.090291|0.098578|\n", | |
"|1980-12-19| 0.098578| 6| 6|0.092908|0.103376|\n", | |
"|1980-12-22| 0.103376| 7| 7|0.098578|0.107739|\n", | |
"|1980-12-23| 0.107739| 8| 8|0.103376|0.113409|\n", | |
"|1980-12-24| 0.113409| 9| 9|0.107739|0.123877|\n", | |
"|1980-12-26| 0.123877| 10| 10|0.113409|0.125622|\n", | |
"+----------+---------+----+----------+--------+--------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"# Or just: ordered_annual_window = annual_window.orderBy('date')\n", | |
"ordered_annual_window = Window.partitionBy('year').orderBy('date')\n", | |
"\n", | |
"# Rank do not need a column as parameter. As everything was already ordered.\n", | |
"(\n", | |
" apple_df.withColumn('year', F.substring(apple_df.date, 0, 4).cast(T.IntegerType()))\n", | |
" .select(\n", | |
" 'date', 'adj_close',\n", | |
" F.rank().over(ordered_annual_window).alias('rank'),\n", | |
" F.dense_rank().over(ordered_annual_window).alias('dense_rank'),\n", | |
" F.lag('adj_close', 1).over(ordered_annual_window).alias('lag'),\n", | |
" F.lead('adj_close', 1).over(ordered_annual_window).alias('lead'),\n", | |
" )\n", | |
" .show(10)\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 49, | |
"id": "d6df467e", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:15.398633Z", | |
"iopub.status.busy": "2022-04-18T16:14:15.397933Z", | |
"iopub.status.idle": "2022-04-18T16:14:15.703349Z", | |
"shell.execute_reply": "2022-04-18T16:14:15.702551Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:30.991164Z" | |
}, | |
"papermill": { | |
"duration": 0.614771, | |
"end_time": "2022-04-18T16:14:15.703545", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:15.088774", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+----------+---------+----+----------+--------+--------+\n", | |
"| date|adj_close|rank|dense_rank| lag| lead|\n", | |
"+----------+---------+----+----------+--------+--------+\n", | |
"|1980-12-12| 0.100323| 1| 1| null|0.095089|\n", | |
"|1980-12-15| 0.095089| 2| 2|0.100323| 0.08811|\n", | |
"|1980-12-16| 0.08811| 3| 3|0.095089|0.090291|\n", | |
"|1980-12-17| 0.090291| 4| 4| 0.08811|0.092908|\n", | |
"|1980-12-18| 0.092908| 5| 5|0.090291|0.098578|\n", | |
"|1980-12-19| 0.098578| 6| 6|0.092908|0.103376|\n", | |
"|1980-12-22| 0.103376| 7| 7|0.098578|0.107739|\n", | |
"|1980-12-23| 0.107739| 8| 8|0.103376|0.113409|\n", | |
"|1980-12-24| 0.113409| 9| 9|0.107739|0.123877|\n", | |
"|1980-12-26| 0.123877| 10| 10|0.113409|0.125622|\n", | |
"+----------+---------+----+----------+--------+--------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"spark.sql('''\n", | |
" WITH tdf AS (\n", | |
" SELECT *, CAST(SUBSTRING(date, 0, 4) AS INTEGER) year FROM apple_df\n", | |
" )\n", | |
" SELECT date, adj_close, \n", | |
" RANK() OVER (PARTITION BY year ORDER BY date) rank,\n", | |
" DENSE_RANK() OVER (PARTITION BY year ORDER BY date) dense_rank,\n", | |
" LAG(adj_close) OVER (PARTITION BY year ORDER BY date) lag,\n", | |
" LEAD(adj_close) OVER (PARTITION BY year ORDER BY date) lead\n", | |
" FROM tdf\n", | |
"''').show(10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "990a6b17", | |
"metadata": { | |
"papermill": { | |
"duration": 0.307054, | |
"end_time": "2022-04-18T16:14:16.349583", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:16.042529", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"### Flexible window functions with boundaries" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "aeb97bdc", | |
"metadata": { | |
"papermill": { | |
"duration": 0.301615, | |
"end_time": "2022-04-18T16:14:16.951050", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:16.649435", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"We are able to fine-tune the boundaries of a window (respect to the current row). We can build static, growing, and unbounded windows based on rows and ranges.\n", | |
"\n", | |
"Spark provides `Window.unboundedPreceding`, `Window.unboundedFollowing`, `Window.currentRow` for the first, the last and the current row of the window respectively." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 50, | |
"id": "853376e4", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:17.562689Z", | |
"iopub.status.busy": "2022-04-18T16:14:17.562035Z", | |
"iopub.status.idle": "2022-04-18T16:14:17.806791Z", | |
"shell.execute_reply": "2022-04-18T16:14:17.807627Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:31.226376Z" | |
}, | |
"papermill": { | |
"duration": 0.553081, | |
"end_time": "2022-04-18T16:14:17.807907", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:17.254826", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+----------+---------+-------------------+\n", | |
"| date|adj_close| ma10|\n", | |
"+----------+---------+-------------------+\n", | |
"|1980-12-12| 0.100323| 0.100323|\n", | |
"|1980-12-15| 0.095089| 0.097706|\n", | |
"|1980-12-16| 0.08811|0.09450733333333333|\n", | |
"|1980-12-17| 0.090291| 0.09345325|\n", | |
"|1980-12-18| 0.092908| 0.0933442|\n", | |
"|1980-12-19| 0.098578| 0.0942165|\n", | |
"|1980-12-22| 0.103376| 0.095525|\n", | |
"|1980-12-23| 0.107739| 0.09705175|\n", | |
"|1980-12-24| 0.113409|0.09886922222222222|\n", | |
"|1980-12-26| 0.123877| 0.10137|\n", | |
"+----------+---------+-------------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"22/04/18 16:14:17 WARN WindowExec: No Partition Defined for Window operation! Moving all data to a single partition, this can cause serious performance degradation.\n", | |
"22/04/18 16:14:17 WARN WindowExec: No Partition Defined for Window operation! Moving all data to a single partition, this can cause serious performance degradation.\n", | |
"22/04/18 16:14:17 WARN WindowExec: No Partition Defined for Window operation! Moving all data to a single partition, this can cause serious performance degradation.\n" | |
] | |
} | |
], | |
"source": [ | |
"(\n", | |
" apple_df.select('date', 'adj_close',\n", | |
" F.mean('adj_close').over(Window.orderBy('date').rowsBetween(-9, Window.currentRow)).alias('ma10'))\n", | |
" .show(10)\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 51, | |
"id": "2ea60dd8", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:18.482209Z", | |
"iopub.status.busy": "2022-04-18T16:14:18.481517Z", | |
"iopub.status.idle": "2022-04-18T16:14:18.670611Z", | |
"shell.execute_reply": "2022-04-18T16:14:18.669953Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:31.434380Z" | |
}, | |
"papermill": { | |
"duration": 0.492437, | |
"end_time": "2022-04-18T16:14:18.670755", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:18.178318", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+----------+---------+-------------------+\n", | |
"| date|adj_close| ma10|\n", | |
"+----------+---------+-------------------+\n", | |
"|1980-12-12| 0.100323| 0.100323|\n", | |
"|1980-12-15| 0.095089| 0.097706|\n", | |
"|1980-12-16| 0.08811|0.09450733333333333|\n", | |
"|1980-12-17| 0.090291| 0.09345325|\n", | |
"|1980-12-18| 0.092908| 0.0933442|\n", | |
"|1980-12-19| 0.098578| 0.0942165|\n", | |
"|1980-12-22| 0.103376| 0.095525|\n", | |
"|1980-12-23| 0.107739| 0.09705175|\n", | |
"|1980-12-24| 0.113409|0.09886922222222222|\n", | |
"|1980-12-26| 0.123877| 0.10137|\n", | |
"+----------+---------+-------------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"22/04/18 16:14:18 WARN WindowExec: No Partition Defined for Window operation! Moving all data to a single partition, this can cause serious performance degradation.\n", | |
"22/04/18 16:14:18 WARN WindowExec: No Partition Defined for Window operation! Moving all data to a single partition, this can cause serious performance degradation.\n", | |
"22/04/18 16:14:18 WARN WindowExec: No Partition Defined for Window operation! Moving all data to a single partition, this can cause serious performance degradation.\n" | |
] | |
} | |
], | |
"source": [ | |
"spark.sql('''\n", | |
" SELECT date, adj_close,\n", | |
" MEAN(adj_close) OVER (ORDER BY date ROWS BETWEEN 9 PRECEDING AND CURRENT ROW) ma10\n", | |
" FROM apple_df\n", | |
"''').show(10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "65d94f2b", | |
"metadata": { | |
"papermill": { | |
"duration": 0.300691, | |
"end_time": "2022-04-18T16:14:19.275558", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:18.974867", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"The difference between `rowsBetween` and `rangeBetween` is that `rowsBetween` looks for rows while `rangeBetween` looks for values." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 52, | |
"id": "e114a30e", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:19.887171Z", | |
"iopub.status.busy": "2022-04-18T16:14:19.886463Z", | |
"iopub.status.idle": "2022-04-18T16:14:20.244473Z", | |
"shell.execute_reply": "2022-04-18T16:14:20.243795Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:31.600217Z" | |
}, | |
"papermill": { | |
"duration": 0.667651, | |
"end_time": "2022-04-18T16:14:20.244619", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:19.576968", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"22/04/18 16:14:19 WARN WindowExec: No Partition Defined for Window operation! Moving all data to a single partition, this can cause serious performance degradation.\n", | |
"22/04/18 16:14:19 WARN WindowExec: No Partition Defined for Window operation! Moving all data to a single partition, this can cause serious performance degradation.\n", | |
"22/04/18 16:14:20 WARN WindowExec: No Partition Defined for Window operation! Moving all data to a single partition, this can cause serious performance degradation.\n" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+----------+---------+-------------------+\n", | |
"| date|adj_close| true_ma10|\n", | |
"+----------+---------+-------------------+\n", | |
"|1980-12-12| 0.100323| 0.100323|\n", | |
"|1980-12-15| 0.095089| 0.097706|\n", | |
"|1980-12-16| 0.08811|0.09450733333333333|\n", | |
"|1980-12-17| 0.090291| 0.09345325|\n", | |
"|1980-12-18| 0.092908| 0.0933442|\n", | |
"|1980-12-19| 0.098578| 0.0942165|\n", | |
"|1980-12-22| 0.103376|0.09472533333333333|\n", | |
"|1980-12-23| 0.107739|0.09658442857142857|\n", | |
"|1980-12-24| 0.113409| 0.0986875|\n", | |
"|1980-12-26| 0.123877|0.10431114285714285|\n", | |
"+----------+---------+-------------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"# This will calculate the mean within 10 days (not twenty rows). \n", | |
"# Remember that 10 days are not eqivalent to 10 rows because of missing dates.\n", | |
"( \n", | |
" apple_df.withColumn('date_stamp', F.datediff(apple_df.date, F.lit('1980-01-01')))\n", | |
" .select('date', 'adj_close',\n", | |
" F.mean('adj_close').over(Window.orderBy('date_stamp').rangeBetween(-9, Window.currentRow)).alias('true_ma10'))\n", | |
" .show(10)\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 53, | |
"id": "9234e18d", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:20.853052Z", | |
"iopub.status.busy": "2022-04-18T16:14:20.851948Z", | |
"iopub.status.idle": "2022-04-18T16:14:21.046726Z", | |
"shell.execute_reply": "2022-04-18T16:14:21.047542Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:32.016335Z" | |
}, | |
"papermill": { | |
"duration": 0.50323, | |
"end_time": "2022-04-18T16:14:21.047738", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:20.544508", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+----------+---------+-------------------+\n", | |
"| date|adj_close| true_ma10|\n", | |
"+----------+---------+-------------------+\n", | |
"|1980-12-12| 0.100323| 0.100323|\n", | |
"|1980-12-15| 0.095089| 0.097706|\n", | |
"|1980-12-16| 0.08811|0.09450733333333333|\n", | |
"|1980-12-17| 0.090291| 0.09345325|\n", | |
"|1980-12-18| 0.092908| 0.0933442|\n", | |
"|1980-12-19| 0.098578| 0.0942165|\n", | |
"|1980-12-22| 0.103376|0.09472533333333333|\n", | |
"|1980-12-23| 0.107739|0.09658442857142857|\n", | |
"|1980-12-24| 0.113409| 0.0986875|\n", | |
"|1980-12-26| 0.123877|0.10431114285714285|\n", | |
"+----------+---------+-------------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"22/04/18 16:14:20 WARN WindowExec: No Partition Defined for Window operation! Moving all data to a single partition, this can cause serious performance degradation.\n", | |
"22/04/18 16:14:20 WARN WindowExec: No Partition Defined for Window operation! Moving all data to a single partition, this can cause serious performance degradation.\n", | |
"22/04/18 16:14:20 WARN WindowExec: No Partition Defined for Window operation! Moving all data to a single partition, this can cause serious performance degradation.\n" | |
] | |
} | |
], | |
"source": [ | |
"spark.sql('''\n", | |
" WITH tdf as (\n", | |
" SELECT date, adj_close, DATEDIFF(date, \"1980-01-01\") date_stamp FROM apple_df\n", | |
" )\n", | |
" SELECT date, adj_close, MEAN(adj_close) OVER (ORDER BY date_stamp RANGE BETWEEN 9 PRECEDING AND CURRENT ROW) true_ma10\n", | |
" FROM tdf\n", | |
"''').show(10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "aeeb863e", | |
"metadata": { | |
"papermill": { | |
"duration": 0.306702, | |
"end_time": "2022-04-18T16:14:21.658057", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:21.351355", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"### Window functions and pandas udf" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "562a101e", | |
"metadata": { | |
"papermill": { | |
"duration": 0.306016, | |
"end_time": "2022-04-18T16:14:22.272368", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:21.966352", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"The recipe for applying a pandas UDF is very simple:\n", | |
"- We need to use a Series to Scalar UDF (or a group aggregate UDF). PySpark will apply the UDF to every window (once per record) and put the (scalar) value as a result.\n", | |
"- A UDF over unbounded window frames is only supported by Spark 2.4 and above.\n", | |
"- A UDF over bounded window frames is only supported by Spark 3.0 and above." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 54, | |
"id": "dca47c1c", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:22.887083Z", | |
"iopub.status.busy": "2022-04-18T16:14:22.886415Z", | |
"iopub.status.idle": "2022-04-18T16:14:23.711006Z", | |
"shell.execute_reply": "2022-04-18T16:14:23.710423Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:32.268452Z" | |
}, | |
"papermill": { | |
"duration": 1.136672, | |
"end_time": "2022-04-18T16:14:23.711156", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:22.574484", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"22/04/18 16:14:22 WARN WindowInPandasExec: No Partition Defined for Window operation! Moving all data to a single partition, this can cause serious performance degradation.\n", | |
"22/04/18 16:14:22 WARN WindowInPandasExec: No Partition Defined for Window operation! Moving all data to a single partition, this can cause serious performance degradation.\n", | |
"22/04/18 16:14:23 WARN WindowInPandasExec: No Partition Defined for Window operation! Moving all data to a single partition, this can cause serious performance degradation.\n" | |
] | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+----------+---------+-------------------+\n", | |
"| date|adj_close| ema10|\n", | |
"+----------+---------+-------------------+\n", | |
"|1980-12-12| 0.100323| 0.100323|\n", | |
"|1980-12-15| 0.095089| 0.0982294|\n", | |
"|1980-12-16| 0.08811|0.09418163999999998|\n", | |
"|1980-12-17| 0.090291|0.09262538399999998|\n", | |
"|1980-12-18| 0.092908|0.09273843039999999|\n", | |
"|1980-12-19| 0.098578|0.09507425823999999|\n", | |
"|1980-12-22| 0.103376| 0.098394954944|\n", | |
"|1980-12-23| 0.107739|0.10213257296639999|\n", | |
"|1980-12-24| 0.113409| 0.10664314377984|\n", | |
"|1980-12-26| 0.123877| 0.113536686267904|\n", | |
"+----------+---------+-------------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"@F.pandas_udf(T.DoubleType())\n", | |
"def Ema(values: pd.Series) -> float:\n", | |
" alpha = 0.4\n", | |
" ema = values.iloc[0]\n", | |
" for val in values:\n", | |
" ema = alpha * val + (1 - alpha) * ema\n", | |
" return ema\n", | |
"\n", | |
"(\n", | |
" apple_df.select(\n", | |
" 'date', 'adj_close',\n", | |
" Ema(apple_df.adj_close).over(Window.orderBy('date').rowsBetween(-9, Window.currentRow)).alias('ema10')\n", | |
" )\n", | |
" .show(10)\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "6724f83a", | |
"metadata": { | |
"papermill": { | |
"duration": 0.307028, | |
"end_time": "2022-04-18T16:14:24.326415", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:24.019387", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"## Machine learning with PySpark" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "d39c08e9", | |
"metadata": { | |
"papermill": { | |
"duration": 0.325156, | |
"end_time": "2022-04-18T16:14:24.961027", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:24.635871", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"Transformers and estimators are the two main components of ML pipelines. \n", | |
"- Transformers are objects that, through a transform() method, modify a data frame based on a set of `Param`s that drives its behavior. We use a transformer stage when we want to deterministically transform a data frame.\n", | |
"- Estimators are objects that, through a `fit()` method, take a data frame and return a fully parameterized transformer called a model. We use an estimator stage when we want to transform a data frame using a data-dependent transformer." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "7205e24c", | |
"metadata": { | |
"papermill": { | |
"duration": 0.303981, | |
"end_time": "2022-04-18T16:14:25.578020", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:25.274039", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"In this section, we will use the customer classification tabular dataset, a simple dataset for building a machine learning model. Note that this is a pyspark brief tutorial, not a machine learning course. So, we won't dive deep into machine learning." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "399b6e89", | |
"metadata": { | |
"papermill": { | |
"duration": 0.311064, | |
"end_time": "2022-04-18T16:14:26.224275", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:25.913211", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"### Data overview" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 55, | |
"id": "35390c65", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:26.840088Z", | |
"iopub.status.busy": "2022-04-18T16:14:26.839257Z", | |
"iopub.status.idle": "2022-04-18T16:14:27.072784Z", | |
"shell.execute_reply": "2022-04-18T16:14:27.071945Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:33.484917Z" | |
}, | |
"papermill": { | |
"duration": 0.542997, | |
"end_time": "2022-04-18T16:14:27.073019", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:26.530022", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"root\n", | |
" |-- region: integer (nullable = true)\n", | |
" |-- tenure: integer (nullable = true)\n", | |
" |-- age: integer (nullable = true)\n", | |
" |-- income: integer (nullable = true)\n", | |
" |-- marital: integer (nullable = true)\n", | |
" |-- address: integer (nullable = true)\n", | |
" |-- ed: integer (nullable = true)\n", | |
" |-- employ: integer (nullable = true)\n", | |
" |-- retire: integer (nullable = true)\n", | |
" |-- gender: integer (nullable = true)\n", | |
" |-- reside: integer (nullable = true)\n", | |
" |-- custcat: string (nullable = true)\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"cust_data_path = '../input/customersegmentation/Telecust1.csv'\n", | |
"\n", | |
"cust_df = spark.read.csv(cust_data_path, inferSchema=True, header=True)\n", | |
"cust_df = cust_df.toDF(*[col.lower().replace('-', '_') for col in cust_df.columns])\n", | |
"cust_df.printSchema()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 56, | |
"id": "91499a26", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:27.719854Z", | |
"iopub.status.busy": "2022-04-18T16:14:27.718857Z", | |
"iopub.status.idle": "2022-04-18T16:14:27.863448Z", | |
"shell.execute_reply": "2022-04-18T16:14:27.862678Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:33.744726Z" | |
}, | |
"papermill": { | |
"duration": 0.465208, | |
"end_time": "2022-04-18T16:14:27.863617", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:27.398409", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+------+------+---+------+-------+-------+---+------+------+------+------+-------+\n", | |
"|region|tenure|age|income|marital|address| ed|employ|retire|gender|reside|custcat|\n", | |
"+------+------+---+------+-------+-------+---+------+------+------+------+-------+\n", | |
"| 2| 13| 44| 64| 1| 9| 4| 5| 0| 0| 2| A|\n", | |
"| 3| 11| 33| 136| 1| 7| 5| 5| 0| 0| 6| D|\n", | |
"| 3| 68| 52| 116| 1| 24| 1| 29| 0| 1| 2| C|\n", | |
"| 2| 33| 33| 33| 0| 12| 2| 0| 0| 1| 1| A|\n", | |
"| 2| 23| 30| 30| 1| 9| 1| 2| 0| 0| 4| C|\n", | |
"| 2| 41| 39| 78| 0| 17| 2| 16| 0| 1| 1| C|\n", | |
"| 3| 45| 22| 19| 1| 2| 2| 4| 0| 1| 5| B|\n", | |
"| 2| 38| 35| 76| 0| 5| 2| 10| 0| 0| 3| D|\n", | |
"| 3| 45| 59| 166| 1| 7| 4| 31| 0| 0| 5| C|\n", | |
"| 1| 68| 41| 72| 1| 21| 1| 22| 0| 0| 3| B|\n", | |
"+------+------+---+------+-------+-------+---+------+------+------+------+-------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"cust_df.show(10)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 57, | |
"id": "6697ada2", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:28.510849Z", | |
"iopub.status.busy": "2022-04-18T16:14:28.510025Z", | |
"iopub.status.idle": "2022-04-18T16:14:29.029443Z", | |
"shell.execute_reply": "2022-04-18T16:14:29.028727Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:34.087672Z" | |
}, | |
"papermill": { | |
"duration": 0.833548, | |
"end_time": "2022-04-18T16:14:29.029604", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:28.196056", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+------+------+---+------+-------+-------+---+------+------+------+------+-------+\n", | |
"|region|tenure|age|income|marital|address| ed|employ|retire|gender|reside|custcat|\n", | |
"+------+------+---+------+-------+-------+---+------+------+------+------+-------+\n", | |
"| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0|\n", | |
"+------+------+---+------+-------+-------+---+------+------+------+------+-------+\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"# Number of nan values\n", | |
"null_df = cust_df.select(*[F.count(F.when(F.col(c).isNull() | F.isnan(F.col(c)), c)).alias(c) for c in cust_df.columns])\n", | |
"null_df.show()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 58, | |
"id": "aba287c2", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:29.657279Z", | |
"iopub.status.busy": "2022-04-18T16:14:29.656305Z", | |
"iopub.status.idle": "2022-04-18T16:14:29.658776Z", | |
"shell.execute_reply": "2022-04-18T16:14:29.658198Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:34.504774Z" | |
}, | |
"papermill": { | |
"duration": 0.314727, | |
"end_time": "2022-04-18T16:14:29.658944", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:29.344217", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"categorical_cols = ['region', 'marital', 'address', 'ed', 'retire', 'gender', 'reside', 'custcat']\n", | |
"numeric_cols = ['tenure', 'age', 'income', 'employ']" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 59, | |
"id": "66e0df1d", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:30.280453Z", | |
"iopub.status.busy": "2022-04-18T16:14:30.279722Z", | |
"iopub.status.idle": "2022-04-18T16:14:30.716483Z", | |
"shell.execute_reply": "2022-04-18T16:14:30.717330Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:34.514284Z" | |
}, | |
"papermill": { | |
"duration": 0.749524, | |
"end_time": "2022-04-18T16:14:30.717595", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:29.968071", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+------+-------+-------+---+------+------+------+-------+\n", | |
"|region|marital|address| ed|retire|gender|reside|custcat|\n", | |
"+------+-------+-------+---+------+------+------+-------+\n", | |
"| 3| 2| 50| 5| 2| 2| 8| 4|\n", | |
"+------+-------+-------+---+------+------+------+-------+\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"# Number of unique values\n", | |
"unique_cnt_df = cust_df.select(*[F.countDistinct(c).alias(c) for c in categorical_cols])\n", | |
"unique_cnt_df.show()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 60, | |
"id": "05fcdabb", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:31.403341Z", | |
"iopub.status.busy": "2022-04-18T16:14:31.402628Z", | |
"iopub.status.idle": "2022-04-18T16:14:32.521522Z", | |
"shell.execute_reply": "2022-04-18T16:14:32.522071Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:34.931084Z" | |
}, | |
"papermill": { | |
"duration": 1.44068, | |
"end_time": "2022-04-18T16:14:32.522272", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:31.081592", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"# Plot data distribution of a column\n", | |
"from matplotlib import pyplot as plt\n", | |
"import seaborn as sns\n", | |
"from itertools import product\n", | |
"%matplotlib inline\n", | |
"plt.rcParams['figure.dpi'] = 150\n", | |
"\n", | |
"\n", | |
"def plot_column_dist(df, col, ax=None):\n", | |
" if ax is None:\n", | |
" ax = plt.gca()\n", | |
" agg_df = df.groupBy(col).agg(F.count('*').alias('count')).toPandas()\n", | |
" agg_df = agg_df.sort_values('count', ascending=False)\n", | |
" sns.barplot(data=agg_df, x=col, y='count',\n", | |
" color='#7db0bc', ax=ax)\n", | |
" \n", | |
"def plot_columns_dist(df, cols, figrows, figcols, size=(12, 6)):\n", | |
" assert len(cols) <= figrows * figcols\n", | |
" fig, axs = plt.subplots(figrows, figcols, figsize=size)\n", | |
" if figrows == 1:\n", | |
" axs = [axs]\n", | |
" if figcols == 1:\n", | |
" axs = [[ax] for ax in axs]\n", | |
" for i, j in product(range(figrows), range(figcols)):\n", | |
" if i*figcols + j >= len(cols):\n", | |
" continue\n", | |
" col = cols[i*figcols + j]\n", | |
" plot_column_dist(df, col, ax=axs[i][j])\n", | |
" axs[i][j].set_xticklabels(axs[i][j].get_xticklabels(), rotation=30)\n", | |
" return fig" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 61, | |
"id": "9a1ec38d", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:33.152183Z", | |
"iopub.status.busy": "2022-04-18T16:14:33.151512Z", | |
"iopub.status.idle": "2022-04-18T16:14:36.950418Z", | |
"shell.execute_reply": "2022-04-18T16:14:36.949852Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:35.857651Z" | |
}, | |
"papermill": { | |
"duration": 4.119302, | |
"end_time": "2022-04-18T16:14:36.950561", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:32.831259", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"image/png": "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\n", | |
"text/plain": [ | |
"<Figure size 1800x900 with 2 Axes>" | |
] | |
}, | |
"metadata": { | |
"needs_background": "light" | |
}, | |
"output_type": "display_data" | |
}, | |
{ | |
"data": { | |
"image/png": "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\n", | |
"text/plain": [ | |
"<Figure size 1800x900 with 2 Axes>" | |
] | |
}, | |
"metadata": { | |
"needs_background": "light" | |
}, | |
"output_type": "display_data" | |
}, | |
{ | |
"data": { | |
"image/png": "iVBORw0KGgoAAAANSUhEUgAABfAAAAMPCAYAAACaLvSBAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAABcSAAAXEgFnn9JSAABPcklEQVR4nO3dfbh2ZV0n/O9PUJA3AZVqRFARdcCgzDd0QkZhQpFExCmbMlHLecrCQG0qLEvHnkxQ50nHaVBgnpqeFExDHFSiG1+LFMUER5QUZUwTEHmRN/X3/HGtjZvLvff9dt33Xntfn89x7OPc17nO31rn4uDY91rfvfa5qrsDAAAAAACMyz1WewIAAAAAAMAPEuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjNCaDfCr6ieq6j9V1Tur6pqq6qrqTah7XlVdUlU3V9X1VfXeqnrCRmqeOIy7fqi7pKqeu5GafavqzKr6alXdVlVXVtXvV9XOm3uuAAAAAADMn+reaOY9SlX1riTPmO7v7lqh5g1JTkpya5L3J9k5yVOSVJITuvtdS9Q8K8lfZvLLjg8muXao2TPJad390iVqHprkY0nul+QzSa5I8ugkD0nykSRP6e7bN/FUAQAAAACYQ2s5wP/NJLsm+Yfh60tJdlouwK+qI5N8IMl1SQ7r7s8P/Ycl2ZDk20ke3N03LKrZO8kXk+yR5Fnd/c6h/4eSfDjJQ5P82+7eMHWsDyd5YpL/0t0nDX07Jnl7kmcm+f3ufuVW/icAAAAAAGAdW7MB/rSqui0rB/jvTfLUJL/R3W+Y2vbGJL+e5KXdfdqi/pcn+aMk7+7u46ZqnpnknUne093HLup/bJK/T/IvSfZb/KT9EPx/JcnNSfbp7u9s8QkDAAAAALCurdk18DdHVd07yZOHj+csMWSh79ip/mNWqDk/yW1Jjpxa136h5rzpZXK6++tJPpRkryT/ZtNmDwAAAADAPJqLAD/Jw5PslOQb3X3NEtsvHdpDpvoPndp+l+6+I5P17XdO8rBNqdnIsQAAAAAA4C47rvYEtpP9hnap8D7dfUtV3ZBkr6ravbtvqqo9ktxnpbqh/9FJ9k/y6U051qL+/Tdl4lV1+TKbHpbJy3i/sin7AQBgzXhgkm939w+v9kTYvqrqa0l2iWt8AID1Zouv8eclwN9taL+9wphbkuyZZPckNy2qWanulqHdfTOOtVTNlrjHTjvttPsBBxxw0FbuBwCAEbnqqqty++23b3wg69EurvEBANafrbnGn5cAf83q7oOX6q+qyw844ICDLr98uQf0AQBYiw4++OBcccUVnsCeT19xjQ8AsP5szTX+vKyBf/PQ7rLCmF2H9qapmpXqpms25VhL1QAAAAAAwN3MS4D/5aHdd6mNVbVrJsvnfLO7b0qS7r4xybdWqlvUf/WmHmuZGgAAAAAAuJt5CfA/l+T2JPevqgcssf1RQ/vpqf7LprbfparumeSRSW5LcuWm1GzkWAAAAAAAcJe5CPC7+9YkFw0fn73EkBOG9ryp/vOnti/29CQ7J7mwu29boubYqtppcUFV/VCSn0zyzSQf2bTZAwAAAAAwj+YiwB+cPrSnVtWBC51VdViSFyW5Iclbp2rOSHJjkmdU1fGLavZJ8trh42mLC7r7kkzC+X2S/NGimh2TvDnJPZP8l+6+c+tPCQAAAACA9WrH1Z7AlqqqY5K8YlHXvYb+v1vU96ruPj9JuvvCqnpjkpOSfKqqPjDUHJWkkpzY3TcsPkZ3X19Vz0/y9iTnVNWGJNclOTKTNfNP7+4NS0zvxCQfS3JSVT05yRVJHpPkIUk+muQPt/jEAQAAAACYC2s2wE9y/ySPW6L/cVNj7tLdL6mqTyV5cSbB/R1JLswk6P/oUgfp7nOr6vAkpyZ5fCah/xVJ/qS7z16m5vNV9eNJ/iDJ0UmemcnLbV+V5DXdffumniQAAAAAAPNpzQb43X1WkrO2R113fyTJUzez5iuZPIkPAAAAAACbbZ7WwAcAAAAAgDVDgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCO672BFi7XnPO+as9BWA7+u0TjlntKQAAAMyMXAPmy1rNNTyBDwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAGDNqqoNVdUrfB29TN3zquqSqrq5qq6vqvdW1RM2cqwnDuOuH+ouqarnbpszAwCAZMfVngAAAMAMnJvk5iX6/890R1W9IclJSW5N8v4kOyc5Ksm/q6oTuvtdS9Q8K8lfZvIQ1AeTXJvkKUnOrqpDuvulszkNAAD4PgE+AACwHry0u7+0sUFVdWQm4f11SQ7r7s8P/Ycl2ZDkzKra0N03LKrZO8nbkuyQ5Fnd/c6h/4eSfDjJKVX1nu7eMMsTAgAAS+gAAADz5OShffVCeJ8k3f2xJG9JsmeSF0zVvDDJHknevRDeDzVfT/Ly4eMp22rCAADMLwE+AAAwF6rq3kmePHw8Z4khC33HTvUfs0LN+UluS3JkVe281ZMEAIBFLKEDAACsBy+oqvsm+V6SK5O8q7u/PDXm4Ul2SvKN7r5miX1cOrSHTPUfOrX9Lt19R1V9Jsmjkzwsyae3cP4AAPADBPgAAMB6cOrU59dV1au6+1WL+vYb2qXC+3T3LVV1Q5K9qmr37r6pqvZIcp+V6ob+RyfZP5sQ4FfV5ctsOmBjtQAAzBdL6AAAAGvZB5P8Qibh9y6ZPGX/O0m+k+QPquqkRWN3G9pvr7C/W4Z296maleqmawAAYCY8gQ8AAKxZ3f27U11XJnlNVX08yfuSvLKq/rS7b93+s1tadx+8VP/wZP5B23k6AACMmCfwAQCAdae735/k40n2TPK4ofvmod1lhdJdh/amqZqV6qZrAABgJgT4AADAevX5of2RoV14qe2+Sw2uql0zCfy/2d03JUl335jkWyvVLeq/emsmCwAA0wT4AADAerXX0C6sUf+5JLcnuX9VPWCJ8Y8a2ukX0V42tf0uVXXPJI9Mclsmy/cAAMDMCPABAIB1p6run+Qnh4+XJsmwDv5FQ9+zlyg7YWjPm+o/f2r7Yk9PsnOSC7v7ti2eMAAALEGADwAArElV9YSqOq6qdpjqf1CSv8pkbfq/7u5rFm0+fWhPraoDF9UcluRFSW5I8tapQ52R5MYkz6iq4xfV7JPktcPH07b6hAAAYMqOqz0BAACALfSwJGcm+VpVXZpJ+L5/kp/I5Kn4y5P80uKC7r6wqt6Y5KQkn6qqDyS5V5KjklSSE7v7hqma66vq+UnenuScqtqQ5LokR2ayZv7p3b1hm5whAABzTYAPAACsVX+f5L8meVySx2Sy5v0tST6V5B1J/uuwbM7ddPdLqupTSV6cSXB/R5ILk7yquz+61IG6+9yqOjzJqUken0nof0WSP+nus2d7WgAAMCHABwAA1qTu/mySX9nC2rOSnLWZNR9J8tQtOR4AAGwJa+ADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGKEdV3sCAAAAjMNrzjl/tacAbEe/fcIxqz0FADbCE/gAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACM0dwF+VT2mqt5eVV+tqjur6oaq+lBVnVhVtcT4HarqN6rqH6vq1qr6xlD/rzdynGOr6uKqunH42lBVx2y7MwMAAAAAYD2ZqwC/qp6V5GNJnp3kn5O8M8mlSR6f5G1J/mxq/D2SvCPJ6Un2TXJ+ksuTnJDk41X12GWO85Ikf53kCUk+kuSiJI9N8p6qevGszwsAAAAAgPVnbgL8qtoxyZuT7JDkP3T3T3T3z3T3k5MckuT6JD9XVf92UdnzkzwzyeeTPKK7T+juIzL5BcAuSf582O/i4zw8yeuS3J7k8O5+ancfl+THklyX5PVV9dBtd6YAAAAAAKwHcxPgJ3lEkn2SfK67/+fiDd392Xz/6fvHLNp08tC+vLu/vmj8uZk8Yf/QJM+YOs5JmfyS4C3d/bFFNVcm+c9JdhzGAAAAAADAsuYpwL99E8ddlyRV9eAk/zrJrZksnTPtnKE9dqr/mKntm1IDAAAAAAB3M08B/j8luSrJw6vq5xZvGF5I+/NJvpnkr4buQ4f2M9195xL7u3RoD1m0nz2T7Dd8/OR0QXd/Jcm1Sfavqj227DQAAAAAAJgHO258yPrQ3d+tql9M8p5M1q4/JZO17fdJ8pNJrkjyvO6+fihZCOKvWWaXC/37L+pbqPlmd9+yQt39hrp/3Ni8q+ryZTYdsLFaAAAAAADWrrkJ8JOkuz9SVU/K5Cn7Rw1fSXJHkg9k8pT+gt2G9tvL7G4hoN99M2qWqwMAAAAAgLuZpyV0UlXPSXJJkq8keVwmgfvDkpyV5JQkF1XVTqs2wSV098FLfWWyHBAAAAAAAOvU3AT4VXVgkrMzWYP+6d19SXff0t2f7+4XZbK0zqOSPH8ouXlod1lml7sO7U2L+jZWs1wdAAAAAADczdwE+El+Nsk9k1zQ3Tcvsf3tQ3v40H55aPddZn8L/Vcv6luo2auqds3SlqoDAAAAAIC7macAfyE4/9Yy2xf69xray4b2kVV1zyXGL6yf/+mFju6+Id8P8X98uqCqHpjJC2yv7u4bN23aAAAAAADMo3kK8L82tI9eZvtjhvZLSdLdX0zy2ST3TnLMEuNPGNrzpvrPn9q+KTUAAAAAAHA38xTgv3toD6+q/2vxhqp6fJLfGD6es2jT6UP72qraZ9H445P8dJIvLNrvgjcm+W6S/zjsd6HmwCS/k+Q7wxgAAAAAAFjW3AT43X1pktcNH99cVZ+pqrdX1YeTfCSTl8v+aXdfuKjsbUn+KsmBSf53Vb2jqv42k5D/1iQ/393fmTrO55K8LMlOST5UVe+tqndlsiTPfZOc3N1f2GYnCgAAAADAujA3AX6SdPfLkhyf5P1JfjjJM5MclOTiJD/X3S+aGv+9JM9OckqSryZ5epIfTXJukkd3998vc5zXZ/KE/seS/GSSpyT5eJJju/v/mf2ZAQAAAACw3uy42hPY3rr7rzJ5qn5Tx383k6V0Tt/Y2Km682KtewAAAAAAttBcPYEPAAAAAABrhQAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAABYF6rqvlX1L1XVVfWFjYx9XlVdUlU3V9X1VfXeqnrCRmqeOIy7fqi7pKqeO9uzAACA7xPgAwAA68VpSe63sUFV9YYkZyZ5ZJILk1yS5KgkH6yq45apeVaSi5McneTTSS5IcmCSs6vqdTOYOwAA/AABPgAAsOZV1VOS/GKS/76RcUcmOSnJdUkO7e7juvvoJIcn+W6SM6tqz6mavZO8LckOSU7o7iO6+4Qkj0jyhSSnVNURMz0hAACIAB8AAFjjqureSf5bkiuSbOxp+JOH9tXd/fmFzu7+WJK3JNkzyQumal6YZI8k7+7udy6q+XqSlw8fT9nS+QMAwHIE+AAAwFr3e0kekuQ/JrlzuUFD0P/k4eM5SwxZ6Dt2qv+YFWrOT3JbkiOraudNnTAAAGwKAT4AALBmVdUhmTz9fmZ3f2gjwx+eZKck3+jua5bYfunQHjLVf+jU9rt09x1JPpNk5yQP29R5AwDApthxtScAAACwJarqHknOSHJDvr+UzUr2G9qlwvt09y1VdUOSvapq9+6+qar2SHKfleqG/kcn2T+TF9xubN6XL7PpgI3VAgAwXzyBDwAArFW/luQxSV7W3ddtwvjdhvbbK4y5ZWh3n6pZqW66BgAAZsIT+AAAwJpTVfsleXWSi7v7rFWezmbp7oOX6h+ezD9oO08HAIAR8wQ+AACwFr0pyb0yeXHtprp5aHdZYcyuQ3vTVM1KddM1AAAwE57ABwAA1qKnZ7L2/VuqanH/zkP7gKraMHz/s939tSRfHj7vu9QOq2rXJHsm+WZ335Qk3X1jVX0rk3Xw901yxRKlC/u7ektOBAAAliPABwAA1qo9kzxpmW07L9q2EOp/LsntSe5fVQ/o7v8zVfOooZ1+Ee1lSQ4ftt8twK+qeyZ5ZJLbkly5mfMHAIAVWUIHAABYc7q7lvpK8uBhyFWL+r801Nya5KJh+7OX2O0JQ3veVP/5U9sXe3omvyC4sLtv29LzAQCApQjwAQCAeXL60J5aVQcudFbVYUlelMmyPG+dqjkjyY1JnlFVxy+q2SfJa4ePp22rCQMAML8E+AAAwNzo7guTvDHJfZN8qqreVVXvTfLBTJYYPbG7b5iquT7J85N8L8k5VXVRVb0jkyV5Hprk9O7esP3OAgCAeSHABwAA5kp3vyTJiUk+m+SoJIcluTDJ4d39rmVqzs1kHfz3JfnxJE9L8oUkz+vuU7b9rAEAmEdeYgsAAKwbw3r3tQnjzkpy1mbu+yNJnrol8wIAgC3hCXwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBGaywC/qu5fVa+rqs9V1a1VdX1VXVpVf7zM+GOr6uKqunH42lBVx2zkGAdX1Tuq6hvDMf6xql5SVXP53xwAAAAAgM0zd2FyVf1Eks8mOSXJnUneneTvkuyd5DeWGP+SJH+d5AlJPpLkoiSPTfKeqnrxMsc4LMk/JDkhyT8N9fdL8vok/19V1UxPCgAAAACAdWfH1Z7A9lRV909yQZJ7J3lGd//11PbHTn1+eJLXJbk9yb/t7o8N/Q9L8tEkr6+qC7r7C4tq7pnkz4djnNzdrx/6d0vy/iTPTvLeJGdti3MEAAAAAGB9mLcn8H8/kyfhXzYd3idJd18y1XVSkh2SvGUhvB/GXZnkP2fyC5CTpmqemeTBSS5bCO+HmpuTLDyxf8pWngcAAAAAAOvc3AT4VXXvJD+f5JYkZ25i2cI69+cssW2h79hNrenuSzNZUueRVfWgTZwDAAAAAABzaJ6W0Hl0kt2TfLi7b62qpyY5KsnOSa5M8vbu/urC4KraM8l+w8dPTu+su79SVdcm2b+q9ujuG4dNhw7tpcvM49IkD0lySJIvbdUZAQAAAACwbs1TgH/Q0P5LVb0ryTOmtr+mql7Q3X8xfF4I77/Z3bcss89rMlmSZ/8k/zhVd80KNRlqNqqqLl9m0wGbUg8AAAAAwNo0N0voJNlraH86ydFJfjXJPkkelMmLau+d5Oyq+rFh3G5D++0V9rkQ7O++qG9jdUvVAAAAAADA3czTE/gLv6zYMcnvdPebF217WVXtn+TZSV6W5D9s78ktp7sPXqp/eDL/oKW2AQAAAACw9s3TE/g3L/p+qZfYLvQ9aWr8Livsc9ehvWmJ4yxXt1QNAAAAAADczTwF+FcP7be7+xtLbP/S0O4ztF8e2r2qatcfHJ4k2Xdq34vr9s3SlqoBAAAAAIC7macA/5NDe++q2mmJ7XsP7c1J0t035Pth/I9PD66qB2byAturu/vGRZsuG9pHLTOPhf5Pb9q0AQAAAACYR3MT4Hf3lzMJ1yvfXyZnsYW+Ty7qO39oT1hi/ELfeVP9y9ZU1Y8neUiSz3T3lzY+awAAAAAA5tXcBPiD1w7t66rqRxY6q+rHkpwyfHzLovFvTPLdJP+xqh6/aPyBSX4nyXeGMYv9VZIvJjm0qn5jUc2uSd40fDxtq88EAAAAAIB1ba4C/O7+n0nOTvKjSa6oqvOr6qIkf5fJEjr/vbvfsWj855K8LMlOST5UVe+tqndl8iT/fZOc3N1fmDrGnUl+PsmtSU6vqr+rqr9M8vkkhyU5Z5gDAAAAAAAsa64C/MGJSX45yVVJjkjy2CSXJnled//y9ODufn2Sn07ysSQ/meQpST6e5Nju/n+WOkB3fzTJY5Kcm+ShQ/31SU5O8jPd3bM9JQAAAAAA1psdV3sC29sQnv/34WtTa87LD651v7Gay7P02vkAAAAAALBR8/gEPgAAAAAAjJ4AHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAGDNqqqTq+qdVfX5qvpWVd1eVVdX1f+oqh9doe55VXVJVd1cVddX1Xur6gkbOdYTh3HXD3WXVNVzZ39WAAAwIcAHAADWst9O8tQk1yf5myTnJ7ktyS8k+URVPX26oKrekOTMJI9McmGSS5IcleSDVXXcUgepqmcluTjJ0Uk+neSCJAcmObuqXjfTMwIAgMGOqz0BAACArfCMJJ/o7tsWd1bVryR5U5Izqmrf7v7O0H9kkpOSXJfksO7+/NB/WJINSc6sqg3dfcOife2d5G1JdkjyrO5+59D/Q0k+nOSUqnpPd2/YlicKAMD88QQ+AACwZnX3R6bD+6H/zUmuSvJDSQ5atOnkoX31Qng/jP9Ykrck2TPJC6Z298IkeyR590J4P9R8PcnLh4+nbN2ZAADADxLgAwAA69WdQ3tHklTVvZM8eeg7Z4nxC33HTvUfs0LNwpI9R1bVzls+VQAA+EECfAAAYN2pql9I8vAknx++MnzeKck3uvuaJcouHdpDpvoPndp+l+6+I8lnkuyc5GFbOW0AALgba+ADAABrXlW9LMnBSXZN8q+H77+a5Dnd/d1h2H5Du1R4n+6+papuSLJXVe3e3TdV1R5J7rNS3dD/6CT7Z/KC243N9fJlNh2wsVoAAOaLAB8AAFgPfirJUxZ9vjrJc7v7E4v6dhvab6+wn1syWQd/9yQ3LapZqe6Wod19UycLAACbwhI6AADAmtfdR3Z3JdkryeGZLJtzcVX9zurO7Ad198FLfWXy0l0AALiLAB8AAFg3uvuG7v5Qkqcl+USSV1XVY4bNNw/tLivsYtehvWmqZqW66RoAAJgJAT4AALDudPedSf4ySSU5duj+8tDuu1RNVe2ayfI53+zum4b93JjkWyvVLeq/eutmDQAAdyfABwAA1qtrh/b+Q/u5JLcnuX9VPWCJ8Y8a2ukX0V42tf0uVXXPJI9McluSK7dqtgAAMEWADwAArFdPGtqrkqS7b01y0dD37CXGnzC05031nz+1fbGnJ9k5yYXdfduWTxUAAH6QAB8AAFiTquqJVXV0Vd1jqv+eVfVrSX4hya2ZLKWz4PShPbWqDlxUc1iSFyW5Iclbpw51RpIbkzyjqo5fVLNPktcOH0/b+jMCAIC723G1JwAAALCFDkxyZpJrq+oTSa5Lcr8kP5rkRzJZ1uZ53f2VhYLuvrCq3pjkpCSfqqoPJLlXkqMyWS//xO6+YfFBuvv6qnp+krcnOaeqNgzHOjKTNfNP7+4N2+40AQCYVwJ8AABgrbo4yWsyWSrnkEzC+zuSfCnJOUn+S3d/Ybqou19SVZ9K8uJMgvs7klyY5FXd/dGlDtTd51bV4UlOTfL4TEL/K5L8SXefPdvTAgCACQE+AACwJnX3F5P8zhbWnpXkrM2s+UiSp27J8QAAYEvMdA38qtqvqvbehHF7VdV+szw2AAAwDu4LAABgNmb9EtsvJvnjTRj32iT/NONjAwAA4+C+AAAAZmDWAX4NX5s6FgAAWH/cFwAAwAzMOsDfVPdLcusqHRsAABgH9wUAALCCrX6JbVUdPtX1w0v0LT7ew5P8VJLLt/bYAADAOLgvAACA2dvqAD/JhiS96PNPDV/LqWH8aTM4NgAAMA4b4r4AAABmahYB/v/I9y/UfzHJVUk+sszYO5J8Ncl53X3pDI4NAACMg/sCAACYsa0O8Lv7eQvfV9UvJvlwdz9/a/cLAACsHe4LAABg9mbxBP5dunu1XooLAACMhPsCAACYDRfWAAAAAAAwQjN9Aj9JqmqnJM9JcniSH0my0zJDu7ufMuvjAwAAq899AQAAbL2ZBvhV9YAkf5PkwCS1keG9ke0AAMAa5L4AAABmY9ZP4P9xkocl+WiS05NcmeSmGR8DAAAYN/cFAAAwA7MO8H8qyZeTHNndt8143wAAwNrgvgAAAGZg1i+x3SnJ37tIBwCAuea+AAAAZmDWAf4/JrnfjPcJAACsLe4LAABgBmYd4P9RksOr6rEz3i8AALB2uC8AAIAZmPUa+Jdm8pKqv6mq05N8IMk1Sb631ODu/vKMjw8AAKw+9wUAADADsw7wv5Skk1SSU4ev5fQ2OD4AALD6vhT3BQAAsNVmfaH8wUwuwAEAgPnlvgAAAGZgpgF+dx8xy/0BAABrj/sCAACYjVm/xBYAAAAAAJgBAT4AAAAAAIzQTJfQqarf3Yzh3d2vmuXxAQCA1ee+AAAAZmPWL7F9ZSYvq6plti+8yKqG712oAwDA+vPKuC8AAICtNusA/8Rl+u+R5IFJjkryxCRvSvLxGR8bAAAYB/cFAAAwAzMN8Lv77I0M+YOqenmS303yp7M8NgAAMA7uCwAAYDa2+0tsu/u1Sa5J8prtfWwAAGAc3BcAAMDGbfcAf/CPSf7NKh0bAAAYB/cFAACwgtUK8A/I7NffBwAA1hb3BQAAsILtGuBX1V5VdVqSH0tyyfY8NgAAMA7uCwAAYNPM9GmXqvqnFTbvluS+SSrJrUl+a5bHBgAAxsF9AQAAzMas/1z1QStsuzPJV5JcnOSPuvuKGR8bAAAYhwetsM19AQAAbKKZBvjdvVpr6gMAACPhvgAAAGbDhTUAAAAAAIzQNg/whxdU7bWtjwMAAIyX+wIAANh82yTAr6qnVdX7qurmJNcmubaqbq6qC6rqadvimAAAwLi4LwAAgK0z8wC/ql6f5LwkRyXZJcmNSb41fP/vkpxXVafP+rgAAMB4uC8AAICtN9MAv6p+JslJSb6R5NeT7NXde3X33kn2TPJrSf4lyUlV9e9neWwAAGAc3BcAAMBszPoJ/F9JcluSw7v7T7r7WwsbuvvG7n5TkicluX0YCwAArD/uCwAAYAZmHeAfmuSi7r5yuQHDtouS/NiMjw0AAIyD+wIAAJiBWQf490pyyyaMu2UYCwAArD/uCwAAYAZmHeBfleRJVbXrcgOqapdM/lz2qhkfGwAAGAf3BQAAMAOzDvDfnmSfJO+qqgOnN1bVAUnemeT+Sf5yxscGAADGwX0BAADMwI4z3t/rkjwjyVOSXFFVlyb50rBt/yQ/kWSHJB9PctqMjw0AAIyD+wIAAJiBmQb43X1rVR2R5A+TPD/JY4avBbcmeVuS3+ruW2d5bAAAYBzcFwAAwGzM+gn8dPfNSX6tqn4zkydr/tWw6atJPtHd3571MQEAgHFxXwAAAFtvpgF+Ve2W5CFJvtrd1yb50BJj7pfJxftV3X3LLI8PAACsPvcFAAAwG7N+ie3JST6Z5IAVxhwwjDlpxscGAADGwX0BAADMwKwD/GOTfKG7/365AcO2q5IcN+NjAwAA4+C+AAAAZmDWAf5DkvzvTRj32SQPnvGxAQCAcXBfAAAAMzDrAP/eSW7dhHG3JtltxscGAADGwX0BAADMwKwD/K8kecwmjHtMkq/O+NgAAMA4uC8AAIAZmHWA/74kD6qq31huQFWdlMmfyV4w42MDAADj4L4AAABmYMcZ7++1SX4hyeuq6ilJ/jSTF1MlyQFJfjnJU5PcOIwFAADWH/cFAAAwAzMN8Lv7mqr66STnJnlaJhfli1WSa5M8u7uvnuWxAQCAcXBfAAAAszHrJ/DT3R+qqocn+aUkT0nywGHTV5JcmOSM7v7mrI8LAACMh/sCAADYejMP8JNkuBB/bfw5LAAAzC33BQAAsHVm/RJbAAAAAABgBgT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACM0FwH+FV136r6l6rqqvrCRsY+r6ouqaqbq+r6qnpvVT1hIzVPHMZdP9RdUlXPne1ZAAAAAACwHs11gJ/ktCT329igqnpDkjOTPDLJhUkuSXJUkg9W1XHL1DwrycVJjk7y6SQXJDkwydlV9boZzB0AAAAAgHVsbgP8qnpKkl9M8t83Mu7IJCcluS7Jod19XHcfneTwJN9NcmZV7TlVs3eStyXZIckJ3X1Ed5+Q5BFJvpDklKo6YqYnBAAAAADAujKXAX5V3TvJf0tyRZKNPQ1/8tC+urs/v9DZ3R9L8pYkeyZ5wVTNC5PskeTd3f3ORTVfT/Ly4eMpWzp/AAAAAADWv7kM8JP8XpKHJPmPSe5cbtAQ9D95+HjOEkMW+o6d6j9mhZrzk9yW5Miq2nlTJwwAAAAAwHyZuwC/qg7J5On3M7v7QxsZ/vAkOyX5Rndfs8T2S4f2kKn+Q6e236W770jymSQ7J3nYps4bAAAAAID5MlcBflXdI8kZSW7I95eyWcl+Q7tUeJ/uvmXY115VtftwjD2S3GelukX9+2/CHAAAAAAAmEM7rvYEtrNfS/KYJCd293WbMH63of32CmNuyWQd/N2T3LSoZqW6W4Z2941NoKouX2bTARurBQAAAABg7ZqbJ/Crar8kr05ycXeftcrTAQAAAACAFc3TE/hvSnKvTF5cu6luHtpdVhiz69DeNFWzUHfjJtQsq7sPXqp/eDL/oI3VAwAAAACwNs1TgP/0TNarf0tVLe7feWgfUFUbhu9/tru/luTLw+d9l9phVe2ayfI53+zum5Kku2+sqm9lsg7+vkmuWKJ0YX9Xb8mJAAAAAACw/s1TgJ9MwvYnLbNt50XbFkL9zyW5Pcn9q+oB3f1/pmoeNbSfnuq/LMnhw/a7BfhVdc8kj0xyW5IrN3P+AAAAAADMiblZA7+7a6mvJA8ehly1qP9LQ82tSS4atj97id2eMLTnTfWfP7V9sadn8guCC7v7ti09HwAAAAAA1re5CfC3wulDe2pVHbjQWVWHJXlRJsvyvHWq5oxM1r5/RlUdv6hmnySvHT6etq0mDAAAAADA2ifA34juvjDJG5PcN8mnqupdVfXeJB/MZAmiE7v7hqma65M8P8n3kpxTVRdV1TsyWZLnoUlO7+4N2+8sAAAAAABYawT4m6C7X5LkxCSfTXJUksOSXJjk8O5+1zI152ayDv77kvx4kqcl+UKS53X3Kdt+1gAAAAAArGXz9hLbHzCsd1+bMO6sJGdt5r4/kuSpWzIvAAAAAADmmyfwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAACsSVW1S1UdV1VvrarPVdVtVXVLVV1WVb9bVbutUPu8qrqkqm6uquur6r1V9YSNHO+Jw7jrh7pLquq5sz8zAACYEOADAABr1c8l+askz0/y3SR/neRDSR6c5PeT/ENV7TNdVFVvSHJmkkcmuTDJJUmOSvLBqjpuqQNV1bOSXJzk6CSfTnJBkgOTnF1Vr5vlSQEAwAIBPgAAsFbdmeRPkxzU3Qd197/v7qOTPDzJJ5M8IskbFhdU1ZFJTkpyXZJDu/u4oebwTH4JcGZV7TlVs3eStyXZIckJ3X1Ed58w7P8LSU6pqiO21UkCADC/BPgAAMCa1N1nd/eLuvuzU/3/nORXh4/HV9W9Fm0+eWhf3d2fX1TzsSRvSbJnkhdMHeqFSfZI8u7ufueimq8nefnw8ZStPB0AAPgBAnwAAGA9umxod0py3ySpqnsnefLQf84SNQt9x071H7NCzflJbktyZFXtvMWzBQCAJQjwAQCA9eghQ3tnkuuH7x+eSaD/je6+ZomaS4f2kKn+Q6e236W770jymSQ7J3nY1kwYAACm7bjaEwAAANgGThraC7r79uH7/YZ2qfA+3X1LVd2QZK+q2r27b6qqPZLcZ6W6of/RSfbP5AW3K6qqy5fZdMDGagEAmC+ewAcAANaVqnpaJuvY35nkFYs27Ta0316h/Jah3X2qZqW66RoAAJgJT+ADAADrRlU9IsmfJakkL+vuyzZSst1198FL9Q9P5h+0nacDAMCIeQIfAABYF6rqAUkuSLJXktO7+41TQ24e2l1W2M2uQ3vTVM1KddM1AAAwEwJ8AABgzauqvZO8P5N16M9M8tIlhn15aPddZh+7JtkzyTe7+6Yk6e4bk3xrpbpF/Vdv9sQBAGAFAnwAAGBNq6rdkvyvTJafeWeSX+ruXmLo55LcnuT+w9P60x41tNMvor1savviY98zySOT3Jbkys2fPQAALE+ADwAArFlVtVOSdyd5bJL3JXlOd393qbHdfWuSi4aPz15iyAlDe95U//lT2xd7epKdk1zY3bdtxtQBAGCjBPgAAMCaVFU7JPmLJE9O8qEkx3f3HRspO31oT62qAxft67AkL0pyQ5K3TtWckeTGJM+oquMX1eyT5LXDx9O28DQAAGBZO672BAAAALbQi5M8c/j+2iRvrqqlxr20u69Nku6+sKremOSkJJ+qqg8kuVeSo5JUkhO7+4bFxd19fVU9P8nbk5xTVRuSXJfkyEzWzD+9uzfM9MwAACACfAAAYO3aa9H3z1x2VPLKTAL+JEl3v6SqPpXJLwCOSnJHkguTvKq7P7rUDrr73Ko6PMmpSR6fSeh/RZI/6e6zt+IcAABgWQJ8AABgTeruV2YSzm9J7VlJztrMmo8keeqWHA8AALaENfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCE5ibAr6pdquq4qnprVX2uqm6rqluq6rKq+t2q2m2F2udV1SVVdXNVXV9V762qJ2zkeE8cxl0/1F1SVc+d/ZkBAAAAALAezU2An+TnkvxVkucn+W6Sv07yoSQPTvL7Sf6hqvaZLqqqNyQ5M8kjk1yY5JIkRyX5YFUdt9SBqupZSS5OcnSSTye5IMmBSc6uqtfN8qQAAAAAAFif5inAvzPJnyY5qLsP6u5/391HJ3l4kk8meUSSNywuqKojk5yU5Lokh3b3cUPN4Zn8EuDMqtpzqmbvJG9LskOSE7r7iO4+Ydj/F5KcUlVHbKuTBAAAAABgfZibAL+7z+7uF3X3Z6f6/znJrw4fj6+qey3afPLQvrq7P7+o5mNJ3pJkzyQvmDrUC5PskeTd3f3ORTVfT/Ly4eMpW3k6AAAAAACsc3MT4G/EZUO7U5L7JklV3TvJk4f+c5aoWeg7dqr/mBVqzk9yW5Ijq2rnLZ4tAAAAAADrngB/4iFDe2eS64fvH55JoP+N7r5miZpLh/aQqf5Dp7bfpbvvSPKZJDsnedjWTBgAAAAAgPVtx9WewEicNLQXdPftw/f7De1S4X26+5aquiHJXlW1e3ffVFV7JLnPSnVD/6OT7J/JC25XVFWXL7PpgI3VAgAAAACwds39E/hV9bRM1rG/M8krFm3abWi/vUL5LUO7+1TNSnXTNQAAAAAA8APm+gn8qnpEkj9LUkle1t2XbaRku+vug5fqH57MP2g7TwcAAAAAgO1kbp/Ar6oHJLkgyV5JTu/uN04NuXlod1lhN7sO7U1TNSvVTdcAAAAAAMAPmMsAv6r2TvL+TNahPzPJS5cY9uWh3XeZfeyaZM8k3+zum5Kku29M8q2V6hb1X73ZEwcAAAAAYG7MXYBfVbsl+V+ZLD/zziS/1N29xNDPJbk9yf2Hp/WnPWpop19Ee9nU9sXHvmeSRya5LcmVmz97AABgsar6iar6T1X1zqq6pqq6qpa6vp+ue15VXVJVN1fV9VX13qp6wkZqnjiMu36ou6Sqnju7swEAgLubqwC/qnZK8u4kj03yviTP6e7vLjW2u29NctHw8dlLDDlhaM+b6j9/avtiT0+yc5ILu/u2zZg6AACwtFck+cMkz0yy1IM3P6Cq3pDJX+I+MsmFSS5JclSSD1bVccvUPCvJxUmOzuQhnguSHJjk7Kp63VadAQAALGNuAvyq2iHJXyR5cpIPJTm+u+/YSNnpQ3tqVR24aF+HJXlRkhuSvHWq5owkNyZ5RlUdv6hmnySvHT6etoWnAQAA3N3HkrwqyU8n+ZFM/op2WVV1ZJKTklyX5NDuPq67j05yeJLvJjmzqvacqtk7yduS7JDkhO4+ortPSPKIJF9IckpVHTHDcwIAgCTJjqs9ge3oxZk8lZMk1yZ5c1UtNe6l3X1tknT3hVX1xkwu8D9VVR9Icq9Mns6pJCd29w2Li7v7+qp6fpK3JzmnqjZkcnNwZCZr5p/e3RtmemYAADCnuvuPFn9e5hp/sZOH9tXd/flF+/lYVb0lya8neUHu/tDNC5PskeTd3f3ORTVfr6qXZ7I05ylJNmzhaQAAwJLmKcDfa9H3z1x2VPLKTAL+JEl3v6SqPpXJLwCOSnJHJn9m+6ru/uhSO+juc6vq8CSnJnl8JqH/FUn+pLvP3opzAAAAtlBV3TuTv8hNknOWGHJOJgH+sbl7gH/MCjXnZ/KOqyOramdLZQIAMEtzE+B39yszCee3pPasJGdtZs1Hkjx1S44HAABsEw9PslOSb3T3NUtsv3RoD5nqP3Rq+126+46q+kySRyd5WCbr4wMAwEzMTYAPAADMvf2GdqnwPt19S1XdkGSvqtq9u2+qqj2S3GeluqH/0Un2zyYE+FV1+TKbDthYLQAA82VuXmILAADMvd2G9tsrjLllaHefqlmpbroGAABmwhP4AAAA21F3H7xU//Bk/kHbeToAAIyYJ/ABAIB5cfPQ7rLCmF2H9qapmpXqpmsAAGAmBPgAAMC8+PLQ7rvUxqraNcmeSb7Z3TclSXffmORbK9Ut6r96NtMEAIAJAT4AADAvPpfk9iT3r6oHLLH9UUM7/SLay6a236Wq7pnkkUluS3LljOYJAABJBPgAAMCc6O5bk1w0fHz2EkNOGNrzpvrPn9q+2NOT7Jzkwu6+basnCQAAiwjwAQCAeXL60J5aVQcudFbVYUlelOSGJG+dqjkjyY1JnlFVxy+q2SfJa4ePp22rCQMAML92XO0JAAAAbKmqOibJKxZ13Wvo/7tFfa/q7vOTpLsvrKo3Jjkpyaeq6gNDzVFJKsmJ3X3D4mN09/VV9fwkb09yTlVtSHJdkiMzWTP/9O7eMPOTAwBg7gnwAQCAtez+SR63RP/jpsbcpbtfUlWfSvLiTIL7O5JcmEnQ/9GlDtLd51bV4UlOTfL4TEL/K5L8SXefvbUnAQAASxHgAwAAa1Z3n5XkrO1R190fSfLUzT0WAABsKWvgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCO672BABgLXjNOeev9hSA7ei3TzhmtacAAADgCXwAAAAAABgjAT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAvxtpKruXVV/UFVXVtVtVfXVqnpbVT1gtecGAABsPtf4AABsbwL8baCqdk5yUZJXJNktybuTfCXJiUk+WVUPWcXpAQAAm8k1PgAAq0GAv22cmuTxST6W5GHd/TPd/bgkpyS5f5K3rebkAACAzeYaHwCA7U6AP2NVda8kLx4+/mp337ywrbtPT/LpJE+qqp9YjfkBAACbxzU+AACrRYA/e09Mcp8kV3X3J5fYfs7QHrv9pgQAAGwF1/gAAKwKAf7sHTq0ly6zfaH/kO0wFwAAYOu5xgcAYFXsuNoTWIf2G9prltm+0L//puysqi5fZtMjrrrqqhx88MGbM7eZuvbGmzc+CFg3/vz3dlvtKawqP/Ngvqzmz7yrrroqSR64ahNgKa7xgXXJNb6feTBP1uo1vgB/9hb+T/j2MttvGdrdt/I437v99ttvueKKK76ylfuBzXHA0F61qrNgVfzLak8Atj8/8+bYKv/Me2CWv5ZkdbjGZz3z790cc43PHPIzb46t1Wt8Af7IdffqPX4DUxaeFvP/JTAP/MwDthU/VxgT/94B88TPPNYia+DP3sLfX+2yzPZdh/am7TAXAABg67nGBwBgVQjwZ+/LQ7vvMtsX+q/eDnMBAAC2nmt8AABWhQB/9i4b2kcts32h/9PbYS4AAMDWc40PAMCqEODP3keSfCvJAVX1Y0tsP2Foz9tuMwIAALaGa3wAAFaFAH/GuvuOJH8yfHxTVS2sh5mqOjnJIUku7u5PrMb8AACAzeMaHwCA1VLdvdpzWHeqauckG5I8Lsk/J/lQkv2Hz99I8vju/qdVmyAAALBZXOMDALAaBPjbSFXdO8lvJfm5JA9Mcn2SC5K8oruvWc25AQAAm881PgAA25sAHwAAAAAARsga+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiADwAAAAAAIyTABwAAAACAERLgAwAAAADACAnwgc1SVbXacwAAAGbHNT4AjFd192rPARixqnpIkvsmub67r1rt+QBsD1W1Q3d/d7XnAQDbgmt8YB65xmet8gQ+sKSquk9VnZHkE0n+Nsnnq+p/VdXxqzw1gG2iqvauqlckiQt7ANYj1/jAvHGNz3qw42pPABifqvrRJG9N8ugk70vy2ST7J3lmkidV1a8neXt337h6swSYnap6XpL/kmS3qvrn7j6jqnbs7u+s8tQAYCZc4wPzxjU+64Un8IG7LFr78umZXNifluQ53X1ydz8rySlJrk/yh0mevzqzBJidqrpvVf12JoHGbkP3a6rqXt39napyrQTAmuYaH5g3rvFZb/wPC9ylu7uq7pnkhUm+k+SN3X1DVe00DPnTJL+dZJck/6mq/s0qTRVgqw0X7s9I8vIk30hyfJLzktwvye8vDFud2QHAbLjGB+aJa3zWIwE+cJfh6Zz7JrktydVJbhxe8nJ7knT3LUnekeSNSfZJ8rurNVeArdXd30uyc5IvJTm1u9+VSYCRJL9ZVQ/u7u9W1Q6rNEUA2Gqu8YF54hqf9UiAD9yluzvJzZn8ZvqAJA8d/mG7x6Ixt2Zycf+5JEdW1XNXZbIAs3FGkpckOTNJuvvyTH7GJcnpQ5+XXQGwZrnGB+aQa3zWFQE+cDfdfXOSdw8fTxj6vjc15utJ/u/h4wuqarcArEHdfUd3bxiCjHsO3b+X5IYkz6iqI5OkqnZcrTkCwNZyjQ/ME9f4rDcCfGApFye5NckTq+qg5G4vv1rw3iSXJDk0yZO37/QAZq+77xyWFLgxyW8N3a8ftn1niZ+DALCWuMYH5o5rfNYDAT6wlL9J8o9JfiLJU6vqHsOf3i72rSR/nWSPJPtt5/kBbCvfS5Lu/m9JPpXk4Kr6lWGbdTIBWMtc4wPzyjU+a5oAH/gB3f21JH+RZMdM/sT2sOTuT+h09x1Jrho+PnZ7zxFgW+juXvSntC8b2j+qqt2GJ3TutVpzA4Ct4RofmFeu8VnrBPjAct6W5MNJHpPkuVW19/CP3g6L/uG7LMkdSXapwWpNFmBWuvs7Q/s3Sc5JsmuSPxz67kiSqtrThT4Aa5BrfGAuucZnLRPgA0vq7psyeYnVl5I8N8lvLNr2neHbg5LcK8kXe7C95wmwLVTVwp/SLqyT+atV9a+Gbb+b5H8keeJqzA0AtpRrfGCeucZnrRLgAyu5KMlpSSrJ71TVryb54SSpqicnOTXJN5KcsWozBNgGuvu7VXWv7r4qk591SXJeVW1I8sokT88k3ACAtcY1PjCXXOOzVpVfpgMrGf5k9qQkpw9dX0vyL0n2TbJ3kj9I8p+TfMfTOcB6VFVPSvKeTP7MNknekeSl3f2V1ZsVAGw51/jAvHONz1oiwAc2SVX9VJJfSnJwJm9pvzrJq7r7g6s6MYBtpKoemMlLrn4myf2TfDLJS7r7Q6s6MQCYEdf4wLxxjc9aJMAHNllVLSy7tX93f3FVJwOwDQ0v8js1ye8m+WaS3+xuSwkAsO64xgfmhWt81qodNz4E4C7V3d9N4sIeWNe6+ztVdWGS25K8vrtvX+05AcA24hofmAuu8VmrPIEPAAAAAAAjdI+NDwEAAAAAALY3AT4AAAAAAIyQAB8AAAAAAEZIgA8AAAAAACMkwAcAAAAAgBES4AMAAAAAwAgJ8AEAAAAAYIQE+AAAAAAAMEICfAAAAAAAGCEBPgAAAAAAjJAAHwAAAAAARkiAD8CaU1Vfqqpe7XkAAADbXlUdUVVdVWet9lwAtjcBPgCjUlUPGi7ON6z2XAAAAABW046rPQEA2AJPSXLP1Z4EAAAAwLYkwAdgzenuq1Z7DgAAAADbmiV0ANgqi5e8qao9qur0qvpiVd1ZVW8YxuxdVX9YVVdU1a1V9a2quqiqnj61r1cm+eLw8UnDfnt6vcul1sDflHlszlwAAGC9qqrjq+rvqurbVXVtVb2jqh5aVa8crqmfNzV+l6r6rar6ZFXdPHz9XVX94jL77+GafYeq+s2qurKqbq+qr1TVH1XVTsvUHVxV76qqb1bVTVX1oao6eiPnUlX1nOGa/ptVdVtVfXY4l12WGL9hmN+DqurnhvO4qapu2PT/ggDbjyfwAZiVeye5OMn+Q3tpkm9W1cOSXJjkgUm+lOR9SXZP8vgk51XVy7r7dcM+PpXk3CTPSvL1JBcs2v+Ht2YeSbKZcwEAgHWnqk5K8oYk30vywSRfS/K4JJckOW+J8fsk+UCSQ4axFyepJE9IclZVPbq7f22Zw/3PJE9LsiHJ55L8ZJKXJ3lAkp+fOs6jk/xtkt2SfGb4OjDJe5P812XO5R5J/izJc5LcnOTjmVz7PzrJ7yV5alUd0d23LlH+W0lemOQjSd6TyT0CwOhUd298FAAso6oelO8/Nf+xJE/r7huGbTsk+WSSH83kQv207v7esO2hSd6fZL8kP9bdn5na38XdfcQyx/xSkv27uzZlHls6FwAAWE+q6iFJPjt8PLq7/3bo3zHJnyY5cdh2YnefNWw7P5MQ/o1JfrO7bx/6fyiT4PvRSZ7a3RcsOs5C2PTZJE/u7q8N/Q/O5AGbPZM8dGFpzKqqTAL7g5L8QXf/3qJ9/UqSNw0fz+7u5y3a9rIkr83kFwTPWXSceyV5c5IXJPmj7v5Pi2o2JHlSktuG/wYXb8Z/QoDtzhI6AMzSry8OzZMcm0lgfm53//FCYJ4k3f2FJKck2SHJL23jeazmXAAAYCyen+ReSf7fhfA+Sbr7O0lOzuQp9rtU1Y9lEt7/Q5KTF8L7oebrSX55+Ph/LXO8X18I1YeaL2byxHwyeRp/wRGZhPf/lOQPFu+gu9+c5O+ndzz80uHlSW5J8rNTx7kjya9l8hcDvzw8qT/trcJ7YC0Q4AMwK//c3R+f6vt3Q/vOZWo+NLSP3cbzWK25AADAmDxxaN8xvWF4AOb9U90L19DvWvwAzKKaT2YS+i91DX1nJkviTLtyaH9kUd9CmH9Od393iZq/WKLvUUnul+Sjwy8Tpud2a5JPJNkrk6V4pv31En0AoyPAB2BWvrxE34OG9s+nXkjbw5/VfmPYfr9tPI/VmgsAAIzJQmj+lWW2T19LP2ho//NS19DDdfRuWfoa+mvLhPE3De3iF9n+q6G9epl5fWmJvoW5HbXC3I4Zxiw1v+XuGwBGxUtsAZiV25boW/hF8QWZvJR2Oddu43ms1lwAAGAtW7iG/nCSqzaz9gee2J+xhbl9IZMX0a7kuiX6lrtvABgVAT4A29I1Q3tGd5+7qjMZ11wAAGA1/HOShyd5YJIrltj+wKnPC9fQ7+ru07bxvJJk/2W2L9W/MLf/vfjFtgDrjSV0ANiWPjC0z9yMmjuGdta/ZN6SuQAAwHqy8KT6s6Y3VNV98v017xdsr2vohfdRPWuZF87+7BJ9/5DkW0meVFV7b7OZAawyAT4A29K5mTzZ8x+q6hVVtXidy9TEE6vqiYu6r83khVcHVNUOqzwXAABYT87M5IGZ51bV4Qudw3X3aUl2Xzy4u/8+kxD/iVX1pqraY3qHVXVoVR29lfPakOR/JzkgyalT+39RksOmC7r79iSvHeb8zqp6yBJze0BV/cJWzg1gVQnwAdhmuvs7SY5L8sUkf5Dky1X1gar686p6X5KvZbKe5mMW1dyRyTr1P5zksqr6H1V1RlWduL3nAgAA60l3X5Xk5Zm8QPZvq+qiqvqLJFdm8lT+nw1D71hU9vNJPpnkV5JcXVV/O1xDv6eqvpzkU0m2KsDv7u8leV6SW5L8flV9uqr+Z1VdkuS/JnnzMqX/d5L/N8mTkny2qv6uqv6iqs6tqs9k8rLeU7ZmbgCrTYAPwDbV3Z9P8uOZPElzTZLHJzk+ycMyuRH41Xz/RmHBCzO5EL9vkp9L8oJMLspXYy4AALBudPcbk5yQ5OOZXA//VCYh/OPy/Re7Xrdo/L8keUKSX8/kL1p/fKg/JMk/JXlZktfNYF5/n8mT9ucl2S/JTyf5TpJjk7xjmZrvdfdzkzwjk78UeHAmv4j4N8O5/HGS52/t3ABWU3X3as8BAAAAgFU0LKPz6ST/Osm/6u6vrfKUAIgn8AEAAADmRlUdUFV7TvXtlMl68gcl+RvhPcB47LjaEwAAAABgu3l2JuvMfyKTNeL3SHJokh9Jcm2SF6/i3ACYYgkdAAAAgDlRVY9JcnIm69/fP5OHO/9Pkvcl+cPu/soqTg+AKQJ8AAAAAAAYIWvgAwAAAADACAnwAQAAAABghAT4AAAAAAAwQgJ8AAAAAAAYIQE+AAAAAACMkAAfAAAAAABGSIAPAAAAAAAjJMAHAAAAAIAREuADAAAAAMAICfABAAAAAGCEBPgAAAAAADBCAnwAAAAAABghAT4AAAAAAIzQ/w+MU+isp4YdywAAAABJRU5ErkJggg==\n", | |
"text/plain": [ | |
"<Figure size 1800x900 with 2 Axes>" | |
] | |
}, | |
"metadata": { | |
"needs_background": "light" | |
}, | |
"output_type": "display_data" | |
}, | |
{ | |
"data": { | |
"image/png": "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\n", | |
"text/plain": [ | |
"<Figure size 1800x900 with 2 Axes>" | |
] | |
}, | |
"metadata": { | |
"needs_background": "light" | |
}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"i = 0\n", | |
"while i < len(categorical_cols):\n", | |
" cols = categorical_cols[i:i+2]\n", | |
" plot_columns_dist(cust_df, cols, 1, 2).show()\n", | |
" i += 2" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 62, | |
"id": "a84416ed", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:37.593252Z", | |
"iopub.status.busy": "2022-04-18T16:14:37.592327Z", | |
"iopub.status.idle": "2022-04-18T16:14:40.997794Z", | |
"shell.execute_reply": "2022-04-18T16:14:40.997244Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:39.363870Z" | |
}, | |
"papermill": { | |
"duration": 3.73122, | |
"end_time": "2022-04-18T16:14:40.997980", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:37.266760", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+------+-----+-----+-----+-----+\n", | |
"|region|cnt_A|cnt_B|cnt_C|cnt_D|\n", | |
"+------+-----+-----+-----+-----+\n", | |
"| 1| 75| 78| 95| 74|\n", | |
"| 2| 92| 69| 92| 81|\n", | |
"| 3| 99| 70| 94| 81|\n", | |
"+------+-----+-----+-----+-----+\n", | |
"\n", | |
"+------+-----+-----+-----+-----+\n", | |
"|tenure|cnt_A|cnt_B|cnt_C|cnt_D|\n", | |
"+------+-----+-----+-----+-----+\n", | |
"| 1| 10| 1| 2| 0|\n", | |
"| 2| 3| 0| 2| 2|\n", | |
"| 3| 11| 1| 1| 7|\n", | |
"| 4| 10| 0| 5| 4|\n", | |
"| 5| 6| 2| 7| 4|\n", | |
"| 6| 5| 2| 6| 2|\n", | |
"| 7| 12| 0| 2| 4|\n", | |
"| 8| 5| 2| 3| 4|\n", | |
"| 9| 11| 1| 1| 2|\n", | |
"| 10| 6| 4| 2| 6|\n", | |
"+------+-----+-----+-----+-----+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+---+-----+-----+-----+-----+\n", | |
"|age|cnt_A|cnt_B|cnt_C|cnt_D|\n", | |
"+---+-----+-----+-----+-----+\n", | |
"| 18| 0| 0| 1| 0|\n", | |
"| 19| 2| 2| 0| 0|\n", | |
"| 20| 4| 1| 2| 3|\n", | |
"| 21| 4| 2| 2| 0|\n", | |
"| 22| 4| 5| 4| 2|\n", | |
"| 23| 4| 1| 5| 6|\n", | |
"| 24| 9| 1| 7| 3|\n", | |
"| 25| 9| 4| 5| 5|\n", | |
"| 26| 9| 2| 3| 7|\n", | |
"| 27| 7| 7| 0| 10|\n", | |
"+---+-----+-----+-----+-----+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+------+-----+-----+-----+-----+\n", | |
"|income|cnt_A|cnt_B|cnt_C|cnt_D|\n", | |
"+------+-----+-----+-----+-----+\n", | |
"| 9| 2| 2| 3| 0|\n", | |
"| 10| 1| 0| 2| 0|\n", | |
"| 11| 2| 0| 0| 0|\n", | |
"| 12| 1| 0| 1| 1|\n", | |
"| 13| 0| 1| 1| 0|\n", | |
"| 14| 2| 1| 2| 2|\n", | |
"| 15| 1| 2| 3| 2|\n", | |
"| 16| 2| 1| 0| 1|\n", | |
"| 17| 5| 3| 4| 1|\n", | |
"| 18| 7| 2| 1| 5|\n", | |
"+------+-----+-----+-----+-----+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+-------+-----+-----+-----+-----+\n", | |
"|marital|cnt_A|cnt_B|cnt_C|cnt_D|\n", | |
"+-------+-----+-----+-----+-----+\n", | |
"| 0| 155| 102| 142| 106|\n", | |
"| 1| 111| 115| 139| 130|\n", | |
"+-------+-----+-----+-----+-----+\n", | |
"\n", | |
"+-------+-----+-----+-----+-----+\n", | |
"|address|cnt_A|cnt_B|cnt_C|cnt_D|\n", | |
"+-------+-----+-----+-----+-----+\n", | |
"| 0| 10| 12| 17| 17|\n", | |
"| 1| 21| 17| 12| 18|\n", | |
"| 2| 21| 14| 15| 16|\n", | |
"| 3| 24| 12| 13| 12|\n", | |
"| 4| 18| 10| 18| 15|\n", | |
"| 5| 18| 9| 12| 11|\n", | |
"| 6| 10| 7| 10| 9|\n", | |
"| 7| 21| 5| 15| 12|\n", | |
"| 8| 8| 12| 9| 10|\n", | |
"| 9| 19| 7| 7| 8|\n", | |
"+-------+-----+-----+-----+-----+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+---+-----+-----+-----+-----+\n", | |
"| ed|cnt_A|cnt_B|cnt_C|cnt_D|\n", | |
"+---+-----+-----+-----+-----+\n", | |
"| 1| 75| 29| 91| 9|\n", | |
"| 2| 83| 54| 98| 52|\n", | |
"| 3| 53| 53| 54| 49|\n", | |
"| 4| 46| 59| 34| 95|\n", | |
"| 5| 9| 22| 4| 31|\n", | |
"+---+-----+-----+-----+-----+\n", | |
"\n", | |
"+------+-----+-----+-----+-----+\n", | |
"|employ|cnt_A|cnt_B|cnt_C|cnt_D|\n", | |
"+------+-----+-----+-----+-----+\n", | |
"| 0| 34| 24| 24| 24|\n", | |
"| 1| 19| 14| 13| 20|\n", | |
"| 2| 23| 9| 12| 15|\n", | |
"| 3| 18| 12| 5| 15|\n", | |
"| 4| 13| 12| 13| 14|\n", | |
"| 5| 20| 9| 14| 11|\n", | |
"| 6| 11| 12| 10| 11|\n", | |
"| 7| 16| 10| 12| 10|\n", | |
"| 8| 9| 7| 11| 11|\n", | |
"| 9| 7| 11| 11| 10|\n", | |
"+------+-----+-----+-----+-----+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+------+-----+-----+-----+-----+\n", | |
"|retire|cnt_A|cnt_B|cnt_C|cnt_D|\n", | |
"+------+-----+-----+-----+-----+\n", | |
"| 0| 255| 210| 259| 229|\n", | |
"| 1| 11| 7| 22| 7|\n", | |
"+------+-----+-----+-----+-----+\n", | |
"\n", | |
"+------+-----+-----+-----+-----+\n", | |
"|gender|cnt_A|cnt_B|cnt_C|cnt_D|\n", | |
"+------+-----+-----+-----+-----+\n", | |
"| 0| 131| 98| 139| 115|\n", | |
"| 1| 135| 119| 142| 121|\n", | |
"+------+-----+-----+-----+-----+\n", | |
"\n", | |
"+------+-----+-----+-----+-----+\n", | |
"|reside|cnt_A|cnt_B|cnt_C|cnt_D|\n", | |
"+------+-----+-----+-----+-----+\n", | |
"| 1| 121| 76| 106| 72|\n", | |
"| 2| 59| 63| 85| 65|\n", | |
"| 3| 32| 29| 39| 38|\n", | |
"| 4| 30| 31| 31| 28|\n", | |
"| 5| 16| 14| 13| 17|\n", | |
"| 6| 6| 4| 6| 13|\n", | |
"| 7| 2| 0| 0| 2|\n", | |
"| 8| 0| 0| 1| 1|\n", | |
"+------+-----+-----+-----+-----+\n", | |
"\n", | |
"+-------+-----+-----+-----+-----+\n", | |
"|custcat|cnt_A|cnt_B|cnt_C|cnt_D|\n", | |
"+-------+-----+-----+-----+-----+\n", | |
"| A| 266| 0| 0| 0|\n", | |
"| B| 0| 217| 0| 0|\n", | |
"| C| 0| 0| 281| 0|\n", | |
"| D| 0| 0| 0| 236|\n", | |
"+-------+-----+-----+-----+-----+\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"for c in cust_df.columns:\n", | |
" agg_df = (\n", | |
" cust_df.groupBy(c)\n", | |
" .agg(\n", | |
" *[F.count(F.when(F.col('custcat') == category, True)).alias('cnt_'+category) for category in ['A', 'B', 'C', 'D']]\n", | |
" )\n", | |
" .orderBy(c)\n", | |
" )\n", | |
" agg_df.show(10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "c41b74e4", | |
"metadata": { | |
"papermill": { | |
"duration": 0.323555, | |
"end_time": "2022-04-18T16:14:41.647934", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:41.324379", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"## Data preprocessing" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 63, | |
"id": "c4e5c164", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:42.356984Z", | |
"iopub.status.busy": "2022-04-18T16:14:42.355917Z", | |
"iopub.status.idle": "2022-04-18T16:14:42.371044Z", | |
"shell.execute_reply": "2022-04-18T16:14:42.370392Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:42.216590Z" | |
}, | |
"papermill": { | |
"duration": 0.342222, | |
"end_time": "2022-04-18T16:14:42.371188", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:42.028966", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"root\n", | |
" |-- region: integer (nullable = true)\n", | |
" |-- tenure: integer (nullable = true)\n", | |
" |-- age: integer (nullable = true)\n", | |
" |-- income: integer (nullable = true)\n", | |
" |-- marital: integer (nullable = true)\n", | |
" |-- ed: integer (nullable = true)\n", | |
" |-- employ: integer (nullable = true)\n", | |
" |-- retire: integer (nullable = true)\n", | |
" |-- gender: integer (nullable = true)\n", | |
" |-- custcat: string (nullable = true)\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"# Drop 'reside' and 'address' because of their unclear meaning. \n", | |
"cust_df = cust_df.drop('reside', 'address')\n", | |
"cust_df.printSchema()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 64, | |
"id": "dc87b185", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:43.023441Z", | |
"iopub.status.busy": "2022-04-18T16:14:43.022373Z", | |
"iopub.status.idle": "2022-04-18T16:14:43.024767Z", | |
"shell.execute_reply": "2022-04-18T16:14:43.025388Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:42.236951Z" | |
}, | |
"papermill": { | |
"duration": 0.331901, | |
"end_time": "2022-04-18T16:14:43.025565", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:42.693664", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"categorical_cols = ['region', 'marital', 'ed', 'retire', 'gender']\n", | |
"numeric_cols = ['tenure', 'age', 'income', 'employ']" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 65, | |
"id": "7faddeb5", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:43.667000Z", | |
"iopub.status.busy": "2022-04-18T16:14:43.665988Z", | |
"iopub.status.idle": "2022-04-18T16:14:45.153643Z", | |
"shell.execute_reply": "2022-04-18T16:14:45.152666Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:42.243656Z" | |
}, | |
"papermill": { | |
"duration": 1.80945, | |
"end_time": "2022-04-18T16:14:45.153875", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:43.344425", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+------+------+---+------+-------+---+------+------+------+-------+----------+\n", | |
"|region|tenure|age|income|marital| ed|employ|retire|gender|custcat|region_idx|\n", | |
"+------+------+---+------+-------+---+------+------+------+-------+----------+\n", | |
"| 2| 13| 44| 64| 1| 4| 5| 0| 0| A| 1.0|\n", | |
"| 3| 11| 33| 136| 1| 5| 5| 0| 0| D| 0.0|\n", | |
"| 3| 68| 52| 116| 1| 1| 29| 0| 1| C| 0.0|\n", | |
"| 2| 33| 33| 33| 0| 2| 0| 0| 1| A| 1.0|\n", | |
"| 2| 23| 30| 30| 1| 1| 2| 0| 0| C| 1.0|\n", | |
"| 2| 41| 39| 78| 0| 2| 16| 0| 1| C| 1.0|\n", | |
"| 3| 45| 22| 19| 1| 2| 4| 0| 1| B| 0.0|\n", | |
"| 2| 38| 35| 76| 0| 2| 10| 0| 0| D| 1.0|\n", | |
"| 3| 45| 59| 166| 1| 4| 31| 0| 0| C| 0.0|\n", | |
"| 1| 68| 41| 72| 1| 1| 22| 0| 0| B| 2.0|\n", | |
"+------+------+---+------+-------+---+------+------+------+-------+----------+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+-----------+------+----------+----------+-------------+-------------+-------------+-------------+-------------+\n", | |
"|marital_idx|ed_idx|retire_idx|gender_idx| region_vec| marital_vec| ed_vec| retire_vec| gender_vec|\n", | |
"+-----------+------+----------+----------+-------------+-------------+-------------+-------------+-------------+\n", | |
"| 1.0| 1.0| 0.0| 1.0|(3,[1],[1.0])|(2,[1],[1.0])|(5,[1],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"| 1.0| 4.0| 0.0| 1.0|(3,[0],[1.0])|(2,[1],[1.0])|(5,[4],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"| 1.0| 3.0| 0.0| 0.0|(3,[0],[1.0])|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 0.0| 0.0| 0.0| 0.0|(3,[1],[1.0])|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 1.0| 3.0| 0.0| 1.0|(3,[1],[1.0])|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"| 0.0| 0.0| 0.0| 0.0|(3,[1],[1.0])|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 1.0| 0.0| 0.0| 0.0|(3,[0],[1.0])|(2,[1],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 0.0| 0.0| 0.0| 1.0|(3,[1],[1.0])|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"| 1.0| 1.0| 0.0| 1.0|(3,[0],[1.0])|(2,[1],[1.0])|(5,[1],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"| 1.0| 3.0| 0.0| 1.0|(3,[2],[1.0])|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"+-----------+------+----------+----------+-------------+-------------+-------------+-------------+-------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"# One hot encoding\n", | |
"from pyspark.ml.feature import StringIndexer, OneHotEncoder\n", | |
"\n", | |
"\n", | |
"def modify_cols(cols, prefix='', postfix=''):\n", | |
" return [prefix + col + postfix for col in cols]\n", | |
"\n", | |
"indexer = StringIndexer(inputCols=categorical_cols, outputCols=modify_cols(categorical_cols, postfix='_idx'))\n", | |
"indexed_df = indexer.fit(cust_df).transform(cust_df)\n", | |
"\n", | |
"encoder = OneHotEncoder(inputCols=modify_cols(categorical_cols, postfix='_idx'), \n", | |
" outputCols=modify_cols(categorical_cols, postfix='_vec'),\n", | |
" dropLast=False)\n", | |
"encoded_df = encoder.fit(indexed_df).transform(indexed_df)\n", | |
"\n", | |
"show_split(encoded_df, 11, 10)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 66, | |
"id": "c4aa7162", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:45.923678Z", | |
"iopub.status.busy": "2022-04-18T16:14:45.922999Z", | |
"iopub.status.idle": "2022-04-18T16:14:46.929276Z", | |
"shell.execute_reply": "2022-04-18T16:14:46.927533Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:43.638305Z" | |
}, | |
"papermill": { | |
"duration": 1.354128, | |
"end_time": "2022-04-18T16:14:46.929555", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:45.575427", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+------+------+---+------+-------+---+------+------+\n", | |
"|region|tenure|age|income|marital| ed|employ|retire|\n", | |
"+------+------+---+------+-------+---+------+------+\n", | |
"| 2| 13| 44| 64| 1| 4| 5| 0|\n", | |
"| 3| 11| 33| 136| 1| 5| 5| 0|\n", | |
"| 3| 68| 52| 116| 1| 1| 29| 0|\n", | |
"| 2| 33| 33| 33| 0| 2| 0| 0|\n", | |
"| 2| 23| 30| 30| 1| 1| 2| 0|\n", | |
"| 2| 41| 39| 78| 0| 2| 16| 0|\n", | |
"| 3| 45| 22| 19| 1| 2| 4| 0|\n", | |
"| 2| 38| 35| 76| 0| 2| 10| 0|\n", | |
"| 3| 45| 59| 166| 1| 4| 31| 0|\n", | |
"| 1| 68| 41| 72| 1| 1| 22| 0|\n", | |
"+------+------+---+------+-------+---+------+------+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+------+-------+----------+-----------+------+----------+----------+-------------+\n", | |
"|gender|custcat|region_idx|marital_idx|ed_idx|retire_idx|gender_idx| region_vec|\n", | |
"+------+-------+----------+-----------+------+----------+----------+-------------+\n", | |
"| 0| A| 1.0| 1.0| 1.0| 0.0| 1.0|(3,[1],[1.0])|\n", | |
"| 0| D| 0.0| 1.0| 4.0| 0.0| 1.0|(3,[0],[1.0])|\n", | |
"| 1| C| 0.0| 1.0| 3.0| 0.0| 0.0|(3,[0],[1.0])|\n", | |
"| 1| A| 1.0| 0.0| 0.0| 0.0| 0.0|(3,[1],[1.0])|\n", | |
"| 0| C| 1.0| 1.0| 3.0| 0.0| 1.0|(3,[1],[1.0])|\n", | |
"| 1| C| 1.0| 0.0| 0.0| 0.0| 0.0|(3,[1],[1.0])|\n", | |
"| 1| B| 0.0| 1.0| 0.0| 0.0| 0.0|(3,[0],[1.0])|\n", | |
"| 0| D| 1.0| 0.0| 0.0| 0.0| 1.0|(3,[1],[1.0])|\n", | |
"| 0| C| 0.0| 1.0| 1.0| 0.0| 1.0|(3,[0],[1.0])|\n", | |
"| 0| B| 2.0| 1.0| 3.0| 0.0| 1.0|(3,[2],[1.0])|\n", | |
"+------+-------+----------+-----------+------+----------+----------+-------------+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+-------------+-------------+-------------+-------------+--------------------+--------------------+\n", | |
"| marital_vec| ed_vec| retire_vec| gender_vec| numeric| numericScaled|\n", | |
"+-------------+-------------+-------------+-------------+--------------------+--------------------+\n", | |
"|(2,[1],[1.0])|(5,[1],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|[13.0,44.0,64.0,5.0]|[0.16901408450704...|\n", | |
"|(2,[1],[1.0])|(5,[4],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|[11.0,33.0,136.0,...|[0.14084507042253...|\n", | |
"|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|(2,[0],[1.0])|[68.0,52.0,116.0,...|[0.94366197183098...|\n", | |
"|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|(2,[0],[1.0])|[33.0,33.0,33.0,0.0]|[0.45070422535211...|\n", | |
"|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|[23.0,30.0,30.0,2.0]|[0.30985915492957...|\n", | |
"|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|(2,[0],[1.0])|[41.0,39.0,78.0,1...|[0.56338028169014...|\n", | |
"|(2,[1],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|(2,[0],[1.0])|[45.0,22.0,19.0,4.0]|[0.61971830985915...|\n", | |
"|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|[38.0,35.0,76.0,1...|[0.52112676056338...|\n", | |
"|(2,[1],[1.0])|(5,[1],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|[45.0,59.0,166.0,...|[0.61971830985915...|\n", | |
"|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|[68.0,41.0,72.0,2...|[0.94366197183098...|\n", | |
"+-------------+-------------+-------------+-------------+--------------------+--------------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"# Minmax scaling\n", | |
"from pyspark.ml.feature import VectorAssembler, MinMaxScaler\n", | |
"\n", | |
"\n", | |
"numeric_assembler = VectorAssembler(inputCols=numeric_cols, outputCol='numeric')\n", | |
"assembled_df = numeric_assembler.transform(encoded_df)\n", | |
"\n", | |
"scaler = MinMaxScaler(inputCol='numeric', outputCol='numericScaled')\n", | |
"scaled_df = scaler.fit(assembled_df).transform(assembled_df)\n", | |
"show_split(scaled_df, 8, 10)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 67, | |
"id": "5637a50b", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:47.886908Z", | |
"iopub.status.busy": "2022-04-18T16:14:47.884252Z", | |
"iopub.status.idle": "2022-04-18T16:14:48.478609Z", | |
"shell.execute_reply": "2022-04-18T16:14:48.477673Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:44.487799Z" | |
}, | |
"papermill": { | |
"duration": 0.978968, | |
"end_time": "2022-04-18T16:14:48.478819", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:47.499851", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+------+------+---+------+-------+---+------+------+------+-------+\n", | |
"|region|tenure|age|income|marital| ed|employ|retire|gender|custcat|\n", | |
"+------+------+---+------+-------+---+------+------+------+-------+\n", | |
"| 2| 13| 44| 64| 1| 4| 5| 0| 0| A|\n", | |
"| 3| 11| 33| 136| 1| 5| 5| 0| 0| D|\n", | |
"| 3| 68| 52| 116| 1| 1| 29| 0| 1| C|\n", | |
"| 2| 33| 33| 33| 0| 2| 0| 0| 1| A|\n", | |
"| 2| 23| 30| 30| 1| 1| 2| 0| 0| C|\n", | |
"| 2| 41| 39| 78| 0| 2| 16| 0| 1| C|\n", | |
"| 3| 45| 22| 19| 1| 2| 4| 0| 1| B|\n", | |
"| 2| 38| 35| 76| 0| 2| 10| 0| 0| D|\n", | |
"| 3| 45| 59| 166| 1| 4| 31| 0| 0| C|\n", | |
"| 1| 68| 41| 72| 1| 1| 22| 0| 0| B|\n", | |
"+------+------+---+------+-------+---+------+------+------+-------+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+----------+-----------+------+----------+----------+-------------+-------------+-------------+-------------+-------------+\n", | |
"|region_idx|marital_idx|ed_idx|retire_idx|gender_idx| region_vec| marital_vec| ed_vec| retire_vec| gender_vec|\n", | |
"+----------+-----------+------+----------+----------+-------------+-------------+-------------+-------------+-------------+\n", | |
"| 1.0| 1.0| 1.0| 0.0| 1.0|(3,[1],[1.0])|(2,[1],[1.0])|(5,[1],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"| 0.0| 1.0| 4.0| 0.0| 1.0|(3,[0],[1.0])|(2,[1],[1.0])|(5,[4],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"| 0.0| 1.0| 3.0| 0.0| 0.0|(3,[0],[1.0])|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 1.0| 0.0| 0.0| 0.0| 0.0|(3,[1],[1.0])|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 1.0| 1.0| 3.0| 0.0| 1.0|(3,[1],[1.0])|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"| 1.0| 0.0| 0.0| 0.0| 0.0|(3,[1],[1.0])|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 0.0| 1.0| 0.0| 0.0| 0.0|(3,[0],[1.0])|(2,[1],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 1.0| 0.0| 0.0| 0.0| 1.0|(3,[1],[1.0])|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"| 0.0| 1.0| 1.0| 0.0| 1.0|(3,[0],[1.0])|(2,[1],[1.0])|(5,[1],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"| 2.0| 1.0| 3.0| 0.0| 1.0|(3,[2],[1.0])|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"+----------+-----------+------+----------+----------+-------------+-------------+-------------+-------------+-------------+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+--------------------+--------------------+--------------------+\n", | |
"| numeric| numericScaled| feature|\n", | |
"+--------------------+--------------------+--------------------+\n", | |
"|[13.0,44.0,64.0,5.0]|[0.16901408450704...|(18,[1,4,6,10,13,...|\n", | |
"|[11.0,33.0,136.0,...|[0.14084507042253...|(18,[0,4,9,10,13,...|\n", | |
"|[68.0,52.0,116.0,...|[0.94366197183098...|(18,[0,4,8,10,12,...|\n", | |
"|[33.0,33.0,33.0,0.0]|[0.45070422535211...|(18,[1,3,5,10,12,...|\n", | |
"|[23.0,30.0,30.0,2.0]|[0.30985915492957...|(18,[1,4,8,10,13,...|\n", | |
"|[41.0,39.0,78.0,1...|[0.56338028169014...|(18,[1,3,5,10,12,...|\n", | |
"|[45.0,22.0,19.0,4.0]|[0.61971830985915...|(18,[0,4,5,10,12,...|\n", | |
"|[38.0,35.0,76.0,1...|[0.52112676056338...|(18,[1,3,5,10,13,...|\n", | |
"|[45.0,59.0,166.0,...|[0.61971830985915...|(18,[0,4,6,10,13,...|\n", | |
"|[68.0,41.0,72.0,2...|[0.94366197183098...|(18,[2,4,8,10,13,...|\n", | |
"+--------------------+--------------------+--------------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"# Gather essential cols to build the features\n", | |
"feature_cols = ['region_vec', 'marital_vec', 'ed_vec', 'retire_vec', 'gender_vec', 'numericScaled']\n", | |
"features_assembler = VectorAssembler(inputCols=feature_cols, outputCol='feature')\n", | |
"features_df = features_assembler.transform(scaled_df)\n", | |
"show_split(features_df, 10, 10)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 68, | |
"id": "546f2131", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:49.201929Z", | |
"iopub.status.busy": "2022-04-18T16:14:49.198192Z", | |
"iopub.status.idle": "2022-04-18T16:14:49.875920Z", | |
"shell.execute_reply": "2022-04-18T16:14:49.875231Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:45.087771Z" | |
}, | |
"papermill": { | |
"duration": 1.00758, | |
"end_time": "2022-04-18T16:14:49.876088", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:48.868508", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+------+------+---+------+-------+---+------+------+------+-------+\n", | |
"|region|tenure|age|income|marital| ed|employ|retire|gender|custcat|\n", | |
"+------+------+---+------+-------+---+------+------+------+-------+\n", | |
"| 2| 13| 44| 64| 1| 4| 5| 0| 0| A|\n", | |
"| 3| 11| 33| 136| 1| 5| 5| 0| 0| D|\n", | |
"| 3| 68| 52| 116| 1| 1| 29| 0| 1| C|\n", | |
"| 2| 33| 33| 33| 0| 2| 0| 0| 1| A|\n", | |
"| 2| 23| 30| 30| 1| 1| 2| 0| 0| C|\n", | |
"| 2| 41| 39| 78| 0| 2| 16| 0| 1| C|\n", | |
"| 3| 45| 22| 19| 1| 2| 4| 0| 1| B|\n", | |
"| 2| 38| 35| 76| 0| 2| 10| 0| 0| D|\n", | |
"| 3| 45| 59| 166| 1| 4| 31| 0| 0| C|\n", | |
"| 1| 68| 41| 72| 1| 1| 22| 0| 0| B|\n", | |
"+------+------+---+------+-------+---+------+------+------+-------+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+----------+-----------+------+----------+----------+-------------+-------------+-------------+-------------+-------------+\n", | |
"|region_idx|marital_idx|ed_idx|retire_idx|gender_idx| region_vec| marital_vec| ed_vec| retire_vec| gender_vec|\n", | |
"+----------+-----------+------+----------+----------+-------------+-------------+-------------+-------------+-------------+\n", | |
"| 1.0| 1.0| 1.0| 0.0| 1.0|(3,[1],[1.0])|(2,[1],[1.0])|(5,[1],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"| 0.0| 1.0| 4.0| 0.0| 1.0|(3,[0],[1.0])|(2,[1],[1.0])|(5,[4],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"| 0.0| 1.0| 3.0| 0.0| 0.0|(3,[0],[1.0])|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 1.0| 0.0| 0.0| 0.0| 0.0|(3,[1],[1.0])|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 1.0| 1.0| 3.0| 0.0| 1.0|(3,[1],[1.0])|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"| 1.0| 0.0| 0.0| 0.0| 0.0|(3,[1],[1.0])|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 0.0| 1.0| 0.0| 0.0| 0.0|(3,[0],[1.0])|(2,[1],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 1.0| 0.0| 0.0| 0.0| 1.0|(3,[1],[1.0])|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"| 0.0| 1.0| 1.0| 0.0| 1.0|(3,[0],[1.0])|(2,[1],[1.0])|(5,[1],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"| 2.0| 1.0| 3.0| 0.0| 1.0|(3,[2],[1.0])|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|(2,[1],[1.0])|\n", | |
"+----------+-----------+------+----------+----------+-------------+-------------+-------------+-------------+-------------+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+--------------------+--------------------+--------------------+-----------+-------------+\n", | |
"| numeric| numericScaled| feature|custcat_idx| custcat_vec|\n", | |
"+--------------------+--------------------+--------------------+-----------+-------------+\n", | |
"|[13.0,44.0,64.0,5.0]|[0.16901408450704...|(18,[1,4,6,10,13,...| 1.0|(4,[1],[1.0])|\n", | |
"|[11.0,33.0,136.0,...|[0.14084507042253...|(18,[0,4,9,10,13,...| 2.0|(4,[2],[1.0])|\n", | |
"|[68.0,52.0,116.0,...|[0.94366197183098...|(18,[0,4,8,10,12,...| 0.0|(4,[0],[1.0])|\n", | |
"|[33.0,33.0,33.0,0.0]|[0.45070422535211...|(18,[1,3,5,10,12,...| 1.0|(4,[1],[1.0])|\n", | |
"|[23.0,30.0,30.0,2.0]|[0.30985915492957...|(18,[1,4,8,10,13,...| 0.0|(4,[0],[1.0])|\n", | |
"|[41.0,39.0,78.0,1...|[0.56338028169014...|(18,[1,3,5,10,12,...| 0.0|(4,[0],[1.0])|\n", | |
"|[45.0,22.0,19.0,4.0]|[0.61971830985915...|(18,[0,4,5,10,12,...| 3.0|(4,[3],[1.0])|\n", | |
"|[38.0,35.0,76.0,1...|[0.52112676056338...|(18,[1,3,5,10,13,...| 2.0|(4,[2],[1.0])|\n", | |
"|[45.0,59.0,166.0,...|[0.61971830985915...|(18,[0,4,6,10,13,...| 0.0|(4,[0],[1.0])|\n", | |
"|[68.0,41.0,72.0,2...|[0.94366197183098...|(18,[2,4,8,10,13,...| 3.0|(4,[3],[1.0])|\n", | |
"+--------------------+--------------------+--------------------+-----------+-------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"label_indexer = StringIndexer(inputCol='custcat', outputCol='custcat_idx')\n", | |
"train_df = label_indexer.fit(features_df).transform(features_df)\n", | |
"label_encoder = OneHotEncoder(inputCol='custcat_idx', outputCol='custcat_vec', dropLast=False)\n", | |
"train_df = label_encoder.fit(train_df).transform(train_df)\n", | |
"show_split(train_df, 10, 10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "1e5ff53a", | |
"metadata": { | |
"papermill": { | |
"duration": 0.318151, | |
"end_time": "2022-04-18T16:14:50.519819", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:50.201668", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"## Machine learning pipeline" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "e21c0aec", | |
"metadata": { | |
"papermill": { | |
"duration": 0.331986, | |
"end_time": "2022-04-18T16:14:51.177806", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:50.845820", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"We can combine all transformers and estimators into a single pipeline" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 69, | |
"id": "a37657ce", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:51.821500Z", | |
"iopub.status.busy": "2022-04-18T16:14:51.819621Z", | |
"iopub.status.idle": "2022-04-18T16:14:53.353205Z", | |
"shell.execute_reply": "2022-04-18T16:14:53.352302Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:45.775228Z" | |
}, | |
"papermill": { | |
"duration": 1.85841, | |
"end_time": "2022-04-18T16:14:53.353436", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:51.495026", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+------+------+---+------+-------+---+------+------+------+-------+\n", | |
"|region|tenure|age|income|marital| ed|employ|retire|gender|custcat|\n", | |
"+------+------+---+------+-------+---+------+------+------+-------+\n", | |
"| 2| 13| 44| 64| 1| 4| 5| 0| 0| A|\n", | |
"| 3| 11| 33| 136| 1| 5| 5| 0| 0| D|\n", | |
"| 3| 68| 52| 116| 1| 1| 29| 0| 1| C|\n", | |
"| 2| 33| 33| 33| 0| 2| 0| 0| 1| A|\n", | |
"| 2| 23| 30| 30| 1| 1| 2| 0| 0| C|\n", | |
"| 2| 41| 39| 78| 0| 2| 16| 0| 1| C|\n", | |
"| 3| 45| 22| 19| 1| 2| 4| 0| 1| B|\n", | |
"| 2| 38| 35| 76| 0| 2| 10| 0| 0| D|\n", | |
"| 3| 45| 59| 166| 1| 4| 31| 0| 0| C|\n", | |
"| 1| 68| 41| 72| 1| 1| 22| 0| 0| B|\n", | |
"+------+------+---+------+-------+---+------+------+------+-------+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+----------+-----------+------+----------+----------+--------------------+-------------+-------------+-------------+-------------+\n", | |
"|region_idx|marital_idx|ed_idx|retire_idx|gender_idx| numeric| region_vec| marital_vec| ed_vec| retire_vec|\n", | |
"+----------+-----------+------+----------+----------+--------------------+-------------+-------------+-------------+-------------+\n", | |
"| 1.0| 1.0| 1.0| 0.0| 1.0|[13.0,44.0,64.0,5.0]|(3,[1],[1.0])|(2,[1],[1.0])|(5,[1],[1.0])|(2,[0],[1.0])|\n", | |
"| 0.0| 1.0| 4.0| 0.0| 1.0|[11.0,33.0,136.0,...|(3,[0],[1.0])|(2,[1],[1.0])|(5,[4],[1.0])|(2,[0],[1.0])|\n", | |
"| 0.0| 1.0| 3.0| 0.0| 0.0|[68.0,52.0,116.0,...|(3,[0],[1.0])|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|\n", | |
"| 1.0| 0.0| 0.0| 0.0| 0.0|[33.0,33.0,33.0,0.0]|(3,[1],[1.0])|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 1.0| 1.0| 3.0| 0.0| 1.0|[23.0,30.0,30.0,2.0]|(3,[1],[1.0])|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|\n", | |
"| 1.0| 0.0| 0.0| 0.0| 0.0|[41.0,39.0,78.0,1...|(3,[1],[1.0])|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 0.0| 1.0| 0.0| 0.0| 0.0|[45.0,22.0,19.0,4.0]|(3,[0],[1.0])|(2,[1],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 1.0| 0.0| 0.0| 0.0| 1.0|[38.0,35.0,76.0,1...|(3,[1],[1.0])|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 0.0| 1.0| 1.0| 0.0| 1.0|[45.0,59.0,166.0,...|(3,[0],[1.0])|(2,[1],[1.0])|(5,[1],[1.0])|(2,[0],[1.0])|\n", | |
"| 2.0| 1.0| 3.0| 0.0| 1.0|[68.0,41.0,72.0,2...|(3,[2],[1.0])|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|\n", | |
"+----------+-----------+------+----------+----------+--------------------+-------------+-------------+-------------+-------------+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+-------------+--------------------+--------------------+-----------+-------------+\n", | |
"| gender_vec| numericScaled| feature|custcat_idx| custcat_vec|\n", | |
"+-------------+--------------------+--------------------+-----------+-------------+\n", | |
"|(2,[1],[1.0])|[0.16901408450704...|(18,[1,4,6,10,13,...| 1.0|(4,[1],[1.0])|\n", | |
"|(2,[1],[1.0])|[0.14084507042253...|(18,[0,4,9,10,13,...| 2.0|(4,[2],[1.0])|\n", | |
"|(2,[0],[1.0])|[0.94366197183098...|(18,[0,4,8,10,12,...| 0.0|(4,[0],[1.0])|\n", | |
"|(2,[0],[1.0])|[0.45070422535211...|(18,[1,3,5,10,12,...| 1.0|(4,[1],[1.0])|\n", | |
"|(2,[1],[1.0])|[0.30985915492957...|(18,[1,4,8,10,13,...| 0.0|(4,[0],[1.0])|\n", | |
"|(2,[0],[1.0])|[0.56338028169014...|(18,[1,3,5,10,12,...| 0.0|(4,[0],[1.0])|\n", | |
"|(2,[0],[1.0])|[0.61971830985915...|(18,[0,4,5,10,12,...| 3.0|(4,[3],[1.0])|\n", | |
"|(2,[1],[1.0])|[0.52112676056338...|(18,[1,3,5,10,13,...| 2.0|(4,[2],[1.0])|\n", | |
"|(2,[1],[1.0])|[0.61971830985915...|(18,[0,4,6,10,13,...| 0.0|(4,[0],[1.0])|\n", | |
"|(2,[1],[1.0])|[0.94366197183098...|(18,[2,4,8,10,13,...| 3.0|(4,[3],[1.0])|\n", | |
"+-------------+--------------------+--------------------+-----------+-------------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"from pyspark.ml import Pipeline\n", | |
"\n", | |
"ml_pipeline = Pipeline(stages=[indexer, numeric_assembler, encoder, scaler, features_assembler, label_indexer, label_encoder])\n", | |
"model = ml_pipeline.fit(cust_df)\n", | |
"train_df = model.transform(cust_df)\n", | |
"show_split(train_df, 10, 10)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "99e4bc63", | |
"metadata": { | |
"papermill": { | |
"duration": 0.321398, | |
"end_time": "2022-04-18T16:14:54.014785", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:53.693387", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"## Training" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "0721f396", | |
"metadata": { | |
"papermill": { | |
"duration": 0.319905, | |
"end_time": "2022-04-18T16:14:54.656939", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:54.337034", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"Machine learning model is just an estimator in PySpark." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 70, | |
"id": "052d977e", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:14:55.318252Z", | |
"iopub.status.busy": "2022-04-18T16:14:55.317436Z", | |
"iopub.status.idle": "2022-04-18T16:14:59.798448Z", | |
"shell.execute_reply": "2022-04-18T16:14:59.797320Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:47.124457Z" | |
}, | |
"papermill": { | |
"duration": 4.812172, | |
"end_time": "2022-04-18T16:14:59.798793", | |
"exception": false, | |
"start_time": "2022-04-18T16:14:54.986621", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+------+------+---+------+-------+---+------+------+------+-------+\n", | |
"|region|tenure|age|income|marital| ed|employ|retire|gender|custcat|\n", | |
"+------+------+---+------+-------+---+------+------+------+-------+\n", | |
"| 2| 13| 44| 64| 1| 4| 5| 0| 0| A|\n", | |
"| 3| 11| 33| 136| 1| 5| 5| 0| 0| D|\n", | |
"| 3| 68| 52| 116| 1| 1| 29| 0| 1| C|\n", | |
"| 2| 33| 33| 33| 0| 2| 0| 0| 1| A|\n", | |
"| 2| 23| 30| 30| 1| 1| 2| 0| 0| C|\n", | |
"| 2| 41| 39| 78| 0| 2| 16| 0| 1| C|\n", | |
"| 3| 45| 22| 19| 1| 2| 4| 0| 1| B|\n", | |
"| 2| 38| 35| 76| 0| 2| 10| 0| 0| D|\n", | |
"| 3| 45| 59| 166| 1| 4| 31| 0| 0| C|\n", | |
"| 1| 68| 41| 72| 1| 1| 22| 0| 0| B|\n", | |
"+------+------+---+------+-------+---+------+------+------+-------+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+----------+-----------+------+----------+----------+--------------------+-------------+-------------+-------------+-------------+\n", | |
"|region_idx|marital_idx|ed_idx|retire_idx|gender_idx| numeric| region_vec| marital_vec| ed_vec| retire_vec|\n", | |
"+----------+-----------+------+----------+----------+--------------------+-------------+-------------+-------------+-------------+\n", | |
"| 1.0| 1.0| 1.0| 0.0| 1.0|[13.0,44.0,64.0,5.0]|(3,[1],[1.0])|(2,[1],[1.0])|(5,[1],[1.0])|(2,[0],[1.0])|\n", | |
"| 0.0| 1.0| 4.0| 0.0| 1.0|[11.0,33.0,136.0,...|(3,[0],[1.0])|(2,[1],[1.0])|(5,[4],[1.0])|(2,[0],[1.0])|\n", | |
"| 0.0| 1.0| 3.0| 0.0| 0.0|[68.0,52.0,116.0,...|(3,[0],[1.0])|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|\n", | |
"| 1.0| 0.0| 0.0| 0.0| 0.0|[33.0,33.0,33.0,0.0]|(3,[1],[1.0])|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 1.0| 1.0| 3.0| 0.0| 1.0|[23.0,30.0,30.0,2.0]|(3,[1],[1.0])|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|\n", | |
"| 1.0| 0.0| 0.0| 0.0| 0.0|[41.0,39.0,78.0,1...|(3,[1],[1.0])|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 0.0| 1.0| 0.0| 0.0| 0.0|[45.0,22.0,19.0,4.0]|(3,[0],[1.0])|(2,[1],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 1.0| 0.0| 0.0| 0.0| 1.0|[38.0,35.0,76.0,1...|(3,[1],[1.0])|(2,[0],[1.0])|(5,[0],[1.0])|(2,[0],[1.0])|\n", | |
"| 0.0| 1.0| 1.0| 0.0| 1.0|[45.0,59.0,166.0,...|(3,[0],[1.0])|(2,[1],[1.0])|(5,[1],[1.0])|(2,[0],[1.0])|\n", | |
"| 2.0| 1.0| 3.0| 0.0| 1.0|[68.0,41.0,72.0,2...|(3,[2],[1.0])|(2,[1],[1.0])|(5,[3],[1.0])|(2,[0],[1.0])|\n", | |
"+----------+-----------+------+----------+----------+--------------------+-------------+-------------+-------------+-------------+\n", | |
"only showing top 10 rows\n", | |
"\n", | |
"+-------------+--------------------+--------------------+-----------+-------------+--------------------+--------------------+----------+\n", | |
"| gender_vec| numericScaled| feature|custcat_idx| custcat_vec| rawPrediction| probability|prediction|\n", | |
"+-------------+--------------------+--------------------+-----------+-------------+--------------------+--------------------+----------+\n", | |
"|(2,[1],[1.0])|[0.16901408450704...|(18,[1,4,6,10,13,...| 1.0|(4,[1],[1.0])|[2.69883097174239...|[0.13494154858711...| 2.0|\n", | |
"|(2,[1],[1.0])|[0.14084507042253...|(18,[0,4,9,10,13,...| 2.0|(4,[2],[1.0])|[1.25867963894834...|[0.06293398194741...| 2.0|\n", | |
"|(2,[0],[1.0])|[0.94366197183098...|(18,[0,4,8,10,12,...| 0.0|(4,[0],[1.0])|[9.71293038529637...|[0.48564651926481...| 0.0|\n", | |
"|(2,[0],[1.0])|[0.45070422535211...|(18,[1,3,5,10,12,...| 1.0|(4,[1],[1.0])|[5.48212592252259...|[0.27410629612612...| 1.0|\n", | |
"|(2,[1],[1.0])|[0.30985915492957...|(18,[1,4,8,10,13,...| 0.0|(4,[0],[1.0])|[9.14844309573381...|[0.45742215478669...| 0.0|\n", | |
"|(2,[0],[1.0])|[0.56338028169014...|(18,[1,3,5,10,12,...| 0.0|(4,[0],[1.0])|[6.28417748455353...|[0.31420887422767...| 0.0|\n", | |
"|(2,[0],[1.0])|[0.61971830985915...|(18,[0,4,5,10,12,...| 3.0|(4,[3],[1.0])|[5.63792417136418...|[0.28189620856820...| 1.0|\n", | |
"|(2,[1],[1.0])|[0.52112676056338...|(18,[1,3,5,10,13,...| 2.0|(4,[2],[1.0])|[5.34298824100371...|[0.26714941205018...| 1.0|\n", | |
"|(2,[1],[1.0])|[0.61971830985915...|(18,[0,4,6,10,13,...| 0.0|(4,[0],[1.0])|[5.89253225861025...|[0.29462661293051...| 2.0|\n", | |
"|(2,[1],[1.0])|[0.94366197183098...|(18,[2,4,8,10,13,...| 3.0|(4,[3],[1.0])|[9.06482369427615...|[0.45324118471380...| 0.0|\n", | |
"+-------------+--------------------+--------------------+-----------+-------------+--------------------+--------------------+----------+\n", | |
"only showing top 10 rows\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"from pyspark.ml.classification import RandomForestClassifier\n", | |
"\n", | |
"lr = RandomForestClassifier(featuresCol=\"feature\", labelCol=\"custcat_idx\", predictionCol=\"prediction\")\n", | |
"ml_pipeline = Pipeline(stages=[indexer, numeric_assembler, encoder, scaler, features_assembler, \n", | |
" label_indexer, label_encoder, lr])\n", | |
"ml_model = ml_pipeline.fit(cust_df)\n", | |
"result_df = ml_model.transform(cust_df)\n", | |
"show_split(result_df, 10, 10)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 71, | |
"id": "f190a7f3", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:15:00.485043Z", | |
"iopub.status.busy": "2022-04-18T16:15:00.484348Z", | |
"iopub.status.idle": "2022-04-18T16:15:01.563000Z", | |
"shell.execute_reply": "2022-04-18T16:15:01.562449Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:51.446198Z" | |
}, | |
"papermill": { | |
"duration": 1.406319, | |
"end_time": "2022-04-18T16:15:01.563142", | |
"exception": false, | |
"start_time": "2022-04-18T16:15:00.156823", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"image/png": "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\n", | |
"text/plain": [ | |
"<Figure size 1800x900 with 2 Axes>" | |
] | |
}, | |
"metadata": { | |
"needs_background": "light" | |
}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"plot_columns_dist(result_df, ['custcat_idx', 'prediction'], 1, 2).show()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 72, | |
"id": "2b3c2176", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:15:02.247826Z", | |
"iopub.status.busy": "2022-04-18T16:15:02.245614Z", | |
"iopub.status.idle": "2022-04-18T16:15:03.218265Z", | |
"shell.execute_reply": "2022-04-18T16:15:03.217304Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:52.390144Z" | |
}, | |
"papermill": { | |
"duration": 1.308965, | |
"end_time": "2022-04-18T16:15:03.218502", | |
"exception": false, | |
"start_time": "2022-04-18T16:15:01.909537", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+-----------+---+---+---+---+\n", | |
"|custcat_idx|0.0|1.0|2.0|3.0|\n", | |
"+-----------+---+---+---+---+\n", | |
"| 0.0|153| 87| 28| 13|\n", | |
"| 1.0| 48|184| 28| 6|\n", | |
"| 2.0| 54| 53|117| 12|\n", | |
"| 3.0| 67| 50| 46| 54|\n", | |
"+-----------+---+---+---+---+\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"# evaluate\n", | |
"confusion_matrix = result_df.groupBy('custcat_idx').pivot('prediction').count().orderBy('custcat_idx')\n", | |
"confusion_matrix.show()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 73, | |
"id": "baadaeb2", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:15:03.879563Z", | |
"iopub.status.busy": "2022-04-18T16:15:03.878781Z", | |
"iopub.status.idle": "2022-04-18T16:15:04.370169Z", | |
"shell.execute_reply": "2022-04-18T16:15:04.369583Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:53.280888Z" | |
}, | |
"papermill": { | |
"duration": 0.819848, | |
"end_time": "2022-04-18T16:15:04.370329", | |
"exception": false, | |
"start_time": "2022-04-18T16:15:03.550481", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Precision\n", | |
"0.0 0.475155\n", | |
"1.0 0.491979\n", | |
"2.0 0.534247\n", | |
"3.0 0.635294\n", | |
"dtype: float64\n", | |
"Recall\n", | |
"custcat_idx\n", | |
"0.0 0.544484\n", | |
"1.0 0.691729\n", | |
"2.0 0.495763\n", | |
"3.0 0.248848\n", | |
"dtype: float64\n" | |
] | |
} | |
], | |
"source": [ | |
"# We can calculate precision and recall from confusion matrix or directly with pyspark\n", | |
"cmat = confusion_matrix.toPandas()\n", | |
"cmat.index = cmat['custcat_idx']\n", | |
"cmat = cmat.drop('custcat_idx', axis=1)\n", | |
"correct = np.diagonal(cmat)\n", | |
"precision = correct / np.sum(cmat, axis=0)\n", | |
"recall = correct / np.sum(cmat, axis=1)\n", | |
"print('Precision')\n", | |
"print(precision)\n", | |
"print('Recall')\n", | |
"print(recall)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 74, | |
"id": "c3c0cfaf", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:15:05.033112Z", | |
"iopub.status.busy": "2022-04-18T16:15:05.032362Z", | |
"iopub.status.idle": "2022-04-18T16:15:07.135943Z", | |
"shell.execute_reply": "2022-04-18T16:15:07.135082Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:53.781753Z" | |
}, | |
"papermill": { | |
"duration": 2.43491, | |
"end_time": "2022-04-18T16:15:07.136188", | |
"exception": false, | |
"start_time": "2022-04-18T16:15:04.701278", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Precision\n", | |
"0 0.4751552795031056\n", | |
"1 0.4919786096256685\n", | |
"2 0.5342465753424658\n", | |
"3 0.6352941176470588\n", | |
"Recall\n", | |
"0 0.5444839857651246\n", | |
"1 0.6917293233082706\n", | |
"2 0.4957627118644068\n", | |
"3 0.2488479262672811\n" | |
] | |
} | |
], | |
"source": [ | |
"# This code is legacy\n", | |
"from pyspark.ml.evaluation import MulticlassClassificationEvaluator\n", | |
"\n", | |
"evaluator = MulticlassClassificationEvaluator(predictionCol='prediction', labelCol='custcat_idx')\n", | |
"\n", | |
"print('Precision')\n", | |
"for i in range(4):\n", | |
" print(i, evaluator.evaluate(result_df, {evaluator.metricName: \"precisionByLabel\", evaluator.metricLabel: i}))\n", | |
" \n", | |
"print('Recall')\n", | |
"for i in range(4):\n", | |
" print(i, evaluator.evaluate(result_df, {evaluator.metricName: \"recallByLabel\", evaluator.metricLabel: i}))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 75, | |
"id": "8065caf9", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:15:07.823799Z", | |
"iopub.status.busy": "2022-04-18T16:15:07.822971Z", | |
"iopub.status.idle": "2022-04-18T16:15:08.031673Z", | |
"shell.execute_reply": "2022-04-18T16:15:08.030639Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:55.494138Z" | |
}, | |
"papermill": { | |
"duration": 0.542437, | |
"end_time": "2022-04-18T16:15:08.031948", | |
"exception": false, | |
"start_time": "2022-04-18T16:15:07.489511", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"0.49452109250342424" | |
] | |
}, | |
"execution_count": 75, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"evaluator.evaluate(result_df, {evaluator.metricName: \"f1\"})" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "f3aeff89", | |
"metadata": { | |
"papermill": { | |
"duration": 0.331308, | |
"end_time": "2022-04-18T16:15:08.772817", | |
"exception": false, | |
"start_time": "2022-04-18T16:15:08.441509", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"## Validation scheme" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 76, | |
"id": "13fdb1e0", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:15:09.445137Z", | |
"iopub.status.busy": "2022-04-18T16:15:09.444031Z", | |
"iopub.status.idle": "2022-04-18T16:15:09.447250Z", | |
"shell.execute_reply": "2022-04-18T16:15:09.447753Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:55.696320Z" | |
}, | |
"papermill": { | |
"duration": 0.336593, | |
"end_time": "2022-04-18T16:15:09.447959", | |
"exception": false, | |
"start_time": "2022-04-18T16:15:09.111366", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"def accuracy(model, df):\n", | |
" df = model.transform(df)\n", | |
" return evaluator.evaluate(result_df, {evaluator.metricName: \"accuracy\"}) " | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 77, | |
"id": "d07c56aa", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:15:10.141968Z", | |
"iopub.status.busy": "2022-04-18T16:15:10.140947Z", | |
"iopub.status.idle": "2022-04-18T16:15:15.119456Z", | |
"shell.execute_reply": "2022-04-18T16:15:15.118697Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:55.704443Z" | |
}, | |
"papermill": { | |
"duration": 5.316554, | |
"end_time": "2022-04-18T16:15:15.119660", | |
"exception": false, | |
"start_time": "2022-04-18T16:15:09.803106", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"0.508" | |
] | |
}, | |
"execution_count": 77, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"# train test split\n", | |
"train_df, test_df = cust_df.randomSplit([0.7, 0.3], seed=19032000)\n", | |
"model = ml_pipeline.fit(train_df)\n", | |
"accuracy(model, test_df)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 78, | |
"id": "bfe349b3", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:15:15.814787Z", | |
"iopub.status.busy": "2022-04-18T16:15:15.814085Z", | |
"iopub.status.idle": "2022-04-18T16:15:27.219904Z", | |
"shell.execute_reply": "2022-04-18T16:15:27.220768Z", | |
"shell.execute_reply.started": "2022-04-18T14:31:58.017022Z" | |
}, | |
"papermill": { | |
"duration": 11.749372, | |
"end_time": "2022-04-18T16:15:27.221109", | |
"exception": false, | |
"start_time": "2022-04-18T16:15:15.471737", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"[0.3707128486580884]" | |
] | |
}, | |
"execution_count": 78, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"# Cross validation\n", | |
"# We can use cross validation to do grid search with built in params\n", | |
"from pyspark.ml.tuning import CrossValidator, ParamGridBuilder\n", | |
"\n", | |
"params_grid = ParamGridBuilder().build()\n", | |
"evaluator = MulticlassClassificationEvaluator(predictionCol='prediction', labelCol='custcat_idx', metricName=\"accuracy\")\n", | |
"cv = CrossValidator(estimator=ml_pipeline, estimatorParamMaps=params_grid, evaluator=evaluator, numFolds=4, seed=19032000)\n", | |
"\n", | |
"cv_model = cv.fit(cust_df)\n", | |
"cv_model.avgMetrics" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "f5971936", | |
"metadata": { | |
"papermill": { | |
"duration": 0.336217, | |
"end_time": "2022-04-18T16:15:27.894379", | |
"exception": false, | |
"start_time": "2022-04-18T16:15:27.558162", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"## Custom transformer/estimator" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "31738d44", | |
"metadata": { | |
"papermill": { | |
"duration": 0.330387, | |
"end_time": "2022-04-18T16:15:28.561130", | |
"exception": false, | |
"start_time": "2022-04-18T16:15:28.230743", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"Behind transformers and estimators, PySpark has the concept of `Param`/`Params`, self-documenting attributes that govern how a transformer or estimator behaves. When creating custom transformers/estimators, we create their `Param` first, and then use them in `transform()`-/`fit()`-like instance attributes. PySpark provides standard Params for frequent use cases in the `pyspark.ml.param.shared` module.\n", | |
"\n", | |
"Below is example of a custom transformer and estimator with detailed explanation in comment." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 79, | |
"id": "7949f689", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:15:29.237555Z", | |
"iopub.status.busy": "2022-04-18T16:15:29.236489Z", | |
"iopub.status.idle": "2022-04-18T16:15:30.011066Z", | |
"shell.execute_reply": "2022-04-18T16:15:30.011609Z", | |
"shell.execute_reply.started": "2022-04-18T14:32:08.394183Z" | |
}, | |
"papermill": { | |
"duration": 1.116286, | |
"end_time": "2022-04-18T16:15:30.011798", | |
"exception": false, | |
"start_time": "2022-04-18T16:15:28.895512", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+----+----+-----+\n", | |
"| a| b| c|\n", | |
"+----+----+-----+\n", | |
"| 1| 4.0| GFG1|\n", | |
"| 5| 1.2| GFC2|\n", | |
"| 5|6.25| null|\n", | |
"| 8|7.11|CLC22|\n", | |
"|null|0.22| SSv4|\n", | |
"|null|3.48| MNM0|\n", | |
"| 0|null| TNT2|\n", | |
"+----+----+-----+\n", | |
"\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
" \r" | |
] | |
} | |
], | |
"source": [ | |
"# We will use a meaningless and simple data to test our transformer and estimator\n", | |
"temp_df = df = spark.createDataFrame([\n", | |
" (1, 4., 'GFG1'),\n", | |
" (5, 1.2, 'GFC2'),\n", | |
" (5, 6.25, None),\n", | |
" (8, 7.11, 'CLC22'),\n", | |
" (None, 0.22, 'SSv4'),\n", | |
" (None, 3.48, 'MNM0'),\n", | |
" (0, None, 'TNT2'),\n", | |
"], schema='a long, b double, c string')\n", | |
"\n", | |
"temp_df.show()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 80, | |
"id": "2fdfcd8f", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:15:30.702079Z", | |
"iopub.status.busy": "2022-04-18T16:15:30.701177Z", | |
"iopub.status.idle": "2022-04-18T16:15:30.703946Z", | |
"shell.execute_reply": "2022-04-18T16:15:30.704539Z", | |
"shell.execute_reply.started": "2022-04-18T15:39:35.267808Z" | |
}, | |
"papermill": { | |
"duration": 0.356356, | |
"end_time": "2022-04-18T16:15:30.704716", | |
"exception": false, | |
"start_time": "2022-04-18T16:15:30.348360", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"from pyspark.ml import Transformer\n", | |
"from pyspark.ml.param import Param, Params, TypeConverters\n", | |
"from pyspark.ml.param.shared import HasInputCol, HasOutputCol, HasInputCols, HasOutputCols\n", | |
"from pyspark import keyword_only\n", | |
"\n", | |
"\n", | |
"# Commonly used Params are defined in special classes called Mixin under the pyspark.ml.param.shared module.\n", | |
"# A Mixin will define the param and the getter for us.\n", | |
"# We will learn how to define a Mixin later\n", | |
"class ConstantNullFiller(Transformer, HasInputCol, HasOutputCol, HasInputCols, HasOutputCols):\n", | |
" \n", | |
" # A custom param consists of\n", | |
" # - parent, which carries the value of the transformer once the transformer is instantiated.\n", | |
" # Every custom Param we create needs to have Params._dummy() as a parent; this ensures that \n", | |
" # PySpark will be able to copy and change the Params\n", | |
" # - name, which is the name of our Param. By convention, we set it to the same name as our Param.\n", | |
" # - doc, which is the documentation of our Param. This allows us to embed documentation for our Param \n", | |
" # when the transformer will be used.\n", | |
" # - typeConverter, which governs the type of the Param. This provides a standardized way to convert an \n", | |
" # input value to the right type. It also gives a relevant error message if, for example, you expect\n", | |
" # floating-point number, but the user of the transformer provides a string.\n", | |
" filler = Param(Params._dummy(), 'filler', 'Fill null(s) with this value', TypeConverters.toFloat)\n", | |
" \n", | |
" # As the name suggest keyword_only forces keyword arguments\n", | |
" # The keyword_only decorator provides the_input_kwargs attribute containing a dictionary of the arguments \n", | |
" # provided to setParams().\n", | |
" # The params that was not set manually by the programmer will not exist in the dictionary (instead of None)\n", | |
" @keyword_only\n", | |
" def __init__(self, inputCol=None, outputCol=None, inputCols=None, outputCols=None, filler=None):\n", | |
" # Call initialization of parent classes, including Mixin(s)\n", | |
" super(ConstantNullFiller, self).__init__()\n", | |
" # Initialize param. The other params do not need this because they were already initialized in parent classes\n", | |
" self._setDefault(filler=None)\n", | |
" # Set the Params with arguments pass\n", | |
" self.setParams(**self._input_kwargs)\n", | |
" \n", | |
" # Getter of filler Param.\n", | |
" # Other Params do not need getter as they were implemented in parent classes\n", | |
" def getFiller(self):\n", | |
" return self.getOrDefault(self.filler)\n", | |
" \n", | |
" # Based on the design of every PySpark transformer we have used so far, the simplest way to create \n", | |
" # setters is as follows: we first create a general method, setParams(), that allows us to change \n", | |
" # multiple parameters passed as keyword arguments. Then, creating the setter for any other Param \n", | |
" # will simply call setParams() with the relevant keyword argument.\n", | |
" @keyword_only\n", | |
" def setParams(self, inputCol=None, outputCol=None, inputCols=None, outputCols=None, filler=None):\n", | |
" # We finally use the _set() method provided by the Transformer class to update every Params\n", | |
" return self._set(**self._input_kwargs)\n", | |
" \n", | |
" def setFiller(self, value):\n", | |
" return self.setParams(filler=value)\n", | |
" \n", | |
" def setInputCol(self, value):\n", | |
" return self.setParams(inputCol=value)\n", | |
" \n", | |
" def setOutputCol(self, value):\n", | |
" return self.setParams(outputCol=value)\n", | |
" \n", | |
" def setInputCols(self, value):\n", | |
" return self.setParams(inputCols=value)\n", | |
" \n", | |
" def setOutputCols(self, value):\n", | |
" return self.setParams(outputCols=value)\n", | |
" \n", | |
" # Transformer class has implemented transform() method that allows for an optional argument, params, \n", | |
" # in case we want to pass a Param map at transformation time\n", | |
" def _transform(self, dataset):\n", | |
" # Check params\n", | |
" if self.isSet('inputCol') and self.isSet('inputCols'):\n", | |
" raise ValueError('Only \"inputCol\" or \"inputCols\" should be set')\n", | |
" if not (self.isSet('inputCol') or self.isSet('inputCols')):\n", | |
" raise ValueError('At least \"inputCol\" or \"inputCols\" must be set')\n", | |
" if not (self.isSet('outputCol') or self.isSet('outputCols')):\n", | |
" raise ValueError('At least \"outputCol\" or \"outputCols\" must be set')\n", | |
" if self.isSet('inputCols') and (len(self.getInputCols()) != len(self.getOutputCols())):\n", | |
" raise ValueError('Length of \"iputCols\" and \"outputCols\" must be exactly the same')\n", | |
" # If inputCols == outputCols, no need to try to create new column\n", | |
" input_cols = self.getInputCols() if self.isSet('inputCols') else [self.getInputCol()]\n", | |
" output_cols = self.getOutputCols() if self.isSet('outputCols') else [self.getOutputCol()]\n", | |
" for input_col, output_col in zip(input_cols, output_cols):\n", | |
" if input_col == output_col:\n", | |
" continue\n", | |
" dataset = dataset.withColumn(output_col, dataset[input_col])\n", | |
" return dataset.fillna(self.getFiller(), output_cols)\n", | |
" " | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 81, | |
"id": "19aa7888", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:15:31.374166Z", | |
"iopub.status.busy": "2022-04-18T16:15:31.373155Z", | |
"iopub.status.idle": "2022-04-18T16:15:31.566804Z", | |
"shell.execute_reply": "2022-04-18T16:15:31.565919Z", | |
"shell.execute_reply.started": "2022-04-18T15:25:18.758968Z" | |
}, | |
"papermill": { | |
"duration": 0.530875, | |
"end_time": "2022-04-18T16:15:31.567036", | |
"exception": false, | |
"start_time": "2022-04-18T16:15:31.036161", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+---+----+-----+-----+\n", | |
"| a| b| c|b_new|\n", | |
"+---+----+-----+-----+\n", | |
"| 1| 4.0| GFG1| 4.0|\n", | |
"| 5| 1.2| GFC2| 1.2|\n", | |
"| 5|6.25| null| 6.25|\n", | |
"| 8|7.11|CLC22| 7.11|\n", | |
"| 0|0.22| SSv4| 0.22|\n", | |
"| 0|3.48| MNM0| 3.48|\n", | |
"| 0|null| TNT2| 0.0|\n", | |
"+---+----+-----+-----+\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"const_imputer = ConstantNullFiller(filler=0, inputCols=['a', 'b'], outputCols=['a', 'b_new'])\n", | |
"const_imputer.transform(temp_df).show()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 82, | |
"id": "341fc858", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:15:32.309205Z", | |
"iopub.status.busy": "2022-04-18T16:15:32.308160Z", | |
"iopub.status.idle": "2022-04-18T16:15:32.324700Z", | |
"shell.execute_reply": "2022-04-18T16:15:32.325259Z", | |
"shell.execute_reply.started": "2022-04-18T16:04:24.142067Z" | |
}, | |
"papermill": { | |
"duration": 0.361519, | |
"end_time": "2022-04-18T16:15:32.325445", | |
"exception": false, | |
"start_time": "2022-04-18T16:15:31.963926", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [], | |
"source": [ | |
"from pyspark.ml import Model, Estimator\n", | |
"\n", | |
"\n", | |
"# Our own Mixin. Define the params for transformer/estimator/model\n", | |
"# We can modulize this for code reuse. And as we separated params and the main class, we can handle them separately.\n", | |
"class HasStdScorerParams(HasInputCol, HasOutputCol):\n", | |
" \n", | |
" fillNull = Param(Params._dummy(), 'fillNull', 'Fill null(s) with 0', TypeConverters.toBoolean)\n", | |
" \n", | |
" def __init__(self):\n", | |
" super(HasStdScorerParams, self).__init__()\n", | |
" self._setDefault(fillNull=None)\n", | |
" \n", | |
" def getFillNull(self):\n", | |
" return self.getOrDefault(self.fillNull)\n", | |
" \n", | |
" @keyword_only\n", | |
" def setParams(self, inputCol=None, outputCol=None, fillNull=None):\n", | |
" return self._set(**self._input_kwargs)\n", | |
" \n", | |
" def setInputCol(self, value):\n", | |
" return self.setParams(inputCol=value)\n", | |
" \n", | |
" def setOutputCol(self, value):\n", | |
" return self.setParams(outputCol=value)\n", | |
" \n", | |
" def setFillNull(self, value):\n", | |
" return self.setParams(fillNull=value)\n", | |
" \n", | |
"# A model is what an estimator return when we call fit() method\n", | |
"# Just like a Transformer, it must override _transform method\n", | |
"class StdScorerModel(Model, HasStdScorerParams):\n", | |
" \n", | |
" # Custom params that is only stay inside the Model (and not the estimator)\n", | |
" mean = Param(Params._dummy(), 'mean', 'Mean of the column', TypeConverters.toFloat)\n", | |
" stddev = Param(Params._dummy(), 'stddev', 'Standard deviation of the column', TypeConverters.toFloat)\n", | |
" \n", | |
" @keyword_only\n", | |
" def __init__(self, inputCol=None, outputCol=None, fillNull=None, mean=None, stddev=None):\n", | |
" super(StdScorerModel, self).__init__()\n", | |
" self._setDefault(mean=None, stddev=None)\n", | |
" self.setParams(**self._input_kwargs)\n", | |
" \n", | |
" def getMean(self):\n", | |
" return self.getOrDefault(self.mean)\n", | |
" \n", | |
" def getStddev(self):\n", | |
" return self.getOrDefault(self.stddev)\n", | |
" \n", | |
" # setParams method has been implemented in HasStdScorerParams but it did not have enough params\n", | |
" @keyword_only\n", | |
" def setParams(self, inputCol=None, outputCol=None, fillNull=None, mean=None, stddev=None):\n", | |
" return self._set(**self._input_kwargs)\n", | |
" \n", | |
" def setMean(self, value):\n", | |
" return self.setParams(mean=value)\n", | |
" \n", | |
" def setStddev(self, value):\n", | |
" return self.setParams(stddev=value)\n", | |
" \n", | |
" def _transform(self, dataset):\n", | |
" if not (self.isSet('inputCol') and self.isSet('outputCol')):\n", | |
" raise ValueError('Both \"inputCol\" and \"outputCol\" must be set')\n", | |
" input_col = self.getInputCol()\n", | |
" output_col = self.getOutputCol()\n", | |
" dataset = dataset.withColumn(output_col, (dataset[input_col] - self.getMean()) / self.getStddev())\n", | |
" if self.getFillNull():\n", | |
" dataset = dataset.fillna(0, output_col)\n", | |
" return dataset\n", | |
" \n", | |
"class StdScorer(Estimator, HasStdScorerParams):\n", | |
" \n", | |
" @keyword_only\n", | |
" def __init__(self, inputCol=None, outputCol=None, fillNull=None):\n", | |
" super(StdScorer, self).__init__()\n", | |
" self.setParams(**self._input_kwargs)\n", | |
" \n", | |
" # getters and setters have been implemented in HasStdScorerParams\n", | |
" \n", | |
" # In the _fit() method, we will return a configured model\n", | |
" def _fit(self, dataset):\n", | |
" input_col = self.getInputCol()\n", | |
" output_col = self.getOutputCol()\n", | |
" mean, stddev = dataset.agg(F.mean(input_col), F.stddev(input_col)).head()\n", | |
" return StdScorerModel(inputCol=input_col, outputCol=output_col, fillNull=self.getFillNull(), \n", | |
" mean=mean, stddev=stddev) " | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 83, | |
"id": "68d5a3ca", | |
"metadata": { | |
"execution": { | |
"iopub.execute_input": "2022-04-18T16:15:32.994852Z", | |
"iopub.status.busy": "2022-04-18T16:15:32.994188Z", | |
"iopub.status.idle": "2022-04-18T16:15:33.286038Z", | |
"shell.execute_reply": "2022-04-18T16:15:33.285343Z", | |
"shell.execute_reply.started": "2022-04-18T16:04:26.718585Z" | |
}, | |
"papermill": { | |
"duration": 0.62962, | |
"end_time": "2022-04-18T16:15:33.286188", | |
"exception": false, | |
"start_time": "2022-04-18T16:15:32.656568", | |
"status": "completed" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"+----+----+-----+-------------------+\n", | |
"| a| b| c| a_new|\n", | |
"+----+----+-----+-------------------+\n", | |
"| 1| 4.0| GFG1|-0.8559849767220402|\n", | |
"| 5| 1.2| GFC2| 0.3668507043094459|\n", | |
"| 5|6.25| null| 0.3668507043094459|\n", | |
"| 8|7.11|CLC22| 1.2839774650830604|\n", | |
"|null|0.22| SSv4| 0.0|\n", | |
"|null|3.48| MNM0| 0.0|\n", | |
"| 0|null| TNT2|-1.1616938969799118|\n", | |
"+----+----+-----+-------------------+\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"z = StdScorer(inputCol='a', outputCol='a_new', fillNull=True)\n", | |
"zmodel = z.fit(temp_df)\n", | |
"zmodel.transform(temp_df).show()" | |
] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"language": "python", | |
"name": "python3" | |
}, | |
"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.7.12" | |
}, | |
"papermill": { | |
"default_parameters": {}, | |
"duration": 616.666529, | |
"end_time": "2022-04-18T16:15:34.534698", | |
"environment_variables": {}, | |
"exception": null, | |
"input_path": "__notebook__.ipynb", | |
"output_path": "__notebook__.ipynb", | |
"parameters": {}, | |
"start_time": "2022-04-18T16:05:17.868169", | |
"version": "2.3.3" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 5 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment