Skip to content

Instantly share code, notes, and snippets.

@deeplook
Last active April 3, 2020 09:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save deeplook/11428e31ec64b7a734055ff6ed131210 to your computer and use it in GitHub Desktop.
Save deeplook/11428e31ec64b7a734055ff6ed131210 to your computer and use it in GitHub Desktop.
A sperical view to the COVID-19 distribution.
Display the source blob
Display the rendered blob
Raw
{
"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