diff --git a/CHANGELOG.md b/CHANGELOG.md
index bfefa57d2..a15174f7f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
## 0.10.7dev
+* [Feature] Add Spark Connection as a dialect for Jupysql ([#965](https://github.com/ploomber/jupysql/issues/965)) (by [@gilandose](https://github.com/gilandose))
+
## 0.10.6 (2023-12-21)
* [Fix] Fix error when `%sql` includes a query with negative numbers ([#958](https://github.com/ploomber/jupysql/issues/958))
diff --git a/doc/_toc.yml b/doc/_toc.yml
index 2d9850b10..f667e807b 100644
--- a/doc/_toc.yml
+++ b/doc/_toc.yml
@@ -43,6 +43,7 @@ parts:
- file: integrations/duckdb-native
- file: integrations/compatibility
- file: integrations/chdb
+ - file: integrations/spark
- caption: API Reference
chapters:
diff --git a/doc/api/configuration.md b/doc/api/configuration.md
index e2bb114a5..254ea712a 100644
--- a/doc/api/configuration.md
+++ b/doc/api/configuration.md
@@ -234,6 +234,26 @@ value enables the ones from previous values plus new ones:
- `2`: All feedback
- Footer to distinguish pandas/polars data frames from JupySQL's result sets
+## `lazy_execution`
+
+```{versionadded} 0.10.7
+This option only works when connecting to Spark
+```
+
+Default: `False`
+
+Return lazy relation to dataset rather than executing through JupySql.
+
+```{code-cell} ipython3
+%config SqlMagic.lazy_execution = True
+df = %sql SELECT * FROM languages
+```
+
+```{code-cell} ipython3
+%config SqlMagic.lazy_execution = False
+res = %sql SELECT * FROM languages
+```
+
## `named_parameters`
```{versionadded} 0.9
diff --git a/doc/conf.py b/doc/conf.py
index 5e1792154..39080d77d 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -27,6 +27,7 @@
"integrations/oracle.ipynb",
"integrations/snowflake.ipynb",
"integrations/redshift.ipynb",
+ "integrations/spark.ipynb",
]
nb_execution_in_temp = True
nb_execution_show_tb = True
diff --git a/doc/integrations/compatibility.md b/doc/integrations/compatibility.md
index 4e6b36432..d59760a98 100644
--- a/doc/integrations/compatibility.md
+++ b/doc/integrations/compatibility.md
@@ -114,4 +114,20 @@ These table reflects the compatibility status of JupySQL `>=0.7`
- Listing tables with `%sqlcmd tables` ✅
- Listing columns with `%sqlcmd columns` ✅
- Parametrized SQL queries via `{{parameter}}` ✅
-- Interactive SQL queries via `--interact` ✅
\ No newline at end of file
+- Interactive SQL queries via `--interact` ✅
+
+## Spark
+
+- Running queries with `%%sql` ✅
+- CTEs with `%%sql --save NAME` ✅
+- Plotting with `%%sqlplot boxplot` ❓
+- Plotting with `%%sqlplot bar` ✅
+- Plotting with `%%sqlplot pie` ✅
+- Plotting with `%%sqlplot histogram` ✅
+- Plotting with `ggplot` ✅
+- Profiling tables with `%sqlcmd profile` ✅
+- Listing tables with `%sqlcmd tables` ❌
+- Listing columns with `%sqlcmd columns` ❌
+- Parametrized SQL queries via `{{parameter}}` ✅
+- Interactive SQL queries via `--interact` ✅
+- Persisting Dataframes via `--persist` ✅
\ No newline at end of file
diff --git a/doc/integrations/spark.ipynb b/doc/integrations/spark.ipynb
new file mode 100644
index 000000000..4f150500d
--- /dev/null
+++ b/doc/integrations/spark.ipynb
@@ -0,0 +1,1399 @@
+{
+ "cells": [
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Spark\n",
+ "\n",
+ "This tutorial will show you how to get a Spark instance up and running locally to integrate with JupySQL. You can run this in a Jupyter notebook. We'll use [Spark Connect](https://spark.apache.org/docs/latest/api/python/getting_started/quickstart_connect.html) which is the new thin client for Spark"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Pre-requisites\n",
+ "\n",
+ "To run this tutorial, you need to install following Python packages:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Note: you may need to restart the kernel to use updated packages.\n"
+ ]
+ }
+ ],
+ "source": [
+ "%pip install jupysql pyspark==3.4.1 arrow pyarrow==12.0.1 pandas grpcio-status --quiet"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Start Spark instance\n",
+ "\n",
+ "We fetch the official image, create a new database, and user (this will take a few seconds)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "12f699ee8e8e35ab10186f3c39024a7e443691bb4213e56ca3c2e90cd80daf1b\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%bash\n",
+ "docker run -p 15002:15002 -p 4040:4040 -d --name spark wh1isper/sparglim-server"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Our database is running, let's load some data!"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Load sample data\n",
+ "\n",
+ "Now, let's fetch some sample data. We'll be using the [NYC taxi dataset](https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "from pyspark.sql.connect.session import SparkSession\n",
+ "\n",
+ "spark = SparkSession.builder.remote(\"sc://localhost\").getOrCreate()\n",
+ "\n",
+ "df = pd.read_parquet(\n",
+ " \"https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2021-01.parquet\"\n",
+ ")\n",
+ "sparkDf = spark.createDataFrame(df.head(10000))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Set [eagerEval](https://spark.apache.org/docs/latest/api/python/getting_started/quickstart_df.html#Viewing-Data) on to print dataframes, This makes Spark print dataframes eagerly in notebook environments, rather than it's default lazy execution which requires .show() to see the data. In Spark 3.4.1 we need to override, as below, but in 3.5.0 it will print in html. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def __pretty_(self, p, cycle):\n",
+ " self.show(truncate=False)\n",
+ "\n",
+ "\n",
+ "from pyspark.sql.connect.dataframe import DataFrame\n",
+ "\n",
+ "DataFrame._repr_pretty_ = __pretty_\n",
+ "spark.conf.set(\"spark.sql.repl.eagerEval.enabled\", True)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Add dataset to temporary view to allow querying:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "sparkDf.createOrReplaceTempView(\"taxi\")"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Query\n",
+ "\n",
+ "Now, let's start JupySQL, authenticate, and query the data!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The sql extension is already loaded. To reload it, use:\n",
+ " %reload_ext sql\n"
+ ]
+ }
+ ],
+ "source": [
+ "%load_ext sql"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "%sql spark"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```{important}\n",
+ "If the cell above fails, you might have some missing packages. Message us on [Slack](https://ploomber.io/community) and we'll help you!\n",
+ "```"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "List the tables in the database:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Running query in 'SparkSession'"
+ ],
+ "text/plain": [
+ "Running query in 'SparkSession'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ " \n",
+ " \n",
+ " namespace | \n",
+ " viewName | \n",
+ " isTemporary | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | \n",
+ " taxi | \n",
+ " True | \n",
+ "
\n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "+-----------+----------+-------------+\n",
+ "| namespace | viewName | isTemporary |\n",
+ "+-----------+----------+-------------+\n",
+ "| | taxi | True |\n",
+ "+-----------+----------+-------------+"
+ ]
+ },
+ "execution_count": 22,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%sql show views in default"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We can turn on `lazy_spark` to avoid executing spark plan and return a Spark Dataframe"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%config SqlMagic.lazy_execution = True"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Running query in 'SparkSession'"
+ ],
+ "text/plain": [
+ "Running query in 'SparkSession'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "+---------+--------+-----------+\n",
+ "|namespace|viewName|isTemporary|\n",
+ "+---------+--------+-----------+\n",
+ "| |taxi |true |\n",
+ "+---------+--------+-----------+\n",
+ "\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": []
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%sql show views in default"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%config SqlMagic.lazy_execution = False"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "List columns in the taxi table:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Running query in 'SparkSession'"
+ ],
+ "text/plain": [
+ "Running query in 'SparkSession'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "root\n",
+ " |-- VendorID: long (nullable = true)\n",
+ " |-- tpep_pickup_datetime: timestamp (nullable = true)\n",
+ " |-- tpep_dropoff_datetime: timestamp (nullable = true)\n",
+ " |-- passenger_count: double (nullable = true)\n",
+ " |-- trip_distance: double (nullable = true)\n",
+ " |-- RatecodeID: double (nullable = true)\n",
+ " |-- store_and_fwd_flag: string (nullable = true)\n",
+ " |-- PULocationID: long (nullable = true)\n",
+ " |-- DOLocationID: long (nullable = true)\n",
+ " |-- payment_type: long (nullable = true)\n",
+ " |-- fare_amount: double (nullable = true)\n",
+ " |-- extra: double (nullable = true)\n",
+ " |-- mta_tax: double (nullable = true)\n",
+ " |-- tip_amount: double (nullable = true)\n",
+ " |-- tolls_amount: double (nullable = true)\n",
+ " |-- improvement_surcharge: double (nullable = true)\n",
+ " |-- total_amount: double (nullable = true)\n",
+ " |-- congestion_surcharge: double (nullable = true)\n",
+ " |-- airport_fee: double (nullable = true)\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "df = %sql select * from taxi\n",
+ "df.sqlaproxy.dataframe.printSchema()"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Query our data:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Running query in 'SparkSession'"
+ ],
+ "text/plain": [
+ "Running query in 'SparkSession'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " count(1) | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 10000 | \n",
+ "
\n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "+----------+\n",
+ "| count(1) |\n",
+ "+----------+\n",
+ "| 10000 |\n",
+ "+----------+"
+ ]
+ },
+ "execution_count": 26,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT COUNT(*) FROM taxi"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Parameterize queries"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "threshold = 10"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Running query in 'SparkSession'"
+ ],
+ "text/plain": [
+ "Running query in 'SparkSession'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " count(1) | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 9476 | \n",
+ "
\n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "+----------+\n",
+ "| count(1) |\n",
+ "+----------+\n",
+ "| 9476 |\n",
+ "+----------+"
+ ]
+ },
+ "execution_count": 28,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT COUNT(*) FROM taxi\n",
+ "WHERE trip_distance < {{threshold}}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "threshold = 0.5"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Running query in 'SparkSession'"
+ ],
+ "text/plain": [
+ "Running query in 'SparkSession'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " count(1) | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 642 | \n",
+ "
\n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "+----------+\n",
+ "| count(1) |\n",
+ "+----------+\n",
+ "| 642 |\n",
+ "+----------+"
+ ]
+ },
+ "execution_count": 30,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql\n",
+ "SELECT COUNT(*) FROM taxi\n",
+ "WHERE trip_distance < {{threshold}}"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## CTEs"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Running query in 'SparkSession'"
+ ],
+ "text/plain": [
+ "Running query in 'SparkSession'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Skipping execution..."
+ ],
+ "text/plain": [
+ "Skipping execution..."
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "%%sql --save many_passengers --no-execute\n",
+ "SELECT *\n",
+ "FROM taxi\n",
+ "WHERE passenger_count > 3\n",
+ "-- remove top 1% outliers for better visualization\n",
+ "AND trip_distance < 18.93"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Running query in 'SparkSession'"
+ ],
+ "text/plain": [
+ "Running query in 'SparkSession'"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " \n",
+ " min(trip_distance) | \n",
+ " avg(trip_distance) | \n",
+ " max(trip_distance) | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0.0 | \n",
+ " 3.1091381872213963 | \n",
+ " 18.46 | \n",
+ "
\n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "+--------------------+--------------------+--------------------+\n",
+ "| min(trip_distance) | avg(trip_distance) | max(trip_distance) |\n",
+ "+--------------------+--------------------+--------------------+\n",
+ "| 0.0 | 3.1091381872213963 | 18.46 |\n",
+ "+--------------------+--------------------+--------------------+"
+ ]
+ },
+ "execution_count": 32,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%sql --save trip_stats --with many_passengers\n",
+ "SELECT MIN(trip_distance), AVG(trip_distance), MAX(trip_distance)\n",
+ "FROM many_passengers"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This is what JupySQL executes:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {
+ "scrolled": true,
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "WITH `many_passengers` AS (\n",
+ "SELECT *\n",
+ "FROM taxi\n",
+ "WHERE passenger_count > 3\n",
+ "\n",
+ "AND trip_distance < 18.93)\n",
+ "SELECT MIN(trip_distance), AVG(trip_distance), MAX(trip_distance)\n",
+ "FROM many_passengers\n"
+ ]
+ }
+ ],
+ "source": [
+ "query = %sqlcmd snippets trip_stats\n",
+ "print(query)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Profiling"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " Following statistics are not available in\n",
+ " SparkSession: STD, 25%, 50%, 75%
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " VendorID | \n",
+ " tpep_pickup_datetime | \n",
+ " tpep_dropoff_datetime | \n",
+ " passenger_count | \n",
+ " trip_distance | \n",
+ " RatecodeID | \n",
+ " store_and_fwd_flag | \n",
+ " PULocationID | \n",
+ " DOLocationID | \n",
+ " payment_type | \n",
+ " fare_amount | \n",
+ " extra | \n",
+ " mta_tax | \n",
+ " tip_amount | \n",
+ " tolls_amount | \n",
+ " improvement_surcharge | \n",
+ " total_amount | \n",
+ " congestion_surcharge | \n",
+ " airport_fee | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " count | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 10000 | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ " unique | \n",
+ " 2 | \n",
+ " 8766 | \n",
+ " 8745 | \n",
+ " 7 | \n",
+ " 1243 | \n",
+ " 6 | \n",
+ " 2 | \n",
+ " 173 | \n",
+ " 230 | \n",
+ " 4 | \n",
+ " 228 | \n",
+ " 8 | \n",
+ " 3 | \n",
+ " 504 | \n",
+ " 18 | \n",
+ " 3 | \n",
+ " 959 | \n",
+ " 3 | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ " top | \n",
+ " nan | \n",
+ " 2021-01-01 00:41:19 | \n",
+ " 2021-01-02 00:00:00 | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " N | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " None | \n",
+ "
\n",
+ " \n",
+ " freq | \n",
+ " nan | \n",
+ " 4 | \n",
+ " 7 | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " 9808 | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " nan | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ " mean | \n",
+ " 1.6901 | \n",
+ " nan | \n",
+ " nan | \n",
+ " 1.5080 | \n",
+ " 3.1002 | \n",
+ " 1.0712 | \n",
+ " nan | \n",
+ " 158.5551 | \n",
+ " 154.7296 | \n",
+ " 1.3819 | \n",
+ " 11.8822 | \n",
+ " 0.8259 | \n",
+ " 0.4864 | \n",
+ " 1.7846 | \n",
+ " 0.2246 | \n",
+ " 0.2945 | \n",
+ " 16.9696 | \n",
+ " 2.1063 | \n",
+ " nan | \n",
+ "
\n",
+ " \n",
+ " std | \n",
+ " 0.4625 | \n",
+ " nan | \n",
+ " nan | \n",
+ " 1.1354 | \n",
+ " 3.5970 | \n",
+ " 1.0755 | \n",
+ " nan | \n",
+ " 70.9288 | \n",
+ " 75.2504 | \n",
+ " 0.5552 | \n",
+ " 10.8420 | \n",
+ " 1.1167 | \n",
+ " 0.1041 | \n",
+ " 2.4351 | \n",
+ " 1.2730 | \n",
+ " 0.0570 | \n",
+ " 12.5023 | \n",
+ " 0.9562 | \n",
+ " nan | \n",
+ "
\n",
+ " \n",
+ " min | \n",
+ " 1 | \n",
+ " nan | \n",
+ " nan | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 1.0 | \n",
+ " nan | \n",
+ " 1 | \n",
+ " 1 | \n",
+ " 1 | \n",
+ " -100.0 | \n",
+ " -0.5 | \n",
+ " -0.5 | \n",
+ " -1.07 | \n",
+ " -6.12 | \n",
+ " -0.3 | \n",
+ " -100.3 | \n",
+ " -2.5 | \n",
+ " nan | \n",
+ "
\n",
+ " \n",
+ " 25% | \n",
+ " 1.0000 | \n",
+ " nan | \n",
+ " nan | \n",
+ " 1.0000 | \n",
+ " 1.0400 | \n",
+ " 1.0000 | \n",
+ " nan | \n",
+ " 100.0000 | \n",
+ " 83.0000 | \n",
+ " 1.0000 | \n",
+ " 6.0000 | \n",
+ " 0.0000 | \n",
+ " 0.5000 | \n",
+ " 0.0000 | \n",
+ " 0.0000 | \n",
+ " 0.3000 | \n",
+ " 10.3000 | \n",
+ " 2.5000 | \n",
+ " nan | \n",
+ "
\n",
+ " \n",
+ " 50% | \n",
+ " 2.0000 | \n",
+ " nan | \n",
+ " nan | \n",
+ " 1.0000 | \n",
+ " 1.9300 | \n",
+ " 1.0000 | \n",
+ " nan | \n",
+ " 152.0000 | \n",
+ " 151.0000 | \n",
+ " 1.0000 | \n",
+ " 8.5000 | \n",
+ " 0.5000 | \n",
+ " 0.5000 | \n",
+ " 1.5400 | \n",
+ " 0.0000 | \n",
+ " 0.3000 | \n",
+ " 13.5500 | \n",
+ " 2.5000 | \n",
+ " nan | \n",
+ "
\n",
+ " \n",
+ " 75% | \n",
+ " 2.0000 | \n",
+ " nan | \n",
+ " nan | \n",
+ " 2.0000 | \n",
+ " 3.6000 | \n",
+ " 1.0000 | \n",
+ " nan | \n",
+ " 234.0000 | \n",
+ " 234.0000 | \n",
+ " 2.0000 | \n",
+ " 13.5000 | \n",
+ " 2.5000 | \n",
+ " 0.5000 | \n",
+ " 2.6500 | \n",
+ " 0.0000 | \n",
+ " 0.3000 | \n",
+ " 19.3000 | \n",
+ " 2.5000 | \n",
+ " nan | \n",
+ "
\n",
+ " \n",
+ " max | \n",
+ " 2 | \n",
+ " nan | \n",
+ " nan | \n",
+ " 6.0 | \n",
+ " 45.92 | \n",
+ " 99.0 | \n",
+ " nan | \n",
+ " 265 | \n",
+ " 265 | \n",
+ " 4 | \n",
+ " 121.0 | \n",
+ " 3.5 | \n",
+ " 0.5 | \n",
+ " 80.0 | \n",
+ " 25.5 | \n",
+ " 0.3 | \n",
+ " 137.76 | \n",
+ " 2.5 | \n",
+ " nan | \n",
+ "
\n",
+ " \n",
+ "
"
+ ],
+ "text/plain": [
+ "+--------+----------+----------------------+-----------------------+-----------------+---------------+------------+--------------------+--------------+--------------+--------------+-------------+--------+---------+------------+--------------+-----------------------+--------------+----------------------+-------------+\n",
+ "| | VendorID | tpep_pickup_datetime | tpep_dropoff_datetime | passenger_count | trip_distance | RatecodeID | store_and_fwd_flag | PULocationID | DOLocationID | payment_type | fare_amount | extra | mta_tax | tip_amount | tolls_amount | improvement_surcharge | total_amount | congestion_surcharge | airport_fee |\n",
+ "+--------+----------+----------------------+-----------------------+-----------------+---------------+------------+--------------------+--------------+--------------+--------------+-------------+--------+---------+------------+--------------+-----------------------+--------------+----------------------+-------------+\n",
+ "| count | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 10000 | 0 |\n",
+ "| unique | 2 | 8766 | 8745 | 7 | 1243 | 6 | 2 | 173 | 230 | 4 | 228 | 8 | 3 | 504 | 18 | 3 | 959 | 3 | 0 |\n",
+ "| top | nan | 2021-01-01 00:41:19 | 2021-01-02 00:00:00 | nan | nan | nan | N | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | None |\n",
+ "| freq | nan | 4 | 7 | nan | nan | nan | 9808 | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | nan | 0 |\n",
+ "| mean | 1.6901 | nan | nan | 1.5080 | 3.1002 | 1.0712 | nan | 158.5551 | 154.7296 | 1.3819 | 11.8822 | 0.8259 | 0.4864 | 1.7846 | 0.2246 | 0.2945 | 16.9696 | 2.1063 | nan |\n",
+ "| std | 0.4625 | nan | nan | 1.1354 | 3.5970 | 1.0755 | nan | 70.9288 | 75.2504 | 0.5552 | 10.8420 | 1.1167 | 0.1041 | 2.4351 | 1.2730 | 0.0570 | 12.5023 | 0.9562 | nan |\n",
+ "| min | 1 | nan | nan | 0.0 | 0.0 | 1.0 | nan | 1 | 1 | 1 | -100.0 | -0.5 | -0.5 | -1.07 | -6.12 | -0.3 | -100.3 | -2.5 | nan |\n",
+ "| 25% | 1.0000 | nan | nan | 1.0000 | 1.0400 | 1.0000 | nan | 100.0000 | 83.0000 | 1.0000 | 6.0000 | 0.0000 | 0.5000 | 0.0000 | 0.0000 | 0.3000 | 10.3000 | 2.5000 | nan |\n",
+ "| 50% | 2.0000 | nan | nan | 1.0000 | 1.9300 | 1.0000 | nan | 152.0000 | 151.0000 | 1.0000 | 8.5000 | 0.5000 | 0.5000 | 1.5400 | 0.0000 | 0.3000 | 13.5500 | 2.5000 | nan |\n",
+ "| 75% | 2.0000 | nan | nan | 2.0000 | 3.6000 | 1.0000 | nan | 234.0000 | 234.0000 | 2.0000 | 13.5000 | 2.5000 | 0.5000 | 2.6500 | 0.0000 | 0.3000 | 19.3000 | 2.5000 | nan |\n",
+ "| max | 2 | nan | nan | 6.0 | 45.92 | 99.0 | nan | 265 | 265 | 4 | 121.0 | 3.5 | 0.5 | 80.0 | 25.5 | 0.3 | 137.76 | 2.5 | nan |\n",
+ "+--------+----------+----------------------+-----------------------+-----------------+---------------+------------+--------------------+--------------+--------------+--------------+-------------+--------+---------+------------+--------------+-----------------------+--------------+----------------------+-------------+"
+ ]
+ },
+ "execution_count": 34,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%sqlcmd profile -t taxi"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Plotting"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 35,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "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VtOmTTVkyBBt27ZNISEheuedd5Sdna2FCxeWehtt27ZVv3799PrrrysvL0+33367kpOTdfDgwSu+d9GiRXr99dfVu3dvNW7cWCdPntRbb70lu92ue+65R9Lvl4wiIyP1/vvvq2nTpqpdu7Zatmypli1bqkuXLpo5c6bOnTunG264QevWrVNGRsZVzYX0+z1VL7zwgl588UV17txZDz74oHx8fLRt2zaFhYVp+vTpstvtmj9/vgYMGKB27dqpb9++CgoKUmZmplatWqU77rhDr7766lX3UBoTJkzQe++9p549e2rMmDGqXbu2Fi1apIyMDH3wwQclbgivaOXxGb300ktKT09XcnKyatasqdatW2vy5MmaNGmSHnroIfPnAagy3PeAGwDD+P3x7tjYWKNmzZqGJPMR/OJH2i/2CPulHruPjY011q5da7Ru3drw8fExmjdvbixbtqzMPZ05c8YYM2aMUadOHaNGjRrGfffdZxw+fPiKj93v2LHD6Nevn9GwYUPDx8fHCA4ONu69915j+/btLtvfvHmzERUVZXh7e7ts86effjJ69+5tBAYGGgEBAcbDDz9sHDlypMR+L/VY+8XmxTAM45133jFuueUWw8fHx6hVq5bRtWtXIykpyaVmw4YNRkxMjBEQEGD4+voajRs3NgYNGlSi99K43GP3sbGxF33P999/bzz00ENGYGCg4evra9x2223GypUrS/QoqcRneqmflUv1URp/5jNKS0szPD09jdGjR7ts8/z588att95qhIWFGSdOnHDpEXA3m2GU4912ANwmPDxcLVu21MqVK93dCgBcc7iHCAAAWB73EAEWcqUvZ/Tz8+MJIACWRCACLKRevXqXHR84cKASExMrpxkAqEIIRMB1ojTfRJ2UlHTZ8bCwsHLqBgCuLdxUDQAALI+bqgEAgOVxyawUioqKdOTIEdWsWZM/TAgAwDXCMAydPHlSYWFhV/yCUwJRKRw5cqTEX5EGAADXhsOHD6t+/fqXrSEQlULNmjUl/T6hdrvdzd0AAIDScDqdatCggfl7/HIIRKVQfJnMbrcTiAAAuMaU5nYXbqoGAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACWRyACAACW5+nu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FAzD0KhRo7R8+XKtX79eERERLuNRUVHy8vJymd/9+/crMzOT+f0TioqKlJ+fz/yWg+7du2vXrl1KT083l/bt26t///7mv5nj8nXq1Cl9//33qlevXvn+DP+JG79RRS1ZssTw8fExEhMTjb179xrDhw83AgMDjaysLHe3dk06efKk8fXXXxtff/21Icl45ZVXjK+//tr48ccfDcMwjBkzZhiBgYHGRx99ZOzcudN44IEHjIiICOPMmTNu7rzqGzlypBEQEGBs3LjROHr0qLn89ttvZs2IESOMhg0bGuvXrze2b99uOBwOw+FwuLHra8uECROMlJQUIyMjw9i5c6cxYcIEw2azGevWrTMMg/mtCBc+ZWYYzPGf9cwzzxgbN240MjIyjC+//NKIjo426tata+Tk5BiGUX7zSyC6Tv3zn/80GjZsaHh7exu33XabsWXLFne3dM3asGGDIanEMnDgQMMwfn/0/m9/+5sREhJi+Pj4GN27dzf279/v3qavERebV0nGwoULzZozZ84YTz31lFGrVi2jevXqRu/evY2jR4+6r+lrzODBg41GjRoZ3t7eRlBQkNG9e3czDBkG81sR/hiImOM/55FHHjHq1atneHt7GzfccIPxyCOPGAcPHjTHy2t+bYZhGOVwBgsAAOCaxT1EAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEAADA8ghEANwqISFBbdu2rdB93HnnnYqPjzdfh4eHa86cORW6TwDXFgIRgArxxxByKc8++6zL3yGqDNu2bdPw4cNLVUt4AqzB090NALAmwzBUWFgof39/+fv7V+q+g4KCKnV/AKo+zhABKHeDBg1SSkqK5s6dK5vNJpvNpsTERNlsNq1evVpRUVHy8fHRF198UeKS2aBBg9SrVy9NnTpVQUFBstvtGjFihAoKCkq179OnT+vxxx+Xv7+/6tWrp1mzZpWoufCsj2EYSkhIUMOGDeXj46OwsDCNGTNG0u9nuX788UeNHTvWPA5J+vXXX9WvXz/dcMMNql69ulq1aqX33nvPZR933nmnxowZo/Hjx6t27doKDQ1VQkKCS01ubq6efPJJhYSEyNfXVy1bttTKlSvN8S+++EKdO3eWn5+fGjRooDFjxuj06dOlmgcAZUMgAlDu5s6dK4fDoWHDhuno0aM6evSoGjRoIEmaMGGCZsyYoW+//VatW7e+6PuTk5P17bffauPGjXrvvff04YcfaurUqaXa93PPPaeUlBR99NFHWrdunTZu3KgdO3Zcsv6DDz7Q7Nmz9cYbb+jAgQNasWKFWrVqJUn68MMPVb9+fU2bNs08Dkk6e/asoqKitGrVKu3evVvDhw/XgAED9NVXX7lse9GiRapRo4a2bt2qmTNnatq0aUpKSpIkFRUVqWfPnvryyy/17rvvau/evZoxY4aqVasmSfr+++919913q0+fPtq5c6fef/99ffHFFxo1alSp5gFAGZXXX6MFgAv98S9+b9iwwZBkrFixwqVuypQpRps2bczXAwcONGrXrm2cPn3aXDd//nzD39/fKCwsvOw+T548aXh7extLly411/3666+Gn5+fSy+NGjUyZs+ebRiGYcyaNcto2rSpUVBQcNFtXlh7ObGxscYzzzxjvu7atavRqVMnl5pbb73VeP755w3DMIy1a9caHh4exv79+y+6vSFDhhjDhw93Wff5558bHh4expkzZ67YD4Cy4QwRgErVvn37K9a0adNG1atXN187HA6dOnVKhw8fvuz7vv/+exUUFKhDhw7mutq1a6tZs2aXfM/DDz+sM2fO6MYbb9SwYcO0fPlynT9//rL7KSws1IsvvqhWrVqpdu3a8vf319q1a5WZmelS98czYPXq1VNOTo4kKT09XfXr11fTpk0vuo9vvvlGiYmJ5j1W/v7+iomJUVFRkTIyMi7bH4Cy46ZqAJWqRo0a7m7BRYMGDbR//3599tlnSkpK0lNPPaWXX35ZKSkp8vLyuuh7Xn75Zc2dO1dz5sxRq1atVKNGDcXHx5e4z+mP77fZbCoqKpIk+fn5XbavU6dO6cknnzTvZ7pQw4YNy3KIAEqBQASgQnh7e6uwsPCq3vvNN9/ozJkzZmjYsmWL/P39zfuQLqVx48by8vLS1q1bzdBw4sQJfffdd+ratesl3+fn56f77rtP9913n+Li4tS8eXPt2rVL7dq1u+hxfPnll3rggQf02GOPSfr9fqDvvvtOkZGRpT7G1q1b66efftJ333130bNE7dq10969e9WkSZNSbxPA1eOSGYAKER4erq1bt+rQoUP65ZdfzDMjpVFQUKAhQ4Zo7969+vTTTzVlyhSNGjVKHh6X/0+Wv7+/hgwZoueee07r16/X7t27NWjQoMu+LzExUf/617+0e/du/fDDD3r33Xfl5+enRo0amcexadMm/fzzz/rll18kSTfddJOSkpK0efNmffvtt3ryySeVnZ1d6uOTpK5du6pLly7q06ePkpKSlJGRodWrV2vNmjWSpOeff16bN2/WqFGjlJ6ergMHDuijjz7ipmqgghCIAFSIZ599VtWqVVNkZKSCgoJK3F9zOd27d9dNN92kLl266JFHHtH9999f4pH1S3n55ZfVuXNn3XfffYqOjlanTp0UFRV1yfrAwEC99dZbuuOOO9S6dWt99tln+uSTT1SnTh1J0rRp03To0CE1btzY/P6iSZMmqV27doqJidGdd96p0NBQ9erVq9THV+yDDz7Qrbfeqn79+ikyMlLjx483z0a1bt1aKSkp+u6779S5c2fdcsstmjx5ssLCwsq8HwBXZjMMw3B3EwBQbNCgQcrNzdWKFSvc3QoAC+EMEQAAsDwCEYBrRmZmpstj6H9cynJZDgAuxCUzANeM8+fP69ChQ5ccDw8Pl6cnD88CKDsCEQAAsDwumQEAAMsjEAEAAMsjEAEAAMsjEAEAAMsjEAEAAMsjEAEAAMsjEAEAAMsjEAEAAMv7f+dZjQYV06HwAAAAAElFTkSuQmCC",
+ "text/plain": [
+ "