Last active
April 11, 2023 23:40
-
-
Save dshemetov/43c6c988e3c9237f15930fc6190b6d77 to your computer and use it in GitHub Desktop.
A Few General Notes on Python Internals and Making It Fast
This file contains hidden or 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": [ | |
{ | |
"attachments": {}, | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# A Few General Notes on Python Internals and Fast Code\n", | |
"\n", | |
"Python is very efficient for developer time. But when an application needs to be performant, many tricks are required to make Python go fast.\n", | |
"\n", | |
"This notebook contains some common tricks and some insights about Python under the hood, that may help you make better guesses about what is fast and what is slow.\n", | |
" \n", | |
"> When I teach courses on Python for scientific computing, I make this point [very early](http://nbviewer.ipython.org/github/jakevdp/2013_fall_ASTR599/blob/master/notebooks/11_EfficientNumpy.ipynb) in the course, and tell the students why: it boils down to Python being a dynamically typed, interpreted language, where values are stored not in dense buffers but in scattered objects. And then I talk about how to get around this by using NumPy, SciPy, and related tools for vectorization of operations and calling into compiled code, and go on from there.\n", | |
" \n", | |
"See: https://jakevdp.github.io/blog/2014/05/09/why-python-is-slow/\n", | |
"\n", | |
"We will touch on:\n", | |
"- Numpy\n", | |
"- Pandas\n", | |
"- Numba\n", | |
"- Cython\n", | |
"- Polars\n", | |
"\n", | |
"Also, to get a general frame of reference on computer speed, see (co-authored by Julia Evans): https://computers-are-fast.github.io/" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# !pip install numpy cython numba pandas polars PIL ipykernel" | |
] | |
}, | |
{ | |
"attachments": {}, | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## How Much Faster Can C Be Than Python?" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import numpy as np\n", | |
"\n", | |
"def sum_list(l: list[int]) -> int:\n", | |
" total = 0\n", | |
" for i in l:\n", | |
" total += i\n", | |
" return total\n", | |
"\n", | |
"lp = list(range(1_000_000))\n", | |
"ln = np.array(lp)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 9, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"%load_ext Cython" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"%%cython --annotate\n", | |
"\n", | |
"cimport cython\n", | |
"\n", | |
"cpdef long sum_cython(long[:] l, int N):\n", | |
" cdef long total = 0\n", | |
" cdef long i = 0\n", | |
" for i in range(N):\n", | |
" total += l[i]\n", | |
" return total" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 57, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"39.8 ms ± 467 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" | |
] | |
} | |
], | |
"source": [ | |
"%%timeit \n", | |
"sum_list(lp)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 58, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"580 µs ± 4.31 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n" | |
] | |
} | |
], | |
"source": [ | |
"%%timeit\n", | |
"sum_cython(ln, len(ln))" | |
] | |
}, | |
{ | |
"attachments": {}, | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Numpy Is C Under the Hood\n", | |
"\n", | |
"For very regular, structured data like n-dimensional arrays, Numpy helps us get away from the bulkiness of Python objects and interfaces directly with C arrays under the hood.\n", | |
"\n", | |
"Plenty of built-ins: https://numpy.org/doc/stable/reference/routines.array-manipulation.html\n", | |
"- Reshaping, transpose, changing dimensions,\n", | |
"- Concatenating, splitting, appending, inserting, deleting\n", | |
"- Sorting, searching, counting\n", | |
"- Etc.\n", | |
"\n", | |
"Numpy data type model is: https://numpy.org/doc/stable/reference/arrays.scalars.html#sized-aliases\n", | |
"- integers - int8, int16, int32, int64\n", | |
"- unsigned integers - uint8, uint16, uint32, uint64\n", | |
"- floating point - float16, float32, float64, float128\n", | |
"- complex floating point - complex64, complex128, complex256\n", | |
"- boolean - bool\n", | |
"- string - str\n", | |
"- object - object\n", | |
"- datetime - datetime64, timedelta64\n", | |
"- more - void, bytes, unicode\n", | |
"\n", | |
"\n", | |
"\n", | |
"There are also structured dtypes, which are like a table of data. See: https://numpy.org/doc/stable/user/basics.rec.html. I don't recommend using these for tabular computations (see below)." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 18, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"340 µs ± 7.61 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n" | |
] | |
} | |
], | |
"source": [ | |
"%%timeit\n", | |
"np.sum(ln)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Avoid Structured Arrays for Tabular Computations\n", | |
"\n", | |
"> Users looking to manipulate tabular data, such as stored in csv files, may find other pydata projects more suitable, such as xarray, pandas, or DataArray. These provide a high-level interface for tabular data analysis and are better optimized for that use. For instance, the C-struct-like memory layout of structured arrays in numpy can lead to poor cache behavior in comparison.\n", | |
"> See: https://numpy.org/doc/stable/user/basics.rec.html" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 10, | |
"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>source</th>\n", | |
" <th>signal</th>\n", | |
" <th>geo_value</th>\n", | |
" <th>time_value</th>\n", | |
" <th>geo_type</th>\n", | |
" <th>time_type</th>\n", | |
" <th>direction</th>\n", | |
" <th>issue</th>\n", | |
" <th>lag</th>\n", | |
" <th>missing_value</th>\n", | |
" <th>missing_stderr</th>\n", | |
" <th>missing_sample_size</th>\n", | |
" <th>value</th>\n", | |
" <th>stderr</th>\n", | |
" <th>sample_size</th>\n", | |
" </tr>\n", | |
" </thead>\n", | |
" <tbody>\n", | |
" <tr>\n", | |
" <th>0</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01001</td>\n", | |
" <td>20200122</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20200514</td>\n", | |
" <td>113</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>0.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>1</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01001</td>\n", | |
" <td>20200123</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20200514</td>\n", | |
" <td>112</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>0.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>2</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01001</td>\n", | |
" <td>20200124</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20200514</td>\n", | |
" <td>111</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>0.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>3</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01001</td>\n", | |
" <td>20200125</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20200514</td>\n", | |
" <td>110</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>0.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>4</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01001</td>\n", | |
" <td>20200126</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20200514</td>\n", | |
" <td>109</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>0.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>...</th>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>69072</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01133</td>\n", | |
" <td>20221113</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20221114</td>\n", | |
" <td>1</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>8875.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>69073</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01133</td>\n", | |
" <td>20221114</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20221115</td>\n", | |
" <td>1</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>8875.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>69074</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01133</td>\n", | |
" <td>20221115</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20221116</td>\n", | |
" <td>1</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>8875.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>69075</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01133</td>\n", | |
" <td>20221116</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20221117</td>\n", | |
" <td>1</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>8877.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>69076</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01133</td>\n", | |
" <td>20221117</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20221118</td>\n", | |
" <td>1</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>8877.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" </tbody>\n", | |
"</table>\n", | |
"<p>69077 rows × 15 columns</p>\n", | |
"</div>" | |
], | |
"text/plain": [ | |
" source signal geo_value time_value geo_type \n", | |
"0 jhu-csse confirmed_cumulative_num 01001 20200122 county \\\n", | |
"1 jhu-csse confirmed_cumulative_num 01001 20200123 county \n", | |
"2 jhu-csse confirmed_cumulative_num 01001 20200124 county \n", | |
"3 jhu-csse confirmed_cumulative_num 01001 20200125 county \n", | |
"4 jhu-csse confirmed_cumulative_num 01001 20200126 county \n", | |
"... ... ... ... ... ... \n", | |
"69072 jhu-csse confirmed_cumulative_num 01133 20221113 county \n", | |
"69073 jhu-csse confirmed_cumulative_num 01133 20221114 county \n", | |
"69074 jhu-csse confirmed_cumulative_num 01133 20221115 county \n", | |
"69075 jhu-csse confirmed_cumulative_num 01133 20221116 county \n", | |
"69076 jhu-csse confirmed_cumulative_num 01133 20221117 county \n", | |
"\n", | |
" time_type direction issue lag missing_value missing_stderr \n", | |
"0 day NaN 20200514 113 0 5 \\\n", | |
"1 day NaN 20200514 112 0 5 \n", | |
"2 day NaN 20200514 111 0 5 \n", | |
"3 day NaN 20200514 110 0 5 \n", | |
"4 day NaN 20200514 109 0 5 \n", | |
"... ... ... ... ... ... ... \n", | |
"69072 day NaN 20221114 1 0 5 \n", | |
"69073 day NaN 20221115 1 0 5 \n", | |
"69074 day NaN 20221116 1 0 5 \n", | |
"69075 day NaN 20221117 1 0 5 \n", | |
"69076 day NaN 20221118 1 0 5 \n", | |
"\n", | |
" missing_sample_size value stderr sample_size \n", | |
"0 5 0.0 NaN NaN \n", | |
"1 5 0.0 NaN NaN \n", | |
"2 5 0.0 NaN NaN \n", | |
"3 5 0.0 NaN NaN \n", | |
"4 5 0.0 NaN NaN \n", | |
"... ... ... ... ... \n", | |
"69072 5 8875.0 NaN NaN \n", | |
"69073 5 8875.0 NaN NaN \n", | |
"69074 5 8875.0 NaN NaN \n", | |
"69075 5 8877.0 NaN NaN \n", | |
"69076 5 8877.0 NaN NaN \n", | |
"\n", | |
"[69077 rows x 15 columns]" | |
] | |
}, | |
"execution_count": 10, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"import pandas as pd\n", | |
"from typing import Iterable, Dict\n", | |
"\n", | |
"df = pd.read_csv(\"/home/dskel/Documents/Code/Delphi/delphi-dev/confirmed_cumulative_num_01_counties.csv\")\n", | |
"df = df.set_index([\"source\", \"signal\", \"geo_value\", \"time_value\"]).sort_index().reset_index()\n", | |
"df[\"time_value\"] = df[\"time_value\"].astype(int) \n", | |
"df[\"issue\"] = df[\"issue\"].astype(int) \n", | |
"df[\"source\"] = df[\"source\"].astype(\"category\")\n", | |
"df[\"signal\"] = df[\"signal\"].astype(\"category\")\n", | |
"df[\"geo_type\"] = df[\"geo_type\"].astype(\"category\")\n", | |
"df[\"time_type\"] = df[\"time_type\"].astype(\"category\")\n", | |
"df[\"geo_value\"] = df[\"geo_value\"].astype(str).str.zfill(5)\n", | |
"row_dicts = df.to_dict(orient=\"records\")\n", | |
"df" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 11, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"{'source': 'jhu-csse',\n", | |
" 'signal': 'confirmed_cumulative_num',\n", | |
" 'geo_value': '01001',\n", | |
" 'time_value': 20200122,\n", | |
" 'geo_type': 'county',\n", | |
" 'time_type': 'day',\n", | |
" 'direction': nan,\n", | |
" 'issue': 20200514,\n", | |
" 'lag': 113,\n", | |
" 'missing_value': 0,\n", | |
" 'missing_stderr': 5,\n", | |
" 'missing_sample_size': 5,\n", | |
" 'value': 0.0,\n", | |
" 'stderr': nan,\n", | |
" 'sample_size': nan}" | |
] | |
}, | |
"execution_count": 11, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"row_dicts[0]" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 12, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([(b'', b'', b'', b'', b'', 20200122, 20200514, 113, 0, 5, 5, 0., nan, nan),\n", | |
" (b'', b'', b'', b'', b'', 20200123, 20200514, 112, 0, 5, 5, 0., nan, nan),\n", | |
" (b'', b'', b'', b'', b'', 20200124, 20200514, 111, 0, 5, 5, 0., nan, nan),\n", | |
" ...,\n", | |
" (b'', b'', b'', b'', b'', 20221115, 20221116, 1, 0, 5, 5, 8875., nan, nan),\n", | |
" (b'', b'', b'', b'', b'', 20221116, 20221117, 1, 0, 5, 5, 8877., nan, nan),\n", | |
" (b'', b'', b'', b'', b'', 20221117, 20221118, 1, 0, 5, 5, 8877., nan, nan)],\n", | |
" dtype=[('geo_value', 'S'), ('signal', 'S'), ('source', 'S'), ('geo_type', 'S'), ('time_type', 'S'), ('time_value', '<i4'), ('issue', '<i4'), ('lag', '<i4'), ('missing_value', 'i1'), ('missing_stderr', 'i1'), ('missing_sample_size', 'i1'), ('value', '<f8'), ('stderr', '<f8'), ('sample_size', '<f8')])" | |
] | |
}, | |
"execution_count": 12, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"ndarray_dtypes = np.dtype([\n", | |
" (\"geo_value\", np.string_),\n", | |
" (\"signal\", np.string_),\n", | |
" (\"source\", np.string_),\n", | |
" (\"geo_type\", np.string_),\n", | |
" (\"time_type\", np.string_),\n", | |
" (\"time_value\", \"i4\"),\n", | |
" (\"issue\", \"i4\"),\n", | |
" (\"lag\", \"i4\"),\n", | |
" (\"missing_value\", \"i1\"),\n", | |
" (\"missing_stderr\", \"i1\"),\n", | |
" (\"missing_sample_size\", \"i1\"),\n", | |
" (\"value\", float),\n", | |
" (\"stderr\", float),\n", | |
" (\"sample_size\", float),\n", | |
"])\n", | |
"\n", | |
"def dicts_to_structured_array(rows: Iterable[Dict]) -> np.ndarray:\n", | |
" \"\"\"Numpy structured arrays are slow.\"\"\"\n", | |
" row_order = [\"geo_value\", \"signal\", \"source\", \"geo_type\", \"time_type\", \"time_value\", \"direction\", \"issue\", \"lag\", \"missing_value\", \"missing_stderr\", \"missing_sample_size\", \"value\", \"stderr\", \"sample_size\"]\n", | |
" return np.array([tuple(row[k] for k in row_order if k != \"direction\") for row in rows], dtype=ndarray_dtypes)\n", | |
"\n", | |
"structured_array = dicts_to_structured_array(row_dicts)\n", | |
"structured_array" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 13, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"{'geo_value': array([b'0', b'0', b'0', ..., b'0', b'0', b'0'], dtype='|S1'),\n", | |
" 'signal': array([b'c', b'c', b'c', ..., b'c', b'c', b'c'], dtype='|S1'),\n", | |
" 'source': array([b'j', b'j', b'j', ..., b'j', b'j', b'j'], dtype='|S1'),\n", | |
" 'geo_type': array([b'c', b'c', b'c', ..., b'c', b'c', b'c'], dtype='|S1'),\n", | |
" 'time_type': array([b'd', b'd', b'd', ..., b'd', b'd', b'd'], dtype='|S1'),\n", | |
" 'time_value': array([20200122, 20200123, 20200124, ..., 20221115, 20221116, 20221117],\n", | |
" dtype=int32),\n", | |
" 'issue': array([20200514, 20200514, 20200514, ..., 20221116, 20221117, 20221118],\n", | |
" dtype=int32),\n", | |
" 'lag': array([113, 112, 111, ..., 1, 1, 1], dtype=int32),\n", | |
" 'missing_value': array([0, 0, 0, ..., 0, 0, 0], dtype=int8),\n", | |
" 'missing_stderr': array([5, 5, 5, ..., 5, 5, 5], dtype=int8),\n", | |
" 'missing_sample_size': array([5, 5, 5, ..., 5, 5, 5], dtype=int8),\n", | |
" 'value': array([ 0., 0., 0., ..., 8875., 8877., 8877.]),\n", | |
" 'stderr': array([nan, nan, nan, ..., nan, nan, nan]),\n", | |
" 'sample_size': array([nan, nan, nan, ..., nan, nan, nan])}" | |
] | |
}, | |
"execution_count": 13, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"ndarray_dtypes = np.dtype([\n", | |
" (\"geo_value\", np.string_),\n", | |
" (\"signal\", np.string_),\n", | |
" (\"source\", np.string_),\n", | |
" (\"geo_type\", np.string_),\n", | |
" (\"time_type\", np.string_),\n", | |
" (\"time_value\", \"i4\"),\n", | |
" (\"issue\", \"i4\"),\n", | |
" (\"lag\", \"i4\"),\n", | |
" (\"missing_value\", \"i1\"),\n", | |
" (\"missing_stderr\", \"i1\"),\n", | |
" (\"missing_sample_size\", \"i1\"),\n", | |
" (\"value\", float),\n", | |
" (\"stderr\", float),\n", | |
" (\"sample_size\", float),\n", | |
"])\n", | |
"def dicts_to_arrays(rows: Iterable[Dict]) -> tuple[np.ndarray, dict]:\n", | |
" \"\"\"Convert dictionaries to a dictionary of Numpy arrays.\n", | |
"\n", | |
" This is to get away from using structured arrays, which are slow for tabular computations.\n", | |
" \"\"\"\n", | |
" rows = list(rows)\n", | |
" arrays = {\n", | |
" k: np.empty(len(rows), dtype=ndarray_dtypes[k])\n", | |
" for k in ndarray_dtypes.names\n", | |
" }\n", | |
" for i, row in enumerate(rows):\n", | |
" for k in arrays:\n", | |
" arrays[k][i] = row[k]\n", | |
" return arrays\n", | |
"\n", | |
"dict_arrays = dicts_to_arrays(row_dicts)\n", | |
"dict_arrays" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"145 µs ± 3.4 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n" | |
] | |
} | |
], | |
"source": [ | |
"%%timeit\n", | |
"structured_array[\"time_value\"].max()\n", | |
"structured_array[\"lag\"].sum()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"40.9 µs ± 206 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n" | |
] | |
} | |
], | |
"source": [ | |
"%%timeit\n", | |
"dict_arrays[\"time_value\"].max()\n", | |
"dict_arrays[\"lag\"].sum()" | |
] | |
}, | |
{ | |
"attachments": {}, | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Pandas\n", | |
"\n", | |
"Built on top of Numpy. Let's look at how the data is structured under the hood.\n", | |
"\n", | |
"The data is stored in Blocks, which are 2D Numpy arrays of columns with the same dtype.\n", | |
"\n", | |
"Two implications:\n", | |
"1. Appending rows is slow because it requires copying the entire array for every block.\n", | |
"2. Appending columns is somewhat faster, since by default new Blocks are created separate from the existing data, but occasionally a Block consolidation is triggered, which is slow. See here for more: https://uwekorn.com/2020/05/24/the-one-pandas-internal.html" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 14, | |
"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>source</th>\n", | |
" <th>signal</th>\n", | |
" <th>geo_value</th>\n", | |
" <th>time_value</th>\n", | |
" <th>geo_type</th>\n", | |
" <th>time_type</th>\n", | |
" <th>direction</th>\n", | |
" <th>issue</th>\n", | |
" <th>lag</th>\n", | |
" <th>missing_value</th>\n", | |
" <th>missing_stderr</th>\n", | |
" <th>missing_sample_size</th>\n", | |
" <th>value</th>\n", | |
" <th>stderr</th>\n", | |
" <th>sample_size</th>\n", | |
" </tr>\n", | |
" </thead>\n", | |
" <tbody>\n", | |
" <tr>\n", | |
" <th>0</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01001</td>\n", | |
" <td>20200122</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20200514</td>\n", | |
" <td>113</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>0.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>1</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01001</td>\n", | |
" <td>20200123</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20200514</td>\n", | |
" <td>112</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>0.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>2</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01001</td>\n", | |
" <td>20200124</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20200514</td>\n", | |
" <td>111</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>0.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>3</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01001</td>\n", | |
" <td>20200125</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20200514</td>\n", | |
" <td>110</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>0.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>4</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01001</td>\n", | |
" <td>20200126</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20200514</td>\n", | |
" <td>109</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>0.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>...</th>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" <td>...</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>69072</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01133</td>\n", | |
" <td>20221113</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20221114</td>\n", | |
" <td>1</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>8875.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>69073</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01133</td>\n", | |
" <td>20221114</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20221115</td>\n", | |
" <td>1</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>8875.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>69074</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01133</td>\n", | |
" <td>20221115</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20221116</td>\n", | |
" <td>1</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>8875.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>69075</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01133</td>\n", | |
" <td>20221116</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20221117</td>\n", | |
" <td>1</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>8877.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" <tr>\n", | |
" <th>69076</th>\n", | |
" <td>jhu-csse</td>\n", | |
" <td>confirmed_cumulative_num</td>\n", | |
" <td>01133</td>\n", | |
" <td>20221117</td>\n", | |
" <td>county</td>\n", | |
" <td>day</td>\n", | |
" <td>NaN</td>\n", | |
" <td>20221118</td>\n", | |
" <td>1</td>\n", | |
" <td>0</td>\n", | |
" <td>5</td>\n", | |
" <td>5</td>\n", | |
" <td>8877.0</td>\n", | |
" <td>NaN</td>\n", | |
" <td>NaN</td>\n", | |
" </tr>\n", | |
" </tbody>\n", | |
"</table>\n", | |
"<p>69077 rows × 15 columns</p>\n", | |
"</div>" | |
], | |
"text/plain": [ | |
" source signal geo_value time_value geo_type \n", | |
"0 jhu-csse confirmed_cumulative_num 01001 20200122 county \\\n", | |
"1 jhu-csse confirmed_cumulative_num 01001 20200123 county \n", | |
"2 jhu-csse confirmed_cumulative_num 01001 20200124 county \n", | |
"3 jhu-csse confirmed_cumulative_num 01001 20200125 county \n", | |
"4 jhu-csse confirmed_cumulative_num 01001 20200126 county \n", | |
"... ... ... ... ... ... \n", | |
"69072 jhu-csse confirmed_cumulative_num 01133 20221113 county \n", | |
"69073 jhu-csse confirmed_cumulative_num 01133 20221114 county \n", | |
"69074 jhu-csse confirmed_cumulative_num 01133 20221115 county \n", | |
"69075 jhu-csse confirmed_cumulative_num 01133 20221116 county \n", | |
"69076 jhu-csse confirmed_cumulative_num 01133 20221117 county \n", | |
"\n", | |
" time_type direction issue lag missing_value missing_stderr \n", | |
"0 day NaN 20200514 113 0 5 \\\n", | |
"1 day NaN 20200514 112 0 5 \n", | |
"2 day NaN 20200514 111 0 5 \n", | |
"3 day NaN 20200514 110 0 5 \n", | |
"4 day NaN 20200514 109 0 5 \n", | |
"... ... ... ... ... ... ... \n", | |
"69072 day NaN 20221114 1 0 5 \n", | |
"69073 day NaN 20221115 1 0 5 \n", | |
"69074 day NaN 20221116 1 0 5 \n", | |
"69075 day NaN 20221117 1 0 5 \n", | |
"69076 day NaN 20221118 1 0 5 \n", | |
"\n", | |
" missing_sample_size value stderr sample_size \n", | |
"0 5 0.0 NaN NaN \n", | |
"1 5 0.0 NaN NaN \n", | |
"2 5 0.0 NaN NaN \n", | |
"3 5 0.0 NaN NaN \n", | |
"4 5 0.0 NaN NaN \n", | |
"... ... ... ... ... \n", | |
"69072 5 8875.0 NaN NaN \n", | |
"69073 5 8875.0 NaN NaN \n", | |
"69074 5 8875.0 NaN NaN \n", | |
"69075 5 8877.0 NaN NaN \n", | |
"69076 5 8877.0 NaN NaN \n", | |
"\n", | |
"[69077 rows x 15 columns]" | |
] | |
}, | |
"execution_count": 14, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"df[\"lag\"] = df[\"lag\"].astype(\"i4\")\n", | |
"df" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 16, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"BlockManager\n", | |
"Items: Index(['source', 'signal', 'geo_value', 'time_value', 'geo_type', 'time_type',\n", | |
" 'direction', 'issue', 'lag', 'missing_value', 'missing_stderr',\n", | |
" 'missing_sample_size', 'value', 'stderr', 'sample_size'],\n", | |
" dtype='object')\n", | |
"Axis 1: RangeIndex(start=0, stop=69077, step=1)\n", | |
"NumericBlock: [ 6 12 13 14], 4 x 69077, dtype: float64\n", | |
"NumericBlock: slice(9, 12, 1), 3 x 69077, dtype: int64\n", | |
"ExtensionBlock: slice(5, 6, 1), 1 x 69077, dtype: category\n", | |
"NumericBlock: slice(3, 4, 1), 1 x 69077, dtype: int64\n", | |
"ObjectBlock: slice(2, 3, 1), 1 x 69077, dtype: object\n", | |
"ExtensionBlock: slice(1, 2, 1), 1 x 69077, dtype: category\n", | |
"ExtensionBlock: slice(0, 1, 1), 1 x 69077, dtype: category\n", | |
"NumericBlock: slice(7, 8, 1), 1 x 69077, dtype: int64\n", | |
"ExtensionBlock: slice(4, 5, 1), 1 x 69077, dtype: category\n", | |
"NumericBlock: slice(8, 9, 1), 1 x 69077, dtype: int32" | |
] | |
}, | |
"execution_count": 16, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"# This hows us the Numpy arrays that back the DataFrame\n", | |
"df._data" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 12, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([20200122, 20200123, 20200124, ..., 20221115, 20221116, 20221117])" | |
] | |
}, | |
"execution_count": 12, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"df[\"time_value\"].values" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 13, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([20200122, 20200123, 20200124, ..., 20221115, 20221116, 20221117])" | |
] | |
}, | |
"execution_count": 13, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"df[\"time_value\"].values.base" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 14, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([[ nan, nan, nan, ..., nan, nan, nan],\n", | |
" [ 0., 0., 0., ..., 8875., 8877., 8877.],\n", | |
" [ nan, nan, nan, ..., nan, nan, nan],\n", | |
" [ nan, nan, nan, ..., nan, nan, nan]])" | |
] | |
}, | |
"execution_count": 14, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"# We can see that values shares an array with the other float64 columns: stderr, sample_size, direction\n", | |
"df[\"value\"].values.base" | |
] | |
}, | |
{ | |
"attachments": { | |
"image.png": { | |
"image/png": "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" | |
} | |
}, | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Pandas Apply, Map, Transform Are A Performance Trap\n", | |
"\n", | |
"These functions are there so you could pipe a custom method into a chain of Pandas operations.\n", | |
"\n", | |
"Under the hood though, they call a Python for loop over the rows of the dataframe, which is slow.\n", | |
"\n", | |
"\n", | |
"\n", | |
"See more here: https://pythonspeed.com/articles/pandas-vectorization/" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 162, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"assert (df.groupby(\"geo_value\").apply(lambda x: x[\"value\"].sum()) == df.groupby(\"geo_value\")[\"value\"].sum()).all()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 160, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"16.7 ms ± 355 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" | |
] | |
} | |
], | |
"source": [ | |
"%%timeit \n", | |
"df.groupby(\"geo_value\").apply(lambda x: x[\"value\"].sum())" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 161, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"3.38 ms ± 18 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" | |
] | |
} | |
], | |
"source": [ | |
"%%timeit\n", | |
"df.groupby(\"geo_value\")[\"value\"].sum()" | |
] | |
}, | |
{ | |
"attachments": {}, | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Numba Can Speed Up Python Code With Low Effort\n", | |
"\n", | |
"Numba is a JIT compiler for Python. It can speed up Python code by compiling it to machine code. See: https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#jit-functions\n", | |
"\n", | |
"\n", | |
"What it's useful for:\n", | |
"- numerical code\n", | |
"- code that uses NumPy arrays\n", | |
"- code with fixed-size data structures\n", | |
"- limited/buggy support for datetimes and timedeltas, see: https://github.com/numba/numba/issues/5780\n", | |
"\n", | |
"What it's not useful for:\n", | |
"- code that uses Python objects, like lists and dictionaries\n", | |
"- code with variable-size data structures\n", | |
"- code that uses Python's built-in types, like strings and tuples\n", | |
"- code that requires C extensions\n", | |
"\n", | |
"See more here: \n", | |
"- speeding up an Ising model using Numba https://matthewrocklin.com/blog/work/2015/02/28/Ising\n", | |
"- speeding up an Advent of Code problem using Numba https://github.com/dshemetov/advent-of-code-solutions/blob/main/src/advent2022/p11.py" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 18, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import numba as nb\n", | |
"\n", | |
"# The first argument is the type annotation and it is optional\n", | |
"# see here for the full list of supported types: https://numba.readthedocs.io/en/stable/reference/types.html#signatures \n", | |
"@nb.jit(\"int64(int64[:])\", cache=True, nopython=True)\n", | |
"def sum_numba(l: np.ndarray) -> int:\n", | |
" total = 0\n", | |
" for i in l:\n", | |
" total += i\n", | |
" return total" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 60, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"566 µs ± 2.09 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n" | |
] | |
} | |
], | |
"source": [ | |
"%%timeit\n", | |
"sum_numba(ln)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 23, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"@nb.njit()\n", | |
"def diff(x: np.ndarray) -> np.ndarray:\n", | |
" return np.diff(x)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 20, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"diff (array(int64, 1d, C),)\n", | |
"--------------------------------------------------------------------------------\n", | |
"# File: /tmp/ipykernel_138759/4104025119.py\n", | |
"# --- LINE 1 --- \n", | |
"\n", | |
"@nb.njit()\n", | |
"\n", | |
"# --- LINE 2 --- \n", | |
"\n", | |
"def diff(x: np.ndarray) -> np.ndarray:\n", | |
"\n", | |
" # --- LINE 3 --- \n", | |
" # label 0\n", | |
" # x = arg(0, name=x) :: array(int64, 1d, C)\n", | |
" # $2load_global.0 = global(np: <module 'numpy' from '/home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/numpy/__init__.py'>) :: Module(<module 'numpy' from '/home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/numpy/__init__.py'>)\n", | |
" # $4load_method.1 = getattr(value=$2load_global.0, attr=diff) :: Function(<function diff at 0x7fc09027b880>)\n", | |
" # del $2load_global.0\n", | |
" # $8call_method.3 = call $4load_method.1(x, func=$4load_method.1, args=[Var(x, 4104025119.py:3)], kws=(), vararg=None, varkwarg=None, target=None) :: (array(int64, 1d, C), omitted(default=1)) -> array(int64, 1d, C)\n", | |
" # del x\n", | |
" # del $4load_method.1\n", | |
" # $10return_value.4 = cast(value=$8call_method.3) :: array(int64, 1d, C)\n", | |
" # del $8call_method.3\n", | |
" # return $10return_value.4\n", | |
"\n", | |
" return np.diff(x)\n", | |
"\n", | |
"\n", | |
"================================================================================\n" | |
] | |
} | |
], | |
"source": [ | |
"diff.inspect_types()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 135, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"ename": "TypingError", | |
"evalue": "Failed in nopython mode pipeline (step: nopython frontend)\nNo implementation of function Function(datetime64[]) found for signature:\n \n >>> <unknown function>(Literal[str](2023-03-02))\n \nThere are 2 candidate implementations:\n - Of which 1 did not match due to:\n Overload in function 'make_callable_template.<locals>.generic': File: numba/core/typing/templates.py: Line 174.\n With argument(s): '(unicode_type)':\n Rejected as the implementation raised a specific error:\n TypingError: Casting unicode_type to datetime64[] directly is unsupported.\n raised from /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/numba/core/typing/builtins.py:833\n - Of which 1 did not match due to:\n Overload in function 'make_callable_template.<locals>.generic': File: numba/core/typing/templates.py: Line 174.\n With argument(s): '(Literal[str](2023-03-02))':\n Rejected as the implementation raised a specific error:\n TypingError: Casting Literal[str](2023-03-02) to datetime64[] directly is unsupported.\n raised from /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/numba/core/typing/builtins.py:833\n\nDuring: resolving callee type: class(datetime64[])\nDuring: typing of call at /tmp/ipykernel_133357/1513295171.py (4)\n\n\nFile \"../../../../../../tmp/ipykernel_133357/1513295171.py\", line 4:\n<source missing, REPL/exec in use?>\n", | |
"output_type": "error", | |
"traceback": [ | |
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", | |
"\u001b[0;31mTypingError\u001b[0m Traceback (most recent call last)", | |
"Cell \u001b[0;32mIn[135], line 6\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[39m@nb\u001b[39m\u001b[39m.\u001b[39mjit(nopython\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m)\n\u001b[1;32m 3\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39madd_dates\u001b[39m():\n\u001b[1;32m 4\u001b[0m \u001b[39mreturn\u001b[39;00m np\u001b[39m.\u001b[39mdatetime64(\u001b[39m\"\u001b[39m\u001b[39m2023-03-02\u001b[39m\u001b[39m\"\u001b[39m) \u001b[39m+\u001b[39m np\u001b[39m.\u001b[39mtimedelta64(\u001b[39m1\u001b[39m, \u001b[39m\"\u001b[39m\u001b[39mD\u001b[39m\u001b[39m\"\u001b[39m)\n\u001b[0;32m----> 6\u001b[0m add_dates()\n", | |
"File \u001b[0;32m~/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/numba/core/dispatcher.py:468\u001b[0m, in \u001b[0;36m_DispatcherBase._compile_for_args\u001b[0;34m(self, *args, **kws)\u001b[0m\n\u001b[1;32m 464\u001b[0m msg \u001b[39m=\u001b[39m (\u001b[39mf\u001b[39m\u001b[39m\"\u001b[39m\u001b[39m{\u001b[39;00m\u001b[39mstr\u001b[39m(e)\u001b[39m.\u001b[39mrstrip()\u001b[39m}\u001b[39;00m\u001b[39m \u001b[39m\u001b[39m\\n\u001b[39;00m\u001b[39m\\n\u001b[39;00m\u001b[39mThis error may have been caused \u001b[39m\u001b[39m\"\u001b[39m\n\u001b[1;32m 465\u001b[0m \u001b[39mf\u001b[39m\u001b[39m\"\u001b[39m\u001b[39mby the following argument(s):\u001b[39m\u001b[39m\\n\u001b[39;00m\u001b[39m{\u001b[39;00margs_str\u001b[39m}\u001b[39;00m\u001b[39m\\n\u001b[39;00m\u001b[39m\"\u001b[39m)\n\u001b[1;32m 466\u001b[0m e\u001b[39m.\u001b[39mpatch_message(msg)\n\u001b[0;32m--> 468\u001b[0m error_rewrite(e, \u001b[39m'\u001b[39;49m\u001b[39mtyping\u001b[39;49m\u001b[39m'\u001b[39;49m)\n\u001b[1;32m 469\u001b[0m \u001b[39mexcept\u001b[39;00m errors\u001b[39m.\u001b[39mUnsupportedError \u001b[39mas\u001b[39;00m e:\n\u001b[1;32m 470\u001b[0m \u001b[39m# Something unsupported is present in the user code, add help info\u001b[39;00m\n\u001b[1;32m 471\u001b[0m error_rewrite(e, \u001b[39m'\u001b[39m\u001b[39munsupported_error\u001b[39m\u001b[39m'\u001b[39m)\n", | |
"File \u001b[0;32m~/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/numba/core/dispatcher.py:409\u001b[0m, in \u001b[0;36m_DispatcherBase._compile_for_args.<locals>.error_rewrite\u001b[0;34m(e, issue_type)\u001b[0m\n\u001b[1;32m 407\u001b[0m \u001b[39mraise\u001b[39;00m e\n\u001b[1;32m 408\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m--> 409\u001b[0m \u001b[39mraise\u001b[39;00m e\u001b[39m.\u001b[39mwith_traceback(\u001b[39mNone\u001b[39;00m)\n", | |
"\u001b[0;31mTypingError\u001b[0m: Failed in nopython mode pipeline (step: nopython frontend)\nNo implementation of function Function(datetime64[]) found for signature:\n \n >>> <unknown function>(Literal[str](2023-03-02))\n \nThere are 2 candidate implementations:\n - Of which 1 did not match due to:\n Overload in function 'make_callable_template.<locals>.generic': File: numba/core/typing/templates.py: Line 174.\n With argument(s): '(unicode_type)':\n Rejected as the implementation raised a specific error:\n TypingError: Casting unicode_type to datetime64[] directly is unsupported.\n raised from /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/numba/core/typing/builtins.py:833\n - Of which 1 did not match due to:\n Overload in function 'make_callable_template.<locals>.generic': File: numba/core/typing/templates.py: Line 174.\n With argument(s): '(Literal[str](2023-03-02))':\n Rejected as the implementation raised a specific error:\n TypingError: Casting Literal[str](2023-03-02) to datetime64[] directly is unsupported.\n raised from /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/numba/core/typing/builtins.py:833\n\nDuring: resolving callee type: class(datetime64[])\nDuring: typing of call at /tmp/ipykernel_133357/1513295171.py (4)\n\n\nFile \"../../../../../../tmp/ipykernel_133357/1513295171.py\", line 4:\n<source missing, REPL/exec in use?>\n" | |
] | |
} | |
], | |
"source": [ | |
"# This is a bug: https://github.com/numba/numba/issues/5780\n", | |
"@nb.jit(nopython=True)\n", | |
"def add_dates():\n", | |
" return np.datetime64(\"2023-03-02\") + np.timedelta64(1, \"D\")\n", | |
"\n", | |
"add_dates()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 134, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"numpy.datetime64('2023-03-03')" | |
] | |
}, | |
"execution_count": 134, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"# This is a workaround\n", | |
"start_date = np.datetime64(\"2023-03-02\")\n", | |
"day_delta = np.timedelta64(1, \"D\")\n", | |
"\n", | |
"@nb.jit(nopython=True)\n", | |
"def add_dates():\n", | |
" return start_date + day_delta\n", | |
"\n", | |
"add_dates()" | |
] | |
}, | |
{ | |
"attachments": {}, | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Cython Can Speed Up Python Code With Moderate Effort\n", | |
"\n", | |
"Cython is a superset of Python that compiles to C and can then be imported into your Python code.\n", | |
"\n", | |
"In this notebook, we've been using the convenient %%cython magic command, which compiles the code in the cell and imports it into the notebook.\n", | |
"But, there are many compilation workflows for it, see: https://cython.readthedocs.io/en/stable/src/userguide/source_files_and_compilation.html\n", | |
"\n", | |
"It is useful for:\n", | |
"- similar to Numba, but more flexible (can import C and C++ libs, can use C++ classes, etc.)\n", | |
"\n", | |
"It is not useful for:\n", | |
"- quick speedups, requires a lot more investment\n", | |
"- can quickly turn into a full-on C/C++ coding project\n", | |
"\n", | |
"See:\n", | |
"- Speeding up an Ising model with Cython https://jakevdp.github.io/blog/2017/12/11/live-coding-cython-ising-model/" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 101, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"In file included from /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/numpy/core/include/numpy/ndarraytypes.h:1948,\n", | |
" from /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n", | |
" from /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/numpy/core/include/numpy/arrayobject.h:5,\n", | |
" from /home/dskel/.cache/ipython/cython/_cython_magic_9de3c4c32d43323bd19c6fd47083842c.c:769:\n", | |
"/home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n", | |
" 17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n", | |
" | ^~~~~~~\n" | |
] | |
} | |
], | |
"source": [ | |
"%%cython\n", | |
"\n", | |
"cimport cython\n", | |
"\n", | |
"import numpy as np\n", | |
"cimport numpy as np\n", | |
"\n", | |
"from libc.math cimport exp\n", | |
"from libc.stdlib cimport rand\n", | |
"cdef extern from \"limits.h\":\n", | |
" int RAND_MAX\n", | |
"\n", | |
"\n", | |
"@cython.boundscheck(False)\n", | |
"@cython.wraparound(False)\n", | |
"def cy_ising_step(np.int64_t[:, :] field, float beta=0.4):\n", | |
" cdef int N = field.shape[0]\n", | |
" cdef int M = field.shape[1]\n", | |
" cdef int n_offset, m_offset, n, m\n", | |
" for n_offset in range(2):\n", | |
" for m_offset in range(2):\n", | |
" for n in range(n_offset, N, 2):\n", | |
" for m in range(m_offset, M, 2):\n", | |
" _cy_ising_update(field, n, m, beta)\n", | |
" return np.array(field)\n", | |
"\n", | |
"\n", | |
"@cython.boundscheck(False)\n", | |
"@cython.wraparound(False)\n", | |
"cdef _cy_ising_update(np.int64_t[:, :] field, int n, int m, float beta):\n", | |
" cdef int total = 0\n", | |
" cdef int N = field.shape[0]\n", | |
" cdef int M = field.shape[1]\n", | |
" cdef int i, j\n", | |
" for i in range(n-1, n+2):\n", | |
" for j in range(m-1, m+2):\n", | |
" if i == n and j == m:\n", | |
" continue\n", | |
" total += field[i % N, j % M]\n", | |
" cdef float dE = 2 * field[n, m] * total\n", | |
" if dE <= 0:\n", | |
" field[n, m] *= -1\n", | |
" elif exp(-dE * beta) * RAND_MAX > rand():\n", | |
" field[n, m] *= -1" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 103, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"application/vnd.jupyter.widget-view+json": { | |
"model_id": "fc351e9613db4bdebbf2feb1f6b173cd", | |
"version_major": 2, | |
"version_minor": 0 | |
}, | |
"text/plain": [ | |
"interactive(children=(IntSlider(value=25, description='frame', max=50), Output()), _dom_classes=('widget-inter…" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
}, | |
{ | |
"data": { | |
"text/plain": [ | |
"<function __main__.display_ising_sequence.<locals>._show(frame=(0, 50))>" | |
] | |
}, | |
"execution_count": 103, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"from ipywidgets import interact\n", | |
"from PIL import Image\n", | |
"\n", | |
"def random_spin_field(N, M):\n", | |
" return np.random.choice([-1, 1], size=(N, M))\n", | |
"\n", | |
"def display_spin_field(field):\n", | |
" return Image.fromarray(np.uint8((field + 1) * 0.5 * 255)) # 0 ... 255\n", | |
"\n", | |
"def display_ising_sequence(images):\n", | |
" def _show(frame=(0, len(images) - 1)):\n", | |
" return display_spin_field(images[frame])\n", | |
" return interact(_show)\n", | |
"\n", | |
"images = [random_spin_field(200, 200)]\n", | |
"for i in range(50):\n", | |
" images.append(cy_ising_step(images[-1].copy()))\n", | |
"display_ising_sequence(images)" | |
] | |
}, | |
{ | |
"attachments": {}, | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Polars Exists and Is Fast\n", | |
"\n", | |
"Polars is a DataFrame library that is written in Rust and has bindings for Python, R, and Go. It is very fast. See: \n", | |
"- Broad comparison between analytics libraries/databases: https://h2oai.github.io/db-benchmark/\n", | |
"- This comparison with Pandas: https://gist.github.com/koaning/5a0f3f27164859c42da5f20148ef3856#file-polars-ipynb\n", | |
"\n", | |
"Polars uses an Expressions syntax that feels (to me) like a blend between Pandas and SQL. See here: https://pola-rs.github.io/polars-book/user-guide/dsl/expressions.html\n", | |
"\n", | |
"Polars:\n", | |
"- supports lazy evaluation (unlike Pandas)\n", | |
"- supports parallel execution (unlike Pandas)\n", | |
"\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 89, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/html": [ | |
"<div><style>\n", | |
".dataframe > thead > tr > th,\n", | |
".dataframe > tbody > tr > td {\n", | |
" text-align: right;\n", | |
"}\n", | |
"</style>\n", | |
"<small>shape: (69077, 15)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>geo_value</th><th>signal</th><th>source</th><th>geo_type</th><th>time_type</th><th>time_value</th><th>direction</th><th>issue</th><th>lag</th><th>missing_value</th><th>missing_stderr</th><th>missing_sample_size</th><th>value</th><th>stderr</th><th>sample_size</th></tr><tr><td>i64</td><td>str</td><td>str</td><td>str</td><td>str</td><td>i64</td><td>str</td><td>i64</td><td>i64</td><td>i64</td><td>i64</td><td>i64</td><td>f64</td><td>str</td><td>str</td></tr></thead><tbody><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td></tr><tr><td>1003</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td></tr><tr><td>1005</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td></tr><tr><td>1007</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td></tr><tr><td>1009</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td></tr><tr><td>1011</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td></tr><tr><td>1013</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td></tr><tr><td>1015</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td></tr><tr><td>1017</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td></tr><tr><td>1019</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td></tr><tr><td>1021</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td></tr><tr><td>1023</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td></tr><tr><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td></tr><tr><td>1111</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>6057.0</td><td>null</td><td>null</td></tr><tr><td>1113</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>12125.0</td><td>null</td><td>null</td></tr><tr><td>1115</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>30679.0</td><td>null</td><td>null</td></tr><tr><td>1117</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>72537.0</td><td>null</td><td>null</td></tr><tr><td>1119</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>3003.0</td><td>null</td><td>null</td></tr><tr><td>1121</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>26982.0</td><td>null</td><td>null</td></tr><tr><td>1123</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>13678.0</td><td>null</td><td>null</td></tr><tr><td>1125</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>66506.0</td><td>null</td><td>null</td></tr><tr><td>1127</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>22519.0</td><td>null</td><td>null</td></tr><tr><td>1129</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>4190.0</td><td>null</td><td>null</td></tr><tr><td>1131</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>3457.0</td><td>null</td><td>null</td></tr><tr><td>1133</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>8877.0</td><td>null</td><td>null</td></tr></tbody></table></div>" | |
], | |
"text/plain": [ | |
"shape: (69_077, 15)\n", | |
"┌───────────┬──────────────┬──────────┬──────────┬───┬────────────┬─────────┬────────┬─────────────┐\n", | |
"│ geo_value ┆ signal ┆ source ┆ geo_type ┆ … ┆ missing_sa ┆ value ┆ stderr ┆ sample_size │\n", | |
"│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ mple_size ┆ --- ┆ --- ┆ --- │\n", | |
"│ i64 ┆ str ┆ str ┆ str ┆ ┆ --- ┆ f64 ┆ str ┆ str │\n", | |
"│ ┆ ┆ ┆ ┆ ┆ i64 ┆ ┆ ┆ │\n", | |
"╞═══════════╪══════════════╪══════════╪══════════╪═══╪════════════╪═════════╪════════╪═════════════╡\n", | |
"│ 1001 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 5 ┆ 0.0 ┆ null ┆ null │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1003 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 5 ┆ 0.0 ┆ null ┆ null │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1005 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 5 ┆ 0.0 ┆ null ┆ null │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1007 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 5 ┆ 0.0 ┆ null ┆ null │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │\n", | |
"│ 1127 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 5 ┆ 22519.0 ┆ null ┆ null │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1129 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 5 ┆ 4190.0 ┆ null ┆ null │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1131 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 5 ┆ 3457.0 ┆ null ┆ null │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1133 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 5 ┆ 8877.0 ┆ null ┆ null │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"└───────────┴──────────────┴──────────┴──────────┴───┴────────────┴─────────┴────────┴─────────────┘" | |
] | |
}, | |
"execution_count": 89, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"import polars as pl\n", | |
"\n", | |
"pldf = pl.read_csv(\"/home/dskel/Documents/Code/Delphi/delphi-dev/confirmed_cumulative_num_01_counties.csv\")\n", | |
"pldf" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 84, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/html": [ | |
"<div><style>\n", | |
".dataframe > thead > tr > th,\n", | |
".dataframe > tbody > tr > td {\n", | |
" text-align: right;\n", | |
"}\n", | |
"</style>\n", | |
"<small>shape: (69077,)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>time_value</th></tr><tr><td>i64</td></tr></thead><tbody><tr><td>20200122</td></tr><tr><td>20200122</td></tr><tr><td>20200122</td></tr><tr><td>20200122</td></tr><tr><td>20200122</td></tr><tr><td>20200122</td></tr><tr><td>20200122</td></tr><tr><td>20200122</td></tr><tr><td>20200122</td></tr><tr><td>20200122</td></tr><tr><td>20200122</td></tr><tr><td>20200122</td></tr><tr><td>…</td></tr><tr><td>20221117</td></tr><tr><td>20221117</td></tr><tr><td>20221117</td></tr><tr><td>20221117</td></tr><tr><td>20221117</td></tr><tr><td>20221117</td></tr><tr><td>20221117</td></tr><tr><td>20221117</td></tr><tr><td>20221117</td></tr><tr><td>20221117</td></tr><tr><td>20221117</td></tr><tr><td>20221117</td></tr></tbody></table></div>" | |
], | |
"text/plain": [ | |
"shape: (69_077,)\n", | |
"Series: 'time_value' [i64]\n", | |
"[\n", | |
"\t20200122\n", | |
"\t20200122\n", | |
"\t20200122\n", | |
"\t20200122\n", | |
"\t20200122\n", | |
"\t20200122\n", | |
"\t20200122\n", | |
"\t20200122\n", | |
"\t20200122\n", | |
"\t20200122\n", | |
"\t20200122\n", | |
"\t20200122\n", | |
"\t…\n", | |
"\t20221117\n", | |
"\t20221117\n", | |
"\t20221117\n", | |
"\t20221117\n", | |
"\t20221117\n", | |
"\t20221117\n", | |
"\t20221117\n", | |
"\t20221117\n", | |
"\t20221117\n", | |
"\t20221117\n", | |
"\t20221117\n", | |
"\t20221117\n", | |
"\t20221117\n", | |
"]" | |
] | |
}, | |
"execution_count": 84, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"pldf[\"time_value\"]" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 90, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/html": [ | |
"<div><style>\n", | |
".dataframe > thead > tr > th,\n", | |
".dataframe > tbody > tr > td {\n", | |
" text-align: right;\n", | |
"}\n", | |
"</style>\n", | |
"<small>shape: (69077, 16)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>geo_value</th><th>signal</th><th>source</th><th>geo_type</th><th>time_type</th><th>time_value</th><th>direction</th><th>issue</th><th>lag</th><th>missing_value</th><th>missing_stderr</th><th>missing_sample_size</th><th>value</th><th>stderr</th><th>sample_size</th><th>signal_upper</th></tr><tr><td>i64</td><td>str</td><td>str</td><td>str</td><td>str</td><td>i64</td><td>str</td><td>i64</td><td>i64</td><td>i64</td><td>i64</td><td>i64</td><td>f64</td><td>str</td><td>str</td><td>str</td></tr></thead><tbody><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1003</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1005</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1007</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1009</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1011</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1013</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1015</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1017</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1019</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1021</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1023</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20200122</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td></tr><tr><td>1111</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>6057.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1113</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>12125.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1115</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>30679.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1117</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>72537.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1119</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>3003.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1121</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>26982.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1123</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>13678.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1125</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>66506.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1127</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>22519.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1129</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>4190.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1131</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>3457.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr><tr><td>1133</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>20221117</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>8877.0</td><td>null</td><td>null</td><td>"CONFIRMED_CUMU…</td></tr></tbody></table></div>" | |
], | |
"text/plain": [ | |
"shape: (69_077, 16)\n", | |
"┌───────────┬──────────────┬──────────┬──────────┬───┬─────────┬────────┬─────────────┬────────────┐\n", | |
"│ geo_value ┆ signal ┆ source ┆ geo_type ┆ … ┆ value ┆ stderr ┆ sample_size ┆ signal_upp │\n", | |
"│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ er │\n", | |
"│ i64 ┆ str ┆ str ┆ str ┆ ┆ f64 ┆ str ┆ str ┆ --- │\n", | |
"│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ str │\n", | |
"╞═══════════╪══════════════╪══════════╪══════════╪═══╪═════════╪════════╪═════════════╪════════════╡\n", | |
"│ 1001 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 0.0 ┆ null ┆ null ┆ CONFIRMED_ │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ CUMULATIVE │\n", | |
"│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ _NUM │\n", | |
"│ 1003 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 0.0 ┆ null ┆ null ┆ CONFIRMED_ │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ CUMULATIVE │\n", | |
"│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ _NUM │\n", | |
"│ 1005 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 0.0 ┆ null ┆ null ┆ CONFIRMED_ │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ CUMULATIVE │\n", | |
"│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ _NUM │\n", | |
"│ 1007 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 0.0 ┆ null ┆ null ┆ CONFIRMED_ │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ CUMULATIVE │\n", | |
"│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ _NUM │\n", | |
"│ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │\n", | |
"│ 1127 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 22519.0 ┆ null ┆ null ┆ CONFIRMED_ │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ CUMULATIVE │\n", | |
"│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ _NUM │\n", | |
"│ 1129 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 4190.0 ┆ null ┆ null ┆ CONFIRMED_ │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ CUMULATIVE │\n", | |
"│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ _NUM │\n", | |
"│ 1131 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 3457.0 ┆ null ┆ null ┆ CONFIRMED_ │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ CUMULATIVE │\n", | |
"│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ _NUM │\n", | |
"│ 1133 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 8877.0 ┆ null ┆ null ┆ CONFIRMED_ │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ CUMULATIVE │\n", | |
"│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ _NUM │\n", | |
"└───────────┴──────────────┴──────────┴──────────┴───┴─────────┴────────┴─────────────┴────────────┘" | |
] | |
}, | |
"execution_count": 90, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"pldf.with_columns(\n", | |
" pl.col(\"signal\").str.to_uppercase().alias(\"signal_upper\"),\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 91, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/html": [ | |
"<div><style>\n", | |
".dataframe > thead > tr > th,\n", | |
".dataframe > tbody > tr > td {\n", | |
" text-align: right;\n", | |
"}\n", | |
"</style>\n", | |
"<small>shape: (69077, 16)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>geo_value</th><th>signal</th><th>source</th><th>geo_type</th><th>time_type</th><th>time_value</th><th>direction</th><th>issue</th><th>lag</th><th>missing_value</th><th>missing_stderr</th><th>missing_sample_size</th><th>value</th><th>stderr</th><th>sample_size</th><th>value_diff</th></tr><tr><td>i64</td><td>str</td><td>str</td><td>str</td><td>str</td><td>date</td><td>str</td><td>i64</td><td>i64</td><td>i64</td><td>i64</td><td>i64</td><td>f64</td><td>str</td><td>str</td><td>f64</td></tr></thead><tbody><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2020-01-22</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>null</td></tr><tr><td>1003</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2020-01-22</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>null</td></tr><tr><td>1005</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2020-01-22</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>null</td></tr><tr><td>1007</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2020-01-22</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>null</td></tr><tr><td>1009</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2020-01-22</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>null</td></tr><tr><td>1011</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2020-01-22</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>null</td></tr><tr><td>1013</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2020-01-22</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>null</td></tr><tr><td>1015</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2020-01-22</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>null</td></tr><tr><td>1017</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2020-01-22</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>null</td></tr><tr><td>1019</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2020-01-22</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>null</td></tr><tr><td>1021</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2020-01-22</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>null</td></tr><tr><td>1023</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2020-01-22</td><td>null</td><td>20200514</td><td>113</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>null</td></tr><tr><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td></tr><tr><td>1111</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2022-11-17</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>6057.0</td><td>null</td><td>null</td><td>0.0</td></tr><tr><td>1113</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2022-11-17</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>12125.0</td><td>null</td><td>null</td><td>0.0</td></tr><tr><td>1115</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2022-11-17</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>30679.0</td><td>null</td><td>null</td><td>0.0</td></tr><tr><td>1117</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2022-11-17</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>72537.0</td><td>null</td><td>null</td><td>0.0</td></tr><tr><td>1119</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2022-11-17</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>3003.0</td><td>null</td><td>null</td><td>0.0</td></tr><tr><td>1121</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2022-11-17</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>26982.0</td><td>null</td><td>null</td><td>0.0</td></tr><tr><td>1123</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2022-11-17</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>13678.0</td><td>null</td><td>null</td><td>0.0</td></tr><tr><td>1125</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2022-11-17</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>66506.0</td><td>null</td><td>null</td><td>0.0</td></tr><tr><td>1127</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2022-11-17</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>22519.0</td><td>null</td><td>null</td><td>0.0</td></tr><tr><td>1129</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2022-11-17</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>4190.0</td><td>null</td><td>null</td><td>0.0</td></tr><tr><td>1131</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2022-11-17</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>3457.0</td><td>null</td><td>null</td><td>0.0</td></tr><tr><td>1133</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2022-11-17</td><td>null</td><td>20221118</td><td>1</td><td>0</td><td>5</td><td>5</td><td>8877.0</td><td>null</td><td>null</td><td>0.0</td></tr></tbody></table></div>" | |
], | |
"text/plain": [ | |
"shape: (69_077, 16)\n", | |
"┌───────────┬──────────────┬──────────┬──────────┬───┬─────────┬────────┬─────────────┬────────────┐\n", | |
"│ geo_value ┆ signal ┆ source ┆ geo_type ┆ … ┆ value ┆ stderr ┆ sample_size ┆ value_diff │\n", | |
"│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │\n", | |
"│ i64 ┆ str ┆ str ┆ str ┆ ┆ f64 ┆ str ┆ str ┆ f64 │\n", | |
"╞═══════════╪══════════════╪══════════╪══════════╪═══╪═════════╪════════╪═════════════╪════════════╡\n", | |
"│ 1001 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 0.0 ┆ null ┆ null ┆ null │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1003 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 0.0 ┆ null ┆ null ┆ null │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1005 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 0.0 ┆ null ┆ null ┆ null │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1007 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 0.0 ┆ null ┆ null ┆ null │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │\n", | |
"│ 1127 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 22519.0 ┆ null ┆ null ┆ 0.0 │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1129 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 4190.0 ┆ null ┆ null ┆ 0.0 │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1131 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 3457.0 ┆ null ┆ null ┆ 0.0 │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1133 ┆ confirmed_cu ┆ jhu-csse ┆ county ┆ … ┆ 8877.0 ┆ null ┆ null ┆ 0.0 │\n", | |
"│ ┆ mulative_num ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"└───────────┴──────────────┴──────────┴──────────┴───┴─────────┴────────┴─────────────┴────────────┘" | |
] | |
}, | |
"execution_count": 91, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"pldf = pldf.with_columns(\n", | |
" pl.col(\"time_value\").cast(\"str\").str.strptime(pl.Date, fmt=\"%Y%m%d\")\n", | |
").with_columns(\n", | |
" pl.col(\"value\").diff().over([\"source\", \"signal\", \"geo_value\"]).alias(\"value_diff\")\n", | |
")\n", | |
"pldf" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 99, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"pldf = pldf.with_columns(\n", | |
" pldf.groupby_rolling(\"time_value\", period=\"7d\", by=[\"source\", \"signal\", \"geo_value\"]).agg([\n", | |
" pl.col(\"value_diff\").mean().alias(\"value_diff_smooth\"),\n", | |
" pl.col(\"issue\").max().alias(\"issue\"),\n", | |
" pl.col(\"lag\").max().alias(\"lag\"),\n", | |
" ])\n", | |
")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 100, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/html": [ | |
"<div><style>\n", | |
".dataframe > thead > tr > th,\n", | |
".dataframe > tbody > tr > td {\n", | |
" text-align: right;\n", | |
"}\n", | |
"</style>\n", | |
"<small>shape: (31, 17)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>geo_value</th><th>signal</th><th>source</th><th>geo_type</th><th>time_type</th><th>time_value</th><th>direction</th><th>issue</th><th>lag</th><th>missing_value</th><th>missing_stderr</th><th>missing_sample_size</th><th>value</th><th>stderr</th><th>sample_size</th><th>value_diff</th><th>value_diff_smooth</th></tr><tr><td>i64</td><td>str</td><td>str</td><td>str</td><td>str</td><td>date</td><td>str</td><td>i64</td><td>i64</td><td>i64</td><td>i64</td><td>i64</td><td>f64</td><td>str</td><td>str</td><td>f64</td><td>f64</td></tr></thead><tbody><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-01</td><td>null</td><td>20210401</td><td>37</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>21.285714</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-02</td><td>null</td><td>20210401</td><td>36</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>22.857143</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-03</td><td>null</td><td>20210401</td><td>35</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>20.142857</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-04</td><td>null</td><td>20210401</td><td>34</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>17.285714</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-05</td><td>null</td><td>20210401</td><td>33</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>15.0</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-06</td><td>null</td><td>20210401</td><td>32</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>13.714286</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-07</td><td>null</td><td>20210401</td><td>31</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>11.857143</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-08</td><td>null</td><td>20210401</td><td>30</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>13.428571</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-09</td><td>null</td><td>20210401</td><td>29</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>9.714286</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-10</td><td>null</td><td>20210401</td><td>28</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>12.428571</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-11</td><td>null</td><td>20210401</td><td>27</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>12.142857</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-12</td><td>null</td><td>20210401</td><td>26</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>10.857143</td></tr><tr><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td><td>…</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-20</td><td>null</td><td>20210401</td><td>18</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>13.428571</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-21</td><td>null</td><td>20210401</td><td>17</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>12.428571</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-22</td><td>null</td><td>20210401</td><td>16</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>6.571429</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-23</td><td>null</td><td>20210401</td><td>15</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>7.285714</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-24</td><td>null</td><td>20210401</td><td>14</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>7.142857</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-25</td><td>null</td><td>20210401</td><td>13</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>6.428571</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-26</td><td>null</td><td>20210401</td><td>12</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>6.428571</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-27</td><td>null</td><td>20210401</td><td>11</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>7.428571</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-28</td><td>null</td><td>20210401</td><td>10</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>8.142857</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-29</td><td>null</td><td>20210401</td><td>9</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>8.571429</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-30</td><td>null</td><td>20210401</td><td>8</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>7.857143</td></tr><tr><td>1001</td><td>"confirmed_cumu…</td><td>"jhu-csse"</td><td>"county"</td><td>"day"</td><td>2021-03-31</td><td>null</td><td>20210401</td><td>7</td><td>0</td><td>5</td><td>5</td><td>0.0</td><td>null</td><td>null</td><td>0.0</td><td>8.0</td></tr></tbody></table></div>" | |
], | |
"text/plain": [ | |
"shape: (31, 17)\n", | |
"┌───────────┬─────────────┬──────────┬──────────┬───┬────────┬───────────┬────────────┬────────────┐\n", | |
"│ geo_value ┆ signal ┆ source ┆ geo_type ┆ … ┆ stderr ┆ sample_si ┆ value_diff ┆ value_diff │\n", | |
"│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ ze ┆ --- ┆ _smooth │\n", | |
"│ i64 ┆ str ┆ str ┆ str ┆ ┆ str ┆ --- ┆ f64 ┆ --- │\n", | |
"│ ┆ ┆ ┆ ┆ ┆ ┆ str ┆ ┆ f64 │\n", | |
"╞═══════════╪═════════════╪══════════╪══════════╪═══╪════════╪═══════════╪════════════╪════════════╡\n", | |
"│ 1001 ┆ confirmed_c ┆ jhu-csse ┆ county ┆ … ┆ null ┆ null ┆ 0.0 ┆ 21.285714 │\n", | |
"│ ┆ umulative_n ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ ┆ um ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1001 ┆ confirmed_c ┆ jhu-csse ┆ county ┆ … ┆ null ┆ null ┆ 0.0 ┆ 22.857143 │\n", | |
"│ ┆ umulative_n ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ ┆ um ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1001 ┆ confirmed_c ┆ jhu-csse ┆ county ┆ … ┆ null ┆ null ┆ 0.0 ┆ 20.142857 │\n", | |
"│ ┆ umulative_n ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ ┆ um ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1001 ┆ confirmed_c ┆ jhu-csse ┆ county ┆ … ┆ null ┆ null ┆ 0.0 ┆ 17.285714 │\n", | |
"│ ┆ umulative_n ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ ┆ um ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │\n", | |
"│ 1001 ┆ confirmed_c ┆ jhu-csse ┆ county ┆ … ┆ null ┆ null ┆ 0.0 ┆ 8.142857 │\n", | |
"│ ┆ umulative_n ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ ┆ um ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1001 ┆ confirmed_c ┆ jhu-csse ┆ county ┆ … ┆ null ┆ null ┆ 0.0 ┆ 8.571429 │\n", | |
"│ ┆ umulative_n ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ ┆ um ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1001 ┆ confirmed_c ┆ jhu-csse ┆ county ┆ … ┆ null ┆ null ┆ 0.0 ┆ 7.857143 │\n", | |
"│ ┆ umulative_n ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ ┆ um ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ 1001 ┆ confirmed_c ┆ jhu-csse ┆ county ┆ … ┆ null ┆ null ┆ 0.0 ┆ 8.0 │\n", | |
"│ ┆ umulative_n ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"│ ┆ um ┆ ┆ ┆ ┆ ┆ ┆ ┆ │\n", | |
"└───────────┴─────────────┴──────────┴──────────┴───┴────────┴───────────┴────────────┴────────────┘" | |
] | |
}, | |
"execution_count": 100, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"from datetime import date\n", | |
"\n", | |
"pldf.filter( \n", | |
" (pl.col(\"signal\") == \"confirmed_cumulative_num\") &\n", | |
" (pl.col(\"geo_value\") == 1001) &\n", | |
" (pl.col(\"time_value\").is_between(date(2021, 3, 1), date(2021, 3, 31)))\n", | |
")" | |
] | |
}, | |
{ | |
"attachments": {}, | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Line Profiling Python Code\n", | |
"\n", | |
"This uses the [line_profiler package](https://github.com/pyutils/line_profiler)." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 105, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"%load_ext line_profiler" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 27, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def test():\n", | |
" df = pd.DataFrame(row_dicts)\n", | |
" df[\"time_value\"] = pd.to_datetime(df[\"time_value\"], format=\"%Y%m%d\")\n", | |
" df[\"issue\"] = pd.to_datetime(df[\"issue\"], format=\"%Y%m%d\")\n", | |
" df[\"geo_value\"] = df[\"geo_value\"].astype(str).str.zfill(5).astype(\"category\")\n", | |
" df[\"value_diff\"] = df.groupby([\"source\", \"signal\", \"geo_value\"])[\"value\"].diff()\n", | |
" return df" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 124, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Timer unit: 1e-09 s\n", | |
"\n", | |
"Total time: 0.258499 s\n", | |
"File: /tmp/ipykernel_133357/225339769.py\n", | |
"Function: test at line 1\n", | |
"\n", | |
"Line # Hits Time Per Hit % Time Line Contents\n", | |
"==============================================================\n", | |
" 1 def test():\n", | |
" 2 1 182160020.0 182160020.0 70.5 df = pd.DataFrame(row_dicts)\n", | |
" 3 1 33241850.0 33241850.0 12.9 df[\"time_value\"] = pd.to_datetime(df[\"time_value\"], format=\"%Y%m%d\")\n", | |
" 4 1 2311023.0 2311023.0 0.9 df[\"issue\"] = pd.to_datetime(df[\"issue\"], format=\"%Y%m%d\")\n", | |
" 5 1 31815835.0 31815835.0 12.3 df[\"geo_value\"] = df[\"geo_value\"].astype(str).str.zfill(5).astype(\"category\")\n", | |
" 6 1 8969550.0 8969550.0 3.5 df[\"value_diff\"] = df.groupby([\"source\", \"signal\", \"geo_value\"])[\"value\"].diff()\n", | |
" 7 1 223.0 223.0 0.0 return df" | |
] | |
} | |
], | |
"source": [ | |
"%lprun -s -f test test()" | |
] | |
}, | |
{ | |
"attachments": {}, | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"There is also cProfile, built-in to Python. https://docs.python.org/3/library/profile.html#module-cProfile" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 28, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
" 356543 function calls (356170 primitive calls) in 0.289 seconds\n", | |
"\n", | |
" Ordered by: cumulative time\n", | |
" List reduced from 674 to 34 due to restriction <0.05>\n", | |
"\n", | |
" ncalls tottime percall cumtime percall filename:lineno(function)\n", | |
" 1 0.002 0.002 0.289 0.289 /tmp/ipykernel_138759/225339769.py:1(test)\n", | |
" 1 0.000 0.000 0.197 0.197 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/frame.py:640(__init__)\n", | |
" 1 0.000 0.000 0.180 0.180 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/internals/construction.py:484(nested_data_to_arrays)\n", | |
" 1 0.000 0.000 0.180 0.180 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/internals/construction.py:775(to_arrays)\n", | |
" 1 0.000 0.000 0.090 0.090 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/internals/construction.py:886(_list_of_dict_to_arrays)\n", | |
" 36 0.089 0.002 0.089 0.002 {pandas._libs.lib.maybe_convert_objects}\n", | |
" 1 0.000 0.000 0.089 0.089 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/internals/construction.py:923(_finalize_columns_and_data)\n", | |
" 1 0.000 0.000 0.089 0.089 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/internals/construction.py:1001(convert_object_array)\n", | |
" 1 0.000 0.000 0.089 0.089 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/internals/construction.py:1067(<listcomp>)\n", | |
" 15 0.000 0.000 0.089 0.006 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/internals/construction.py:1023(convert)\n", | |
" 1 0.022 0.022 0.059 0.059 {pandas._libs.lib.fast_unique_multiple_list_gen}\n", | |
" 2 0.000 0.000 0.039 0.020 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/tools/datetimes.py:687(to_datetime)\n", | |
" 69078 0.032 0.000 0.037 0.000 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/internals/construction.py:910(<genexpr>)\n", | |
" 2 0.000 0.000 0.036 0.018 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/tools/datetimes.py:352(_convert_listlike_datetimes)\n", | |
" 2 0.000 0.000 0.035 0.017 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/tools/datetimes.py:473(_array_strptime_with_fallback)\n", | |
" 1 0.000 0.000 0.033 0.033 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/strings/accessor.py:120(wrapper)\n", | |
" 1 0.000 0.000 0.033 0.033 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/strings/accessor.py:1619(zfill)\n", | |
" 1 0.000 0.000 0.033 0.033 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/strings/object_array.py:44(_str_map)\n", | |
" 1 0.013 0.013 0.030 0.030 {pandas._libs.lib.map_infer_mask}\n", | |
" 1 0.024 0.024 0.024 0.024 {pandas._libs.lib.dicts_to_array}\n", | |
" 2 0.021 0.010 0.023 0.011 {pandas._libs.tslibs.strptime.array_strptime}\n", | |
" 1 0.001 0.001 0.018 0.018 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/internals/construction.py:97(arrays_to_mgr)\n", | |
" 69077 0.013 0.000 0.017 0.000 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/strings/accessor.py:1683(<lambda>)\n", | |
" 1 0.000 0.000 0.016 0.016 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/internals/managers.py:2119(create_block_manager_from_column_arrays)\n", | |
" 14 0.006 0.000 0.012 0.001 {built-in method builtins.any}\n", | |
" 1 0.000 0.000 0.010 0.010 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/internals/managers.py:1823(_consolidate_inplace)\n", | |
" 1 0.000 0.000 0.010 0.010 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/internals/managers.py:2262(_consolidate)\n", | |
" 3 0.006 0.002 0.010 0.003 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/internals/managers.py:2279(_merge_blocks)\n", | |
" 1 0.000 0.000 0.009 0.009 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/groupby/groupby.py:3822(diff)\n", | |
" 1 0.000 0.000 0.009 0.009 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/groupby/groupby.py:3776(shift)\n", | |
" 3 0.000 0.000 0.009 0.003 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/algorithms.py:595(factorize)\n", | |
" 3 0.000 0.000 0.008 0.003 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/algorithms.py:533(factorize_array)\n", | |
" 1 0.000 0.000 0.008 0.008 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/groupby/ops.py:871(group_info)\n", | |
" 1 0.000 0.000 0.008 0.008 /home/dskel/Documents/Code/Delphi/delphi-dev/venv/lib/python3.10/site-packages/pandas/core/groupby/ops.py:886(_get_compressed_codes)\n", | |
"\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"import cProfile\n", | |
"import pstats\n", | |
"\n", | |
"with cProfile.Profile() as pr:\n", | |
" pr.enable()\n", | |
" test()\n", | |
" pr.disable()\n", | |
" pr.create_stats()\n", | |
" pstats.Stats(pr).sort_stats(\"cumtime\").print_stats(.05)" | |
] | |
}, | |
{ | |
"attachments": {}, | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## The Dis Module Lets You See Python Byte Code\n", | |
"\n", | |
"The dis module lets you see the Python byte code for a function. See: https://docs.python.org/3/library/dis.html\n", | |
"\n", | |
"The usefulness is multiplicative with familiarity with [Python bytecode instructions](https://docs.python.org/3/library/dis.html#python-bytecode-instructions), but occasionally it can give hints about what is fast in Python and what is slow." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 75, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
" 5 0 LOAD_FAST 0 (a)\n", | |
" 2 LOAD_FAST 1 (b)\n", | |
" 4 ROT_TWO\n", | |
" 6 STORE_FAST 1 (b)\n", | |
" 8 STORE_FAST 0 (a)\n", | |
"\n", | |
" 6 10 LOAD_FAST 0 (a)\n", | |
" 12 LOAD_FAST 1 (b)\n", | |
" 14 BUILD_TUPLE 2\n", | |
" 16 RETURN_VALUE\n" | |
] | |
} | |
], | |
"source": [ | |
"import dis\n", | |
"\n", | |
"def swap(a: int, b: int) -> tuple[int, int]:\n", | |
" b, a = a, b\n", | |
" return a, b\n", | |
"\n", | |
"dis.dis(swap)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 77, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
" 2 0 LOAD_FAST 0 (a)\n", | |
" 2 STORE_FAST 2 (c)\n", | |
"\n", | |
" 3 4 LOAD_FAST 1 (b)\n", | |
" 6 STORE_FAST 0 (a)\n", | |
"\n", | |
" 4 8 LOAD_FAST 2 (c)\n", | |
" 10 STORE_FAST 1 (b)\n", | |
"\n", | |
" 5 12 LOAD_FAST 0 (a)\n", | |
" 14 LOAD_FAST 1 (b)\n", | |
" 16 BUILD_TUPLE 2\n", | |
" 18 RETURN_VALUE\n" | |
] | |
} | |
], | |
"source": [ | |
"def swap2(a: int, b: int) -> tuple[int, int]:\n", | |
" c = a\n", | |
" a = b\n", | |
" b = c\n", | |
" return a, b\n", | |
"\n", | |
"dis.dis(swap2)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 164, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"()" | |
] | |
}, | |
"execution_count": 164, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"swap.__code__.co_names" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 165, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"('a', 'b')" | |
] | |
}, | |
"execution_count": 165, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"swap.__code__.co_varnames" | |
] | |
}, | |
{ | |
"attachments": {}, | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## You Can Change The Meaning of 42\n", | |
"\n", | |
"Taken from: https://jakevdp.github.io/blog/2014/05/09/why-python-is-slow/\n", | |
"\n", | |
"Small integers in Python are cached, so variables with the same value point to the same object. This is a performance optimization." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 65, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"IntStruct(ob_digit=42, refcount=1541)" | |
] | |
}, | |
"execution_count": 65, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"import ctypes\n", | |
"\n", | |
"class IntStruct(ctypes.Structure):\n", | |
" _fields_ = [(\"ob_refcnt\", ctypes.c_long),\n", | |
" (\"ob_type\", ctypes.c_void_p),\n", | |
" (\"ob_size\", ctypes.c_ulong),\n", | |
" (\"ob_digit\", ctypes.c_long)]\n", | |
" \n", | |
" def __repr__(self):\n", | |
" return (\"IntStruct(ob_digit={self.ob_digit}, \"\n", | |
" \"refcount={self.ob_refcnt})\").format(self=self)\n", | |
"\n", | |
"num = 42\n", | |
"IntStruct.from_address(id(42))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 74, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"\u001b[0;31mSignature:\u001b[0m \u001b[0mid\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m/\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", | |
"\u001b[0;31mDocstring:\u001b[0m\n", | |
"Return the identity of an object.\n", | |
"\n", | |
"This is guaranteed to be unique among simultaneously existing objects.\n", | |
"(CPython uses the object's memory address.)\n", | |
"\u001b[0;31mType:\u001b[0m builtin_function_or_method" | |
] | |
} | |
], | |
"source": [ | |
"?id" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 73, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"True" | |
] | |
}, | |
"execution_count": 73, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"x = 42\n", | |
"y = 42\n", | |
"id(x) == id(y)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 66, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"False" | |
] | |
}, | |
"execution_count": 66, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"x = 1234\n", | |
"y = 1234\n", | |
"id(x) == id(y)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# WARNING: Never do this!\n", | |
"id42 = id(42)\n", | |
"iptr = IntStruct.from_address(id42)\n", | |
"iptr.ob_digit = 1 # now Python's 42 contains a 1!\n", | |
"\n", | |
"42 == 1\n", | |
"# True" | |
] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "venv", | |
"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.10.9" | |
}, | |
"orig_nbformat": 4 | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment