Last active
April 3, 2020 09:43
-
-
Save deeplook/11428e31ec64b7a734055ff6ed131210 to your computer and use it in GitHub Desktop.
A sperical view to the COVID-19 distribution.
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": [ | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "# Covidohedron" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 1, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "from math import pi" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 2, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "import geojson\n", | |
| "import ipyvolume as ipv\n", | |
| "from ipyvolume.datasets import UrlCached\n", | |
| "from PIL import Image\n", | |
| "import numpy as np\n", | |
| "import pandas as pd" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Helpers" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 3, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "def radius_sphere(volume):\n", | |
| " return (volume / pi / 4 * 3) ** (1/3)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 4, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "def sphere(x, y, z, radius, num=30, color=None, texture=None, wireframe=False):\n", | |
| " \"\"\"Create a sphere mesh with origin at x, y, z and radius.\n", | |
| " \"\"\"\n", | |
| " assert num > 0\n", | |
| " u = np.linspace(0, 1, num)\n", | |
| " v = np.linspace(0, 1, num)\n", | |
| " u, v = np.meshgrid(u, v)\n", | |
| " phi = u * 2 * np.pi\n", | |
| " theta = v * np.pi\n", | |
| " x = x + radius * np.cos(phi) * np.sin(theta)\n", | |
| " y = y + radius * np.cos(theta)\n", | |
| " z = z + radius * np.sin(phi) * np.sin(theta)\n", | |
| "\n", | |
| " kwargs = dict(color=color or \"blue\", texture=texture, wireframe=wireframe)\n", | |
| " return ipv.plot_mesh(x, y, z, u=0.75-u, v=1-v, **kwargs)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 5, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "pi_div_180 = np.pi / 180\n", | |
| "\n", | |
| "def lonlat2xyz(lon, lat, radius=1, unit='deg'):\n", | |
| " \"Convert lat/lon pair to Cartesian x/y/z triple.\"\n", | |
| "\n", | |
| " if unit == 'deg':\n", | |
| " lat = lat * pi_div_180\n", | |
| " lon = lon * pi_div_180\n", | |
| " cos_lat = np.cos(lat)\n", | |
| " x = radius * cos_lat * np.sin(lon)\n", | |
| " y = radius * np.sin(lat)\n", | |
| " z = radius * cos_lat * np.cos(lon)\n", | |
| " return (x, y, z)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Data processing" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 6, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "base = (\"https://raw.githubusercontent.com/CSSEGISandData/COVID-19/\"\n", | |
| " \"master/csse_covid_19_data/csse_covid_19_time_series/\")\n", | |
| "df_confirmed = pd.read_csv(base + \"time_series_covid19_confirmed_global.csv\")\n", | |
| "df_confirmed[\"Province/State\"].fillna(\"\", inplace=True)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 7, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "df = df_confirmed\n", | |
| "provinces = df[\"Province/State\"]\n", | |
| "countries = df[\"Country/Region\"]\n", | |
| "locations = list(zip(df.Lat, df.Long))\n", | |
| "day_cols = [col for col in df.columns if col.count(\"/\") == 2]" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 8, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Data range: 1/22/20 – 4/2/20.\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "print(f\"Data range: {day_cols[0]} – {day_cols[-1]}.\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 9, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "# df.describe()" | |
| ] | |
| }, | |
| { | |
| "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>Province/State</th>\n", | |
| " <th>Country/Region</th>\n", | |
| " <th>Lat</th>\n", | |
| " <th>Long</th>\n", | |
| " <th>1/22/20</th>\n", | |
| " <th>1/23/20</th>\n", | |
| " <th>1/24/20</th>\n", | |
| " <th>1/25/20</th>\n", | |
| " <th>1/26/20</th>\n", | |
| " <th>1/27/20</th>\n", | |
| " <th>...</th>\n", | |
| " <th>3/24/20</th>\n", | |
| " <th>3/25/20</th>\n", | |
| " <th>3/26/20</th>\n", | |
| " <th>3/27/20</th>\n", | |
| " <th>3/28/20</th>\n", | |
| " <th>3/29/20</th>\n", | |
| " <th>3/30/20</th>\n", | |
| " <th>3/31/20</th>\n", | |
| " <th>4/1/20</th>\n", | |
| " <th>4/2/20</th>\n", | |
| " </tr>\n", | |
| " </thead>\n", | |
| " <tbody>\n", | |
| " <tr>\n", | |
| " <th>0</th>\n", | |
| " <td></td>\n", | |
| " <td>Afghanistan</td>\n", | |
| " <td>33.0000</td>\n", | |
| " <td>65.0000</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>...</td>\n", | |
| " <td>74</td>\n", | |
| " <td>84</td>\n", | |
| " <td>94</td>\n", | |
| " <td>110</td>\n", | |
| " <td>110</td>\n", | |
| " <td>120</td>\n", | |
| " <td>170</td>\n", | |
| " <td>174</td>\n", | |
| " <td>237</td>\n", | |
| " <td>273</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <th>1</th>\n", | |
| " <td></td>\n", | |
| " <td>Albania</td>\n", | |
| " <td>41.1533</td>\n", | |
| " <td>20.1683</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>...</td>\n", | |
| " <td>123</td>\n", | |
| " <td>146</td>\n", | |
| " <td>174</td>\n", | |
| " <td>186</td>\n", | |
| " <td>197</td>\n", | |
| " <td>212</td>\n", | |
| " <td>223</td>\n", | |
| " <td>243</td>\n", | |
| " <td>259</td>\n", | |
| " <td>277</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <th>2</th>\n", | |
| " <td></td>\n", | |
| " <td>Algeria</td>\n", | |
| " <td>28.0339</td>\n", | |
| " <td>1.6596</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>...</td>\n", | |
| " <td>264</td>\n", | |
| " <td>302</td>\n", | |
| " <td>367</td>\n", | |
| " <td>409</td>\n", | |
| " <td>454</td>\n", | |
| " <td>511</td>\n", | |
| " <td>584</td>\n", | |
| " <td>716</td>\n", | |
| " <td>847</td>\n", | |
| " <td>986</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <th>3</th>\n", | |
| " <td></td>\n", | |
| " <td>Andorra</td>\n", | |
| " <td>42.5063</td>\n", | |
| " <td>1.5218</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>...</td>\n", | |
| " <td>164</td>\n", | |
| " <td>188</td>\n", | |
| " <td>224</td>\n", | |
| " <td>267</td>\n", | |
| " <td>308</td>\n", | |
| " <td>334</td>\n", | |
| " <td>370</td>\n", | |
| " <td>376</td>\n", | |
| " <td>390</td>\n", | |
| " <td>428</td>\n", | |
| " </tr>\n", | |
| " <tr>\n", | |
| " <th>4</th>\n", | |
| " <td></td>\n", | |
| " <td>Angola</td>\n", | |
| " <td>-11.2027</td>\n", | |
| " <td>17.8739</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>0</td>\n", | |
| " <td>...</td>\n", | |
| " <td>3</td>\n", | |
| " <td>3</td>\n", | |
| " <td>4</td>\n", | |
| " <td>4</td>\n", | |
| " <td>5</td>\n", | |
| " <td>7</td>\n", | |
| " <td>7</td>\n", | |
| " <td>7</td>\n", | |
| " <td>8</td>\n", | |
| " <td>8</td>\n", | |
| " </tr>\n", | |
| " </tbody>\n", | |
| "</table>\n", | |
| "<p>5 rows × 76 columns</p>\n", | |
| "</div>" | |
| ], | |
| "text/plain": [ | |
| " Province/State Country/Region Lat Long 1/22/20 1/23/20 1/24/20 \\\n", | |
| "0 Afghanistan 33.0000 65.0000 0 0 0 \n", | |
| "1 Albania 41.1533 20.1683 0 0 0 \n", | |
| "2 Algeria 28.0339 1.6596 0 0 0 \n", | |
| "3 Andorra 42.5063 1.5218 0 0 0 \n", | |
| "4 Angola -11.2027 17.8739 0 0 0 \n", | |
| "\n", | |
| " 1/25/20 1/26/20 1/27/20 ... 3/24/20 3/25/20 3/26/20 3/27/20 \\\n", | |
| "0 0 0 0 ... 74 84 94 110 \n", | |
| "1 0 0 0 ... 123 146 174 186 \n", | |
| "2 0 0 0 ... 264 302 367 409 \n", | |
| "3 0 0 0 ... 164 188 224 267 \n", | |
| "4 0 0 0 ... 3 3 4 4 \n", | |
| "\n", | |
| " 3/28/20 3/29/20 3/30/20 3/31/20 4/1/20 4/2/20 \n", | |
| "0 110 120 170 174 237 273 \n", | |
| "1 197 212 223 243 259 277 \n", | |
| "2 454 511 584 716 847 986 \n", | |
| "3 308 334 370 376 390 428 \n", | |
| "4 5 7 7 7 8 8 \n", | |
| "\n", | |
| "[5 rows x 76 columns]" | |
| ] | |
| }, | |
| "execution_count": 10, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "df.head()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Map texture" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 11, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "url = \"https://www.evl.uic.edu/pape/data/Earth/1024/BigEarth.jpg\"\n", | |
| "image_file = UrlCached(url)\n", | |
| "map_texture = Image.open(image_file.fetch())" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Borders" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 12, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "url = \"https://raw.githubusercontent.com/johan/world.geo.json/master/countries.geo.json\"\n", | |
| "borders_file = UrlCached(url)\n", | |
| "data = geojson.load(open(borders_file.fetch()))\n", | |
| "lon, lat = np.array(list(geojson.utils.coords(data))).T\n", | |
| "z = [lonlat2xyz(xi, yi) for (xi, yi) in zip(lon, lat)]\n", | |
| "borders_xyz = np.array(z).T" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## COVID-19 data" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 13, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "lon_lat_conf = list(zip(df.Long, df.Lat, df[\"4/1/20\"]))\n", | |
| "xyz = [lonlat2xyz(lon, lat) for lon, lat, conf in lon_lat_conf]\n", | |
| "c19_size = [radius_sphere(conf) for lon, lat, conf in lon_lat_conf]\n", | |
| "c19_xyz = np.array(xyz).T" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Final plot" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 14, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "application/vnd.jupyter.widget-view+json": { | |
| "model_id": "42a493e7d8294c80871f65f5096f69cb", | |
| "version_major": 2, | |
| "version_minor": 0 | |
| }, | |
| "text/plain": [ | |
| "VBox(children=(Figure(camera=PerspectiveCamera(fov=46.0, position=(0.0, 0.0, 2.0), quaternion=(0.0, 0.0, 0.0, …" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "output_type": "display_data" | |
| } | |
| ], | |
| "source": [ | |
| "fig = ipv.figure()\n", | |
| "s = sphere(0, 0, 0, radius=1, num=24, color=\"white\", texture=map_texture)\n", | |
| "borders = ipv.scatter(*borders_xyz, size=1, color='limegreen', marker='sphere')\n", | |
| "c19 = ipv.scatter(*c19_xyz, size=c19_size, color=\"red\", marker=\"sphere\")\n", | |
| "# ipv.view(azimuth=0, elevation=90) # lon/lat ??\n", | |
| "ipv.show()" | |
| ] | |
| } | |
| ], | |
| "metadata": { | |
| "kernelspec": { | |
| "display_name": "Python 3", | |
| "language": "python", | |
| "name": "python3" | |
| }, | |
| "language_info": { | |
| "codemirror_mode": { | |
| "name": "ipython", | |
| "version": 3 | |
| }, | |
| "file_extension": ".py", | |
| "mimetype": "text/x-python", | |
| "name": "python", | |
| "nbconvert_exporter": "python", | |
| "pygments_lexer": "ipython3", | |
| "version": "3.7.6" | |
| } | |
| }, | |
| "nbformat": 4, | |
| "nbformat_minor": 4 | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment