An Exception was encountered at 'In [5]'.

Warning: This dashboard contains the results of a predictive model that was not built by an epidemiologist.

Note: Click a country name to open a search results page for that country’s COVID-19 news.

Based on data up to: 2021-09-27

Execution using papermill encountered an exception here and stopped:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/tmp/ipykernel_2469/2477690625.py in <module>
      1 #hide_input
----> 2 fig.update_layout(
      3     updatemenus=[
      4         dict(
      5             buttons=[

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/plotly/basedatatypes.py in update_layout(self, dict1, overwrite, **kwargs)
   1401             The Figure object that the update_layout method was called on
   1402         """
-> 1403         self.layout.update(dict1, overwrite=overwrite, **kwargs)
   1404         return self
   1405 

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/plotly/basedatatypes.py in update(self, dict1, overwrite, **kwargs)
   5080             with self.figure.batch_update():
   5081                 BaseFigure._perform_update(self, dict1, overwrite=overwrite)
-> 5082                 BaseFigure._perform_update(self, kwargs, overwrite=overwrite)
   5083         else:
   5084             BaseFigure._perform_update(self, dict1, overwrite=overwrite)

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/contextlib.py in __exit__(self, type, value, traceback)
    118         if type is None:
    119             try:
--> 120                 next(self.gen)
    121             except StopIteration:
    122                 return False

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/plotly/basedatatypes.py in batch_update(self)
   3051 
   3052                 # ### Call plotly_update ###
-> 3053                 self.plotly_update(
   3054                     restyle_data=restyle_data,
   3055                     relayout_data=relayout_data,

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/plotly/basedatatypes.py in plotly_update(self, restyle_data, relayout_data, trace_indexes, **kwargs)
   2914         # Send a plotly_update message to the frontend (if any)
   2915         if restyle_changes or relayout_changes:
-> 2916             self._send_update_msg(
   2917                 restyle_data=restyle_changes,
   2918                 relayout_data=relayout_changes,

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/plotly/basewidget.py in _send_update_msg(self, restyle_data, relayout_data, trace_indexes, source_view_id)
    342         # Send message
    343         # ------------
--> 344         self._py2js_update = update_msg
    345         self._py2js_update = None
    346 

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/plotly/basedatatypes.py in __setattr__(self, prop, value)
    719         if prop.startswith("_") or hasattr(self, prop):
    720             # Let known properties and private properties through
--> 721             super(BaseFigure, self).__setattr__(prop, value)
    722         else:
    723             # Raise error on unknown public properties

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/traitlets/traitlets.py in __set__(self, obj, value)
    604             raise TraitError('The "%s" trait is read-only.' % self.name)
    605         else:
--> 606             self.set(obj, value)
    607 
    608     def _validate(self, obj, value):

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/traitlets/traitlets.py in set(self, obj, value)
    593             # we explicitly compare silent to True just in case the equality
    594             # comparison above returns something other than True/False
--> 595             obj._notify_trait(self.name, old_value, new_value)
    596 
    597     def __set__(self, obj, value):

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/traitlets/traitlets.py in _notify_trait(self, name, old_value, new_value)
   1217 
   1218     def _notify_trait(self, name, old_value, new_value):
-> 1219         self.notify_change(Bunch(
   1220             name=name,
   1221             old=old_value,

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/ipywidgets/widgets/widget.py in notify_change(self, change)
    603             if name in self.keys and self._should_send_property(name, getattr(self, name)):
    604                 # Send new state to front-end
--> 605                 self.send_state(key=name)
    606         super(Widget, self).notify_change(change)
    607 

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/ipywidgets/widgets/widget.py in send_state(self, key)
    487             state, buffer_paths, buffers = _remove_buffers(state)
    488             msg = {'method': 'update', 'state': state, 'buffer_paths': buffer_paths}
--> 489             self._send(msg, buffers=buffers)
    490 
    491 

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/ipywidgets/widgets/widget.py in _send(self, msg, buffers)
    735         """Sends a message to the model in the front-end."""
    736         if self.comm is not None and self.comm.kernel is not None:
--> 737             self.comm.send(data=msg, buffers=buffers)
    738 
    739     def _repr_keys(self):

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/ipykernel/comm/comm.py in send(self, data, metadata, buffers)
    120     def send(self, data=None, metadata=None, buffers=None):
    121         """Send a message to the frontend-side version of this comm"""
--> 122         self._publish_msg('comm_msg',
    123             data=data, metadata=metadata, buffers=buffers,
    124         )

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/ipykernel/comm/comm.py in _publish_msg(self, msg_type, data, metadata, buffers, **keys)
     64         metadata = {} if metadata is None else metadata
     65         content = json_clean(dict(data=data, comm_id=self.comm_id, **keys))
---> 66         self.kernel.session.send(self.kernel.iopub_socket, msg_type,
     67             content,
     68             metadata=json_clean(metadata),

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/jupyter_client/session.py in send(self, stream, msg_or_type, content, parent, ident, buffers, track, header, metadata)
    828         if self.adapt_version:
    829             msg = adapt(msg, self.adapt_version)
--> 830         to_send = self.serialize(msg, ident)
    831         to_send.extend(buffers)
    832         longest = max([len(s) for s in to_send])

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/jupyter_client/session.py in serialize(self, msg, ident)
    702             content = self.none
    703         elif isinstance(content, dict):
--> 704             content = self.pack(content)
    705         elif isinstance(content, bytes):
    706             # content is already packed, as in a relayed message

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/jupyter_client/session.py in json_packer(obj)
     93 
     94 def json_packer(obj):
---> 95     return jsonapi.dumps(
     96         obj,
     97         default=json_default,

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/zmq/utils/jsonapi.py in dumps(o, **kwargs)
     23     Keyword arguments are passed along to :py:func:`json.dumps`.
     24     """
---> 25     return json.dumps(o, **kwargs).encode("utf8")
     26 
     27 

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/json/__init__.py in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
    232     if cls is None:
    233         cls = JSONEncoder
--> 234     return cls(
    235         skipkeys=skipkeys, ensure_ascii=ensure_ascii,
    236         check_circular=check_circular, allow_nan=allow_nan, indent=indent,

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/json/encoder.py in encode(self, o)
    197         # exceptions aren't as detailed.  The list call should be roughly
    198         # equivalent to the PySequence_Fast that ''.join() would do.
--> 199         chunks = self.iterencode(o, _one_shot=True)
    200         if not isinstance(chunks, (list, tuple)):
    201             chunks = list(chunks)

/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/json/encoder.py in iterencode(self, o, _one_shot)
    255                 self.key_separator, self.item_separator, self.sort_keys,
    256                 self.skipkeys, _one_shot)
--> 257         return _iterencode(o, 0)
    258 
    259 def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,

ValueError: Out of range float values are not JSON compliant

World map (interactive)

Hover mouse over map for detailed information.

Tip: Select columns to show on map to from the dropdown menus. The map is zoomable and draggable.

Tables

Projected need for ICU beds

Countries sorted by current estimated need, split into Growing and Recovering countries by current transmission rate. Only for countries with ICU need higher than 0.1 beds per 100k. More details in Appendix.

Growing countries (transmission rate above 5%)

Recovering countries (tranmission rate below 5%)

Appendix

Interactive plot of model predictions and past data

Tip: Choose a country from the drop-down menu to see the calculations used in the tables above and the dynamics of the model.

Projected Affected Population percentages

Top 20 countries with most estimated recent cases. Sorted by number of estimated recent cases during the last 5 days. More details in Appendix.

Methodology

  • I'm not an epidemiologist. This is an attempt to understand what's happening, and what the future looks like if current trends remain unchanged.
  • Everything is approximated and depends heavily on underlying assumptions.
  • Projection is done using a simple SIR model (see examples) combined with the approach in Total Outstanding Cases:
    • Growth rate is calculated over the 5 past days by averaging the daily growth rates.
    • Confidence bounds are calculated by from the weighted standard deviation of the growth rate over the last 5 days. Model predictions are calculated for growth rates within 1 STD of the weighted mean. The maximum and minimum values for each day are used as confidence bands.
    • Transmission rate, and its STD are calculated from growth rate and its STD using active cases estimation mentioned above.
    • For projections (into future) very noisy projections (with broad confidence bounds) are not shown in the tables.
    • Where the rate estimated from Total Outstanding Cases is too high (on down-slopes) recovery probability if 1/20 is used (equivalent 20 days to recover).
  • Total cases are estimated from the reported deaths for each country:
    • Each country has a different testing policy and capacity and cases are under-reported in some countries. Using an estimated IFR (fatality rate) we can estimate the number of cases some time ago by using the total deaths until today.
    • IFRs for each country is estimated using the age adjusted IFRs from International IFRS study and UN demographic data for 2020. These IFRs can be found in df['age_adjusted_ifr'] column.
    • The average fatality lag is assumed to be 8 days on average for a case to go from being confirmed positive (after incubation + testing lag) to death. This is the same figure used by "Estimating The Infected Population From Deaths".
    • Testing bias adjustment: the actual lagged case fatality rate is then divided by the age adjusted IFR to estimate the testing bias in a country. To account for testing bias changes (e.g. increased testing capacity) this is done on a rolling window basis of two months (with at least 300 deaths). The estimated testing bias then multiplies the reported case numbers for each date to estimate the true case numbers (=case numbers that would be consistent with the deaths and the age adjusted IFR).
  • ICU need is calculated and age-adjusted as follows:
    • UK ICU ratio was reported as 4.4% of active reported cases.
    • Using UKs ICU ratio, UK's testing bias, and IFRs corrected for age demographics we can estimate each country's ICU ratio (the number of cases requiring ICU hospitalisation).
    • Active cases for ICU estimation are taken from the SIR model.
    • Pre COVID-19 ICU capacities are from Wikipedia (OECD countries mostly) and CCB capacities in Asia. The current capacities are likely much higher as some countries already doubled or even quadrupled their ICU capacities.