Skip to content

Instantly share code, notes, and snippets.

@xhochy
Created August 20, 2019 17:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xhochy/622a9d0ee55c05c37adad6a9bddb8eed to your computer and use it in GitHub Desktop.
Save xhochy/622a9d0ee55c05c37adad6a9bddb8eed to your computer and use it in GitHub Desktop.
explore-nyc-taxi.ipynb
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# New York Taxi Trip Dataset\n",
"\n",
"The [New York City Taxi & Limousine Commission Trip Record Data](https://www1.nyc.gov/site/tlc/about/tlc-trip-record-data.page) is a really nice dataset to get started with Data Engineering or teaching it. It has several nice properties that make it quite useful that we will show in this notebook. We will look at this data using only `pandas`, not introducing any other tooling."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import os"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"%load_ext lab_black"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To better handle the data, we convert one of the files to Apache Parquet. This enables us to get started much faster when starting the notebook a second time. For a more in-depth reasoning on why to use Parquet, I've made [a write-up on efficient DataFrame storage at one of my former employments](https://tech.jda.com/efficient-dataframe-storage-with-apache-parquet/)."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"if not os.path.exists(\"yellow_tripdata_2016-01.parquet\"):\n",
" df = pd.read_csv(\n",
" \"../data/yellow_tripdata_2016-01.csv\",\n",
" dtype={\"store_and_fwd_flag\": \"bool\"},\n",
" parse_dates=[\"tpep_pickup_datetime\", \"tpep_dropoff_datetime\"],\n",
" index_col=False,\n",
" infer_datetime_format=True,\n",
" true_values=[\"Y\"],\n",
" false_values=[\"N\"],\n",
" )\n",
" df.to_parquet(\"yellow_tripdata_2016-01.parquet\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"While a single month of the data still fits easily into the main memory of a laptop, the whole dataset is so large that you won't be able to fit it into main memory of a consumer device. As you can see below, just the yellow cabs from 2009 until today need 225 GB of storage in CSV and the whole dataset using all available vehicle types add up to 267 GB.\n",
"\n",
"This makes it very well suited to use it in excerises where you don't want someone to be able to compute the result easily in main memory using just `pandas` but actually show the needs for additional tooling."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"While the size is already a nice property, we can have a look at the data itself. For that, we load a single month into memory and have a brief look into it with `pandas`."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"df = pd.read_parquet(\"yellow_tripdata_2016-01.parquet\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The first nice property one doesn't realise anymore is that the data fits into a table and thus is well-suited for a `pandas.DataFrame`. Other types of data, e.g. emails can surely be loaded into a `DataFrame` but is not as straight forward as the taxi data. While we may want to also look at how to transform such data into tabular format, we slowly want to introduce people to it. By using a dataset that is already tabular, we can gradually introduce new concepts instead of requiring them before we even have the data in `pandas`."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>VendorID</th>\n",
" <th>tpep_pickup_datetime</th>\n",
" <th>tpep_dropoff_datetime</th>\n",
" <th>passenger_count</th>\n",
" <th>trip_distance</th>\n",
" <th>pickup_longitude</th>\n",
" <th>pickup_latitude</th>\n",
" <th>RatecodeID</th>\n",
" <th>store_and_fwd_flag</th>\n",
" <th>dropoff_longitude</th>\n",
" <th>dropoff_latitude</th>\n",
" <th>payment_type</th>\n",
" <th>fare_amount</th>\n",
" <th>extra</th>\n",
" <th>mta_tax</th>\n",
" <th>tip_amount</th>\n",
" <th>tolls_amount</th>\n",
" <th>improvement_surcharge</th>\n",
" <th>total_amount</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>2</td>\n",
" <td>2016-01-01</td>\n",
" <td>2016-01-01</td>\n",
" <td>2</td>\n",
" <td>1.10</td>\n",
" <td>-73.990372</td>\n",
" <td>40.734695</td>\n",
" <td>1</td>\n",
" <td>False</td>\n",
" <td>-73.981842</td>\n",
" <td>40.732407</td>\n",
" <td>2</td>\n",
" <td>7.5</td>\n",
" <td>0.5</td>\n",
" <td>0.5</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.3</td>\n",
" <td>8.8</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2</td>\n",
" <td>2016-01-01</td>\n",
" <td>2016-01-01</td>\n",
" <td>5</td>\n",
" <td>4.90</td>\n",
" <td>-73.980782</td>\n",
" <td>40.729912</td>\n",
" <td>1</td>\n",
" <td>False</td>\n",
" <td>-73.944473</td>\n",
" <td>40.716679</td>\n",
" <td>1</td>\n",
" <td>18.0</td>\n",
" <td>0.5</td>\n",
" <td>0.5</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.3</td>\n",
" <td>19.3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>2</td>\n",
" <td>2016-01-01</td>\n",
" <td>2016-01-01</td>\n",
" <td>1</td>\n",
" <td>10.54</td>\n",
" <td>-73.984550</td>\n",
" <td>40.679565</td>\n",
" <td>1</td>\n",
" <td>False</td>\n",
" <td>-73.950272</td>\n",
" <td>40.788925</td>\n",
" <td>1</td>\n",
" <td>33.0</td>\n",
" <td>0.5</td>\n",
" <td>0.5</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.3</td>\n",
" <td>34.3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>2</td>\n",
" <td>2016-01-01</td>\n",
" <td>2016-01-01</td>\n",
" <td>1</td>\n",
" <td>4.75</td>\n",
" <td>-73.993469</td>\n",
" <td>40.718990</td>\n",
" <td>1</td>\n",
" <td>False</td>\n",
" <td>-73.962242</td>\n",
" <td>40.657333</td>\n",
" <td>2</td>\n",
" <td>16.5</td>\n",
" <td>0.0</td>\n",
" <td>0.5</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.3</td>\n",
" <td>17.3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>2</td>\n",
" <td>2016-01-01</td>\n",
" <td>2016-01-01</td>\n",
" <td>3</td>\n",
" <td>1.76</td>\n",
" <td>-73.960625</td>\n",
" <td>40.781330</td>\n",
" <td>1</td>\n",
" <td>False</td>\n",
" <td>-73.977264</td>\n",
" <td>40.758514</td>\n",
" <td>2</td>\n",
" <td>8.0</td>\n",
" <td>0.0</td>\n",
" <td>0.5</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.3</td>\n",
" <td>8.8</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" VendorID tpep_pickup_datetime tpep_dropoff_datetime passenger_count \\\n",
"0 2 2016-01-01 2016-01-01 2 \n",
"1 2 2016-01-01 2016-01-01 5 \n",
"2 2 2016-01-01 2016-01-01 1 \n",
"3 2 2016-01-01 2016-01-01 1 \n",
"4 2 2016-01-01 2016-01-01 3 \n",
"\n",
" trip_distance pickup_longitude pickup_latitude RatecodeID \\\n",
"0 1.10 -73.990372 40.734695 1 \n",
"1 4.90 -73.980782 40.729912 1 \n",
"2 10.54 -73.984550 40.679565 1 \n",
"3 4.75 -73.993469 40.718990 1 \n",
"4 1.76 -73.960625 40.781330 1 \n",
"\n",
" store_and_fwd_flag dropoff_longitude dropoff_latitude payment_type \\\n",
"0 False -73.981842 40.732407 2 \n",
"1 False -73.944473 40.716679 1 \n",
"2 False -73.950272 40.788925 1 \n",
"3 False -73.962242 40.657333 2 \n",
"4 False -73.977264 40.758514 2 \n",
"\n",
" fare_amount extra mta_tax tip_amount tolls_amount \\\n",
"0 7.5 0.5 0.5 0.0 0.0 \n",
"1 18.0 0.5 0.5 0.0 0.0 \n",
"2 33.0 0.5 0.5 0.0 0.0 \n",
"3 16.5 0.0 0.5 0.0 0.0 \n",
"4 8.0 0.0 0.5 0.0 0.0 \n",
"\n",
" improvement_surcharge total_amount \n",
"0 0.3 8.8 \n",
"1 0.3 19.3 \n",
"2 0.3 34.3 \n",
"3 0.3 17.3 \n",
"4 0.3 8.8 "
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"About half of all columns are floating point but we also have integers, booleans and even datetime types. This gives us a wide range of types we can work on but still omits the types where the handling with Pandas isn't as good as with those included. For the types in the dataset, `pandas` and `numpy` provide highly optimized routines and we can use their plain-and-simple APIs and get decent performance. This is really good to start with the basics and not need to dive into more low-level techniques to efficiently handle the data eventhough one has a datatype that is not supported by them.\n",
"\n",
"Strings would be a typical datatype that will occur quite often in real-life datasets but currently `pandas` doesn't have good native support for it. It is represented as `object` datatype which basically means that all routines on it fall back to pure Python and cannot make use of the highly optimised `numpy` algorithms."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<class 'pandas.core.frame.DataFrame'>\n",
"RangeIndex: 10906858 entries, 0 to 10906857\n",
"Data columns (total 19 columns):\n",
"VendorID int64\n",
"tpep_pickup_datetime datetime64[ns]\n",
"tpep_dropoff_datetime datetime64[ns]\n",
"passenger_count int64\n",
"trip_distance float64\n",
"pickup_longitude float64\n",
"pickup_latitude float64\n",
"RatecodeID int64\n",
"store_and_fwd_flag bool\n",
"dropoff_longitude float64\n",
"dropoff_latitude float64\n",
"payment_type int64\n",
"fare_amount float64\n",
"extra float64\n",
"mta_tax float64\n",
"tip_amount float64\n",
"tolls_amount float64\n",
"improvement_surcharge float64\n",
"total_amount float64\n",
"dtypes: bool(1), datetime64[ns](2), float64(12), int64(4)\n",
"memory usage: 1.5 GB\n"
]
}
],
"source": [
"df.info()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Looking at the different values a column has, we see that we have some high entropy columns, some medium entropy and some low entropy columns. This gives us a good mix of column values for aggregation operations."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 2h 5min 24s, sys: 41.9 s, total: 2h 6min 6s\n",
"Wall time: 2h 6min 9s\n"
]
},
{
"data": {
"text/plain": [
"VendorID 2\n",
"tpep_pickup_datetime 2368616\n",
"tpep_dropoff_datetime 2372528\n",
"passenger_count 10\n",
"trip_distance 4513\n",
"pickup_longitude 35075\n",
"pickup_latitude 62184\n",
"RatecodeID 7\n",
"store_and_fwd_flag 2\n",
"dropoff_longitude 53813\n",
"dropoff_latitude 87358\n",
"payment_type 5\n",
"fare_amount 1878\n",
"extra 35\n",
"mta_tax 16\n",
"tip_amount 3551\n",
"tolls_amount 940\n",
"improvement_surcharge 7\n",
"total_amount 11166\n",
"dtype: int64"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"# This takes quite some time to compute as the high entropy columns have expensive set operations.\n",
"df.nunique()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The data is even so dense that we can also use it to do simple streaming application tests. With on average 4 reqs/s, this doesn't relate to real streaming applications but already gives a good start for the engineering challenges of such applications. And as the dataset is quite large, one can increase the pressure in streaming situations by supplying e.g. data for 10s in the actual timespan of only 1s. This increases the load tenfold while still being able to test your streaming application for a year under this load."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 1.36 s, sys: 363 ms, total: 1.72 s\n",
"Wall time: 1.84 s\n"
]
},
{
"data": {
"text/plain": [
"(0.0022222222222222222,\n",
" 4.072154271206691,\n",
" 4.694444444444445,\n",
" 7.919722222222222)"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"events_per_minute = (\n",
" df.iloc[:, 0]\n",
" .groupby(\n",
" by=[df[\"tpep_pickup_datetime\"].dt.dayofyear, df[\"tpep_pickup_datetime\"].dt.hour]\n",
" )\n",
" .count()\n",
" / 60\n",
" / 60\n",
")\n",
"events_per_minute.min(), events_per_minute.mean(), events_per_minute.median(), events_per_minute.max()"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 104 ms, sys: 50.6 ms, total: 155 ms\n",
"Wall time: 229 ms\n"
]
},
{
"data": {
"text/plain": [
"<matplotlib.axes._subplots.AxesSubplot at 0x127928860>"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"events_per_minute.hist()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And finally, we can also use the data to do the most basic form of machine learning: linear regression. We can train a simple estimator that takes the trip distance and estimates the price."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<matplotlib.axes._subplots.AxesSubplot at 0x127bb7320>"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAYYAAAEHCAYAAACqbOGYAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAgAElEQVR4nO3de5wcZZ3v8c+vey4Zch0mIbdJDJcETFiMMifoiYsBVFhFUFk4qLtmPexGPbiw3kjQXQF94UF0UY/oelh1N6wgRrKSrLoolyiiGEg0CUm4zXLLJCEJYyCZMOnMTP/2j64euma6e2oy3dM109/36zWv7n66Lr+u7qlf1fM89ZS5OyIiIlmJSgcgIiLxosQgIiIhSgwiIhKixCAiIiFKDCIiElJT6QCGavLkyT5nzpxKhyEiMqJs3LjxRXefku+9EZ8Y5syZw4YNGyodhojIiGJmzxV6T1VJIiISosQgIiIhSgwiIhKixCAiIiFKDCIiElL2xGBmz5rZo2a2ycw2BGXHmtk9ZvZU8NiYM/3VZtZqZk+Y2bnljK29I8XmHS/R3pEq52pEREaU4equepa7v5jzegVwn7vfYGYrgtfLzWw+cCmwAJgB3Gtm89y9p9QBrdm0k+Wrt1CbSNCVTnPjRadxwcKZpV6NiMiIU6mqpAuBlcHzlcC7c8rvcPeUuz8DtAKLSr3y9o4Uy1dv4XBXmoOpbg53pblq9RadOYiIMDyJwYFfmNlGM1sWlE11990AweNxQflMYEfOvG1BWYiZLTOzDWa2Yd++fYMOqG1/J7WJ8EevTSRo29856GWJiIw2w1GVtNjdd5nZccA9ZvZ4kWktT1m/Owm5+y3ALQAtLS2DvtNQc2MDXel0qKwrnaa5sWGwixIRGXXKfsbg7ruCx73Aj8lUDe0xs+kAwePeYPI2YFbO7M3ArlLH1DSunhsvOo0xtQnG19cwpjbBjRedRtO4+lKvSkRkxCnrGYOZjQUS7n4weP524PPAWmApcEPwuCaYZS1wu5ndRKbxeS7wcDliu2DhTBafNJm2/Z00NzYoKYiIBMpdlTQV+LGZZdd1u7vfbWaPAKvM7DLgeeBiAHffZmargO1AN3B5OXokZTWNq1dCEBHpo6yJwd2fBl6Xp7wdOKfAPNcD15czLhERKUxXPouISIgSg4iIhCgxiIhIiBKDiIiEKDGIiEiIEoOIiIQoMYiISIgSg4iIhCgxiIhIiBKDiIiEKDGIiEiIEoOIiIQoMYiISIgSg4iIhCgxiIhIiBKDiIiEKDGIiEiIEoOIiIQoMYiISIgSg4iIhCgxiIhIiBKDiIiEKDGIiEiIEoOIiIQoMYiISIgSg4iIhCgxiIhIiBKDiIiEKDGIiEiIEoOIiIQMS2Iws6SZ/cHMfhK8PtbM7jGzp4LHxpxprzazVjN7wszOHY74RETkVcN1xnAl8FjO6xXAfe4+F7gveI2ZzQcuBRYA5wHfMrPkMMUoIiIMQ2Iws2bgncB3coovBFYGz1cC784pv8PdU+7+DNAKLCp3jCIi8qrhOGP4GnAVkM4pm+ruuwGCx+OC8pnAjpzp2oKyEDNbZmYbzGzDvn37yhO1iEiVKmtiMLPzgb3uvjHqLHnKvF+B+y3u3uLuLVOmTBlSjCIiElZT5uUvBi4ws3cAY4AJZvZ9YI+ZTXf33WY2HdgbTN8GzMqZvxnYVeYYRUQkR1nPGNz9andvdvc5ZBqV73f3vwDWAkuDyZYCa4Lna4FLzazezI4H5gIPlzNGEREJK/cZQyE3AKvM7DLgeeBiAHffZmargO1AN3C5u/dUKEYRkapk7v2q8EeUlpYW37BhQ6XDEBEZUcxso7u35HtPVz6LiEhIVSeG9o4Um3e8RHtHqtKhiIjERqXaGCpuzaadLF+9hdpEgq50mhsvOo0LFva7ZEJEpOpU5RlDe0eK5au3cLgrzcFUN4e70ly1eovOHEREqNLE0La/k9pE+KPXJhK07e+sUEQiIvFRlYmhubGBrnQ6VNaVTtPc2FChiERE4qMqE0PTuHpuvOg0xtQmGF9fw5jaBDdedBpN4+orHZqISMVVbePzBQtnsvikybTt76S5sUFJQUQkULWJATJnDkoIIiJhVVmVJCIihSkxiIhIiBKDiIiEKDGIiEiIEoOIiIQoMYiISIgSg4iIhCgxiIhIiBKDiIiEKDGIiEiIEoOIiIQoMYiISIgSg4iIhCgxiIhIiBKDiIiEKDGIiEhIpMRgZsdHKRMRkZEv6hnD6jxld5YyEBERiYeit/Y0s1OABcBEM3tvzlsTgDHlDExERCpjoHs+nwycD0wC3pVTfhD4m3IFJSIilVM0Mbj7GmCNmb3J3R8apphERKSCBjpjyGo1s88Ac3Lncff/XWwmMxsDPADUB/Pd6e7XmNmxwA+D5T0LXOLu+4N5rgYuA3qAK9z954P4PCIiMkRRE8Ma4NfAvWR22FGlgLPdvcPMaoEHzew/gfcC97n7DWa2AlgBLDez+cClZNo1ZgD3mtk8dx/MOkVEZAiiJoZj3H35YBfu7g50BC9rgz8HLgSWBOUrgV8Cy4PyO9w9BTxjZq3AIkDVWCIiwyRqd9WfmNk7jmYFZpY0s03AXuAed18PTHX33QDB43HB5DOBHTmztwVlIiIyTKImhivJJIdOMztgZgfN7ECUGd29x90XAs3AIjM7tcjklm8R/SYyW2ZmG8xsw759+yJ9ABERiSZSYnD38e6ecPcGd58QvJ4wmBW5+0tkqozOA/aY2XSA4HFvMFkbMCtntmZgV55l3eLuLe7eMmXKlMGEISIiA4g6JMaZ+f4izDfFzCYFzxuAtwKPA2uBpcFkS8k0bhOUX2pm9cGQG3OBhwf3kUREZCiiNj5/Ouf5GDINwhuBsweYbzqw0sySZJLQKnf/iZk9BKwys8uA54GLAdx9m5mtArYD3cDl6pEkIjK8IiUGd8+96hkzmwXcGGG+LcDr85S3A+cUmOd64PoocYmISOkd7bDbbUCxRmQRERmhIp0xmNk3eLV3UAJYCGwuV1AiIlI5UdsYNuQ87wZ+4O6/KUM8IiJSYVHbGFaaWR0wLyh6onwhiYhIJUWtSlpCZuiKZ8lchDbLzJa6+wPlC01ERCohalXSPwJvd/cnAMxsHvAD4PRyBSYiIpURtVdSbTYpALj7k2QGxBMRkVEmcuOzmX0X+Lfg9QfIXOAmIiKjTNTE8FHgcuAKMm0MDwDfKldQIiJSOVF7JaWAm4I/EREZxaIOone+mf3BzP442GG3RURkZIlalfQ1MrfjfDS4K5uIiIxSUXsl7QC2KimIiIx+Uc8YrgJ+Zma/AlLZQndXm4OIyCgTNTFcD3SQuRdDXfnCERGRSouaGI5197eXNRIREYmFqG0M95qZEoOISBWImhguB+42s051VxURGd2iXuA2vtyBiIhIPERtY8DMGoG5ZBqgAdCw2yIio0/U+zH8NXAl0AxsAt4IPAScXb7QRESkEqK2MVwJ/A/gOXc/C3g9sK9sUYmISMVETQyH3f0wgJnVu/vjwMnlC0tERColahtDm5lNAu4C7jGz/cCu8oUlIiKVErVX0nuCp9ea2TpgInB39n0za3T3/WWIT0REhlnkXklZ7v6rPMX3AW8YejgiIlJpUdsYBmIlWo6IiFRYqRKDhuMWERklSpUYRERklFBVkoiIhERODGb2ZjP7UPB8ipkdn/P2OSWPTEREKiJSYjCza4DlwNVBUS3w/ez77v7HAvPNMrN1ZvaYmW0zsyuD8mPN7B4zeyp4bMyZ52ozazWzJ8zs3KP9YCIicnSinjG8B7gAOATg7ruAKCOudgOfdPfXkhlf6XIzmw+sAO5z97lkurquAAjeuxRYAJwHfMvMktE/joiIDFXUxHDE3Z2g95GZjY0yk7vvdvffB88PAo8BM4ELgZXBZCuBdwfPLwTucPeUuz8DtAKLIsYoIiIlEDUxrDKz/w9MMrO/Ae4F/nkwKzKzOWQG31sPTHX33ZBJHsBxwWQzgR05s7UFZX2XtczMNpjZhn37NJafiEgpRR0S4ytm9jbgAJnB8z7n7vdEXYmZjQNWA3/n7gfMCnZiyvdGv2sk3P0W4BaAlpYWXUMhIlJCAyaGoI7/5+7+ViByMsiZv5ZMUrjN3f89KN5jZtPdfbeZTQf2BuVtwKyc2ZvRYH0iIsNqwKokd+8BXjGziYNduGVODb4LPObuN+W8tRZYGjxfCqzJKb/UzOqD7rBzgYcHu14RETl6UQfROww8amb3EPRMAnD3KwaYbzHwl8G8m4KyzwA3kGm3uAx4Hrg4WN42M1sFbCfTo+nyIDGJiMgwiZoYfhr8DYq7P0jhq6LzXhTn7tcD1w92XSIiUhpRG59XDjyViIiMBpESg5nNBf4vMB8Yky139xPKFJeIiFRI1OsY/gX4JzL1/mcBtwL/Vq6gRESkcqImhgZ3vw8wd3/O3a8Fzi5fWCIiUimReyWZWQJ4ysw+Buzk1auVRURkFCl6xmBm2eqiNcAxwBXA6WS6oC4tNJ+IiIxcA50xnG5mrwE+QGZspFeAT5Y9KhERqZiBEsO3gbuBE4CNZK5J8JxH9UoSERllilYlufv/C+6l8D13P8Hdj899HKYYRURkGEXqleTuHy13ICIiEg+R7/ksIiLVQYlBRERClBhERCREiUFEREKUGEREJESJQUREQpQYREQkRIlBRERClBhEJDbaO1Js3vES7R2pSodS1aIOuy0iUlZrNu1k+eot1CYSdKXT3HjRaVywcGalw6pKOmMQqSJxPSJv70ixfPUWDnelOZjq5nBXmqtWb4ldnNVCZwwiVSLOR+Rt+zupTSQ4TLq3rDaRoG1/J03j6isYWXXSGYNIFYj7EXlzYwNd6XSorCudprmxoUIRVTclBpEqkD0iz5U9Io+DpnH13HjRaYypTTC+voYxtQluvOg0nS1UiKqSRKrASDgiv2DhTBafNJm2/Z00NzYoKVSQzhhEqsBIOSJvGlfP62ZNil1c1UZnDCJVQkfkEpUSg0gVaRpXr4QgA1JVkoiIhCgxiMRUXC9Gk9GvrInBzL5nZnvNbGtO2bFmdo+ZPRU8Nua8d7WZtZrZE2Z2bjljA/3jjTTV9H2t2bSTxV+6n7/4znoWf+l+1m7aWemQRrWR+NsqZ8zlbmP4V+Bm4NacshXAfe5+g5mtCF4vN7P5wKXAAmAGcK+ZzXP3nnIEFuerQKW/avq+ci9Gy14JfNXqLSw+abLaB8qgXL+t9o5U2Rr6y/3/UNYzBnd/APhjn+ILgZXB85XAu3PK73D3lLs/A7QCi8oRV9yvAo2DOB1BVdv3NdDFaHH6bka6cv22ynnGNxz/D5XolTTV3XcDuPtuMzsuKJ8J/C5nuragrB8zWwYsA5g9e/agA9C4LMXF7ei82r6vfBejHelJ83JnF7f97jm+8NPtsfluRrpy/LbKfcY3HP8PcWp8tjxlnm9Cd7/F3VvcvWXKlCmDXtFIuAq0UuJ4dF5t31ffi9Fqk0ZPOs1Hv7+Rz961NVbfzUhXjt9WuYcfGY7/h0okhj1mNh0geNwblLcBs3KmawZ2lSOApnH1XNLSHCq7pKV5VB59DlYcx9RpGlfPP7xzPnVJY2xdMrZX7Q5F3+qhCxbO5DfLz+abH3g9CYPuNBw60r+5rdLfzUhXjivCy73jHo6r2CtRlbQWWArcEDyuySm/3cxuItP4PBd4uBwBtHekWLWhLVS2akMbV54zb1TtbI5GHI/O12zayRd+up26mgRHepxr3jV/VFWfFKq6axpXz8SGOuqSSVLd3XnnrfR3MxpEuSJ8MA3J2R33VX2+01LuW8p9FXtZE4OZ/QBYAkw2szbgGjIJYZWZXQY8D1wM4O7bzGwVsB3oBi4vV4+kaquzHozh+FEPRm7VVtYXfrKd8xZMGxXf1UD10fkSNcDY+iQ9aR91Z06VUuyK8KNpcxuO4UfKeRV7WRODu7+vwFvnFJj+euD68kWU0dzYQGdX+Aiss6tbR16B4R5Tp9jR2GhP4gN9vnyJ+h/eOZ9TZ07UeEfDYCgNySN5+JGqHSvJzMht2868lqzh+lEPdDQWx6qtUory+TT4XeWM9gOTQuLUK2nYtO3vZExNMlQ2piapRrxhFqUH1EgZLvpoFfp8QKgxWsNRV8ZoPzAppCrPGKr1y46bqEdjcT5iLsXVrX0/34OtL7L4S/eP+GsVom6bcl4hPFRxa3MbLlWZGKr1yz4a5fynHUyCHo6qrSifNXeaB1tfHNKFgH3X1zSuPm+d9qfv3DzihsOI2mAbt4sp84nzgUm5VGVigOr8sger3P+0cUrQxT5rdge+defLvVcdH+lJ05NO050mtAOfdEwtC2ZMHPAzFFpfvrOoVLdz+/rn+dtz5pZvA5RQ1AbbkTQm1EhuSD4aVZsYoPq+7MEYrn/aOCToYp/1wdYXuerOzSQwOrsz7+XutHOlup2PfP/3pN2LJtFi62tubOBIT/9e2jeva+X9Z8weEVUyUasIq7VhdySoysZnGdhwXgFd6YbVtv2ddHWHd/bdPWm27XqZT67aRKrbe5PCQF450jPgUBXFtm3TuHo+dlb/M4O6ZPFtH6dhuqNWEaqtL76qOjFkhyFo3XMw0miVpRzVcrDLGu4RNavln7a9I8UXf7adnj6jcnWn4YkXDlIsH9QkCndxLpZEB9q27z9jNvU14X/NVHcPY+uSvTE/8OQ+HnhyL617DvLAk/u46s74jG8VtSfZaO9xNpJVbVVSto4X4HBXmvqkYQkblkaywS6rEg10laj/H0xVSCmqTdZs2slVd24hVWDvv/fg4aLzuzt1CTiSZ/bcHX3rnoNs2vESC2dN4qSp44tu2+y0n3zbPG6690k87aR6nETCOP/mB7mkpZnb1z8fSlh1SeNIn8xW6SqZqFWEcahKlP7MPe8ApiNGS0uLb9iwYVDztHekWPyl+0PDLGSNqU3wm+Vn92sk6zt9vumOdt3FllXqdQ/2H3AoO+DBzDuY5DeURJmNaWxdkvNvfjDvbyDrzg+/kfd9Zz1dfU8nBlBfk+DLf56J6XN3Pcqtv3u+970Pvmk2n7/wT0KxZLdP32nPPGkyDz3TPuj1w9H/RqR6mNlGd2/J915VnjHka/TKSpqxbdfLTGyo6/2HLWUjWb5lJRPGusf3ctYpx5VtSIjbfvcc1/1kO3VJoztdvHE0ayhJId/Ou++RYXtHim27DnCg80jvkftADd35u3MWnjZ3fdkzBCNzf4P8g7pnTBlbwxd/9hhzmhp4au8rg/rsPT1pVty5mc/dtZmXDodXcutDz7Px2XYmNtTR2dXDhIY6Dnd18/TeQ+w71BWa9oHWFwe1XsgkJXfnE2+d11uVld3W2YR46EhPRRv6y3l2EJfPOdJVZWJobmzgYCr/aJWHjvTwoX95hGPqakI7tFLVt+erXz6U6uGatdv4+zVbyzIkxG2/e47P3pW57faR4GMP1MNoKEflrXsO8ukfbeZIj/fuvD+xahPJRIK6ZGZ5+apEciWCBH3mvONC5fm7c6b7defsG/8/nD+fz63ZRk862tH3vkPd7Dv0cqRp++p26O52Xsn/E2Pb7kPAoaNa9kCy1WJf/M/He6tHL2lp7h1NOEq1abmUu0p0sNXDUlhVNj7f+puni77f44Qa8YCSNZLlNriNrX91WI5DBXqzDLWBrr0jxXX/sa1feTJhBRtHh3KznjWbdvKObzzYr867O53ZaWWXd+tDhZMCZHr3/M2tG/r1rsk3ACLAzeue6o0vX/zXro2eFEaLVI/3buvDXenearNs+XA2UJf7BlChM8kKfs7RoirPGNZseSHytNlqm1I2kmWXte7xvVyzdlvoBiylHhKibX8ntclEv77xXT1e8KzjaKuv2jtSXHXnZo50l2YHnOr2fmc2d299IW9CSdqr1XFt+zuL9hiSjOFsoC73NQvFqocr3RA/ElVlYrjwtGl8fV3xs4as3GqbUl4Q1zSunrNOOY6/X7O14Pr6Tn80625ubKAnTweDa941v+DyClVfja1LsnnHS73x9U1Ut61/ntQQkkKCzP1dc1OYp733zGbbrpe5Zu3WfLPySleav79rKz3pNG88oYmOVP9EKGHD2f243N2fC923otTrqRZVmRg+uPiEvIlhbH2SI91p3J2G2pqyd9Ecji6huetImtHVk+aady3gA2e8JjRdbqMgwOVLTuLmda2vtgmc3sz5Nz9IbSLBK0FDhZmRMPjKxa9j8UmT+ea61iHFmu/fOtXjfP3eJ3ngqRdxnJ4i1U+Hg1OJX7e2DymOkSo8kDyR2hiG6yi63L/13OVD5T7naFGV3VU373iJv/jO+lAD9Ni6JNddsICzTsk0dg5nv+rh6ElRqDdIe0eK29Y/zzeDJHC4u4d02hlTm7mN5l//6fGcc/JxvP+7D3OkQKOAkTkDueFn2zlclnvuxcvE+gRp0hxMwZgkBT9zfQJSfTbZjIn1zJw0htpkItQraUf7K0ydMIbXz27kDa9p5JRpE/jlE3t5dOcBLnjddM6ZP43v//YZ1mzZzVtPnsJrZ07iyRcO8PgLB5g6YQzveX0zjWPr2LbrAODMmNgQ+i3FpbeOeiXFR7HuqlWZGEp5bUCp3Pa757juP7ZRm0zQM8BYO6WQTQg33/9Uv4bivhLkP5qvRvU1CX67ov91LrlnW/m65IJHGlxPZLjoOoY+4jKqZ3aHsv7pdr74n48D9DYS5/bNz3eUldnhvAwYC2ZMyDtddpoDnZn+8RMa6lgwYwJ3b32Bz63Z2m8YiEKUFDJqk8aX/zz/0A65ZX2fnzlvyrDFKFIKVZkYoPKX4mf7XNckrF9DKWS6dt70iyeZP2MCX/jpdmoSmWEPPvW2eRzuTvON+5/qbVBNGpy7YBr3Pb6HumSSIz09LJpzLL/5r3aqrIdmUTWJzMV9NQZpz9THe+h9OG/BNF47fQLnLpgWVM28zIHObiY01OiIX6pGVVYllVOUqoNiQ3JIadQkjGTCcIe/Pfsk/uzUaRw60hOqewZ6r7ye0BDtPgoio4WqkkokO8DZnKZjqK1J9qva+edfP80tv3q6t+qlJmFcd+ECzphzbO8gao1j61j3+F6Spn72R6MmQahnkpPZzml3LnjddN77hlksmDEBiNaBQNU8Iv0pMUTUd4CzmoRRk8x0hXPgEz/clGfoZuezP87f716iSQJ/+abXcPZrjwv1tAEG7H2io3+Ro1PViaFYF87c8tY9B0NJATI7/e6088kfbSad9sgNudLfW+Y1cewxdbzc2c2UcXWkepwl8yZz/JTxRY/4teMXKY+qTQy5I2069A6TnG+grx1/LDy6pq6ojSaZMHB44wnHcuZJTWx/4SB/MnMi73lDs3bwIjFTlYmhvSPFJ1ZtDg2q9vFVmxlbl+RTP9pMV86ooFfcsalSYY44b5g1gT0HjrD4xCaWveXEftU+uthIZGSoysTw0H+19xtpsyftXHbrxgpFFH81lrk6fOyYGqaMq2dMbZIZkxqYMame106fxJtObCq601dCEBk5qjIxPNfeUekQYqvpmBpmTDqGA50pXjmSZu7UcXzibSfTcnzTUS+z3MMgiEhpVWVieOHl6h2bffr4OrrSjuMsmDaRM05s4pRp49n/SlfvPYlLqRL3qxaRoanKxLCl7aVKh1AWx9RAQ12S2pokSTNmTBzDqc2TOHnqeGY0HtM7dMZwyXcbzoHuHCcilRe7xGBm5wFfJ9OF/TvufkOp15F757SRJAk01CWY0FDDqTMmcfrsSTzd/gpvnz+Vc+ZPq3R4/ZT75izVSlVzUm6xSgxmlgS+CbwNaAMeMbO17r69lOvZF/Pb/NUCiQRMGlvL65obWXbmCUOq46+Uct+cpRqVs2pOCUeyYpUYgEVAq7s/DWBmdwAXAiVNDE/tLXxdwnCqT8KJk8ex7C0nDHgx10gUl1FsR4tyVs2pLUhyxS0xzAR25LxuA86oUCxDNqHeeN+iOfzrQ8+GbnlZ6Xs/DKdKj2I7mpSrak5tQdJX3BJDvpHl+l1abGbLgGUAs2fPHvRKaoDuAacavHw7/M07XuL2h3eQ6n51jdVWz17Ke2VXs3JVzaktSPqKW2JoA2blvG4GdvWdyN1vAW6BzLDbg13J+Qunc9em3YOa584Pv7FfPf/aTTsHrCZRPbuUSrmq5vQblb7ilhgeAeaa2fHATuBS4P2lXsm7TpuRNzF894Onc878aZEb4aJUk6ieXUqpHFVz+o1KX7G7UY+ZvQP4Gpnemd9z9+uLTX+0N+o596u/5Ik9h3pfnzx1LD//+JJBLycq9fiQuNNvtLoUu1FP7BLDYA3lDm73bX+BX2zfE9vrAEREykV3cCvgnPnTlBBERPpIVDoAERGJFyUGEREJUWIQEZEQJQYREQlRYhARkZAR313VzPYBzw1hEZOBF0sUTjnEPT5QjKUS9xjjHh8oxsF4jbtPyffGiE8MQ2VmGwr15Y2DuMcHirFU4h5j3OMDxVgqqkoSEZEQJQYREQlRYghGaY2xuMcHirFU4h5j3OMDxVgSVd/GICIiYTpjEBGRECUGEREJqdrEYGbnmdkTZtZqZisqHU8+ZvasmT1qZpvM7OjGFi8xM/ueme01s605Zcea2T1m9lTw2BjDGK81s53BttwU3PejUvHNMrN1ZvaYmW0zsyuD8thsxyIxxmk7jjGzh81scxDjdUF5LLZjkfhisw0Lqco2BjNLAk8CbyNzO9FHgPe5+/aKBtaHmT0LtLh7HC6GAcDMzgQ6gFvd/dSg7Ebgj+5+Q5BkG919ecxivBbocPevVCquLDObDkx399+b2XhgI/Bu4K+IyXYsEuMlxGc7GjDW3TvMrBZ4ELgSeC8x2I5F4juPmGzDQqr1jGER0OruT7v7EeAO4MIKxzQiuPsDwB/7FF8IrAyerySzA6mYAjHGhrvvdvffB88PAo8BM4nRdiwSY2x4Rkfwsjb4c2KyHYvEF3vVmhhmAjtyXrcRsx99wIFfmNlGM1tW6WCKmOruuyGzQwGOq3A8hXzMzLYEVU0Vre7KMrM5wOuB9cR0O/aJEWK0Hc0saWabgL3APe4eq+1YID6I0TbMp1oTg+Upi2MmX+zubwD+DLg8qCKRo/NPwInAQmA38C9QHVoAAARhSURBVI+VDQfMbBywGvg7dz9Q6XjyyRNjrLaju/e4+0KgGVhkZqdWMp6+CsQXq22YT7UmhjZgVs7rZmBXhWIpyN13BY97gR+TqQKLoz1BnXS2bnpvhePpx933BP+kaeCfqfC2DOqcVwO3ufu/B8Wx2o75Yozbdsxy95eAX5Kpv4/VdoRwfHHdhrmqNTE8Asw1s+PNrA64FFhb4ZhCzGxs0OiHmY0F3g5sLT5XxawFlgbPlwJrKhhLXtkdReA9VHBbBo2S3wUec/ebct6KzXYsFGPMtuMUM5sUPG8A3go8Tky2Y6H44rQNC6nKXkkAQRexrwFJ4Hvufn2FQwoxsxPInCUA1AC3xyFGM/sBsITM0MF7gGuAu4BVwGzgeeBid69Y42+BGJeQOXV34Fngw9l66ArE92bg18CjQDoo/gyZOvxYbMciMb6P+GzH08g0LifJHOSucvfPm1kTMdiOReL7N2KyDQup2sQgIiL5VWtVkoiIFKDEICIiIUoMIiISosQgIiIhSgwiIhKixCAiIiFKDDKqmdkkM/s/Rd7/bQnW8VdmdnPw/CNm9sEi0y4xs/851HWKlJMSg4x2k4B+iSEYeh13L+lO2t2/7e63FplkCaDEILGmxCCj3Q3AicENUR4Jbj5zO5krejGzjuBxiZk9YGY/NrPtZvZtMyv4/2FmHzKzJ83sV8DinPJrzexTwfMrgmVtMbM7glFKPwJ8PIjnT83sXWa23sz+YGb3mtnUnOV8z8x+aWZPm9kVOev4YLDMzcFVtNnhF1YHn/ERM1uMyFGqqXQAImW2AjjV3Rea2RLgp8HrZ/JMuwiYDzwH3E3mhi939p0oGOvmOuB04GVgHfCHAus+3t1TZjbJ3V8ys2+Tc5OWYMjlN7q7m9lfA1cBnwzmPwU4CxgPPGFm/wTMAz5LZuTdF83s2GDarwNfdfcHzWw28HPgtdE3k8irlBik2jxcIClk33saesdbejN5EgNwBvBLd98XTPtDMjvsvrYAt5nZXWTGk8qnGfhhkGzqgNzYfuruKSBlZnuBqcDZwJ3Zu/rljAH0VmB+Zuw7ACaY2fjgJjsig6KqJKk2h4q813fgsGIDiUUZZOydwDfJnFlsNLN8B2LfAG529z8BPgyMyXkvlfO8h8yBnBVYdwJ4k7svDP5mKinI0VJikNHuIJmqmCgWBUOxJ4D/ReYevfmsB5aYWVNwz4KL+04QLGOWu68jUz00CRiXJ56JwM7g+VIGdh9wSTCCKDlVSb8APpaz/oURliWSlxKDjGru3g78xsy2Al8eYPKHyDRWbyVTpfPjfBMFQyRfG0x/L/D7PJMlge+b2aNk2h++Gtys5T+A92Qbn4Pl/MjMfg28GOHzbAOuB35lZpuB7L0SrgBagkbp7WQauUWOiobdFiHTKwn4lLufX+lYRCpNZwwiIhKiMwaRIsxsPVDfp/gv3f3RSsQjMhyUGEREJERVSSIiEqLEICIiIUoMIiISosQgIiIh/w1KweuQNVBAoQAAAABJRU5ErkJggg==\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"df.sample(10000).plot.scatter(x=\"trip_distance\", y=\"fare_amount\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As we only want to do a very basic estimation, we do a [simple linear regression estimation](https://en.wikipedia.org/wiki/Ordinary_least_squares#Simple_linear_regression_model)."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 342 ms, sys: 168 ms, total: 510 ms\n",
"Wall time: 510 ms\n"
]
},
{
"data": {
"text/plain": [
"(12.486907739140417, 4.6752084884145456e-06)"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"cov_xy = (df[\"trip_distance\"] * df[\"fare_amount\"]).sum() - (\n",
" df[\"trip_distance\"].sum() * df[\"fare_amount\"].sum()\n",
") / len(df)\n",
"var_xy = (df[\"trip_distance\"] ** 2).sum() - df[\"trip_distance\"].sum() ** 2 / len(df)\n",
"beta = cov_xy / var_xy\n",
"alpha = df[\"fare_amount\"].mean() - beta * df[\"trip_distance\"].mean()\n",
"alpha, beta"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<matplotlib.axes._subplots.AxesSubplot at 0x127d23550>"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"sample = df.sample(10000)\n",
"sample[\"price\"] = alpha + beta * sample[\"trip_distance\"]\n",
"ax = sample.plot.scatter(x=\"trip_distance\", y=\"fare_amount\")\n",
"sample.plot.line(x=\"trip_distance\", y=\"price\", ax=ax, color=\"red\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Also, as with every real life dataset, the New York City trip dataset also contains outliers that we need to clean to get a good regression. With the goal just showing the nice properties of the dataset, we aren't doing any sophiscated cleaning here but simply get rid of the noisy data that disturbs our basic regression example."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>VendorID</th>\n",
" <th>tpep_pickup_datetime</th>\n",
" <th>tpep_dropoff_datetime</th>\n",
" <th>passenger_count</th>\n",
" <th>trip_distance</th>\n",
" <th>pickup_longitude</th>\n",
" <th>pickup_latitude</th>\n",
" <th>RatecodeID</th>\n",
" <th>store_and_fwd_flag</th>\n",
" <th>dropoff_longitude</th>\n",
" <th>dropoff_latitude</th>\n",
" <th>payment_type</th>\n",
" <th>fare_amount</th>\n",
" <th>extra</th>\n",
" <th>mta_tax</th>\n",
" <th>tip_amount</th>\n",
" <th>tolls_amount</th>\n",
" <th>improvement_surcharge</th>\n",
" <th>total_amount</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>2</td>\n",
" <td>2016-01-01</td>\n",
" <td>2016-01-01</td>\n",
" <td>2</td>\n",
" <td>1.10</td>\n",
" <td>-73.990372</td>\n",
" <td>40.734695</td>\n",
" <td>1</td>\n",
" <td>False</td>\n",
" <td>-73.981842</td>\n",
" <td>40.732407</td>\n",
" <td>2</td>\n",
" <td>7.5</td>\n",
" <td>0.5</td>\n",
" <td>0.5</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.3</td>\n",
" <td>8.8</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2</td>\n",
" <td>2016-01-01</td>\n",
" <td>2016-01-01</td>\n",
" <td>5</td>\n",
" <td>4.90</td>\n",
" <td>-73.980782</td>\n",
" <td>40.729912</td>\n",
" <td>1</td>\n",
" <td>False</td>\n",
" <td>-73.944473</td>\n",
" <td>40.716679</td>\n",
" <td>1</td>\n",
" <td>18.0</td>\n",
" <td>0.5</td>\n",
" <td>0.5</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.3</td>\n",
" <td>19.3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>2</td>\n",
" <td>2016-01-01</td>\n",
" <td>2016-01-01</td>\n",
" <td>1</td>\n",
" <td>10.54</td>\n",
" <td>-73.984550</td>\n",
" <td>40.679565</td>\n",
" <td>1</td>\n",
" <td>False</td>\n",
" <td>-73.950272</td>\n",
" <td>40.788925</td>\n",
" <td>1</td>\n",
" <td>33.0</td>\n",
" <td>0.5</td>\n",
" <td>0.5</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.3</td>\n",
" <td>34.3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>2</td>\n",
" <td>2016-01-01</td>\n",
" <td>2016-01-01</td>\n",
" <td>1</td>\n",
" <td>4.75</td>\n",
" <td>-73.993469</td>\n",
" <td>40.718990</td>\n",
" <td>1</td>\n",
" <td>False</td>\n",
" <td>-73.962242</td>\n",
" <td>40.657333</td>\n",
" <td>2</td>\n",
" <td>16.5</td>\n",
" <td>0.0</td>\n",
" <td>0.5</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.3</td>\n",
" <td>17.3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>2</td>\n",
" <td>2016-01-01</td>\n",
" <td>2016-01-01</td>\n",
" <td>3</td>\n",
" <td>1.76</td>\n",
" <td>-73.960625</td>\n",
" <td>40.781330</td>\n",
" <td>1</td>\n",
" <td>False</td>\n",
" <td>-73.977264</td>\n",
" <td>40.758514</td>\n",
" <td>2</td>\n",
" <td>8.0</td>\n",
" <td>0.0</td>\n",
" <td>0.5</td>\n",
" <td>0.0</td>\n",
" <td>0.0</td>\n",
" <td>0.3</td>\n",
" <td>8.8</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" VendorID tpep_pickup_datetime tpep_dropoff_datetime passenger_count \\\n",
"0 2 2016-01-01 2016-01-01 2 \n",
"1 2 2016-01-01 2016-01-01 5 \n",
"2 2 2016-01-01 2016-01-01 1 \n",
"3 2 2016-01-01 2016-01-01 1 \n",
"4 2 2016-01-01 2016-01-01 3 \n",
"\n",
" trip_distance pickup_longitude pickup_latitude RatecodeID \\\n",
"0 1.10 -73.990372 40.734695 1 \n",
"1 4.90 -73.980782 40.729912 1 \n",
"2 10.54 -73.984550 40.679565 1 \n",
"3 4.75 -73.993469 40.718990 1 \n",
"4 1.76 -73.960625 40.781330 1 \n",
"\n",
" store_and_fwd_flag dropoff_longitude dropoff_latitude payment_type \\\n",
"0 False -73.981842 40.732407 2 \n",
"1 False -73.944473 40.716679 1 \n",
"2 False -73.950272 40.788925 1 \n",
"3 False -73.962242 40.657333 2 \n",
"4 False -73.977264 40.758514 2 \n",
"\n",
" fare_amount extra mta_tax tip_amount tolls_amount \\\n",
"0 7.5 0.5 0.5 0.0 0.0 \n",
"1 18.0 0.5 0.5 0.0 0.0 \n",
"2 33.0 0.5 0.5 0.0 0.0 \n",
"3 16.5 0.0 0.5 0.0 0.0 \n",
"4 8.0 0.0 0.5 0.0 0.0 \n",
"\n",
" improvement_surcharge total_amount \n",
"0 0.3 8.8 \n",
"1 0.3 19.3 \n",
"2 0.3 34.3 \n",
"3 0.3 17.3 \n",
"4 0.3 8.8 "
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# As with every dataset, we need to first clean the data a bit\n",
"cap_fare = df[\"fare_amount\"].mean() + 3 * df[\"fare_amount\"].std()\n",
"cap_distance = df[\"trip_distance\"].mean() + 3 * df[\"trip_distance\"].std()\n",
"df_filtered = df.query(\n",
" f\"trip_distance > 0 and trip_distance < {cap_distance} and fare_amount > 0 and fare_amount < {cap_fare}\"\n",
")\n",
"df_filtered.head()"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 364 ms, sys: 70.1 ms, total: 434 ms\n",
"Wall time: 434 ms\n"
]
},
{
"data": {
"text/plain": [
"(4.651606864471554, 2.661444816924383)"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%%time\n",
"cov_xy = (df_filtered[\"trip_distance\"] * df_filtered[\"fare_amount\"]).sum() - (\n",
" df_filtered[\"trip_distance\"].sum() * df_filtered[\"fare_amount\"].sum()\n",
") / len(df_filtered)\n",
"var_xy = (df_filtered[\"trip_distance\"] ** 2).sum() - df_filtered[\n",
" \"trip_distance\"\n",
"].sum() ** 2 / len(df_filtered)\n",
"beta = cov_xy / var_xy\n",
"alpha = df_filtered[\"fare_amount\"].mean() - beta * df_filtered[\"trip_distance\"].mean()\n",
"alpha, beta"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<matplotlib.axes._subplots.AxesSubplot at 0x126d7c4e0>"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"sample[\"price\"] = alpha + beta * sample[\"trip_distance\"]\n",
"ax = sample.plot.scatter(x=\"trip_distance\", y=\"fare_amount\")\n",
"sample.plot.line(x=\"trip_distance\", y=\"price\", ax=ax, color=\"red\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Schema changes\n",
"\n",
"Another thing that represents real life issues is that the dataset has a small schema change throughout its history. The columns used in 2015 and in 2018 aren't exactly the same. They have changed the location columns from providing coordinates for pickup and dropoff to using location IDs in later datasets\n",
"\n",
"This is nice as you can use the dataset to explain schema migrations and write methods handling that. Still, the schema change is so small, that you can easily work around it when you aren't interested in dealing with it explicitly."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'DOLocationID',\n",
" 'PULocationID',\n",
" 'RatecodeID',\n",
" 'VendorID',\n",
" 'extra',\n",
" 'fare_amount',\n",
" 'improvement_surcharge',\n",
" 'mta_tax',\n",
" 'passenger_count',\n",
" 'payment_type',\n",
" 'store_and_fwd_flag',\n",
" 'tip_amount',\n",
" 'tolls_amount',\n",
" 'total_amount',\n",
" 'tpep_dropoff_datetime',\n",
" 'tpep_pickup_datetime',\n",
" 'trip_distance'}"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# First line of yellow_tripdata_2018-08.csv\n",
"columns_2018 = \"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\"\n",
"columns_2018 = set(columns_2018.split(\",\"))\n",
"columns_2018"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'RatecodeID',\n",
" 'VendorID',\n",
" 'dropoff_latitude',\n",
" 'dropoff_longitude',\n",
" 'extra',\n",
" 'fare_amount',\n",
" 'improvement_surcharge',\n",
" 'mta_tax',\n",
" 'passenger_count',\n",
" 'payment_type',\n",
" 'pickup_latitude',\n",
" 'pickup_longitude',\n",
" 'store_and_fwd_flag',\n",
" 'tip_amount',\n",
" 'tolls_amount',\n",
" 'total_amount',\n",
" 'tpep_dropoff_datetime',\n",
" 'tpep_pickup_datetime',\n",
" 'trip_distance'}"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# First line of yellow_tripdata_2015-08.csv\n",
"columns_2016 = \"VendorID,tpep_pickup_datetime,tpep_dropoff_datetime,passenger_count,trip_distance,pickup_longitude,pickup_latitude,RatecodeID,store_and_fwd_flag,dropoff_longitude,dropoff_latitude,payment_type,fare_amount,extra,mta_tax,tip_amount,tolls_amount,improvement_surcharge,total_amount\"\n",
"columns_2016 = set(columns_2016.split(\",\"))\n",
"columns_2016"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Removed columns: {'pickup_latitude', 'dropoff_longitude', 'pickup_longitude', 'dropoff_latitude'}\n",
"Added columns: {'DOLocationID', 'PULocationID'}\n",
"\n"
]
}
],
"source": [
"print(\n",
" f\"\"\"\n",
"Removed columns: {columns_2016 - columns_2018}\n",
"Added columns: {columns_2018 - columns_2016}\n",
"\"\"\"\n",
")"
]
}
],
"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.3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment