# Constructing Models

In this notebook example, a step-by-step approach of building a simple model$^1$ of trafficking of high-energy phosphate bonds is demonstrated. Illustrated below is a graphical view of the full system along with the reaction rate equations and numerical values:

The example starts by creating a model of the "use", "distr", and "form" reactions. Then the model is expanded to include the remaining metabolites, reactions, and any additional information that should be defined in the model.

## Creating a Model

```python
import numpy as np
import pandas as pd

from mass import (
    MassConfiguration, MassMetabolite, MassModel, MassReaction)
from mass.util.matrix import left_nullspace, nullspace

mass_config = MassConfiguration()
```

The first step to creating the model is to define the `MassModel` object. A `MassModel` only requires an identifier to be initialized. For best practice, it is recommended to utilize SBML compliant identifiers for all objects.

```python
model = MassModel("Phosphate_Trafficking")
```

The model is initially empty.

```python
print("Number of metabolites: {0}".format(len(model.metabolites)))
print("Number of initial conditions: {0}".format(len(model.initial_conditions)))
print("Number of reactions: {0}".format(len(model.reactions)))
```

    Number of metabolites: 0
    Number of initial conditions: 0
    Number of reactions: 0

The next step is to create `MassMetabolite` and `MassReaction` objects to represent the metabolites and reactions that should exist in the model.

### Defining metabolites

To create a `MassMetabolite`, a unique identifier is required. The `formula` and `charge` attributes are set to ensure mass and charge balancing of reactions in which the metabolite is a participant. The `compartment` attribute indicates where the metabolite is located. In this model, all metabolites exist in a single compartment, abbreviated as "c".

```python
atp_c = MassMetabolite(
    "atp_c",
    name="ATP",
    formula="C10H12N5O13P3",
    charge=-4,
    compartment="c")

adp_c = MassMetabolite(
    "adp_c",
    name="ADP",
    formula="C10H12N5O10P2",
    charge=-3,
    compartment="c")

amp_c = MassMetabolite(
    "amp_c",
    name="AMP",
    formula="C10H12N5O7P",
    charge=-2,
    compartment="c")
```

The metabolite concentrations can be defined as the initial conditions for the metabolites using the `initial_condition` attribute. As previously stated, the concentrations are $\text{[ATP]}=1.6$, $\text{[ADP]}=0.4$, and $\text{[AMP]}=0.1$.

```python
atp_c.initial_condition = 1.6
adp_c.ic = 0.4  # Alias for initial_condition
amp_c.ic = 0.1

for metabolite in [atp_c, adp_c, amp_c]:
    print("{0}: {1}".format(metabolite.id, metabolite.initial_condition))
```

    atp_c: 1.6
    adp_c: 0.4
    amp_c: 0.1

The metabolites are currently not a part of any reaction. Consequently, the `ordinary_differential_equation` attribute is `None`.

```python
print(atp_c.ordinary_differential_equation)
print(adp_c.ode)  # Alias for ordinary_differential_equation
print(amp_c.ode)
```

    None
    None
    None

The next step is to create the reactions in which the metabolites participate.

### Defining reactions

Just like `MassMetabolite` objects, a unique identifier is also required to create a `MassReaction`. The `reversible` attribute determines whether the reaction can proceed in both the forward and reverse directions, or only in the forward direction.

```python
distr = MassReaction("distr", name="Distribution", reversible=True)
use = MassReaction("use", name="ATP Utilization", reversible=False)
form = MassReaction("form", name="ATP Formation", reversible=False)
```

Metabolites are added to reactions using a dictionary of metabolite objects and their stoichiometric coefficients. A group of metabolites can be added either all at once or one at a time. A negative coefficient indicates the metabolite is a reactant, while a positive coefficient indicates the metabolite is a product.

```python
distr.add_metabolites({
    adp_c: -2,
    atp_c: 1,
    amp_c: 1})

use.add_metabolites({
    atp_c: -1,
    adp_c: 1})

form.add_metabolites({
    adp_c: -1,
    atp_c: 1})

for reaction in [distr, use, form]:
    print(reaction)
```

    distr: 2 adp_c <=> amp_c + atp_c
    use: atp_c --> adp_c
    form: adp_c --> atp_c

Once the reactions are created, their parameters can be defined. As stated earlier, the distribution reaction is considerably faster when compared to other reactions in the model. The forward rate constant $k^{\rightarrow}$, represented as `kf`, can be set as $k^{\rightarrow}_{distr}=1000\ \text{min}^{-1}$. The equilibrium constant $K_{eq}$, represented as `Keq`, is approximately $K_{distr}=1$.

```python
distr.forward_rate_constant = 1000
distr.equilibrium_constant = 1
distr.parameters  # Return defined mass action kinetic parameters
```

    {'kf_distr': 1000, 'Keq_distr': 1}

As shown earlier, the forward rate constants are set as $k^{\rightarrow}_{use}=6.25\ \text{min}^{-1}$ and $k^{\rightarrow}_{form}=25\ \text{min}^{-1}$.
The `kf_str` attribute can be used to get the identifier of the forward rate constant as a string.

```python
use.forward_rate_constant = 6.25
form.kf = 25  # Alias for forward_rate_constant

print("{0}: {1}".format(use.kf_str, use.kf))
print("{0}: {1}".format(form.kf_str, form.kf))
```

    kf_use: 6.25
    kf_form: 25

Reactions can be added to the model using the `add_reactions()` method. Adding the reactions to the model also adds the associated metabolites and genes.

```python
model.add_reactions([distr, use, form])

print("Number of metabolites: {0}".format(len(model.metabolites)))
print("Number of initial conditions: {0}".format(len(model.initial_conditions)))
print("Number of reactions: {0}".format(len(model.reactions)))
```

    Number of metabolites: 3
    Number of initial conditions: 3
    Number of reactions: 3

The stoichiometric matrix of the model is automatically constructed with the addition of the reactions and metabolites to the model. It can be accessed through the `stoichiometric_matrix` property (alias `S`).

```python
print(model.S)
```

    [[-2.  1. -1.]
     [ 1. -1.  1.]
     [ 1.  0.  0.]]

The stoichiometric matrix attribute can be updated and stored in various formats using the `update_S()` method. For example, the stoichiometric matrix can be converted and stored as a `pandas.DataFrame`.

```python
model.update_S(array_type="DataFrame", dtype=np.int_, update_model=True)
model.S
```

      distr
      use
      form

      adp_c
      -2
      1
      -1

      atp_c
      1
      -1
      1

      amp_c
      1
      0
      0

Associating the metabolites with reactions allows for the mass action reaction rate expressions to be generated based on the stoichiometry.

```python
print(distr.rate)
```

    kf_distr*(adp_c(t)**2 - amp_c(t)*atp_c(t)/Keq_distr)

Generation of the reaction rates also allows for the metabolite ODEs to be generated.

```python
print(atp_c.ode)
```

    kf_distr*(adp_c(t)**2 - amp_c(t)*atp_c(t)/Keq_distr) + kf_form*adp_c(t) - kf_use*atp_c(t)

The `nullspace()` method can be used to obtain the null space of the stoichiometric matrix. The nullspace reflects the pathways through the system.

```python
ns = nullspace(model.S)  # Get the null space
# Divide by the minimum and round to nearest integer
ns = np.rint(ns / np.min(ns[np.nonzero(ns)]))
pd.DataFrame(
    ns, index=model.reactions.list_attr("id"),  # Rows represent reactions
    columns=["Pathway 1"], dtype=np.int_)
```

      Pathway 1

      distr
      0

      use
      1

      form
      1

In a similar fashion the left nullspace can be obtained using the `left_nullspace` function. The left nullspace represents the conserved moieties in the model.

```python
lns = left_nullspace(model.S)
# Divide by the minimum and round to nearest integer
lns = np.rint(lns / np.min(lns[np.nonzero(lns)]))
pd.DataFrame(
    lns, index=["Total AxP"],
    columns=model.metabolites.list_attr("id"),  # Columns represent metabolites
    dtype=np.int_)
```

      adp_c
      atp_c
      amp_c

      Total AxP
      1
      1
      1

## Expanding an Existing Model

Now, the existing model is expanded to include a buffer reaction, where a phosphagen is utilized to store a high-energy phosphate in order to buffer the ATP/ADP ratio as needed. Because the buffer molecule represents a generic phosphagen, there is no chemical formula for the molecule. Therefore, the buffer molecule can be represented as a moiety in the `formula` attribute using square brackets.

```python
b = MassMetabolite(
    "B",
    name="Phosphagen buffer (Free)",
    formula="[B]",
    charge=0,
    compartment="c")

bp = MassMetabolite(
    "BP",
    name="Phosphagen buffer (Loaded)",
    formula="[B]-PO3",
    charge=-1,
    compartment="c")

buffer = MassReaction("buffer", name="ATP Buffering")
```

When adding metabolites to the reaction, the `get_by_id()` method is used to add already existing metabolites in the model to the reaction.

```python
buffer.add_metabolites({
    b: -1,
    model.metabolites.get_by_id("atp_c"): -1,
    model.metabolites.get_by_id("adp_c"): 1,
    bp: 1})

# Add reaction to model
model.add_reactions([buffer])
```

For this reaction, $k^{\rightarrow}_{buffer}=1000\ \text{min}^{-1}$ and $K_{buffer}=1$. Because the reaction has already been added to the model, the `MassModel.update_parameters()` method can be used to update the reaction parameters using a dictionary:

```python
model.update_parameters({
    buffer.kf_str: 1000,
    buffer.Keq_str: 1})

buffer.parameters
```

    {'kf_buffer': 1000, 'Keq_buffer': 1}

By adding the reaction to the model, the left nullspace expanded to include a conservation pool for the total buffer in the system.

```python
lns = left_nullspace(model.S)
for i, row in enumerate(lns):
    # Divide by the minimum and round to nearest integer
    lns[i] = np.rint(row / np.min(row[np.nonzero(row)]))
pd.DataFrame(lns, index=["Total AxP", "Total Buffer"],
             columns=model.metabolites.list_attr("id"),
             dtype=np.int_)
```

      adp_c
      atp_c
      amp_c
      B
      BP

      Total AxP
      1
      1
      1
      0
      0

      Total Buffer
      0
      0
      0
      1
      1

### Performing symbolic calculations

Although the concentrations for the free and loaded buffer molecules are currently unknown, the total amount of buffer is known and set as $B_{total} = 10$. Because the buffer reaction is assumed to be at equilibrium, it becomes possible to solve for the concentrations of the free and loaded buffer molecules.

Below, the symbolic capabilities of **SymPy** are used to solve for the steady state concentrations of the buffer molecules.

```python
from sympy import Eq, Symbol, pprint, solve

from mass.util import strip_time
```

The first step is to define the equation for the total buffer pool symbolically:

```python
buffer_total_equation = Eq(Symbol("B") + Symbol("BP"), 10)
pprint(buffer_total_equation)
```

    B + BP = 10

The equation for the reaction rate at equilibrium is also defined. The `strip_time()` function is used to strip time dependency from the equation.

```python
buffer_rate_equation = Eq(0, strip_time(buffer.rate))
# Substitute defined concentration values into equation
buffer_rate_equation = buffer_rate_equation.subs({
    "atp_c":  atp_c.initial_condition,
    "adp_c":  adp_c.initial_condition,
    "kf_buffer": buffer.kf,
    "Keq_buffer": buffer.Keq})
pprint(buffer_rate_equation)
```

    0 = 1600.0⋅B - 400.0⋅BP

These two equations can be solved to get the buffer concentrations:

```python
buffer_sol = solve([buffer_total_equation, buffer_rate_equation],
                   [Symbol("B"), Symbol("BP")])
buffer_sol
```

    {B: 2.00000000000000, BP: 8.00000000000000}

Because the metabolites already exist in the model, their initial conditions can be updated to the calculated concentrations using the `MassModel.update_initial_conditions()` method.

```python
# Replace the symbols in the dict
for met_symbol, concentration in buffer_sol.items():
    metabolite = model.metabolites.get_by_id(str(met_symbol))
    # Make value as a float
    buffer_sol[metabolite] = float(buffer_sol.pop(met_symbol))

model.update_initial_conditions(buffer_sol)
model.initial_conditions
```

    {<MassMetabolite adp_c at 0x7fcf242f3190>: 0.4,
     <MassMetabolite atp_c at 0x7fcf242f31d0>: 1.6,
     <MassMetabolite amp_c at 0x7fcf242f3210>: 0.1,
     <MassMetabolite B at 0x7fcf244615d0>: 2.0,
     <MassMetabolite BP at 0x7fcf244612d0>: 8.0}

### Adding boundary reactions

After adding the buffer reactions, the next step is to define the AMP source and demand reactions. The `add_boundary()` method is employed to create and add a boundary reaction to a model.

```python
amp_drain = model.add_boundary(
    model.metabolites.amp_c,
    boundary_type="demand",
    reaction_id="amp_drain")

amp_in = model.add_boundary(
    model.metabolites.amp_c,
    boundary_type="demand",
    reaction_id="amp_in")

print(amp_drain)
print(amp_in)
```

    amp_drain: amp_c -->
    amp_in: amp_c -->

When a boundary reaction is created, a 'boundary metabolite' is also created as a proxy metabolite. The proxy metabolite is the external metabolite concentration (i.e., boundary condition) without instantiating a new `MassMetabolite` object to represent the external metabolite.

```python
amp_in.boundary_metabolite
```

    'amp_b'

The value of the 'boundary metabolite' can be set using the `MassModel.add_boundary_conditions()` method. Boundary conditions are accessed through the `MassModel.boundary_conditions` attribute.

```python
model.add_boundary_conditions({amp_in.boundary_metabolite: 1})
model.boundary_conditions
```

    {'amp_b': 1.0}

The automatic generation of the boundary reaction can be useful. However, sometimes the reaction stoichiometry needs to be switched in order to be intuitive. In this case, the stoichiometry of the AMP source reaction should be reversed to show that AMP enters the system, which is accomplished by using the `MassReaction.reverse_stoichiometry()` method.

```python
amp_in.reverse_stoichiometry(inplace=True)
print(amp_in)
```

    amp_in:  --> amp_c

Note that the addition of these two reactions adds an another pathway to the null space:

```python
ns = nullspace(model.S).T
for i, row in enumerate(ns):
    # Divide by the minimum to get all integers
    ns[i] = np.rint(row / np.min(row[np.nonzero(row)]))
ns = ns.T
pd.DataFrame(
    ns, index=model.reactions.list_attr("id"),  # Rows represent reactions
    columns=["Path 1", "Path 2"], dtype=np.int_)
```

      Path 1
      Path 2

      distr
      0
      0

      use
      1
      0

      form
      1
      0

      buffer
      0
      0

      amp_drain
      0
      1

      amp_in
      0
      1

### Defining custom rates

In this model, the rate for the AMP source reaction should remain at a fixed input value. However, the current rate expression for the AMP source reaction is dependent on an external AMP metabolite that exists as a boundary condition:

```python
print(amp_in.rate)
```

    amp_b*kf_amp_in

Therefore, the rate can be set as a fixed input by using a custom rate expression. Custom rate expressions can be set for reactions in a model using the `MassModel.add_custom_rate()` method as follows: by passing the reaction object, a string representation of the custom rate expression, and a dictionary containing any custom parameter associated with the rate.

```python
model.add_custom_rate(amp_in, custom_rate="b1",
                      custom_parameters={"b1": 0.03})
print(model.rates[amp_in])
```

    b1

## Ensuring Model Completeness
### Inspecting rates and ODEs

According to the [network schema](#Constructing-Models) at the start of the notebook, the network has been fully reconstructed. The reaction rates and metabolite ODEs can be inspected to ensure that the model was built without any issues.

The `MassModel.rates` property is used to return a dictionary containing reactions and symbolic expressions of their rates. The model always prioritizes custom rate expressions over automatically generated mass action rates.

```python
for reaction, rate in model.rates.items():
    print("{0}: {1}".format(reaction.id, rate))
```

    distr: kf_distr*(adp_c(t)**2 - amp_c(t)*atp_c(t)/Keq_distr)
    use: kf_use*atp_c(t)
    form: kf_form*adp_c(t)
    buffer: kf_buffer*(B(t)*atp_c(t) - BP(t)*adp_c(t)/Keq_buffer)
    amp_drain: kf_amp_drain*amp_c(t)
    amp_in: b1

Similarly, the model can access the ODEs for metabolites using the `ordinary_differential_equations` property (alias `odes`) to return a dictionary of metabolites and symbolic expressions of their ODEs.

```python
for metabolite, ode in model.odes.items():
    print("{0}: {1}".format(metabolite.id, ode))
```

    adp_c: kf_buffer*(B(t)*atp_c(t) - BP(t)*adp_c(t)/Keq_buffer) - 2*kf_distr*(adp_c(t)**2 - amp_c(t)*atp_c(t)/Keq_distr) - kf_form*adp_c(t) + kf_use*atp_c(t)
    atp_c: -kf_buffer*(B(t)*atp_c(t) - BP(t)*adp_c(t)/Keq_buffer) + kf_distr*(adp_c(t)**2 - amp_c(t)*atp_c(t)/Keq_distr) + kf_form*adp_c(t) - kf_use*atp_c(t)
    amp_c: b1 - kf_amp_drain*amp_c(t) + kf_distr*(adp_c(t)**2 - amp_c(t)*atp_c(t)/Keq_distr)
    B: -kf_buffer*(B(t)*atp_c(t) - BP(t)*adp_c(t)/Keq_buffer)
    BP: kf_buffer*(B(t)*atp_c(t) - BP(t)*adp_c(t)/Keq_buffer)

### Compartments

Compartments, defined in metabolites, are recognized by the model and can be viewed in the `compartments` attribute.

```python
model.compartments
```

    {'c': ''}

For this model, "c" is an abbreviation for "compartment". The `compartments` attribute can be updated to reflect this mapping using a `dict`:

```python
model.compartments = {"c": "compartment"}
model.compartments
```

    {'c': 'compartment'}

### Units
`Unit` and `UnitDefinition` objects are implemented as per the [SBML Unit](http://sbml.org/Software/libSBML/5.18.0/docs/python-api/classlibsbml_1_1_unit.html) and [SBML UnitDefinition](http://sbml.org/Software/libSBML/5.18.0/docs/python-api/classlibsbml_1_1_unit_definition.html) specifications.
It can be useful for comparative reasons to create `Unit` and `UnitDefinition` objects for the model (e.g., amount, volume, time) to provide additional context. However, the model does not maintain unit consistency automatically. It is the responsibility of the users to ensure consistency among units and associated numerical values in a model.

```python
from mass import Unit, UnitDefinition
from mass.core.units import print_defined_unit_values
```

SBML defines units using a compositional approach. The `Unit` objects represent references to base units. A `Unit` has four attributes: `kind`, `exponent`, `scale`, and `multiplier`. The `kind` attribute indicates the base unit. Valid base units are viewed using the `print_defined_unit_values()` function.

```python
print_defined_unit_values("BaseUnitKinds")
```

    ╒═════════════════════════════╕
    │ SBML Base Unit Kinds        │
    ╞═════════════════════════════╡
    │ Base Unit        SBML Value │
    │ -------------  ------------ │
    │ ampere                    0 │
    │ avogadro                  1 │
    │ becquerel                 2 │
    │ candela                   3 │
    │ coulomb                   5 │
    │ dimensionless             6 │
    │ farad                     7 │
    │ gram                      8 │
    │ gray                      9 │
    │ henry                    10 │
    │ hertz                    11 │
    │ item                     12 │
    │ joule                    13 │
    │ katal                    14 │
    │ kelvin                   15 │
    │ kilogram                 16 │
    │ liter                    17 │
    │ litre                    18 │
    │ lumen                    19 │
    │ lux                      20 │
    │ meter                    21 │
    │ metre                    22 │
    │ mole                     23 │
    │ newton                   24 │
    │ ohm                      25 │
    │ pascal                   26 │
    │ radian                   27 │
    │ second                   28 │
    │ siemens                  29 │
    │ sievert                  30 │
    │ steradian                31 │
    │ tesla                    32 │
    │ volt                     33 │
    │ watt                     34 │
    │ weber                    35 │
    │ invalid                  36 │
    ╘═════════════════════════════╛

The `exponent`, `scale` and `multiplier` attributes indicate how the base unit should be transformed. For this model, the unit for concentration, "Millimolar", which is represented as millimole per liter and composed of the following base units:

```python
millimole = Unit(kind="mole", exponent=1, scale=-3, multiplier=1)
per_liter = Unit(kind="liter", exponent=-1, scale=1, multiplier=1)
```

Combinations of `Unit` objects are contained inside a `UnitDefintion` object. `UnitDefinition` objects have three attributes: an `id`, an optional `name` to represent the combination, and a `list_of_units` attribute that contain references to the `Unit` objects. The concentration unit “Millimolar” is abbreviated as “mM” and defined as follows:

```python
concentration_unit = UnitDefinition(id="mM", name="Millimolar",
                                    list_of_units=[millimole, per_liter])
print("{0}:\n{1!r}\n{2!r}".format(
    concentration_unit.name, *concentration_unit.list_of_units))
```

    Millimolar:
    <Unit at 0x7fcf244c4b90 kind: liter; exponent: -1; scale: 1; multiplier: 1>
    <Unit at 0x7fcf244c49d0 kind: mole; exponent: 1; scale: -3; multiplier: 1>

`UnitDefinition` objects also have the `UnitDefinition.create_unit()` method to directly create `Unit` objects within the `UnitDefintion`.

```python
time_unit = UnitDefinition(id="min", name="Minute")
time_unit.create_unit(kind="second", exponent=1, scale=1, multiplier=60)
print(time_unit)
print(time_unit.list_of_units)
```

    min
    [<Unit at 0x7fcf244c4450 kind: second; exponent: 1; scale: 1; multiplier: 60>]

Once created, `UnitDefintion` objects are added to the model:

```python
model.add_units([concentration_unit, time_unit])
model.units
```

    [<UnitDefinition Millimolar "mM" at 0x7fcf244c4cd0>,
     <UnitDefinition Minute "min" at 0x7fcf244c4fd0>]

### Checking model completeness

Once constructed, the model should be checked for completeness.

```python
from mass import qcqa_model
```

The `qcqa_model()` function can be used to print a report about the model's completeness based on the set kwargs. The `qcqa_model()` function is used to ensure that all numerical values necessary for simulating the model are defined by setting the `parameters` and `concentrations` kwargs as `True`.

```python
qcqa_model(model, parameters=True, concentrations=True)
```

    ╒══════════════════════════════════════════════╕
    │ MODEL ID: Phosphate_Trafficking              │
    │ SIMULATABLE: False                           │
    │ PARAMETERS NUMERICALY CONSISTENT: True       │
    ╞══════════════════════════════════════════════╡
    │ ============================================ │
    │             MISSING PARAMETERS               │
    │ ============================================ │
    │ Reaction Parameters                          │
    │ ---------------------                        │
    │ amp_drain: kf                                │
    │ ============================================ │
    ╘══════════════════════════════════════════════╛

As shown in the report above, the forward rate constant for the AMP drain reaction was never defined. Therefore, the forward rate constant is defined, and the model is checked again.

```python
amp_drain.kf = 0.03

qcqa_model(model, parameters=True, concentrations=True)
```

    ╒══════════════════════════════════════════╕
    │ MODEL ID: Phosphate_Trafficking          │
    │ SIMULATABLE: True                        │
    │ PARAMETERS NUMERICALY CONSISTENT: True   │
    ╞══════════════════════════════════════════╡
    ╘══════════════════════════════════════════╛

Now, the report shows that the model is not missing any values necessary for simulation. See [Checking Model Quality](./quality_assurance.ipynb) for more information on quality assurance functions and the `qcqa` submodule.

## Additional Examples
For additional examples on constructing models, see the following:

* [Constructing Glycolysis](../gallery/workflows/constructing_glycolysis.ipynb)

$^1$ Trafficking model is created from Chapter 8 of Systems Biology: Simulation of Dynamic Network States


# LASSO Regresion on covid-19 cases in NYS

## Load modulues

```python
%matplotlib widget
import os
import requests
import urllib.parse
import json
import io
from zipfile import ZipFile
from datetime import datetime, timedelta

import pandas as pd
import numpy as np
import statsmodels.api as sm
import scipy

import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from statsmodels.sandbox.regression.predstd import wls_prediction_std
from sklearn import linear_model
from scipy import stats as sps
from sklearn.feature_selection import SelectFromModel
from IPython.display import display

locator = mdates.AutoDateLocator()
formatter = mdates.ConciseDateFormatter(locator)

sns.set_style("whitegrid")
GAMMA = 1/7.5

events = {}
df_dict = {}
dataset_info = {}
#may 15 last day
```

```python
dsname = 'New York'
dfs = []
offset = 0
while offset >=0:
    url = "https://health.data.ny.gov/resource/xdss-u53e.csv/?$limit=5000&$offset={}".format(offset)
    df = pd.read_csv(url)
    dfs.append(df)
    if len(df)==5000:
        offset += 5000
    else:
        offset = -1
dfraw = pd.concat(dfs)
#'test_date=2020-03-15T00:00:00.000'

#dfraw = dfraw.rename(columns={'new_positives': 'Positives', 'total_number_of_tests': 'Tests', 'test_date': 'date'})
print(len(dfraw))
#dfraw['date'] = pd.to_datetime(dfraw['date'])

#counties = (df.groupby('date')['Tests']>0).count()
df = dfraw.groupby('test_date').sum()
dfraw.to_csv('NYS_all_data.csv')

```

    8184

## Important dates

```python
# intervention dates
# 03/10/2020 school close in New Rochelle
# https://www.governor.ny.gov/news/during-novel-coronavirus-briefing-governor-cuomo-announces-new-mass-gatherings-regulations
# 03/12/2020 mass gathering reduced to 500 max. 50% occupancy

# 03/17/2020 close of gyms, restaurants and bars, movie theaters, mass gathering up to 50. https://www.governor.ny.gov/news/amid-lack-federal-direction-governor-cuomo-governor-murphy-and-governor-lamont-announce
bars = pd.to_datetime('03-16-2020 20:00', dayfirst=False)
# 03/18/2020 school clousure http://www.nysed.gov/news/2020/state-education-department-issues-updated-guidance-schools-regarding-novel-coronavirus
schools = pd.to_datetime('03-18-2020 20:00', dayfirst=False)

# https://www.governor.ny.gov/news/amid-ongoing-covid-19-pandemic-governor-cuomo-announces-deployment-1000-bed-hospital-ship-usns
# 03/20/2020 00:00 50% of the workforce
workforce_50 = pd.to_datetime('03-20-2020 20:00', dayfirst=False)
# 03/22/2020 20:00 ny_pause
ny_pause = pd.to_datetime('22-03-2020 00:00', dayfirst=True)
# CDC masks https://www.npr.org/sections/goatsandsoda/2020/04/10/829890635/why-there-so-many-different-guidelines-for-face-masks-for-the-public
masks_cdc = pd.to_datetime('03-04-2020 00:00', dayfirst=True)
mask_employers = pd.to_datetime('12-04-2020 00:00', dayfirst=True)
mask_public = pd.to_datetime('17-04-2020 00:00', dayfirst=True)

events_list = [bars, schools, workforce_50, ny_pause, masks_cdc, mask_employers, mask_public]
events['New York'] = events_list
```

## Get data

```python
dsname = 'Alaska'
url = 'https://opendata.arcgis.com/datasets/72c6d13ea1e9420ea398724bdd10372f_0.geojson'
if os.path.isfile('data/alaska.csv'):
    r = requests.get(url)
    data = json.loads(r.content)
    df = pd.DataFrame([x['properties'] for x in data['features']])
    df['date'] = pd.to_datetime(df['Date_'].str[:-3], format='%Y/%m/%d 12:00:00')
    df = df.rename(columns={'daily_positive': 'Positives', 'daily_negative': 'Negatives', 'daily_tests': 'Tests'})
    df = df.set_index('date')
    #df.index = df.index.round('D')
    df['Date'] = df.index
    df.to_csv('data/alaska.csv')

else:
    df = pd.read_csv('data/alaska.csv', index_col='date', parse_dates=['date', 'Date'])
df.Positives = df.Positives.astype(float)
df.Tests = df.Tests.astype(float)
df.Negatives = df.Negatives.astype(float)
df = df[['Date', 'Positives', 'Negatives', 'Tests']]
df['Odds'] = df.Positives /df.Negatives
df = df[df['Date']>'2020-03-23']
df_dict[dsname] = df
dataset_info[dsname] = {'Link to source': url,
                        'Link to information': 'https://coronavirus-response-alaska-dhss.hub.arcgis.com/datasets/daily-test-positivity/data',
                        'Type': 'API'}
```

```python
dsname = 'Colorado'
url = 'https://opendata.arcgis.com/datasets/566216cf203e400f8cbf2e6b4354bc57_0.geojson'
if not os.path.isfile('data/colorado.csv'):
    r = requests.get(url)
    data = json.loads(r.content)
    df = pd.DataFrame([x['properties'] for x in data['features']])
    #df = df.rename(columns={'Cases': 'Positives', 'Tested': 'Tests'})
    df['Date'] = pd.to_datetime(df['Date'])
    df['date'] = pd.to_datetime(df['Date'])
    df = df.set_index('date')
    df = df.sort_index()
    df.to_csv('data/colorado.csv')
else:
    df = pd.read_csv('data/colorado.csv', index_col='date', parse_dates=['date', 'Date'])
df['Tests'] = df['Tested'].diff()
df['Positives'] = df['Cases'].diff()
df['Negatives'] = df['Tests'] - df['Positives']
df = df[['Date', 'Positives', 'Negatives', 'Tests']]
df['Odds'] = df['Positives'] /df['Negatives']
df = df[df.Odds.notna()]
df_dict[dsname] = df
dataset_info[dsname] = {'Link to source': url, 'Link to information': 'https://data-cdphe.opendata.arcgis.com/datasets/cdphe-covid19-daily-state-statistics/data', 'Type': 'API'}
```

```python
dsname = 'Connecticut'

# reopening phase 1 may 20
# https://portal.ct.gov/Coronavirus/Covid-19-Knowledge-Base/Reopen-plan

if not os.path.isfile('data/connecticut.csv'):
    dfs = []
    offset = 0
    while offset >=0:
        url = 'https://data.ct.gov/resource/qfkt-uahj.csv?$limit=5000&$offset={}'.format(offset)
        df = pd.read_csv(url)
        dfs.append(df)
        if len(df)==5000:
            offset += 5000
        else:
            offset = -1
    dfcounty = pd.concat(dfs)
    dfcounty = dfcounty.rename(columns={'number_of_positives': 'Positives', 'number_of_tests': 'Tests', 'number_of_negatives': 'Negatives'})
    dfcounty['Tests'] = dfcounty['Tests'] - dfcounty['number_of_indeterminates']
    dfcounty['date'] = pd.to_datetime(dfcounty['date'])
    df = dfcounty.groupby('date').sum()
    df['Odds'] = df.Positives / df.Negatives
    df['Date'] = pd.to_datetime(df.index)
    df.to_csv('data/connecticut.csv')
    #df = df[df['Date'] <= '2020-05-19']
    df = df[df['Date'] > '2020-03-19']
else:
    df = pd.read_csv('data/connecticut.csv', index_col='date', parse_dates=['date', 'Date'])
    #df = df[df['Date'] <= '2020-05-19']
    df = df[df['Date'] > '2020-03-19']
    #df = df.set_index('date')
df['Date'] = pd.to_datetime(df['Date'])
df.index = pd.to_datetime(df.index)

# last days are not accurate. See S.I.
df = df[df['Date']<datetime.today()-timedelta(7)]

# intervention dates
# https://portal.ct.gov/Coronavirus/Pages/Emergency-Orders-issued-by-the-Governor-and-State-Agencies
# 03/12/2020 no gatherings with more than 250
large_gatherings = pd.to_datetime('03-12-2020 00:00', dayfirst=False)
schools = pd.to_datetime('03-17-2020 00:00', dayfirst=False)
bars = pd.to_datetime('03-16-2020 20:00', dayfirst=False)

malls = pd.to_datetime('03-19-2020 20:00', dayfirst=False)
workforce100 = pd.to_datetime('03-23-2020 20:00', dayfirst=False)
# CDC masks https://www.npr.org/sections/goatsandsoda/2020/04/10/829890635/why-there-so-many-different-guidelines-for-face-masks-for-the-public
masks_cdc = pd.to_datetime('04-03-2020 00:00', dayfirst=False)
mask_public = pd.to_datetime('04-20-2020 00:00', dayfirst=False)

event_list = [large_gatherings, bars, schools, malls, workforce100, masks_cdc,  mask_public]
events[dsname] = event_list
df = df[['Date', 'Tests', 'Positives', 'Negatives', 'Odds']]
df_dict[dsname] = df
dataset_info[dsname] = {'Link to source': 'https://data.ct.gov/resource/qfkt-uahj.csv',
                        'Link to information': 'https://data.ct.gov/Health-and-Human-Services/COVID-19-PCR-Based-Test-Results-by-Date-of-Specime/qfkt-uahj',
                        'Type': 'API'}
```

```python
dsname = 'Delaware'
url = 'https://myhealthycommunity.dhss.delaware.gov/locations/state/download_covid_19_data'
if not os.path.isfile('data/delaware.csv'):
    r = requests.get(url)
    df = pd.read_csv(io.BytesIO(r.content))
    df['date'] = pd.to_datetime(df[['Year', 'Month', 'Day']])
    df = df[df['Unit']=='people']
    #df = df[(df['Statistic']=='New Positive Cases') ]# | (df['Statistic'].str.startswith('Total Persons Tested'))
    df = df.pivot(index='date', columns='Statistic', values='Value')
    df['Date']  = df.index
    df.to_csv('data/delaware.csv')
else:
    df = pd.read_csv('data/delaware.csv', index_col='date', parse_dates=['date', 'Date'])
#print(df.columns)
#df = df[['New Positive Cases', 'Total Persons Tested']]
df['Tests'] = df['Total Persons Tested'].diff()
df['Positives']  = df['New Positive Cases']
#df['neg2'] = df['Tested Negative'].diff()
df = df[['Positives', 'Tests', 'Date']]
df['Negatives'] = df['Tests'] - df['Positives']
df['Odds'] = df.Positives / df.Negatives
df = df.dropna()
#df = df[df['Date']<datetime.today()-timedelta(7)]
df= df[df['Date']>'2020-03-22']
df_dict[dsname] = df
dataset_info[dsname] = {'Link to source': url,
                        'Link to information': 'https://myhealthycommunity.dhss.delaware.gov/locations/state',
                        'Type': 'File'}
```

```python
dsname = 'Indiana'
url = 'https://hub.mph.in.gov/dataset/ab9d97ab-84e3-4c19-97f8-af045ee51882/resource/182b6742-edac-442d-8eeb-62f96b17773e/download/covid_report_date.xlsx'
if not os.path.isfile('data/indiana.csv'):
    r = requests.get(url)
    df =pd.read_excel(io.BytesIO(r.content))
    df['date'] = pd.to_datetime(df['DATE'])
    df = df.set_index('date')
    df = df.sort_index()
    df['Date'] = df.index
    df.to_csv('data/indiana.csv')
else:
    df = pd.read_csv('data/indiana.csv', index_col='date', parse_dates=['date', 'Date'])
df['Tests'] = df['COVID_TEST']
df['Positives'] = df['DAILY_BASE_CASES']
df = df[['Date', 'Tests', 'Positives']]
df['Negatives'] = df['Tests'] - df['Positives']
df['Odds'] = df.Positives / df.Negatives
df = df[df['Date'] > '2020-03-18']
df = df[df['Date']<datetime.today()-timedelta(7)]
df_dict[dsname] = df
dataset_info[dsname] = {'Link to source': url,
                        'Link to information': 'https://hub.mph.in.gov/dataset/covid-19-case-trend/resource/182b6742-edac-442d-8eeb-62f96b17773e',
                        'Type': 'API and file'}
```

```python
dsname = 'Iowa'
cases = pd.read_csv('data/iowa_positive.csv')
tests = pd.read_csv('data/iowa_tested.csv')
df = tests.merge(cases, on='Date')
df['Tests'] = df['Total Individuals Tested']
df['Positives'] = df['Total Positive Cases']
df['date'] = pd.to_datetime(df['Date'])
df = df.set_index('date')
df['Date'] = df.index
df = df[['Date', 'Tests', 'Positives']]
df['Negatives'] = df['Tests'] - df['Positives']
df['Odds'] = df.Positives / df.Negatives
df = df[df['Date']>'2020-03-21']
df_dict[dsname] = df
dataset_info[dsname] = {'Link to source': '  Downloadable from dashboard',
                        'Link to information': 'https://coronavirus.iowa.gov/',
                        'Type': 'File'}
```

```python
dsname = 'Massachusetts'
# massachusets reopening on May 18
# https://www.mass.gov/doc/reopening-massachusetts-may-18-2020/download
yesterday = datetime.today() - timedelta(1)
if not os.path.isfile('data/massachusetts.csv'):
    #yesterday = datetime.today() - timedelta(10)
    yesterday_str = datetime.strftime(yesterday, '%B-%d-%Y').lower()
    fn = 'data/{}.zip'.format(dsname)

    url2 = 'https://www.mass.gov/doc/covid-19-raw-data-june-10-2020/download'
    url = 'https://www.mass.gov/doc/covid-19-raw-data-{}/download'.format(yesterday_str)
    print(url)
    print(url2)
    myfile = requests.get(url, allow_redirects=True)
    #open(fn, 'wb').write(myfile.content)
    zf = ZipFile(io.BytesIO(myfile.content))
    csvf = zf.open('TestingByDate.csv')
    df = pd.read_csv(csvf)

    # https://www.mass.gov/doc/covid-19-raw-data-may-27-2020/download
    #df = pd.read_csv('data/massachusetts/COVID-19-Dashboard-Files-05-24-2020/TestingByDate.csv',
    #                 usecols=['Date', 'New', 'Positive'])

    df = df.rename(columns={'Molecular Positive New': 'Positives', 'Molecular New': 'Tests'})
    df['Negatives'] = df.Tests - df.Positives
    df = df.query('Tests>100')
    df['date'] = pd.to_datetime(df['Date'])
    df= df.set_index('date')
    df['Date'] = pd.to_datetime(df['Date'])
    df['Odds'] = df.Positives / df.Negatives
    #df = df[df['Date']<'2020-05-15']
    df.to_csv('data/massachusetts.csv')
    #df = df[df['Date']<'2020-05-18']
    df = df[df['Date']>'2020-03-15']
else:
    df = pd.read_csv('data/massachusetts.csv')
    df['Date'] = pd.to_datetime(df['Date'])
    df['date'] = pd.to_datetime(df['date'])
    df = df.set_index('date')
    #df = df[df['Date']<'2020-05-18']
    df = df[df['Date']>'2020-03-15']

df = df[df['Date']<datetime.today()-timedelta(7)]
# https://www.mass.gov/info-details/covid-19-state-of-emergency
schools = pd.to_datetime('03-22-2020 00:00', dayfirst=False)
saty_at_home = pd.to_datetime('03-24-2020 00:00', dayfirst=False)
masks_cdc = pd.to_datetime('04-03-2020 00:00', dayfirst=False)
masks_public = pd.to_datetime('05-06-2020 00:00', dayfirst=False)

event_list = [schools, saty_at_home, masks_cdc, masks_public]
events[dsname] = event_list
df = df[['Date', 'Tests', 'Positives', 'Negatives', 'Odds']]
df = df[df['Date']<df.Date.max()-timedelta(7)]
df_dict[dsname] = df
dataset_info[dsname] = {'Link to source': 'https://www.mass.gov/doc/covid-19-raw-data-june-20-2020/download',
                        'Link to information': 'https://www.mass.gov/info-details/covid-19-response-reporting',
                        'Type': 'API'}
```

```python
#display(old_ma.tail(20))
#df.tail(20)
```

```python
dsname = 'Michigan'
url = 'https://www.michigan.gov/documents/coronavirus/Diagnostic_Tests_by_Result_and_County_2020-06-16_693915_7.xlsx'
if os.path.isfile('data/michigan.csv'):
    df = pd.read_excel(url)
    df['date'] = pd.to_datetime(df['MessageDate'])
    df = df[df['COUNTY']!='Correctional']
    df = df.groupby('date').sum()
    df['Date'] = df.index
    df.to_csv('data/michigan.csv')
else:
    df = pd.read_csv('data/michigan.csv', index_col='date', parse_dates=['date', 'Date'])
df = df.rename(columns={'Negative': 'Negatives', 'Positive': 'Positives','Total': 'Tests'})
df['Odds'] = df.Positives / df.Negatives
df = df[df['Date']>'2020-03-18']
df = df[df['Date']<datetime.today()-timedelta(7)]
df_dict[dsname] = df
dataset_info[dsname] = {'Link to source': url,
                        'Link to information': 'https://www.michigan.gov/coronavirus/0,9753,7-406-98163_98173---,00.html',
                        'Type': 'File'}
```

```python
dsname = 'Minnesota'
url = 'https://www.health.state.mn.us/diseases/coronavirus/situation.html'
if not os.path.isfile('data/minnesota.csv'):
    dfs = pd.read_html(url)
    tests = dfs[4]
    tests['Tests'] = tests['Completed tests reported from the MDH Public Health Lab (daily)'] + tests['Completed tests reported from external laboratories (daily)']
    tests['Date reported to MDH'] = tests['Date reported to MDH'] + '/2020'
    tests['date'] = pd.to_datetime(tests['Date reported to MDH'])
    tests = tests[['date', 'Tests']]
    cases = dfs[5]
    cases['Positives'] = cases['Positive cases']
    cases = cases.iloc[:len(cases)-2].copy()
    cases['Specimen collection date'] = cases['Specimen collection date'] + '/2020'
    cases['date'] = pd.to_datetime(cases['Specimen collection date'])
    cases = cases[['date', 'Positives']]
    #df_dict[dsname] = df
    df = tests.join(cases.set_index('date'), on='date', how='inner')
    df = df.set_index('date')
    df['Date'] = df.index
    df.to_csv('data/minnesota.csv')
else:
    df = pd.read_csv('data/minnesota.csv', index_col='date', parse_dates=['date', 'Date'])
df['Negatives'] = df.Tests - df.Positives
df['Odds'] = df.Positives / df.Negatives

df_dict[dsname] = df
dataset_info[dsname] = {'Link to source': 'Downloadable from dashboard',
                        'Link to information': url,
                        'Type': 'HTML table'}
```

```python
dsname = 'New York'
url = "https://health.data.ny.gov/resource/xdss-u53e.csv"
if os.path.isfile('data/ny.csv'):
    dfs = []
    offset = 0
    while offset >=0:
        url = "https://health.data.ny.gov/resource/xdss-u53e.csv/?$limit=5000&$offset={}".format(offset)
        df = pd.read_csv(url, usecols=['test_date', 'total_number_of_tests', 'new_positives'])
        dfs.append(df)
        if len(df)==5000:
            offset += 5000
        else:
            offset = -1
    dfraw = pd.concat(dfs)
    #'test_date=2020-03-15T00:00:00.000'

    dfraw = dfraw.rename(columns={'new_positives': 'Positives', 'total_number_of_tests': 'Tests', 'test_date': 'date'})
    print(len(dfraw))
    dfraw['date'] = pd.to_datetime(dfraw['date'])
    #counties = (df.groupby('date')['Tests']>0).count()
    df = dfraw.groupby('date').sum()
    df['Odds'] = df.Positives / (df.Tests - df.Positives)
    df['Date'] = pd.to_datetime(df.index)
    df.to_csv('data/ny.csv')
    df = df[df['Date'] >= '2020-03-15']
    # last date of full NYS PAUSE
    #df = df[df['Date'] <= '2020-05-15']

else:
    df = pd.read_csv('data/ny.csv')
    df['date'] =pd.to_datetime(df['date'])
    df['Date'] = pd.to_datetime(df['Date'])
    df = df.set_index('date')
    df = df[df['Date'] >= '2020-03-15']
    # last date of full NYS PAUSE
    #df = df[df['Date'] <= '2020-05-15']
df['Negatives'] = df.Tests - df.Positives
df_dict[dsname] = df
dataset_info[dsname] = {'Link to source': url,
                        'Link to information': 'https://health.data.ny.gov/Health/New-York-State-Statewide-COVID-19-Testing/xdss-u53e',
                        'Type': 'API'}
```

    8184

```python
dsname = 'Rhode Island'
#https://docs.google.com/spreadsheets/d/1n-zMS9Al94CPj_Tc3K7Adin-tN9x1RSjjx2UzJ4SV7Q/edit#gid=590763272
url = 'https://docs.google.com/spreadsheets/d/1n-zMS9Al94CPj_Tc3K7Adin-tN9x1RSjjx2UzJ4SV7Q/export?format=csv&id=1n-zMS9Al94CPj_Tc3K7Adin-tN9x1RSjjx2UzJ4SV7Q&gid=590763272'
df  = pd.read_csv(url,
                  usecols=['New positive labs', 'New negative labs', 'Date'])
#df  = pd.read_csv('data/COVID-19 Rhode Island Data - COVID Trends.csv',
#                  usecols=['New positive labs', 'New negative labs', 'Date'])

df = df.rename(columns={'New positive labs': 'Positives', 'New negative labs': 'Negatives'})
df['Tests'] = df.Positives + df.Negatives
df = df.query('Tests>100')
df['date'] = pd.to_datetime(df['Date'])
df= df.set_index('date')
df['Date'] = pd.to_datetime(df['Date'])
df['Odds'] = df.Positives / df.Negatives
df = df[df['Date']>='2020-04-01']
df_dict[dsname] = df
dataset_info[dsname] = {'Link to source': url,
                        'Link to information': 'https://docs.google.com/spreadsheets/d/1n-zMS9Al94CPj_Tc3K7Adin-tN9x1RSjjx2UzJ4SV7Q/edit#gid=590763272',
                        'Type': 'File'}
```

```python
dsname = 'Tennessee'
url = 'https://www.tn.gov/content/dam/tn/health/documents/cedep/novel-coronavirus/datasets/Public-Dataset-Daily-Case-Info.XLSX'
if not os.path.isfile('data/tennessee.csv'):
    df = pd.read_excel(url,
                       usecols=['DATE', 'NEW_CASES', 'POS_TESTS', 'NEG_TESTS', 'TOTAL_TESTS', 'NEW_TESTS'])
    df['date'] = pd.to_datetime(df['DATE'])
    df = df.set_index('date')
    df['Date'] = df.index
    df.to_csv('data/tennessee.csv')
else:
    df = pd.read_csv('data/tennessee.csv', index_col='date', parse_dates=['date', 'Date'])
df['Positives'] = df['POS_TESTS'].diff()
df['Negatives'] = df['NEG_TESTS'].diff()
df['Tests'] = df.Positives + df.Negatives
df['Odds'] = df.Positives / df.Negatives
df = df[['Date', 'Positives', 'Negatives', 'Tests','Odds']]
df = df.dropna()

# data on 2020 06 12 unrelaible. see notes
df[df['Date']=='2020-06-12'] = np.nan
df_dict[dsname] = df
dataset_info[dsname] = {'Link to source': url,
                        'Link to information': 'https://www.tn.gov/health/cedep/ncov/data/downloadable-datasets.html',
                        'Type': 'File'}
```

```python
dsname = 'Texas'
url = 'https://www.dshs.texas.gov/coronavirus/TexasCOVID19CaseCountData.xlsx'
if not os.path.isfile('data/texas.csv'):
    cases = pd.read_excel(url, sheet_name='Trends', skiprows=[0], skipfooter=2)
    tests = pd.read_excel(url, sheet_name='Tests by day', skiprows=[0], skipfooter=2)
    cases = cases[cases['Date']!='06/16/20-TDCJ*']
    cases = cases[cases['Date']!='06/16/2020-TDCJ*']
    cases['date'] = pd.to_datetime(cases['Date'])
    cases['Date'] = pd.to_datetime(cases['Date'])
    #tests['date'] = tests['Date']
    tests = tests.set_index('Date')
    tests = tests.loc[tests['Viral Tests']!='.']
    tests['Tests'] = tests['Viral Tests'].diff()
    cases['Positives'] = cases['Daily\nNew\nCases']
    df = cases.join(tests, on='date', how='inner')
    df = df.dropna()
    df = df.set_index('date')
    df.to_csv('data/texas.csv')
else:
    df = pd.read_csv('data/texas.csv', index_col='date', parse_dates=['date', 'Date'])
df['Negatives'] = df.Tests + df.Positives
df['Odds'] = df.Positives / df.Negatives
df = df[['Date', 'Positives', 'Negatives', 'Tests', 'Odds']]
df = df.dropna()
df_dict[dsname] = df
dataset_info[dsname] = {'Link to source': url,
                        'Link to information': 'https://www.dshs.texas.gov/coronavirus/',
                        'Type': 'File'}
```

```python
dsname = 'Virginia'
if not os.path.isfile('data/virginia.csv'):
    #df  = pd.read_csv('https://www.vdh.virginia.gov/content/uploads/sites/182/2020/05/VDH-COVID-19-PublicUseDataset-Tests_by-LabReportDate.csv')
    df = pd.read_csv('data/VDH-COVID-19-PublicUseDataset-Tests_by-LabReportDate.csv')
    df = df.rename(columns={'Number of Positive PCR Tests': 'Positives', 'Number of PCR Testing Encounters': 'Tests'})
    df = df[df['Lab Report Date']!='Not Reported']
    df['date'] = pd.to_datetime(df['Lab Report Date'])
    df = df.groupby('date').sum()
    df['Date'] = df.index
    df.to_csv('data/virginia.csv')
else:
    df = pd.read_csv('data/virginia.csv', index_col='date', parse_dates=['date', 'Date'])
df = df[['Date', 'Tests', 'Positives']]
df['Negatives'] = df.Tests + df.Positives
df['Odds'] = df.Positives / df.Negatives
df = df[df['Date']>'2020-03-22']

# lab report day might be lagging
df = df[df['Date']<datetime.today()-timedelta(7)]
df_dict[dsname] = df
dataset_info[dsname] = {'Link to source': 'https://www.vdh.virginia.gov/content/uploads/sites/182/2020/05/VDH-COVID-19-PublicUseDataset-Tests_by-LabReportDate.csv',
                        'Link to information': 'https://www.vdh.virginia.gov/coronavirus/',
                        'Type': 'File'}
```

```python
dsname = 'Wisconsin'
url = "https://opendata.arcgis.com/datasets/b913e9591eae4912b33dc5b4e88646c5_10.geojson?where=GEO%20%3D%20'State'"
if not os.path.isfile('data/wisconsin.csv'):
    r = requests.get(url)
    data = json.loads(r.content)
    df = pd.DataFrame([x['properties'] for x in data['features']])

    df['date'] = pd.to_datetime(df['DATE'].str[:-3], format='%Y/%m/%d 14:00:00')
    df = df.set_index('date')
    df['Date'] = df.index
    df.to_csv('data/wisconsin.csv')
else:
    df = pd.read_csv('data/wisconsin.csv', index_col='date', parse_dates=['date', 'Date'])
#print(df.head(20))
df = df.rename(columns={'POS_NEW': 'Positives', 'NEG_NEW': 'Negatives', 'TEST_NEW': 'Tests'})

df['Odds'] = df.Positives / df.Negatives
df = df[['Date', 'Positives', 'Negatives', 'Tests', 'Odds']]
df = df.dropna()
#df = df[df['Odds']>0]
# on the 30th they changed the definition of negative case
df = df[df['Date']>'2020-03-30']
df_dict[dsname] = df
dataset_info[dsname] = {'Link to source': url,
                        'Link to information': 'https://data.dhsgis.wi.gov/datasets/covid-19-historical-data-table/data?where=GEO%20%3D%20%27State%27',
                        'Type': 'API'}
```

```python
dsname = 'Wyoming'
df = pd.read_csv('data/wyoming.csv', encoding='utf-16', sep='\t', thousands=',')
df = df.rename(columns={'All': 'Tests'})
df['date'] = pd.to_datetime(df['Spec Coll Dt'])
df = df.set_index('date')
pos = df.loc[df.Lab.isna(),'Positivity']
nwphl = df.loc[df.Lab=='Non-WPHL', 'Tests']
wphl = df.loc[df.Lab=='WPHL', 'Tests']
df['Tests'] = nwphl + wphl
df['Date'] = df.index

df['Positivity'] = df.Positivity.str[:-1].astype(float)/100
#df['Tests'] = df['Tests'].str.replace(',', '').astype(float)
df['Positives'] = (df['Tests'] * df['Positivity']).round(0)
df['Negatives'] = df.Tests - df.Positives
df['Odds'] = df.Positives / df.Negatives
df = df[['Date', 'Positives', 'Negatives', 'Tests', 'Odds']]
df = df.dropna()
df = df[df['Odds']>0]
df = df[df['Date']>'2020-03-16']
df_dict[dsname] = df
dataset_info[dsname] = {'Link to source': 'Downloadable from dashboard',
                        'Link to information': 'https://health.wyo.gov/publichealth/infectious-disease-epidemiology-unit/disease/novel-coronavirus/covid-19-testing-data/',
                        'Type': 'API'}
```

```python
# df = pd.concat(df_dict).reset_index()

# df = df.rename(columns={'level_0': 'State'})
# df['Odds'] = df['Odds'].astype(float)
# df['Tests'] = df['Tests'].astype(float)
# df['Positives'] = df['Positives'].astype(float)
# df['Tests (right)'] = df['Tests']

```

```python
# dataset_info_df = pd.DataFrame(dataset_info, ).T.reset_index().rename(columns={'index': 'Dataset'})
# pd.options.display.max_colwidth = 150

# #df.loc[df['Link to source']=='Downloadable from dashboard', 'Link to source'] = df['Link to information']
# with open('dataset_table.txt', 'w') as fp:
#     dataset_info_df[['Dataset', 'Link to information', 'Type']].to_latex(fp, index=False, label='tab:datasets', caption='Information about the source of the datasets used in this work')
```

```python
# pd.options.display.max_colwidth
```

```python
# dsname = 'Argentina'
# df1 = pd.read_csv('data/argentina/argentina_tests.csv', parse_dates=[0],
#                   index_col=0)
# df2 = pd.read_csv('data/argentina/argentina_tests2.csv',
#                   parse_dates=[0],
#                   index_col=0)
# df1['Date'] = df1.index
# df2['Date'] = df2.index
# df2['Positives'] = df2.confirmed.diff()
# df = df1.merge(df2, on='Date', how='outer').fillna(0)
# df['Positives'] = df[['new_confirmed', 'Positives']].max(axis=1)
# df['Tests'] = df[['new_tests_x', 'new_tests_y']].max(axis=1)
# df = df[['Date', 'Positives', 'Tests']].set_index('Date')
# df['Date'] = df.index
# df = df.sort_index()

# df = df[df['Date'] > '2020-04-17']
# df['Odds'] = df.Positives / (df.Tests-df.Positives)

# ax = df.plot.scatter(x='Date', y='Odds')
# ax.set_yscale('log')
# df.tail()
```

## Plots

I we plot the number of positive tests we can see that the data is noisy.
But, if we take into account the number of people tested each day, the data looks way more clean.

```python
plt.close(1)

current_palette = sns.color_palette()

df = pd.concat(df_dict).reset_index()

df = df.rename(columns={'level_0': 'State'})
df['Odds'] = df['Odds'].astype(float)
df['Tests'] = df['Tests'].astype(float)
df['Positives'] = df['Positives'].astype(float)
df['Tests (right)'] = df['Tests']
#df = df[df['Odds']>0]

g = sns.FacetGrid(df, col='State', col_wrap=3, xlim=[df.Date.min(), df.Date.max()], sharey=False, aspect=1.3, )
legend_and_hand = []
def myplot(data, y=None, secondary_y=None, legend=None, color=None):
    ax = data.plot(x='Date', y=y[0], ax=plt.gca(), label=y[0])
    legend_and_hand.append(ax.get_legend_handles_labels())
    ax = data.plot(x='Date', y=y[1], secondary_y=secondary_y, ax=ax, label=y[1])
    legend_and_hand.append(ax.get_legend_handles_labels())
    return
g.map_dataframe(myplot, y=['Positives', 'Tests (right)'], secondary_y=['Tests (right)'])
g.axes[0].xaxis.set_major_locator(locator)
g.axes[0].xaxis.set_major_formatter(formatter)

g.add_legend({l[0]: h[0] for h,l in legend_and_hand[:2]}, loc='lower center', bbox_to_anchor=(0.7, 0), ncol=2)
g.set_titles('{col_name}')
plt.tight_layout()
plt.savefig('figs/all_state_testes.jpg', dpi=300)
```

    Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …

## Relationship between the total number of infected individuals and positive tests

As has been shown previously [[1]](https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0002185), the number of new infected individuals in a given day $k_t$ is given by:
$$
k_t = k_{t-1} e^{(R_{t-1}-1)\gamma}
$$

where $R_t$ is the effective reproductuve number and $\gamma^{-1}$ is the infectious period estimated as 7 days accoring to [2].

The following derivation was suggested to my by Will Meierjurgen Farr on this GitHub [Issue](https://github.com/k-sys/covid-19/issues/45#issuecomment-623782130).
Since we do not have access to the total number of infected indiviudals, but only to the population being tested, we have to use some statisticals assumtions on this populations.
If we asume that the people being tested, in a given day, is a sample of the population with COVID-19-like sympoms we can state that:

$$
n_{t} = [P_t(CV|sympoms) P_t(sympoms) +P_t(not CV|sympoms)P_t(sympoms)]Nf_t
$$

where $n_{t}$ is the number of people tested, $P_t(CV|sympoms)$ is the probablity of a pacient being positive for coronavirus given that the she is sympomatic, $P_t(sympoms)$ is the probablity of having COVID-like sympoms, $P_t(not CV|sympoms)$ is the probability of a pacient being coronavirus negative given he has COVID-19-like sympoms, $N$ is the total population, and $f_t$ is the fraction of people with sympoms that is selected to be tested (this number can be different each day, for example if the number of tests availabes changes).
Also, note that the probability of a test being positive in a given day is $Positive_t=P_t(CV|sympoms) P_t(sympoms) N f_t$

Now, if we assume that $P_t(sympoms|CV)=cte$ we can use Bayes theorem to show that:

$$
P_t(CV|sympoms) P_t(sympoms) \propto P_t(CV) = \frac{k_t}{N}
$$

Then:
$$
P_t(CV|sympoms) P_t(sympoms) \propto k_t
$$

Finally, if we assume that $P_t(not CV|sympoms)P_t(sympoms)=cte$:
$$
Odds_t = \frac{P(CV|sympoms) P(sympoms)Nf_t}{P_t(not CV|sympoms)P_t(sympoms)Nf_t}
$$
$$
Odds_t = \frac{P(CV|sympoms) P(sympoms)}{P_t(not CV|sympoms)P_t(sympoms)}
$$
$$
Odds_t \propto k_t
$$

\begin{equation}
Odds_t = Odds_{t-1} e^{(R_{t-1}-1)\gamma} (1)
\end{equation}

We used three hypothesis. First, constant population $N$ (for $P_t \propto k_t$ and for the evolution of $k_t$). Second, that the tested population is a random sample from the population with COVID19-like sympoms ($n_t = [P_t(CV|sympoms) P_t(sympoms) +P_t(not CV|sympoms)P_t(sympoms)]Nf_t$) this is not the case when people is being tested based on contacts for example. And third, that $P_t(not CV|sympoms)P_t(sympoms)=cte$, this is equivalent to say that the number of people with covid-19-like sympoms but without the coronavirus (for example people with the flu) is constant, or at least it changes are negligible compared with the changes in the amount of sympomatic people with coronavirus.

## Linearization

Defining

$$
b_i =  e^{(R_{i-1}-1)\gamma} (2)
$$

We can write [1] as:

\begin{equation}
odd_i = b_{i-1} * odd_{i-1} (3)
\end{equation}

Now, instead of using $b_i$ as the parameters to estimate we decompose each $b$ as follows:

$$
b_i = \prod_{j=0}^{i} a_j (4)
$$

Now, the $a_j$ represent the rate of change of the variable $b_i$. Next, we replace the [4] in [3]
$$
odd_i = \prod_{j=0}^{i-1} a_j * odd_{i-1}
$$
$$
odd_i = \prod_{j=0}^{i-1} a_j * \prod_{j=0}^{i-2} a_j * odd_{i-2}
$$
$$
odd_i = \prod_{k=0}^{i-1}\prod_{j=0}^{k} a_j * odd_{0}
$$
$$
odd_i = \prod_{j=0}^{i-1} a_j^{i-j} * odd_{0}
$$

Now, we liniarize this result and we generalize it to the case where $i=0$ using the $max$ function:

$$
log(odd_i) = \sum_{j=0}^{max(i-1, 0)} (i-j)log(a_j)  +  log(odd_{0}) (5)
$$

We can write [5] as a linear problem with the following definitions:

$$
y = X \beta + \beta_0
$$

$$
y_i = log(odd_i)
$$

$$
X_{i,j} =  max(i-j, 0)
$$

$$
\beta_i =  log(a_i) (6)
$$

Now if we apply a LASSO regression we will find the solution that minimize the following cost function

$$
Err = \sum (y-\hat{y})^2 + \alpha |\beta|_1
$$

Hopefully, this solution will be sparse which means that most of the $\beta_i$ will be $0$, and hence $a_i=1$.
This is equivalent to say that the $b_i$ are almost constant except at the values whete $a_i \neq 1$.

[1] Bettencourt, L. M. A., & Ribeiro, R. M. (2008). Real time bayesian estimation of the epidemic potential of emerging infectious diseases. PLoS ONE, 3(5). https://doi.org/10.1371/journal.pone.0002185

[2] Sanche, S., Lin, Y. T., Xu, C., Romero-Severson, E., Hengartner, N., & Ke, R. (2020). High Contagiousness and Rapid Spread of Severe Acute Respiratory Syndrome Coronavirus 2. Emerging Infectious Diseases, 26(7). https://doi.org/10.3201/eid2607.200282

## Classes

This cell contains the main class: LassoICSelector. Its main method is fit_best_alpha. It works as follows:
```
For each alpha value:
    1. Fits a lasso regression to the data
    2. Selectes the first non zero variable from each chunck
    3. Fits a linear regression with the selected variables
    4. Excludes all non sifgificative (p-value>0.05) variables and fits a linear model again

The linear model with less AIC from step 4 is selected.
```

```python
class FirstInChunkSelector(object):
    '''Selects first element from each non zero chunk.'''

    def __init__(self, clf):
        self.clf = clf
        self.coef = None
        self.mask = None

    def select_coef(self):
        n_features = len(self.clf.coef_)
        no_zero = np.zeros(n_features+1)
        no_zero[1:] = self.clf.coef_ != 0
        #v = np.hstack([np.zeros(np.int(1/GAMMA-2)), np.ones(np.int(1/GAMMA-1))])
        #no_zero[1:] = np.convolve(self.clf.coef_ != 0, v, mode='same') > 0
        self.mask = np.diff(no_zero)>0
        self.mask[0] = True
        self.coef = self.clf.coef_[self.mask]
        return self.coef

    def transform(self, X):
        self.select_coef()
        return X[:, self.mask]

    def get_support(self):
        self.select_coef()
        return self.mask

    def get_number_of_features(self):
        self.select_coef()
        return sum(self.mask)

class LassoICSelector(object):
    """LASSO regression with FirstInChunk selector."""

    def __init__(self, X, y, criterion, alpha=0.05):
        self.lasso = linear_model.LassoLars(alpha=0, max_iter=100000)
        self.criterion = criterion
        self.selector = FirstInChunkSelector(self.lasso)
        self.OLS = sm.OLS
        #self.OLS = sm.RLM
        self.ols = self.OLS(y, X)

        self.ols_results = None
        self.X = X
        self.y = y
        self.final_ols = False
        self.alpha = alpha

    def transform_to_ols(self, X):
        '''Selects only the features of X are used by OLS.
        Also, adds a coloumn with ones for the intercept.
        '''

        X_new = self.selector.transform(X)
        if self.final_ols:
            X_new = X[:, self.support]
        X_new_with_cte = np.hstack([X_new, np.ones((X_new.shape[0], 1))])
        return X_new_with_cte

    def fit(self, X, y):
        '''Selects features and fits the OLS.'''

        # select features
        X_new = self.transform_to_ols(X)

        # fit ols
        self.ols = self.OLS(y, X_new)
        self.ols_results = self.ols.fit()

        # iteratively remove non signicative variables and fit again
        mask = self.ols_results.pvalues < self.alpha / len(self.ols_results.pvalues)
        mask[0] = True
        Xnew = self.transform_to_ols(X)
        Xnew = Xnew[:, mask]
        self.support = self.selector.get_support()
        self.ols = self.OLS(y, Xnew)
        self.ols_results = self.ols.fit()
        while any(self.ols_results.pvalues[1:] >= self.alpha / len(self.ols_results.pvalues)):
            mask.values[mask.values] = (self.ols_results.pvalues < self.alpha / len(self.ols_results.pvalues)).values
            mask[0] = True
            Xnew = self.transform_to_ols(X)
            Xnew = Xnew[:, mask]
            self.support = self.selector.get_support()
            self.ols = self.OLS(y, Xnew)
            self.ols_results = self.ols.fit()

        self.support[self.support] = mask[:-1]

    def fit_best_alpha(self, X, y):
        '''returns the model with the lowst cirterion.'''

        self.lasso.fit(X, y)
        alphas = self.lasso.alphas_
        self.criterions_ = np.zeros(len(alphas))
        self.log_liklehods = np.zeros(len(alphas))

        for i, alpha in enumerate(alphas):
            self.lasso.coef_ = self.lasso.coef_path_[:, i]
            self.fit(X, y)
            self.criterions_[i], self.log_liklehods[i] = self.get_criterion(self.ols.exog, y)

        # we use a list of tuples to find the minimum cirterion value.
        # If there are ties, we use the maximum alpha value.
        criterions_idx = list(zip(self.criterions_, alphas, range(len(alphas))))
        criterion, alpha, idx = min(criterions_idx, key=lambda x: (x[0], -x[1]))

        self.lasso.coef_ = self.lasso.coef_path_[:, idx]
        self.lasso.alpha = alpha
        self.fit(X, y)
        self.final_ols = True

    def predict(self, X):
        '''Predicts y useing the OLS fit.'''

        return self.ols.predict(self.ols_results.params, X)

    def log_liklihood(self, X, y):
        '''Computes the log liklihood assuming normally distributed errors.'''

        eps64 = np.finfo('float64').eps

        # residuals
        R = y - self.predict(X)
        sigma2 = np.var(R)

        loglike = -0.5 * len(R) * np.log(sigma2)
        loglike -= 0.5 * len(R) * np.log(2*np.pi) - 0.5*len(R) + 0.5
        return loglike

    def get_criterion(self, X, y):
        '''Computes AIC or BIC criterion.'''

        n_samples = X.shape[0]
        if self.criterion == 'aic':
            K = 2  # AIC
        elif self.criterion == 'bic':
            K = np.log(n_samples)
        else:
            raise ValueError('criterion should be either bic or aic')

        log_like = self.log_liklihood(X, y)
        df = X.shape[1]

        aic = K * df - 2*log_like
        self.criterion_ = aic

        return self.criterion_, log_like
```

## Fit
Now, we create the linear system and fit the model

```python
plt.close('all')
lics_dict = {}
# fig, axes = plt.subplots(nrows=17, ncols=3, figsize=(12, 8))
gof_list = []
for i, state in enumerate(df.State.unique()):
    dfstate = df[df['State']==state]
    #dfstate = dfstate[(dfstate.Odds.notna()) & (dfstate.Odds!=0)]
    # create the independent and the dependent variables
    y = np.log(dfstate['Odds'])
    X = np.tri(len(y))
    X = np.cumsum(X, axis=0)[:, 1:]
    X = X[(dfstate.Odds.notna()) & (dfstate.Odds!=0), :]
    y = y[(dfstate.Odds.notna()) & (dfstate.Odds!=0)]

    # create lasso instance
    lics = LassoICSelector(X, y.values, 'bic', alpha=0.01)

    # fit
    lics.fit_best_alpha(X, y)
    lics_dict[state] = lics

    #     ax = sns.lineplot(lics.lasso.alphas_, lics.criterions_, ax=axes[i, 2])
    #     ax.vlines(lics.lasso.alpha, min(lics.criterions_), max(lics.criterions_))
    #     ax.set_ylabel('BIC')
    #     ax.set_xlabel('Alpha')
    #     ax.set_xscale('log')
    #     axes[i, 0].plot(lics.lasso.alphas_, lics.lasso.coef_path_.T)
    #     axes[i, 0].set_xscale('log')
    #     axes[i, 0].set_title(state)
    #     axes[i, 0].set_xlabel('Alpha')
    #     axes[i, 0].set_ylabel('Coefficient Value')
    #     axes[i, 0].vlines(lics.lasso.alpha, lics.lasso.coef_path_.min(), lics.lasso.coef_path_.max())
    #     axes[i, 1].plot(lics.lasso.coef_)
    #     axes[i, 1].scatter(np.arange(len(lics.lasso.coef_))[lics.support], lics.lasso.coef_[lics.support])
    #     axes[i, 1].set_xlabel('Coefficient #')
    #     axes[i, 1].set_ylabel('Coefficient Value')

    gof_list.append({"State":state, '$R^2$':lics.ols_results.rsquared, "N": int(lics.ols_results.nobs), "df_model": int(lics.ols_results.df_model), "fvalue":lics.ols_results.fvalue, "f_pvalue":lics.ols_results.f_pvalue})
    #print('{:<13} & {:.2f} & {:n} & {:n} & {:.2f} & {:.2e}'.format(state, lics.ols_results.rsquared, lics.ols_results.nobs, lics.ols_results.df_model, lics.ols_results.fvalue, lics.ols_results.f_pvalue))
    #print(state)
    #display(lics.ols_results.summary())
gof = pd.DataFrame(gof_list).sort_values('$R^2$', ascending=False).rename(columns={'df_model': 'D.F.', 'fvalue': 'F-statistic', 'f_pvalue': 'p-value'})
out = gof.to_latex(index=False,formatters={"$R^2$": "{:0.3f}".format, 'fvalue':"{:0.2f}".format, 'f_pvalue': "{:.2e}".format},
             bold_rows=True, escape=False, label='tab:gof', caption='Goodness of fit for each dataset.')
print(out)
```

    /home/andres/anaconda3/lib/python3.7/site-packages/pandas/core/series.py:679: RuntimeWarning: divide by zero encountered in log
      result = getattr(ufunc, method)(*inputs, **kwargs)
    /home/andres/anaconda3/lib/python3.7/site-packages/pandas/core/series.py:679: RuntimeWarning: divide by zero encountered in log
      result = getattr(ufunc, method)(*inputs, **kwargs)
    /home/andres/anaconda3/lib/python3.7/site-packages/pandas/core/series.py:679: RuntimeWarning: divide by zero encountered in log
      result = getattr(ufunc, method)(*inputs, **kwargs)

    \begin{table}
    \centering
    \caption{Goodness of fit for each dataset.}
    \label{tab:gof}
    \begin{tabular}{lrrrrr}
    \toprule
             State & $R^2$ &    N &  D.F. &  F-statistic &        p-value \\
    \midrule
          New York & 0.997 &  119 &    10 &  3992.658934 &  7.877846e-134 \\
     Massachusetts & 0.987 &   92 &     4 &  1711.946879 &   8.455634e-82 \\
          Michigan & 0.983 &   89 &     5 &   987.866820 &   2.324393e-72 \\
       Connecticut & 0.981 &   88 &     3 &  1438.831575 &   4.534263e-72 \\
      Rhode Island & 0.948 &   98 &     2 &   864.776556 &   1.088772e-61 \\
          Virginia & 0.918 &   86 &     3 &   305.468584 &   2.184829e-44 \\
          Delaware & 0.886 &   92 &     3 &   228.607283 &   2.038413e-41 \\
              Iowa & 0.872 &   86 &     3 &   185.719315 &   1.862787e-36 \\
         Wisconsin & 0.811 &   83 &     2 &   171.824297 &   1.105016e-29 \\
          Colorado & 0.804 &   96 &     2 &   191.334575 &   1.095911e-33 \\
         Minnesota & 0.640 &   84 &     2 &    71.849515 &   1.131865e-18 \\
            Alaska & 0.586 &  107 &     2 &    73.626389 &   1.202176e-20 \\
           Indiana & 0.472 &   93 &     3 &    26.540667 &   2.359064e-12 \\
         Tennessee & 0.398 &   88 &     2 &    28.101998 &   4.283584e-10 \\
           Wyoming & 0.203 &   84 &     2 &    10.312295 &   1.023745e-04 \\
             Texas & 0.079 &   34 &     1 &     2.752898 &   1.068493e-01 \\
    \bottomrule
    \end{tabular}
    \end{table}

Lets copy the fitted values to a dataframe, and calculate the parameters and erros of the model.

```python
data_list = []
for state in df.State.unique():
    print(state)
    lics = lics_dict[state]
    data = df[df['State']==state].copy()
    #data = data[(data.Odds.notna()) & (data.Odds!=0)]
    # yhat = lics.ols_results.fittedvalues
    y = np.log(data['Odds'])
    X = np.tri(len(y))
    X = np.cumsum(X, axis=0)[:, 1:]
    X = X[(data.Odds.notna()) & (data.Odds!=0), :]
    y = y[(data.Odds.notna()) & (data.Odds!=0)]
    data = data[(data.Odds.notna()) & (data.Odds!=0)]
    Xols = lics.transform_to_ols(X)
    yhat = lics.ols.predict(lics.ols_results.params, Xols)
    # from equation 5
    odds_hat = np.exp(yhat)

    # the error in yhat is
    # Xols = lics.transform_to_ols(X)
    (yhat_std, yhat_l, yhat_u) = wls_prediction_std(lics.ols_results, Xols)

    # propagation of errors
    #oddshat_std = np.array([exp_x_sigma(mu, s)[0] for mu, s in zip(yhat, yhat_std)])#odds_hat*yhat_std
    #oddshat_std = exp_x_sigma(yhat, yhat_std)
    oddshat_l = np.exp(yhat-2*yhat_std)
    oddshat_u = np.exp(yhat+2*yhat_std)
    data.loc[:, 'odds_hat'] = odds_hat
    #data.loc[:, 'oddshat_std'] = oddshat_std
    #data.loc[:, 'oddshat_l'] = odds_hat - 2*oddshat_std
    #data.loc[:, 'oddshat_u'] = odds_hat + 2*oddshat_std
    data.loc[:, 'oddshat_l'] = oddshat_l
    data.loc[:, 'oddshat_u'] = oddshat_u

    # use coefficients to calculate Rt
    coef = np.zeros(len(data))
    coef_std = np.zeros_like(coef) * np.nan
    ind = np.squeeze(np.argwhere(lics.support))

    # we do not use the last coefficient since it's the intercept (=log(odds_0))
    coef[ind] = lics.ols_results.params[:-1]

    # using equation 2, 4 and 6
    data.loc[:, 'R'] = np.cumsum(coef)/GAMMA+1

    # get covarinace matrix of coefficients
    cov = lics.ols_results.cov_params().values

    # since the values of Rts are a sum of variables, we use the formula
    # of the sum of gaussian variables with a known covariance matrix
    stds = [np.sqrt(cov[:n, :n].sum()) for n in range(1, cov.shape[0])]
    if len(stds)==1:
        stds = stds[0]
    coef_std[ind] = stds

    # error propagation formula
    data.loc[:, 'Rstd'] = coef_std / GAMMA

    data['Rstd'] = data['Rstd'].fillna(method='ffill')
    data['R_l'] = data['R'] - 2*data['Rstd']
    data['R_u'] = data['R'] + 2*data['Rstd']

    r_index = data.R.diff() != 0
    Rts = data.loc[r_index, ['Date', 'R', 'R_l', 'R_u']]
    Rts['delta'] = Rts['R_u'] - Rts['R_l']
    display(Rts)
    data_list.append(data)
data = pd.concat(data_list)
```

    Alaska

    /home/andres/anaconda3/lib/python3.7/site-packages/pandas/core/series.py:679: RuntimeWarning: divide by zero encountered in log
      result = getattr(ufunc, method)(*inputs, **kwargs)

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Date</th>
      <th>R</th>
      <th>R_l</th>
      <th>R_u</th>
      <th>delta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>2020-03-24</td>
      <td>0.614659</td>
      <td>0.551120</td>
      <td>0.678198</td>
      <td>0.127078</td>
    </tr>
    <tr>
      <th>58</th>
      <td>2020-05-21</td>
      <td>1.261645</td>
      <td>1.193654</td>
      <td>1.329637</td>
      <td>0.135983</td>
    </tr>
  </tbody>
</table>
</div>

    Colorado

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Date</th>
      <th>R</th>
      <th>R_l</th>
      <th>R_u</th>
      <th>delta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>110</th>
      <td>2020-03-18</td>
      <td>1.240505</td>
      <td>1.152075</td>
      <td>1.328936</td>
      <td>0.176861</td>
    </tr>
    <tr>
      <th>138</th>
      <td>2020-04-15</td>
      <td>0.711052</td>
      <td>0.679942</td>
      <td>0.742162</td>
      <td>0.062220</td>
    </tr>
  </tbody>
</table>
</div>

    Connecticut

    /home/andres/anaconda3/lib/python3.7/site-packages/pandas/core/series.py:679: RuntimeWarning: divide by zero encountered in log
      result = getattr(ufunc, method)(*inputs, **kwargs)

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Date</th>
      <th>R</th>
      <th>R_l</th>
      <th>R_u</th>
      <th>delta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>206</th>
      <td>2020-03-20</td>
      <td>1.975427</td>
      <td>1.782746</td>
      <td>2.168108</td>
      <td>0.385362</td>
    </tr>
    <tr>
      <th>215</th>
      <td>2020-03-29</td>
      <td>0.951031</td>
      <td>0.878439</td>
      <td>1.023623</td>
      <td>0.145184</td>
    </tr>
    <tr>
      <th>230</th>
      <td>2020-04-13</td>
      <td>0.606156</td>
      <td>0.591690</td>
      <td>0.620622</td>
      <td>0.028932</td>
    </tr>
  </tbody>
</table>
</div>

    Delaware

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Date</th>
      <th>R</th>
      <th>R_l</th>
      <th>R_u</th>
      <th>delta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>295</th>
      <td>2020-03-23</td>
      <td>1.497509</td>
      <td>1.421711</td>
      <td>1.573307</td>
      <td>0.151596</td>
    </tr>
    <tr>
      <th>324</th>
      <td>2020-04-21</td>
      <td>0.581046</td>
      <td>0.544854</td>
      <td>0.617238</td>
      <td>0.072385</td>
    </tr>
    <tr>
      <th>376</th>
      <td>2020-06-12</td>
      <td>1.091479</td>
      <td>0.789941</td>
      <td>1.393016</td>
      <td>0.603075</td>
    </tr>
  </tbody>
</table>
</div>

    Indiana

    /home/andres/anaconda3/lib/python3.7/site-packages/pandas/core/series.py:679: RuntimeWarning: divide by zero encountered in log
      result = getattr(ufunc, method)(*inputs, **kwargs)

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Date</th>
      <th>R</th>
      <th>R_l</th>
      <th>R_u</th>
      <th>delta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>387</th>
      <td>2020-03-19</td>
      <td>1.971142</td>
      <td>1.679836</td>
      <td>2.262447</td>
      <td>0.582611</td>
    </tr>
    <tr>
      <th>403</th>
      <td>2020-04-04</td>
      <td>0.814250</td>
      <td>0.768377</td>
      <td>0.860124</td>
      <td>0.091748</td>
    </tr>
    <tr>
      <th>476</th>
      <td>2020-06-16</td>
      <td>6.510475</td>
      <td>3.938724</td>
      <td>9.082226</td>
      <td>5.143501</td>
    </tr>
  </tbody>
</table>
</div>

    Iowa

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Date</th>
      <th>R</th>
      <th>R_l</th>
      <th>R_u</th>
      <th>delta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>481</th>
      <td>2020-03-22</td>
      <td>1.388867</td>
      <td>1.339267</td>
      <td>1.438467</td>
      <td>0.099200</td>
    </tr>
    <tr>
      <th>513</th>
      <td>2020-04-23</td>
      <td>0.602167</td>
      <td>0.543551</td>
      <td>0.660782</td>
      <td>0.117231</td>
    </tr>
    <tr>
      <th>536</th>
      <td>2020-05-16</td>
      <td>0.821763</td>
      <td>0.767732</td>
      <td>0.875795</td>
      <td>0.108063</td>
    </tr>
  </tbody>
</table>
</div>

    Massachusetts

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Date</th>
      <th>R</th>
      <th>R_l</th>
      <th>R_u</th>
      <th>delta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>567</th>
      <td>2020-03-16</td>
      <td>1.893045</td>
      <td>1.807977</td>
      <td>1.978113</td>
      <td>0.170136</td>
    </tr>
    <tr>
      <th>579</th>
      <td>2020-03-28</td>
      <td>1.103272</td>
      <td>1.060947</td>
      <td>1.145598</td>
      <td>0.084650</td>
    </tr>
    <tr>
      <th>595</th>
      <td>2020-04-13</td>
      <td>0.681110</td>
      <td>0.670340</td>
      <td>0.691880</td>
      <td>0.021540</td>
    </tr>
    <tr>
      <th>650</th>
      <td>2020-06-07</td>
      <td>0.315284</td>
      <td>0.193782</td>
      <td>0.436786</td>
      <td>0.243003</td>
    </tr>
  </tbody>
</table>
</div>

    Michigan

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Date</th>
      <th>R</th>
      <th>R_l</th>
      <th>R_u</th>
      <th>delta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>659</th>
      <td>2020-03-19</td>
      <td>1.372842</td>
      <td>1.294077</td>
      <td>1.451607</td>
      <td>0.157529</td>
    </tr>
    <tr>
      <th>676</th>
      <td>2020-04-05</td>
      <td>0.436884</td>
      <td>0.398957</td>
      <td>0.474811</td>
      <td>0.075854</td>
    </tr>
    <tr>
      <th>703</th>
      <td>2020-05-02</td>
      <td>0.766759</td>
      <td>0.725773</td>
      <td>0.807744</td>
      <td>0.081971</td>
    </tr>
    <tr>
      <th>729</th>
      <td>2020-05-28</td>
      <td>0.445190</td>
      <td>0.322020</td>
      <td>0.568360</td>
      <td>0.246340</td>
    </tr>
    <tr>
      <th>740</th>
      <td>2020-06-08</td>
      <td>1.291152</td>
      <td>1.011347</td>
      <td>1.570957</td>
      <td>0.559609</td>
    </tr>
  </tbody>
</table>
</div>

    Minnesota

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Date</th>
      <th>R</th>
      <th>R_l</th>
      <th>R_u</th>
      <th>delta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>748</th>
      <td>2020-03-28</td>
      <td>1.462461</td>
      <td>1.346454</td>
      <td>1.578469</td>
      <td>0.232015</td>
    </tr>
    <tr>
      <th>778</th>
      <td>2020-04-27</td>
      <td>0.648631</td>
      <td>0.589697</td>
      <td>0.707566</td>
      <td>0.117869</td>
    </tr>
  </tbody>
</table>
</div>

    New York

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Date</th>
      <th>R</th>
      <th>R_l</th>
      <th>R_u</th>
      <th>delta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>832</th>
      <td>2020-03-15</td>
      <td>1.876019</td>
      <td>1.810956</td>
      <td>1.941082</td>
      <td>0.130126</td>
    </tr>
    <tr>
      <th>846</th>
      <td>2020-03-29</td>
      <td>0.729573</td>
      <td>0.686223</td>
      <td>0.772923</td>
      <td>0.086701</td>
    </tr>
    <tr>
      <th>862</th>
      <td>2020-04-14</td>
      <td>0.429567</td>
      <td>0.404140</td>
      <td>0.454994</td>
      <td>0.050855</td>
    </tr>
    <tr>
      <th>886</th>
      <td>2020-05-08</td>
      <td>0.502555</td>
      <td>0.481010</td>
      <td>0.524100</td>
      <td>0.043090</td>
    </tr>
    <tr>
      <th>915</th>
      <td>2020-06-06</td>
      <td>0.756487</td>
      <td>0.686065</td>
      <td>0.826909</td>
      <td>0.140844</td>
    </tr>
    <tr>
      <th>926</th>
      <td>2020-06-17</td>
      <td>1.209978</td>
      <td>1.077152</td>
      <td>1.342804</td>
      <td>0.265653</td>
    </tr>
    <tr>
      <th>934</th>
      <td>2020-06-25</td>
      <td>0.163532</td>
      <td>-0.262469</td>
      <td>0.589534</td>
      <td>0.852003</td>
    </tr>
    <tr>
      <th>937</th>
      <td>2020-06-28</td>
      <td>1.944296</td>
      <td>1.562275</td>
      <td>2.326316</td>
      <td>0.764041</td>
    </tr>
    <tr>
      <th>941</th>
      <td>2020-07-02</td>
      <td>-0.583596</td>
      <td>-1.266769</td>
      <td>0.099577</td>
      <td>1.366346</td>
    </tr>
    <tr>
      <th>943</th>
      <td>2020-07-04</td>
      <td>1.182859</td>
      <td>0.977824</td>
      <td>1.387895</td>
      <td>0.410071</td>
    </tr>
  </tbody>
</table>
</div>

    Rhode Island

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Date</th>
      <th>R</th>
      <th>R_l</th>
      <th>R_u</th>
      <th>delta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>951</th>
      <td>2020-04-01</td>
      <td>1.063358</td>
      <td>0.978206</td>
      <td>1.148509</td>
      <td>0.170303</td>
    </tr>
    <tr>
      <th>969</th>
      <td>2020-04-19</td>
      <td>0.741079</td>
      <td>0.726930</td>
      <td>0.755227</td>
      <td>0.028296</td>
    </tr>
  </tbody>
</table>
</div>

    Tennessee

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Date</th>
      <th>R</th>
      <th>R_l</th>
      <th>R_u</th>
      <th>delta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>1049</th>
      <td>2020-03-25</td>
      <td>0.823708</td>
      <td>0.775239</td>
      <td>0.872176</td>
      <td>0.096937</td>
    </tr>
    <tr>
      <th>1099</th>
      <td>2020-05-14</td>
      <td>1.196140</td>
      <td>1.128125</td>
      <td>1.264155</td>
      <td>0.136030</td>
    </tr>
  </tbody>
</table>
</div>

    Texas

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Date</th>
      <th>R</th>
      <th>R_l</th>
      <th>R_u</th>
      <th>delta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>1138</th>
      <td>2020-05-16</td>
      <td>1.177963</td>
      <td>0.963445</td>
      <td>1.392481</td>
      <td>0.429037</td>
    </tr>
  </tbody>
</table>
</div>

    Virginia

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Date</th>
      <th>R</th>
      <th>R_l</th>
      <th>R_u</th>
      <th>delta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>1172</th>
      <td>2020-03-23</td>
      <td>1.127613</td>
      <td>1.103038</td>
      <td>1.152189</td>
      <td>0.049151</td>
    </tr>
    <tr>
      <th>1199</th>
      <td>2020-04-19</td>
      <td>0.905442</td>
      <td>0.887780</td>
      <td>0.923104</td>
      <td>0.035324</td>
    </tr>
    <tr>
      <th>1230</th>
      <td>2020-05-20</td>
      <td>0.806608</td>
      <td>0.782033</td>
      <td>0.831184</td>
      <td>0.049151</td>
    </tr>
  </tbody>
</table>
</div>

    Wisconsin

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Date</th>
      <th>R</th>
      <th>R_l</th>
      <th>R_u</th>
      <th>delta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>1258</th>
      <td>2020-03-31</td>
      <td>1.009125</td>
      <td>0.943201</td>
      <td>1.075050</td>
      <td>0.131849</td>
    </tr>
    <tr>
      <th>1284</th>
      <td>2020-04-26</td>
      <td>0.797418</td>
      <td>0.771025</td>
      <td>0.823811</td>
      <td>0.052787</td>
    </tr>
  </tbody>
</table>
</div>

    Wyoming

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>Date</th>
      <th>R</th>
      <th>R_l</th>
      <th>R_u</th>
      <th>delta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>1341</th>
      <td>2020-03-17</td>
      <td>2.174302</td>
      <td>1.500521</td>
      <td>2.848083</td>
      <td>1.347562</td>
    </tr>
    <tr>
      <th>1350</th>
      <td>2020-03-26</td>
      <td>0.895547</td>
      <td>0.844887</td>
      <td>0.946206</td>
      <td>0.101319</td>
    </tr>
  </tbody>
</table>
</div>

```python
res = lics_dict['Delaware'].ols_results
dir(res)
#res.params
pd.Series(2*np.diag(res.cov_params())**0.5, index=res.cov_params().index)
```

    x1       0.010106
    x2       0.013502
    x3       0.042731
    const    0.215323
    dtype: float64

```python
plt.close(1)
stacked = data.set_index(['Date', 'State'])[['Odds', 'odds_hat', 'oddshat_u', 'oddshat_l']]
stacked = stacked.stack().reset_index().rename(columns={'level_2': 'variable', 0: 'val'})
stacked['Branch'] = 'Fit'
stacked.loc[stacked['variable'] == 'Odds', 'Branch'] = 'Data'
stacked = stacked.set_index(['Date', 'State', 'Branch', 'variable']).unstack().reset_index()
stacked.columns = ['Date', 'State', 'Branch', 'Odds', 'odds_hat', 'oddshat_u', 'oddshat_l']
stacked = stacked.sort_values(['State', 'Date'])
#stacked = stacked[stacked.State.isin(['Alaska', 'Colorado', 'Connecticut'])]

g1 = sns.FacetGrid(stacked, col='State', col_wrap=3, hue='Branch', aspect=1.5, dropna=False, hue_order=['Data', 'Fit'], sharey=False, col_order=gof.State)
legend_and_hand = []
def myplot(data, x=None, y=None, color=None, label=None):
    ax = plt.gca()
    ax = data.plot(x=x, y=y, ax=ax, label=label)
    legend_and_hand.append(ax.get_legend_handles_labels())
    return ax
#g1.map_dataframe(myplot, x='Date', y='odds_hat')
g1.map_dataframe(sns.lineplot, x='Date', y='odds_hat')
g1.map_dataframe(sns.scatterplot, x='Date', y='Odds')

g1.map(plt.fill_between, 'Date', 'oddshat_l', 'oddshat_u', alpha=0.1)
hl = g1.axes[0].get_legend_handles_labels()

handlers = [hl[0][1], hl[0][0]]
labels = [hl[1][1], hl[1][0]]

lh = {l:h for h,l in zip(handlers, labels)}
g1.add_legend(lh, loc='lower center', bbox_to_anchor=(0.7, 0), title='', ncol=3)#

# plt.yscale('log')
# plt.xlabel('')
g1.axes[0].xaxis.set_major_locator(locator)
g1.axes[0].xaxis.set_major_formatter(formatter)
g1.set(xlim=[data['Date'].min(), data.loc[data.odds_hat.notna(), 'Date'].max()], ylabel='Odds', yscale='log')
g1.set_titles("{col_name}")
plt.tight_layout()
plt.savefig('figs/all_states_OddsL1.jpg', dpi=300)
plt.show()
```

    Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …

```python
plt.close('all')

g1 = sns.FacetGrid(data.set_index('date'), col='State', col_wrap=3, aspect=1.5, dropna=False, ylim=[0, 2.5])
def myplot(data, y=None, color=None):
    data.plot(x='Date', y=y, ax=plt.gca())
g1.map_dataframe(sns.lineplot, x='Date', y='R')
#g1.map_dataframe(myplot, y='R')
g1.map(plt.fill_between, 'Date', 'R_u', 'R_l', alpha=0.2)
#ax = data.plot(x='Date', y='R', legend=False)
# lines = []
# for ax, dsname in zip(g1.axes[:, 0], data.State.unique()):
#     for line in events[dsname]:
#         if line < pd.to_datetime('03-04-2020 00:00', dayfirst=True):
#             color = current_palette[1]
#             label = 'Mobility restrictions'
#         elif line == pd.to_datetime('03-04-2020 00:00', dayfirst=True):
#             color = 'k'
#             label = 'Masks (CDC)'
#         else:
#             label = 'Masks (State)'
#             color = current_palette[2]
#         lines.append(ax.axvline(line, 0,1, linestyle='--', color=color, label=label))

g1.set(ylabel='$R_t$')
g1.set(yscale='linear')
g#1.axes[0, 0].legend([lines[0], lines[4], lines[5]], ['Mobility restrictions', 'Masks (CDC)', 'Masks (Local)'])
#print(g1.axes[0, 0].legend().get_legend_handler_map())
g1.axes[0].xaxis.set_major_locator(locator)
g1.axes[0].xaxis.set_major_formatter(formatter)
plt.xlabel('')
g1.set_titles('{col_name}')
plt.tight_layout()
plt.savefig('figs/all_states_Rt.jpg', dpi=300)
```

    Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …