{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "source": [ "[this doc on github](https://github.com/dotnet/interactive/tree/main/samples/notebooks/polyglot)\n", "\n", "# Visualizing the Johns Hopkins COVID-19 time series data\n", "\n", "Also, due to travel restrictions, you should run this at home on isolated compute.\n", "\n", "*And don't forget to wash your hands.*\n", "\n", "Since Johns Hopkins has put COVID-19 time series data on [GitHub](https://github.com/CSSEGISandData/COVID-19), let's take a look at it. We can download it using PowerShell:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "pwsh" } }, "outputs": [], "source": [ "Invoke-WebRequest -Uri \"https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv\" -OutFile \"./Confirmed.csv\"\n", "Invoke-WebRequest -Uri \"https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_global.csv\" -OutFile \"./Deaths.csv\"\n", "Invoke-WebRequest -Uri \"https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_recovered_global.csv\" -OutFile \"./Recovered.csv\"" ] }, { "cell_type": "markdown", "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "source": [ "It needs a little cleaning up:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" } }, "outputs": [], "source": [ "using System.IO;\n", "using System.Text.RegularExpressions;\n", "\n", "Clean(\"Confirmed.csv\");\n", "Clean(\"Deaths.csv\");\n", "Clean(\"Recovered.csv\");\n", "\n", "void Clean(string filePath)\n", "{\n", " var raw = File.ReadAllText(filePath);\n", " var regex = new Regex(\"\\\\\\\"(.*?)\\\\\\\"\");\n", " var cleaned = regex.Replace(raw, m => m.Value.Replace(\",\", \" in \")); \n", " File.WriteAllText(filePath, cleaned);\n", "}\n", "\n", "\"All cleaned up!\"" ] }, { "cell_type": "markdown", "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "source": [ "Next, let's load it into a data frame." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" } }, "outputs": [], "source": [ "#r \"nuget:Microsoft.Data.Analysis,0.2.0\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" } }, "outputs": [], "source": [ "using Microsoft.Data.Analysis;\n", "\n", "var deaths = DataFrame.LoadCsv(\"./Deaths.csv\");\n", "var confirmed = DataFrame.LoadCsv(\"./Confirmed.csv\");\n", "var recovered = DataFrame.LoadCsv(\"./Recovered.csv\");\n", "var displayedValue = display(\"Processing data\");\n", "var offset = 4;\n", "var series = new List();\n", "for(var i = offset; i < deaths.Columns.Count; i++){\n", " var date = deaths.Columns[i].Name;\n", " var deathFiltered = deaths[deaths.Columns[i].ElementwiseNotEquals(0)];\n", " var confirmedFiltered = confirmed[confirmed.Columns[i].ElementwiseNotEquals(0)];\n", " var recoveredFiltered = recovered[recovered.Columns[i].ElementwiseNotEquals(0)];\n", "\n", " displayedValue.Update($\"processing {date}\");\n", " series.Add(new {\n", " date = date,\n", " deathsSeries = new {\n", " latitude = deathFiltered[\"Lat\"],\n", " longitude = deathFiltered[\"Long\"],\n", " data = deathFiltered.Columns[i]\n", " },\n", " confirmedSeries = new {\n", " latitude = confirmedFiltered[\"Lat\"],\n", " longitude = confirmedFiltered[\"Long\"],\n", " data = confirmedFiltered.Columns[i]\n", " },\n", " recoveredSeries = new {\n", " latitude = recoveredFiltered[\"Lat\"],\n", " longitude = recoveredFiltered[\"Long\"],\n", " data = recoveredFiltered.Columns[i]\n", " }\n", " });\n", "}\n", "\n", "displayedValue.Update(\"Ready.\");" ] }, { "cell_type": "markdown", "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "source": [ "Because we've stored our data in top-level variables (`deathsSeries`, `confirmedSeries`, `recoveredSeries`, etc.) in the C# kernel, they're accessible from JavaScript latter with [variable sharing](https://github.com/dotnet/interactive/blob/main/docs/variable-sharing.md). The data is obtained as JSON and we can plot it using the library of our choice, pulled in using [RequireJS](https://requirejs.org/). \n", "\n", "We'll use [Plotly](https://plot.ly/)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "javascript" } }, "outputs": [], "source": [ "plot = function (plotTarget,series) {\n", " let loadPlotly = interactive.configureRequire({\n", " context: \"COVID\",\n", " paths: {\n", " plotly: \"https://cdn.plot.ly/plotly-latest.min\"\n", " }\n", " });\n", " \n", " loadPlotly([\"plotly\"], (Plotly) => {\n", " if (typeof (updateInterval) !== 'undefined') {\n", " clearInterval(updateInterval);\n", " }\n", "\n", " let index = 0;\n", "\n", " if (typeof (document.getElementById(plotTarget)) !== 'undefined') {\n", " var { deathsSeries, confirmedSeries, recoveredSeries, date } = series[index];\n", " var recovered = {\n", " name: \"Recovered\",\n", " type: \"scattergeo\",\n", " mode: \"markers\",\n", " geo: \"geo\",\n", " lat: recoveredSeries.latitude,\n", " lon: recoveredSeries.longitude,\n", " text: recoveredSeries.data,\n", " marker: {\n", " symbol: \"square\",\n", " color: \"Green\"\n", " }\n", " };\n", "\n", " var deaths = {\n", " name: \"Fatal\",\n", " type: \"scattergeo\",\n", " geo: \"geo2\",\n", " mode: \"markers\",\n", " lat: deathsSeries.latitude,\n", " lon: deathsSeries.longitude,\n", " text: deathsSeries.data,\n", " marker: {\n", " symbol: \"circle\",\n", " color: \"Black\"\n", " }\n", " };\n", "\n", " var confirmed = {\n", " name: \"Total confirmed\",\n", " type: \"scattergeo\",\n", " geo: \"geo3\",\n", " mode: \"markers\",\n", " lat: confirmedSeries.latitude,\n", " lon: confirmedSeries.longitude,\n", " text: confirmedSeries.data,\n", " marker: {\n", " symbol: \"diamond\",\n", " color: \"#DC7633\"\n", " }\n", " };\n", " \n", "\n", " var traces = [recovered, deaths, confirmed];\n", "\n", " var layout = {\n", " title: \"COVID-19 cases (\" + date + \")\",\n", " grid: { columns: 3, rows: 1 },\n", " geo: {\n", " scope: \"world\",\n", " showland: true,\n", " showcountries: true,\n", " bgcolor: \"rgb(90,90,90)\",\n", " landcolor: \"rgb(250,250,250)\",\n", " domain: {\n", " row: 0,\n", " column: 0\n", " }\n", " },\n", " geo2: {\n", " scope: \"world\",\n", " showland: true,\n", " showcountries: true,\n", " bgcolor: \"rgb(90,90,90)\",\n", " landcolor: \"rgb(250,250,250)\",\n", " domain: {\n", " row: 0,\n", " column: 1\n", " }\n", " },\n", " geo3: {\n", " scope: \"world\",\n", " showland: true,\n", " showcountries: true,\n", " bgcolor: \"rgb(90,90,90)\",\n", " landcolor: \"rgb(250,250,250)\",\n", " domain: {\n", " row: 0,\n", " column: 2\n", " }\n", " }\n", " };\n", " if (typeof (document.getElementById(plotTarget)) !== 'undefined') {\n", " Plotly.newPlot(plotTarget, traces, layout);\n", " }\n", " let updateCovidPlot = () => {\n", " if (typeof (document.getElementById(plotTarget)) !== 'undefined') {\n", " index++;\n", " if (index === series.length) {\n", " clearInterval(updateInterval);\n", " return;\n", " }\n", " var { deathsSeries, confirmedSeries, recoveredSeries, currentSeries, date } = series[index];\n", " Plotly.animate(\"plotlyChartCovid\", {\n", " data: [\n", " {\n", " lat: recoveredSeries.latitude,\n", " lon: recoveredSeries.longitude,\n", " text: recoveredSeries.data\n", " },\n", " {\n", " lat: deathsSeries.latitude,\n", " lon: deathsSeries.longitude,\n", " text: deathsSeries.data\n", " },\n", " {\n", " lat: confirmedSeries.latitude,\n", " lon: confirmedSeries.longitude,\n", " text: confirmedSeries.data\n", " }],\n", " layout: {\n", " title: \"COVID-19 \" + date\n", " }\n", " });\n", " }\n", " }\n", " updateInterval = setInterval(() => updateCovidPlot(), 250);\n", " }\n", " });\n", "};" ] }, { "cell_type": "markdown", "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "source": [ "Notice the `setInterval` call near the end of the previous cell. This rechecks the data in the kernel and updates the plot.\n", "\n", "Back on the kernel, we can now update the data so that the kernel can see it.\n", "\n", "Yes, this is a contrived example, and we're planning to support true streaming data, but it's a start." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "dotnet_interactive": { "language": "html" } }, "outputs": [], "source": [ "
\n", "\n", "#!js\n", "#!share --from csharp series\n", "plot(\"plotlyChartCovid\",series);" ] } ], "metadata": { "kernelspec": { "display_name": ".NET (C#)", "language": "C#", "name": ".net-csharp" }, "language_info": { "name": "polyglot-notebook" }, "polyglot_notebook": { "kernelInfo": { "defaultKernelName": "csharp", "items": [ { "aliases": [ "C#", "c#" ], "languageName": "C#", "name": "csharp" }, { "aliases": [ "F#", "f#" ], "languageName": "F#", "name": "fsharp" }, { "aliases": [], "languageName": "HTML", "name": "html" }, { "aliases": [], "languageName": "KQL", "name": "kql" }, { "aliases": [], "languageName": "Mermaid", "name": "mermaid" }, { "aliases": [ "powershell" ], "languageName": "PowerShell", "name": "pwsh" }, { "aliases": [], "languageName": "SQL", "name": "sql" }, { "aliases": [], "name": "value" } ] } } }, "nbformat": 4, "nbformat_minor": 2 }