# Notebook from vishalbelsare/pyross
Path: examples/deterministic/ex01b_Model.ipynb

## The SIR model

The SIR model of epidemiology partitions the population into three compartments: susceptibles, S, who can catch the disease; infectives, I, who have already caught the disease and infect susceptibles; and removed individuals, R. Since the disease is assumed not to be fatal, the sum $N=S+I+R$ remains constant. The rate at which the susceptibles get infected is
$$
\lambda(t) = \frac{\beta I}{N}
$$
where the parameter $\beta$ is the probability of infection on contact. The infected individuals remove from the disease at a rate $\gamma$. Then, the ordinary differential equations of the SIR model are

\begin{align}
\dot S &= -\lambda(t)S \\
\dot I &= \lambda(t)I - \gamma I \\
\dot R &= \gamma I
\end{align}

This example integrates the above equations to obtain what is called the **epidemic curve**: a plot of the number of susceptibles and infectives as a function of time.

Below we use the class `Model` to simulate SIR model with three age-groups_____no_output_____

# M=3, SIR with three age-groups

import numpy as np
import pyross
import copy

import matplotlib.pyplot as plt

model_spec = { "classes" : ["S", "I"],

             "S" : {"infection" : [ ["I","S", "-beta"] ]},  ## the I class passes infection to S class
             "I" : { "linear"    : [ ["I", "-gamma"] ],     ## this is recovery process for I class
                    "infection" : [ ["I", "S", "beta"]]}

              ## the recovered class R is internally determined by number conservation
             }

parameters = {'beta'  : 0.1,
              'gamma' : 0.1,
              }

M=3;  Ni=1000*np.ones(M);  N=np.sum(Ni)

# Initial conditions as an array
x0 = np.array([
    980, 980, 980,    # S
    20,   20,  20,    # I
])

# Or initial conditions as a dictionary
x0 = {'S': [n-20 for n in Ni], 'I':  [20, 20, 20]  }

CM = np.array( [[1,   0.5, 0.1],
               [0.5, 1,   0.5],
               [0.1, 0.5, 1  ]], dtype=float)

def contactMatrix(t):
    return CM

# duration of simulation and data file
Tf = 160;  Nf=Tf+1;

model = pyross.deterministic.Model(model_spec, parameters, M, Ni)

# simulate model
data = model.simulate(x0, contactMatrix, Tf, Nf)

# plot the data and obtain the epidemic curve
S = np.sum(model.model_class_data('S', data), axis=1)
I = np.sum(model.model_class_data('I', data), axis=1)
R = np.sum(model.model_class_data('R', data), axis=1)
t = data['t']

fig = plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')
plt.rcParams.update({'font.size': 22})

plt.fill_between(t, 0, S/N, color="#348ABD", alpha=0.3)
plt.plot(t, S, '-', color="#348ABD", label='$S$', lw=4)

plt.fill_between(t, 0, I/N, color='#A60628', alpha=0.3)
plt.plot(t, I, '-', color='#A60628', label='$I$', lw=4)

plt.fill_between(t, 0, R/N, color="dimgrey", alpha=0.3)
plt.plot(t, R, '-', color="dimgrey", label='$R$', lw=4)

plt.legend(fontsize=26); plt.grid()
plt.autoscale(enable=True, axis='x', tight=True)
plt.ylabel('Compartment value')
plt.xlabel('Days');_____no_output_____

## A toy model for vaccinations, derived from SEIR
_____no_output_____

model_spec = {
    "classes" : ["S", "E", "I", "R"],

    "S" : {
        "infection" : [ ["I", "S", "-beta"] ]
    },

    "E" : {
        "linear"    : [ ["E", "-gammaE"] ],
        "infection" : [ ["I", "S", "beta"] ]
    },

    "I" : {
        "linear"    : [ ["E", "gammaE"],
                        ["I", "-gammaI"]]
    },

    "R" : {
        "linear"    : [ ["I", "gammaI"] ],
    }
}
_____no_output_____parameters = {
    'beta' : 0.05,
    'gammaE' : 0.5,
    'gammaI' : 0.1
}_____no_output_____M = 3
Ni = 50000*np.ones(M)
N = np.sum(Ni)

# initial conditions as a dictionary
E0 = [30, 30, 30]
S0 = [n-30 for n in Ni]
I0 = [0, 0, 0]
R0 = [0, 0, 0]

x0 = {
    'S' : S0,
    'E' : E0,
    'I' : I0,
    'R' : R0
}

CM = np.array([
    [3,   0.8, 0.1],  # youngest cohort spreading most
    [0.8, 2,   0.5],
    [0.1, 0.5, 1  ]
], dtype=float)

# the contact matrix is time-dependent
def contactMatrix(t):
    if t<40:
        xx = CM
    elif 40<=t<200:
        xx = 0.5*CM
    elif 200<=t<280:
        xx = 0.9*CM
    elif 280<=t<370:
        xx = 0.5*CM
    else:
        xx = CM
    return xx

# duration of simulation and data file
Tf = 1000;  Nf=Tf+1;

model = pyross.deterministic.Model(model_spec, parameters, M, Ni)
# simulate model
data = model.simulate(x0, contactMatrix, Tf, Nf)_____no_output_____# plot the data and obtain the epidemic curve
S = np.sum(model.model_class_data('S', data), axis=1)
E = np.sum(model.model_class_data('E', data), axis=1)
I = np.sum(model.model_class_data('I', data), axis=1)
R = np.sum(model.model_class_data('R', data), axis=1)
t = data['t']

fig = plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')
plt.rcParams.update({'font.size': 22})

plt.semilogy(t, S, '-',  label='$S$', lw=4)
plt.semilogy(t, E, '-',  label='$E$', lw=4)
plt.semilogy(t, I, '-', label='$I$', lw=4)
plt.semilogy(t, R, '-',  label='$R$', lw=4)

plt.legend(fontsize=26); plt.grid()
plt.autoscale(enable=True, axis='x', tight=True)
plt.ylabel('Compartment value')
plt.xlabel('Days');_____no_output_____# age structure
Ii = model.model_class_data('I', data)

fig = plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')
plt.rcParams.update({'font.size': 22})
plt.stackplot(t,np.transpose(Ii),labels=('cohort 1','cohort 2','cohort 3'))
plt.legend(fontsize=26); plt.grid()
plt.ylabel('Infections')
plt.xlabel('Days');_____no_output_____

Without vaccinations, herd immunity is reached after the 3rd wave (no further lockdown). Total number of people that had been infected, by age group:_____no_output_____

np.round(np.sum(Ii,axis=0)*parameters['gammaI'])_____no_output_____

### The SEIR model with vaccinations

This code takes the `Model` specification of SEIR, extends the number of classes by the vaccinated version of classes and sets up all the transitions. There are additional infection terms, the linear terms are the same in the vaccinated compartments (though they might be changed later, to account for a reduction in severity), and finite resource terms move individuals from the unvaccinated to the vaccinated classes.

This avoids typing the whole model specification by hand, and should also work for our more detailed, calibrated models.

The vaccinated version of class X is labeled XV, The extension to different types/doses of vaccinations should be straightforward (XV1, XV2, et.c)._____no_output_____

model_spec_vaccinations = copy.deepcopy(model_spec)

for cl in model_spec["classes"]:
    clV = cl+'V'
    model_spec_vaccinations["classes"].append(clV)

    model_spec_vaccinations[clV] = {}
    if "infection" in model_spec[cl]:
        model_spec_vaccinations[clV]["infection"] = []
        for term in model_spec[cl]["infection"]:

            termI = term.copy() # vaccinated infecting unvaccinated
            termI[0] += 'V'
            termI[2] += "*thetaI"
            model_spec_vaccinations[cl]["infection"].append(termI)

            termS = term.copy() # unvaccinated infecting vaccinated
            termS[1] += 'V'
            termS[2] += "*thetaS"
            model_spec_vaccinations[clV]["infection"].append(termS)

            termSI = term.copy() # vaccinated infecting vaccinated
            termSI[0] += 'V'
            termSI[1] += 'V'
            termSI[2] += "*thetaS*thetaI"
            model_spec_vaccinations[clV]["infection"].append(termSI)

    if "linear" in model_spec[cl]:
        model_spec_vaccinations[clV]["linear"] = []
        for term in model_spec[cl]["linear"]:
            termV = term.copy()
            termV[0] += 'V'
            model_spec_vaccinations[clV]["linear"].append(termV)

    if "finite-resource" in model_spec[cl]:
        model_spec_vaccinations[clV]["finite-resource"] = []
        for term in model_spec[cl]["finite-resource"]:
            termV = term.copy()
            termV[0] += 'V'
            model_spec_vaccinations[clV]["finite-resource"].append(termV)
    else:
        model_spec_vaccinations[cl]["finite-resource"] = []
        model_spec_vaccinations[clV]["finite-resource"] = []
    term = [cl, "vaccination_rate", "vaccination_priority", "one"]   # one: probability that the vaccination is successful. Set to 1, because we already
                                                                     # account for the imperfectness of vaccinations through the theta parameters
    model_spec_vaccinations[clV]["finite-resource"].append(term.copy())
    term[1] = "-"+term[1]
    model_spec_vaccinations[cl]["finite-resource"].append(term)

model_spec_vaccinations
_____no_output_____parameters = {
    'beta' : 0.05,
    'gammaE' : 0.5,
    'gammaI' : 0.1,
    'thetaS' : 0.6,
    'thetaI' : 0.6,
}_____no_output_____

The age-dependence of vaccinations is introduced explicitly through an age-dependent vaccination rate.
Alternatively, one could have specified a scalar vaccination rate, and implicilty realised the age-dependence though age-dependent priorities._____no_output_____

def vaccination_rate(t):
    rate=500*np.array([0.5*np.tanh((t-420)/10)-0.5*np.tanh((t-480)/10),
                       0.5*np.tanh((t-360)/10)-0.5*np.tanh((t-420)/10),
                       0.5*np.tanh((t-300)/20)-0.5*np.tanh((t-360)/10)  ])
    rate*=(rate>1)
    return rate

def parameter_mapping(input_parameters, t):
    output_parameters = {
        'beta' : input_parameters['beta'],
        'gammaE' : input_parameters['gammaE'],
        'gammaI' : input_parameters['gammaI'],
        'beta*thetaS' : input_parameters['beta']*input_parameters['thetaS'],
        'beta*thetaI' : input_parameters['beta']*input_parameters['thetaI'],
        'beta*thetaS*thetaI' : input_parameters['beta']*input_parameters['thetaS']*input_parameters['thetaI'],
        'one' : 1,
        'vaccination_priority' : 1,
        'vaccination_rate' : vaccination_rate(t)
    }

    return output_parameters_____no_output_____t=np.linspace(200,600,1024)
v=[vaccination_rate(tt) for tt in t]
plt.plot(t,v)
plt.xlabel('days')
plt.ylabel('vaccination rate (by age)')
plt.show()_____no_output_____# initial conditions as a dictionary
E0 = [30, 30, 30]
S0 = [n-30 for n in Ni]
I0 = [0, 0, 0]
R0 = [0, 0, 0]

x0 = {
    'S' : S0,
    'E' : E0,
    'I' : I0,
    'R' : R0
}

for cl in model_spec["classes"]:
    x0[cl+'V'] = np.zeros(M)

# duration of simulation and data file
Tf = 1000;  Nf=Tf+1;

model = pyross.deterministic.Model(model_spec_vaccinations, parameters, M, Ni, time_dep_param_mapping=parameter_mapping)
# simulate model
data = model.simulate(x0, contactMatrix, Tf, Nf)

_____no_output_____# plot the data and obtain the epidemic curve
S = np.sum(model.model_class_data('S', data), axis=1)
E = np.sum(model.model_class_data('E', data), axis=1)
I = np.sum(model.model_class_data('I', data), axis=1)
R = np.sum(model.model_class_data('R', data), axis=1)
SV = np.sum(model.model_class_data('SV', data), axis=1)
EV = np.sum(model.model_class_data('EV', data), axis=1)
IV = np.sum(model.model_class_data('IV', data), axis=1)
RV = np.sum(model.model_class_data('RV', data), axis=1)
V=SV+EV+IV+RV
t = data['t']

fig = plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')
plt.rcParams.update({'font.size': 22})

plt.semilogy(t, S+SV, '-',  label='$S$', lw=4)
plt.semilogy(t, E+EV, '-',  label='$E$', lw=4)
plt.semilogy(t, I+IV, '-', label='$I$', lw=4)
plt.semilogy(t, R+RV, '-',  label='$R$', lw=4)
plt.semilogy(t, V, '-',  label='$V$', lw=4)

plt.legend(fontsize=26); plt.grid()
plt.autoscale(enable=True, axis='x', tight=True)
plt.ylabel('Compartment value')
plt.xlabel('Days');_____no_output_____# age structure
Ii = model.model_class_data('I', data)
IVi = model.model_class_data('IV', data)

fig = plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')
plt.rcParams.update({'font.size': 22})
plt.stackplot(t,np.transpose(Ii+IVi),labels=('cohort 1','cohort 2','cohort 3'))
plt.legend(fontsize=26); plt.grid()
plt.ylabel('Infections')
plt.xlabel('Days');_____no_output_____

With vaccinations, the 3rd wave is reduced, despite the lifting of lockdown. Total number of people that had been infected, by age group:_____no_output_____

# unvaccinated
np.round(np.sum(Ii,axis=0)*parameters['gammaI'])_____no_output_____# vaccinated
np.round(np.sum(IVi,axis=0)*parameters['gammaI'])_____no_output_____


# Notebook from oemuaghwor/awesome-open-geoscience
Path: 210103_04-data-load.ipynb

# Introduction to Earth System Data_____no_output_____# XYZ data_____no_output_____## Load data_____no_output_____

# load from csv via pandas, please look up (=google) "pandas" and "numpy"
import pandas as pd
import numpy as np

# data file in Extra/data_examples.zip
df = pd.read_csv('../data_samples/xyz/points3d.csv')
# df_____no_output_____

## Plot data_____no_output_____

# plot the xy point location
from matplotlib import pyplot as plt

# df.plot(x='eastings', y='northings', marker='.')

x,y = [],[]

x.append (df.eastings)
y.append (df.northings)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(y,x,'.')
plt.show()
# What are the map units (degrees or m ?), possible projection (UTM or latlong ? )_____no_output_____

### Color-code by "z" plots_____no_output_____

# color code by Z
from matplotlib import pyplot as plt
from matplotlib import colorbar
x,y,z = [],[],[]

x.append (df.eastings)
y.append (df.northings)
z.append (df.topo)
# color = [str(item/255.) for item in z]
fig = plt.figure()
ax = fig.add_subplot(111)
# colors = plt.cm.coolwarm(z)
cax=ax.scatter(x, y, marker='.', linewidths=3, c=z, cmap=plt.cm.plasma)
plt.colorbar(cax, ax=ax)
plt.show()

# from matplotlib import pyplot as plt

# x = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# y = [125, 32, 54, 253, 67, 87, 233, 56, 67]

# color = [str(item/255.) for item in y]

# plt.scatter(x, y, s=500, c=color)

# plt.show()_____no_output_____

# Time series

see (and quickly go through) also http://earthpy.org/pandas-basics.html_____no_output_____## Load from Excel into pandas_____no_output_____

# this can be used for any data load and eventual plot, from composition, to spectral, time series (present example)
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt

path = '../data_samples/excel/'
filename = 'sample-time-series.xlsx'

import pandas as pd
xl= pd.ExcelFile(path + filename)
xl.sheet_names

df = xl.parse("sample-sheet-2")
df.head()

# Plotting all columns
# df.plot('Date', 'D')
# df.plot('Date', 'E')
# df.plot('Date', 'F', label='test')

f, axarr = plt.subplots(2, 2)
axarr[0, 0].plot(df.Date, df.B)
# axarr[0, 0].set_title('Axis [0,0]')
axarr[0, 1].plot(df.Date, df.D)
# axarr[0, 1].set_title('Axis [0,1]')
axarr[1, 0].plot(df.Date, df.E)
# axarr[1, 0].set_title('Axis [1,0]')
axarr[1, 1].plot(df.Date, df.F)
plt.show()
# Change some of the parameters above to understand how this works ... _____no_output_____

## Subsetting time series_____no_output_____

# for timeseries pandas you need to set a datetime index before resampling
# please have a look at the extensive Time Series functionality of pandas at https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html

df=df.set_index(pd.DatetimeIndex(df['Date']))

k1 = df.loc[(df.Date >= '1990-01-01') & (df.Date < '2001-01-07')]

k1.plot('Date', 'D', label='stuff')
plt.show()_____no_output_____# plot the xy point location
from matplotlib import pyplot as plt

# df.plot(x='eastings', y='northings', marker='.')

x,y = [],[]

x.append (df.eastings)
y.append (df.northings)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(y,x,'.')
plt.show()
# What are the map units (degrees or m ?), possible projection (UTM or latlong ? )### Resampling time series_____no_output_____resampled = df.resample('3M')

# Very Useful for a variety of different fields / topics from financial stock markets to climate series !

# resampled = df.resample('3M', format='%m/%d/%Y %H:%M:%S' )

# B       business day frequency
# C       custom business day frequency (experimental)
# D       calendar day frequency
# W       weekly frequency
# M       month end frequency
# SM      semi-month end frequency (15th and end of month)
# BM      business month end frequency
# CBM     custom business month end frequency
# MS      month start frequency
# SMS     semi-month start frequency (1st and 15th)
# BMS     business month start frequency
# CBMS    custom business month start frequency
# Q       quarter end frequency
# BQ      business quarter endfrequency
# QS      quarter start frequency
# BQS     business quarter start frequency
# A       year end frequency
# BA      business year end frequency
# AS      year start frequency
# BAS     business year start frequency
# BH      business hour frequency
# H       hourly frequency
# T       minutely frequency
# S       secondly frequency
# L       milliseonds
# U       microseconds
# N       nanoseconds

# resampled.plot()
# resampled.var()[:5]_____no_output_____

# Tasks

## T4.1 (time series subset)

* Please change the range of subset and produce a figure, to be included and commented in report (see task description below)_____no_output_____

# alternatively use this query subset to produce a figure (see examples above)

df.query('Date > "2017-07-1"')

subset1 = df.query('Date > "2017-07-1"')

subset1_____no_output_____

# Loading real data: Example time series (e.g. from netCDF)_____no_output_____# Tasks

## Task 4.2 - Time series in excel format

1. Follow the example provided below
2. Examine the excel file, experiment with changing some of the parameters and plotting
3. Compare by using excel or libreoffice (free excel) to plot and get similar results
_____no_output_____e.g something downloaded from the NOAA archives, in this case FTP (see links below):

```
ftp://ftp.ncdc.noaa.gov/pub/data/paleo/historical/europe/

ftp://ftp.ncdc.noaa.gov/pub/data/paleo/historical/europe/dobrovolny2010temperature.xls
```

from:

Dobrovolný, P., A. Moberg, R. Brázdil, C. Pfister, R. Glaser, R. Wilson, A. van Engelen, D. Limanówka, A. Kiss, M. Halíčková, J. Macková, D. Riemann, J. Luterbacher, and R. Böhm.  2010. Monthly, seasonal and annual temperature reconstructions for Central Europe derived from documentary evidence and instrumental records since AD 1500. Climatic Change, 101, 1-2, 69-107.  DOI [10.1007/s10584-009-9724-x](https://doi.org/10.1007/s10584-009-9724-x)

Readme info for the dataset:

```
Central European 500 Year Temperature Reconstructions
-----------------------------------------------------------------------
               World Data Center for Paleoclimatology, Boulder
                                  and
                     NOAA Paleoclimatology Program
-----------------------------------------------------------------------
NOTE: PLEASE CITE ORIGINAL REFERENCE WHEN USING THIS DATA!!!!!

NAME OF DATA SET: Central European 500 Year Temperature Reconstructions
LAST UPDATE: 7/2010 (Original Receipt by WDC Paleo)
CONTRIBUTORS: Dobrovolný, P., A. Moberg, R. Brázdil, C. Pfister,
R. Glaser,  R. Wilson, A. van Engelen, D. Limanówka, A. Kiss,
M. Halícková, J. Macková, D. Riemann, J. Luterbacher, and R. Böhm.

IGBP PAGES/WDCA CONTRIBUTION SERIES NUMBER: 2010-061

WDC PALEO CONTRIBUTION SERIES CITATION:
Dobrovolný, P., et al. 2010.
IGBP PAGES/World Data Center for Paleoclimatology
Data Contribution Series  # 2010-061.
NOAA/NCDC Paleoclimatology Program, Boulder CO, USA.

ORIGINAL REFERENCE:
Dobrovolný, P., A. Moberg, R. Brázdil, C. Pfister, R. Glaser,
R. Wilson, A. van Engelen, D. Limanówka, A. Kiss, M. Halícková,
J. Macková, D. Riemann, J. Luterbacher, and R. Böhm.  2010.
Monthly, seasonal and annual temperature reconstructions
for Central Europe derived from documentary evidence
and instrumental records since AD 1500.
Climatic Change, 101, 1-2, 69-107.  DOI 10.1007/s10584-009-9724-x

ABSTRACT:
Monthly temperature series for Central Europe back to AD 1500
are developed from documentary index series from Germany,
Switzerland and the Czech Republic (1500–1854) and 11 instrumental
temperature records (1760–2007).  Documentary evidence from the
Low Countries, the Carpathian Basin and Poland are used for
cross-checking for earlier centuries. The instrumental station
records are corrected for inhomogeneities, including insufficient
radiation protection of early thermometers and the urban heat island
effect. For overlapping period (1760-1854), the documentary data
series correlate with instrumental temperatures, most strongly
in winter (86% explained variance in January) and least in autumn
(56% in September). For annual average temperatures, 81% of the
variance is explained. Verification statistics indicate high
reconstruction skill for most months and seasons.  The last 20 years
(since 1988) stand out as very likely the warmest 20-year period,
accounting for the calibration uncertainty and decreases in proxy
data quality before the calibration period. The new reconstruction
displays a previously unobserved long-term decrease in DJF, MAM
and JJA temperature variability over last five centuries. Compiled
monthly, seasonal and annual series can be used to improve the
robustness of gridded large-scale European temperature
reconstructions and possible impact studies. Further improvement
of the reconstruction would be achieved if documentary data from
other European countries are further developed.

GEOGRAPHIC REGION: Central Europe
PERIOD OF RECORD: 1500 - 2007 AD

FUNDING SOURCES:
EU-project MILLENNIUM (contract no 017008-2), Anders Moberg
was funded by the Swedish Research Council, VR. Jürg Luterbacher
acknowledges support of the EU/FP6 project CIRCE (grant 036961)
and from the EU/FP7 project ACQWA (grant 212250).

DESCRIPTION:
Monthly, seasonal and annual temperatures for Central Europe
reconstructed from descriptive qualitative documentary sources.
Documentary sources used in the form of indices. Monthly indices
used at 7-degree scale (-3 means extremely cold conditions,
-2 very cold, -1 cold, 0 normal, +1 warm, +2 very warm,
+3 extremely warm). Seasonal and annual indices were calculated
as a sum of corresponding monthly values. Index series from Germany,
Switzerland and the Czech Lands were used for final reconstruction.
Temperatures are expressed as anomalies from the 1961–1990 average.
Documentary indices are unitless.

Central Europe: 45°-53°N, 6°-20°E.

DATA:
17 reconstructions: 12 months, 4 seasons (DJF, MAM, JJA, SON)
and annual values. Each reconstruction is provided in the
following structure:

Column 1: Year AD
Column 2: SPLICED - final reconstruction: CEU.scal (1500-1759) & CEU.instr (1760-2007)
Column 3: INSTR -  instrumental temperatures (1760-2007)
Column 4: REG - reconstruction calibrated by regression (1500-1854)
Column 5: SE - standard error from calibration, adjusted upwards in periods of weak common signal
Column 6: REG + 2SE
Column 7: REG - 2SE
Column 8: SCALED - reconstruction with variance and mean adjusted
Column 9: DE.ind - temperature index for Germany
Column 10: CH.ind - temperature index for Switzerland
Column 11: CZ.ind - temperature index for the Czech Lands

```
_____no_output_____# Plotting_____no_output_____

# this can be used for any data load and eventual plot, from composition, to spectral, time series (present example)
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt

path = '../data_samples/excel/'
filename = 'dobrovolny2010temperature.xls'

import pandas as pd
xl= pd.ExcelFile(path + filename)
xl.sheet_names

Jan = xl.parse("Jan")
Feb = xl.parse("Feb")
Mar = xl.parse("Mar")
Apr = xl.parse("Apr")
May = xl.parse("May")
Jun = xl.parse("Jun")
Jul = xl.parse("Jul")
Aug = xl.parse("Aug")
Sep = xl.parse("Sep")
Oct = xl.parse("Oct")
Nov = xl.parse("Nov")
Dec = xl.parse("Dec")
Annual =  xl.parse("ANN")
df.head()

# Plotting all columns
Annual.plot('Year AD', 'SPLICED')

# trick to make matplotlib happy to call the column.
Annual.columns = [c.replace(' ', '_') for c in Annual.columns]

# df.plot('Date', 'E')
# df.plot('Date', 'F', label='test')

_____no_output_____

# Moving Average / smoothing_____no_output_____

# smoothing can have boundary effects, e.g. see
# http://www.meteo.psu.edu/holocene/public_html/shared/articles/MannGRL08.pdf

import numpy as np
import matplotlib.pyplot as plt

# quick and dirty
def smooth(y, box_pts):
    box = np.ones(box_pts)/box_pts
    y_smooth = np.convolve(y, box, mode='same')
    return y_smooth

smooth_annual = smooth(Annual.SPLICED, 14)

len(smooth_annual)
# len(Annual.SPLICED)

rolling_average_annual = Annual.rolling(window=18).mean()

# Anual.plot('Year_AD', 'SPLICED')
# rolling_average_annual.plot('Year_AD', 'SPLICED')

fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(Annual.Year_AD, Annual.SPLICED, color="blue")
ax.plot(Annual.Year_AD, rolling_average_annual.SPLICED, color="red")
plt.show()_____no_output_____

## Time series from online-accessible netCDF_____no_output_____netCDF, HDF or other complex formats are capable of containing a wide range of data, from raster, to time series.

E.g. from the raster point of view, commercial mapping tools such as ESRI ArcGis (or open source counterparts, such as QGIS) have specific workflows to deal with [netCDF](http://help.arcgis.com/en/arcgisdesktop/10.0/pdf/netcdf-tutorial.pdf) or [HDF](http://www.hdfeos.org/software/arcgis.php), with additional examples:

* https://nsidc.org/support/how/how-import-smap-hdf-data-arcgis
* https://desktop.arcgis.com/en/arcmap/10.3/manage-data/raster-and-images/multidimensional-raster-types.htm
* https://desktop.arcgis.com/en/arcmap/10.3/manage-data/netcdf/about-the-netcdf-tutorial.htm

Note - this cannot be done in excel !_____no_output_____

# e.g. see http://nbviewer.jupyter.org/gist/rsignell-usgs/d55b37c6253f27c53ef0731b610b81b4
%matplotlib inline_____no_output_____import numpy as np
import xarray as xr_____no_output_____import datetime as dt
from datetime import datetime, timedelta #

dayFile = datetime.now() - timedelta(days=1)
dayFile  = dayFile.strftime("%Y%m%d")

url='http://nomads.ncep.noaa.gov:9090/dods/nam/nam%s/nam1hr_00z' %(dayFile)
print(url)http://nomads.ncep.noaa.gov:9090/dods/nam/nam20181024/nam1hr_00z
ds = xr.open_dataset(url)

# Specify desired station time series location
# note we add 360 because of the lon convention in this dataset
#lati = 36.605; loni = -121.85899   # west of Pacific Grove, CA
lati = 41.4; loni = -100.8  # Georges Bank_____no_output_____# extract a dataset closeste to specified point
dsloc = ds.sel(lon=loni, lat=lati, method='nearest')_____no_output_____# select a variable to plot
dsloc['dswrfsfc'].plot()
plt.show()/Users/anrossi/miniconda/envs/python3test/lib/python3.6/site-packages/xarray/plot/utils.py:51: FutureWarning: 'pandas.tseries.converter.register' has been moved and renamed to 'pandas.plotting.register_matplotlib_converters'.
  converter.register()
import xarray as xr

# see http://nbviewer.jupyter.org/gist/rsignell-usgs/e032db75e748cf5922d38d8be9e0ecef

fname = 'http://thredds.ucar.edu/thredds/dodsC/grib/NCEP/GFS/Global_onedeg/Best'  # Remote OPeNDAP Dataset
#fname = 'my_raster_time_series_data.nc'   # Local NetCDF file, if website not working

ds = xr.open_dataset(fname)
dsloc = ds.sel(lon=230.5,lat=55.0,method='nearest')

dsloc['Wind_speed_gust_surface'].plot()
plt.show()_____no_output_____

## Load HDF
_____no_output_____You can also *try* (preferably on your computers, due to java compatibility issues on CLAMV) to load HDF (and inspect the complex structure of the file) with tools such as [Panoply](https://www.giss.nasa.gov/tools/panoply/). It does open also netCDF and GRIB files._____no_output_____# Tasks

## Task 4.3

1. Please open provided sample data files (the same used below in netcdf, hdf5) with Panoply and document with screenshots
2. Continue with the examples below. What is "Cartopy"?_____no_output_____# Load geospatial raster files + plot with Cartopy_____no_output_____### netCDF_____no_output_____

# Using sample data from gerda@piggy;
#
# References:
#http://unidata.github.io/netcdf4-python/
#https://annefou.github.io/metos_python/02-formats/_____no_output_____from netCDF4 import Dataset_____no_output_____# netCDF:
nc_f='../data_samples/netCDF/gistemp250.nc'_____no_output_____nc_fid = Dataset(nc_f, 'r')
print (nc_fid)
#nc_fid.close()<class 'netCDF4._netCDF4.Dataset'>
root group (NETCDF3_CLASSIC data model, file format NETCDF3):
    title: GISTEMP Surface Temperature Analysis
    institution: NASA Goddard Institute for Space Studies
    source: http://data.giss.nasa.gov/gistemp/
    Conventions: CF-1.6
    history: Created 2018-02-09 15:25:45 by SBBX_to_nc 2.0 - ILAND=250,  IOCEAN=none,     Base: 1951-1980
    dimensions(sizes): lat(90), lon(180), time(1657), nv(2)
    variables(dimensions): float32 lat(lat), float32 lon(lon), int32 time(time), int32 time_bnds(time,nv), int16 tempanomaly(time,lat,lon)
    groups:

lats=nc_fid.variables['lat']
lons=nc_fid.variables['lon']
time=nc_fid.variables['time']
temp=nc_fid.variables['tempanomaly']_____no_output_____# using cartopy - http://scitools.org.uk/cartopy/docs/latest/index.html
# We will then plot temperature anomaly according to lat/lon coordinates
import cartopy
# We'll need matplotlib.pyplot to plot
import matplotlib.pyplot as plt
# And astropy.time to convert time from mjd
from astropy.time import Time_____no_output_____def plotTime(timeIndex):
    Date=Time(time[timeIndex],format='mjd', scale='utc') #convert time to date with astropy
    Date.format='iso' # output in iso format
    Date.out_subfmt='date' # only show date
    ax = plt.axes(projection=cartopy.crs.PlateCarree()) #set axes projection
    plt.title('Temperatures anomaly\n' + str(Date))
    plt.contourf(lons[:],lats[:],temp[timeIndex,:,:], 60, transform=cartopy.crs.PlateCarree())
    ax.coastlines()
    plt.show()

plotTime(505)
plotTime(745)
plotTime(985)_____no_output_____

### HDF5_____no_output_____

# see https://sukhbinder.files.wordpress.com/2014/05/pytables_intro_how_to_read_hdf5_with_python.gif
# see https://confluence.slac.stanford.edu/display/PSDM/How+to+access+HDF5+data+from+Python

# alternatively one could use pandas
# import pandas as pd
# pd.read_hdf(filename,key)
_____no_output_____import h5py
import numpy as np
import matplotlib.pyplot as plt/Users/anrossi/miniconda/envs/python3test/lib/python3.6/site-packages/h5py/__init__.py:36: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  from ._conv import register_converters as _register_converters
hdf5_file_name = '../data_samples/hdf/NEONDSTowerTemperatureData.hdf5'
file=h5py.File(hdf5_file_name, 'r')
file_____no_output_____list(file.keys())_____no_output_____d03=file['Domain_03']
list(d03)_____no_output_____OSBS=d03['OSBS']
list(d03['OSBS'])_____no_output_____list(d03['OSBS']['min_1'])_____no_output_____list(d03['OSBS']['min_1']['tower_top'])_____no_output_____dset=d03['OSBS']['min_1']['tower_top']['temperature']_____no_output_____list(dset.attrs)_____no_output_____dset.attrs['Product Name']_____no_output_____meanTmpAtTowerTop=dset['date', 'mean']
print(meanTmpAtTowerTop[:5])[(b'2014-04-01 00:00:00.0', 21.80952205)
 (b'2014-04-01 00:01:00.0', 21.69909421)
 (b'2014-04-01 00:02:00.0', 21.69083417)
 (b'2014-04-01 00:03:00.0', 21.68552605)
 (b'2014-04-01 00:04:00.0', 21.61199099)]
dates=[x[0] for x in meanTmpAtTowerTop]
temps=[y[1] for y in meanTmpAtTowerTop]
print(dates[:5])[b'2014-04-01 00:00:00.0', b'2014-04-01 00:01:00.0', b'2014-04-01 00:02:00.0', b'2014-04-01 00:03:00.0', b'2014-04-01 00:04:00.0']
# values for dates are provided as byte arrays, but
# we need strings to be able to convert to datetime objects for plotting
# so we convert byte array to a string using list comprehension:
datesStr=["".join([chr(y) for y in x[:-2]]) for x in dates]
print(datesStr[:5])['2014-04-01 00:00:00', '2014-04-01 00:01:00', '2014-04-01 00:02:00', '2014-04-01 00:03:00', '2014-04-01 00:04:00']
from datetime import datetime
times = [datetime.strptime(d, '%Y-%m-%d %H:%M:%S') for d in datesStr]
print(times[:5])[datetime.datetime(2014, 4, 1, 0, 0), datetime.datetime(2014, 4, 1, 0, 1), datetime.datetime(2014, 4, 1, 0, 2), datetime.datetime(2014, 4, 1, 0, 3), datetime.datetime(2014, 4, 1, 0, 4)]
plt.plot(times, temps)
plt.show()_____no_output_____# now we can combine the last few steps into a
# function and see how tepmerature differs across the tower:_____no_output_____def plotTmps(dset):
    meanTmpAtTowerTop=dset['date', 'mean']
    dates=[x[0] for x in meanTmpAtTowerTop]
    temps=[y[1] for y in meanTmpAtTowerTop]
    datesStr=["".join([chr(y) for y in x[:-2]]) for x in dates]
    times = [datetime.strptime(d, '%Y-%m-%d %H:%M:%S') for d in datesStr]
    plt.plot(times, temps)_____no_output_____listOfDSetNames=list(d03['OSBS']['min_1'])
listOfDSetNames_____no_output_____gdset=lambda dname: d03['OSBS']['min_1'][dname]['temperature']# make a lamba function to return dataset
listOfDSets=list(map(gdset, listOfDSetNames))# map lambda function to a list_____no_output_____plt.figure(figsize=(15,10)) # Lets make this a bigger figure
for dSet in listOfDSets: plotTmps(dSet)
plt.title('OSBS min_1 temperature')
plt.xlabel('Date')# labels
plt.ylabel('Temperature')
plt.legend(listOfDSetNames, loc='best')
plt.show()_____no_output_____#If the file contains an error, h5py may be unable to open a dataset
hdf5_other_file_name = '../data_samples/hdf/MOD09A1.A2000057.h00v08.005.2006270072347.hdf'
other_file=h5py.File(hdf5_other_file_name, 'r') # This should throw an error_____no_output_____# however, you may still be able to open it with NetCDF:
from netCDF4 import Dataset
hdfDatasetFile='../data_samples/hdf/MOD09A1.A2000057.h00v08.005.2006270072347.hdf'
hdfDataset = Dataset(hdfDatasetFile, 'r')
print(str(hdfDataset)[:500])<class 'netCDF4._netCDF4.Dataset'>
root group (NETCDF4 data model, file format HDF4):
    HDFEOSVersion: HDFEOS_V2.9
    StructMetadata.0: GROUP=SwathStructure
END_GROUP=SwathStructure
GROUP=GridStructure
	GROUP=GRID_1
		GridName="MOD_Grid_500m_Surface_Reflectance"
		XDim=2400
		YDim=2400
		UpperLeftPointMtrs=(-20015109.354000,1111950.519667)
		LowerRightMtrs=(-18903158.834333,-0.000000)
		Projection=GCTP_SNSOID
		ProjParams=(6371007.181000,0,0,0,0,0,0,0,0,0,0,0,0)
		SphereCode=-1
		GROUP=Dimens

# Image load - like thin section_____no_output_____# Tasks

## Task 4.4

1. Continue with the examples below on image anlysis and directional data. Experiment with the paramters.
2. What kind of image data is used below ? What is it showing ?
3. What kind of directional data is used in geosciences ? What do these diagrams show / represent ?_____no_output_____Sources of think section images, e..g

* Sedimentary rocks
    * Carbonate rock think sections - [from SEPM](http://www.sepmstrata.org/page.aspx?&pageid=298&6)
    * Clastic rock thin sections - [from SEPM](http://sepmstrata.org/page.aspx?pageid=110)
* Igneous & sedimentary rocks
    * [Atlas of igneous and sedimentary rocks in think section](http://leggeo.unc.edu/Petunia/IgMetAtlas/mainmenu.html)_____no_output_____

# load image from url,
# searching examples, e.g. from Ooids on http://www.sepmstrata.org/page.aspx?pageid=105
# images loaded from http://www.sepmstrata.org/microscopic_Gallery_list.aspx?gcid=11

from IPython.display import Image
from IPython.core.display import HTML
Image(url= "http://www.sepmstrata.org/Images/_Gallery_Micro/Photos_Related/o-07-5x-01.jpg", width=300)_____no_output_____# use scikit image (skimage) or pillow..?_____no_output_____

one can also use ImageJ (software for Image Analysis)

_____no_output_____## Directional data, e.g. Stereonets

e.g. see http://geologyandpython.com/structural_geology.html

_____no_output_____

import numpy as np
import mplstereonet
import matplotlib.pyplot as plt

%matplotlib inline_____no_output_____strike1, dip1 = 10, 30
strike2, dip2 = 315, 78_____no_output_____fig = plt.figure(figsize=(8,8))
ax = fig.add_subplot(111, projection='stereonet')
ax.plane(strike1, dip1, c='b', label='Bedding %03d/%02d' % (strike1, dip1))
ax.plane(strike2, dip2, c='r', label='Fault %03d/%02d' % (strike2, dip2))
ax.legend()_____no_output_____plunge, bearing = mplstereonet.plane_intersection(strike1, dip1, strike2, dip2)
ax.line(plunge, bearing, 'ko', markersize=5,
        label='Intersection %02d/%03d' % (plunge, bearing))
ax.legend()
# We can also add a grid
ax.grid()
fig_____no_output_____


# Notebook from karenlmasters/marvin
Path: docs/sphinx/tutorials/exercises/resolved_mass_metallicity_relation.ipynb

# Spatially-Resolved Mass-Metallicity Relation

We're going to construct the spatially-resolved mass-metallicity relation (MZR) for a MaNGA galaxy, where mass refers to stellar mass and metallicity refers to gas-phase oxygen abundance.

### Roadmap
1. Compute metallicity.
2. Select spaxels that are
  1. star-forming,
  2. not flagged as "bad data," and
  3. above a signal-to-noise ratio threshold.
3. Compute stellar mass surface density.
4. Plot metallicity as a function of stellar mass surface density._____no_output_____

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline_____no_output_____import os
from os.path import join
path_notebooks = os.path.abspath('.')
path_data = join(path_notebooks, 'data')_____no_output_____

## Load Maps for Galaxy

Import the Marvin `Maps` class from `marvin.tools.maps` and initialize a `Maps` object for the galaxy 8077-6104._____no_output_____## Measure Metallicity

### Pettini & Pagel (2004) N2 metallicity calibration

We are going to use the N2 metallicity calibration (their Equation 1) from Pettini & Pagel (2004):

12 + log(O/H) = 8.90 + 0.57 $\times$ log( $\frac{F([NII])}{F(H\alpha)}$ ).

One of the benefits of this calibration is that the required lines are very close in wavelength, so the reddening correction is negligible.

Get [NII] 6585 and Halpha flux maps from the Marvin `Maps` object.  Note: MaNGA (and Marvin) use the wavelengths of lines in vaccuum, whereas they are usually reported in air, hence the slight offsets._____no_output_____Calculate the necessary line ratio.

Marvin can do map arithmetic, which propagates the inverse variances and masks, so you can just do `+`, `-`, `*`, `/`, and `**` operations as normal.  (Note: taking the log of a Marvin `Map` will work for the values but the inverse variance propagation does not correctly propagate the inverse variance yet.)_____no_output_____Finally, calculate the metallicity._____no_output_____## Select Spaxels
### Using the BPT Diagram to select star-forming spaxels

Metallicity indicators only work for star-forming spaxels, so we need a way to select only these spaxels.

The classic diagnostic diagram for classify the emission from galaxies (or galactic sub-regions) as star-forming or non-star-forming (i.e., from active galactic nuclei (AGN) or evolved stars) was originally proposed in Baldwin, Phillips, & Terlevich (1981) and is known as the **BPT diagram**.

The BPT diagram uses ratios of emission lines to separate thermal and non-thermal emission.

The classic BPT diagram uses [OIII]5007 / Hbeta vs. [NII]6583 / Halpha, but there are several versions of the BPT diagram that use different lines ratios._____no_output_____### BPT Diagrams with Marvin

Let's use Marvin's `maps.get_bpt()` method to make BPT diagrams for this galaxy.

**red line**: maximal starbust (Kewley et al 2001) -- everything to the right is non-star-forming.
**dashed black line**: conservative star-forming cut (Kauffmann et al. 2003) -- everything to the left is star-forming.

Line ratios that fall in between these two lines are designated "Composite" with contributions from both star-forming and non-star-forming emission.

**blue line**: separates non-star-forming spaxels into Seyferts and LINERs.

Seyferts are a type of AGNs.

LINERs (Low Ionization Nuclear Emission Regions) are not always nuclear (LIER is a better acronym) and not always AGN (oftern hot evolved stars).

Sometimes these diagnostic diagrams disagree with each other, hence the "Ambiguous" designation.

Try using `maps.get_bpt?` to read the documentation on how to use this function._____no_output_____The BPT masks are dictionaries of dictionaries of a boolean (True/False) arrays.  We are interested in the spaxels that are classified as star-forming in all three BPT diagrams are designated as ``True``, which is designated with the `global` key.  Print this mask._____no_output_____### Masks
MaNGA (and SDSS generally) use bitmasks to communicate data quality.

Marvin has built-in methods to convert from the bitmasks integer values to individual bits or labels and to create new masks by specifying a set of labels.

Show the mask schema with `n2.pixmask.schema`._____no_output_____Select non-star-forming spaxels (from the BPT mask) and set their mask value to the DAP's DONOTUSE value with the `n2.pixmask.labels_to_value()` method.  Note that we are selecting spaxels that we want from the BPT mask (i.e., `True` is a spaxel to keep), whereas we are using the pixmask to select spaxels that we want to exclude (i.e., `True` is a spaxel to ignore)._____no_output_____Select spaxels classified by the DAP as bad data according to the masks for spaxels with no IFU coverage, with unreliable measurements, or otherwise unfit for science.  Use the `n2.pixmask.get_mask` method._____no_output_____Select spaxels with signal-to-noise ratios (SNRs) > 3 on both [NII] 6585 and Halpha.

`ha.ivar` = inverse variance = $\frac{1}{\sigma^2}$, where $\sigma$ is the error._____no_output_____Do a [bitwise (binary) OR](https://www.tutorialspoint.com/python/bitwise_operators_example.htm) to create a master mask of spaxels to ignore._____no_output_____## Plot the Metallicity Map

Plot the map of metallicity using the `plot()` method from your Marvin `Map` metallicity object.  Also, mask undesirable spaxels and label the colorbar.

Note: solar metallicity is about 8.7._____no_output_____## Compute Stellar Mass Surface Density

1. Read in spaxel stellar mass measurements from the Firefly spectral fitting catalog (Goddard et al. 2017).
  - The [Firefly stellar population fitting results file](https://data.sdss.org/sas/dr14/manga/spectro/firefly/v1_0_3/manga_firefly-v2_1_2-STELLARPOP.fits) is large (1.8 GB), so we have extracted only the measurements needed for our purposes [DOWNLOAD CSV FILE](https://sdss-marvin.readthedocs.io/en/latest/tutorials/exercises/data/manga-8077-6104_mstar.csv).
  - [Summary of the Firefly Value Added Catalog](http://www.sdss.org/dr14/manga/manga-data/manga-firefly-value-added-catalog/)
  - [Datamodel of the Firefly Value Added Catalog](https://data.sdss.org/datamodel/files/MANGA_FIREFLY/FIREFLY_VER/manga_firefly-STELLARPOP.html)
2. Convert spaxel angular size to a physical scale in pc.
3. Divide stellar mass by area to get stellar surface mass density.

### Read in stellar masses

Use [pandas](http://pandas.pydata.org/pandas-docs/stable/) to read in the csv file with stellar masses._____no_output_____

import pandas as pd
mstar = pd.read_csv(join(path_data, 'manga-{}_mstar.csv'.format(maps.plateifu)))_____no_output_____

Plot stellar mass map using `ax.imshow()`.  MaNGA maps are oriented such that you want to specify `origin='lower'`.  Also include a labelled colorbar._____no_output_____### Calculate physical size of a spaxel

MaNGA's maps (and data cubes) have a spaxel size of 0.5 arcsec. Let's convert that into a physical scale for our galaxy._____no_output_____

    _____no_output_____

Get the redshift of the galaxy from the `maps.nsa` attribute._____no_output_____We'll use the **small angle approximation** to estimate the physical scale:

$\theta = \mathrm{tan}^{-1}(\frac{d}{D}) \approx \frac{206,265 \, \mathrm{arcsec}}{1 \, \mathrm{radian}} \frac{d}{D}$,

where
$\theta$ is the angular size of the object (in our case spaxel) in arcsec,
$d$ is the diameter of the object (spaxel), and
$D$ is the angular diameter distance.

The distance (via the **Hubble Law** --- which is fairly accurate for low redshift objects) is

$D \approx \frac{cz}{H_0}$,

where
$c$ is the speed of light in km/s,
$z$ is the redshift, and
$H_0$ is the Hubble constant in km/s/Mpc.

Calculate $D$._____no_output_____Rearrange the small angle formula to solve for the scale ($\frac{d}{\theta}$) in pc / arcsec._____no_output_____Now convert the spaxel size from arcsec to parsecs and calculate the area of a spaxel._____no_output_____Finally, we simply divide the stellar mass by the area to get the stellar mass surface density $\Sigma_\star$ in units of $\frac{M_\odot}{pc^2}$._____no_output_____Let's plot metallicity as a function of $\Sigma_\star$!  Remember to apply the mask.  Also set the axis range to be `[0, 4, 8, 8.8]`._____no_output_____### MaNGA Spatially-Resolved Mass-Metallicity Relation

We have constructed the spatially-resolved MZR for one galaxy, but we are interested in understanding the evolution of galaxies in general, so we want to repeat this exercise for many galaxies.  In [Barrera-Ballesteros et al. (2016)](https://arxiv.org/pdf/1609.01740.pdf), Jorge Barrera-Ballesteros (who gave a talk at Pitt in November 2017) did just this, and here is the analogous figure for 653 disk galaxies.

_____no_output_____The best fit line from Barrera-Ballesteros et al. (2016) is given in the next cell. _____no_output_____

# fitting formula
aa = 8.55
bb = 0.014
cc = 3.14
xx = np.linspace(1, 3, 1000)
yy = aa + bb * (xx - cc) * np.exp(-(xx - cc))_____no_output_____

Remake the spatially-resolved MZR plot for our galaxy showing the he best fit line from Barrera-Ballesteros et al. (2016)._____no_output_____The spaxels in our galaxy are typically above the best fit relation.  Part of the offset may be due to systematic differences in the metallicity calibrator used, but the overal trend of flat metallicity as stellar mass surface densities decreases seems to be in tension with their best fit.  It would be worth investigating this effect for more galaxies to understand if individual galaxies typically obey the best fit relation or whether they typically exhibit a flat trend in this space.

Ultimately, Barrera-Ballesteros et al. (2016) concluded that the spatially-resolved MZR is a scaled version of the global MZR (e.g., see Tremonti et al. 2004)._____no_output_____


# Notebook from Kyledesma/Yelp-ML
Path: .ipynb_checkpoints/Yelp Scrape-checkpoint.ipynb

<code>
from splinter import Browser
from bs4 import BeautifulSoup
import pandas as pd
import time_____no_output_____executable_path = {'executable_path': 'chromedriver.exe'}
browser = Browser('chrome', **executable_path, headless=False)_____no_output_____yelpurl = 'https://www.yelp.com/biz/ravanesi-pizzeria-napoletana-glen-mills?osq=ravanessi'
browser.visit(yelpurl)_____no_output_____yelphtml = browser.html
yelpsoup = BeautifulSoup(yelphtml, 'html.parser')

_____no_output_____rating = yelpsoup.find('div', class_='lemon--div__373c0__1mboc i-stars__373c0__1T6rz i-stars--regular-5__373c0__N5JxY border-color--default__373c0__3-ifU overflow--hidden__373c0__2y4YK')
rating_____no_output_____comment = yelpsoup.find('li', class_='lemon--li__373c0__1r9wz margin-b3__373c0__q1DuY padding-b3__373c0__342DA border--bottom__373c0__3qNtD border-color--default__373c0__3-ifU').\
                   find('span', class_='lemon--span__373c0__3997G raw__373c0__3rcx7').text
comment_____no_output_____# Print the rating
yelphtml = browser.html
yelpsoup = BeautifulSoup(yelphtml, 'html.parser')

allrating = yelpsoup.find_all('div', class_='lemon--div__373c0__1mboc i-stars__373c0__1T6rz i-stars--regular-5__373c0__N5JxY border-color--default__373c0__3-ifU overflow--hidden__373c0__2y4YK')

for ratings in allrating:
    print(ratings["aria-label"])
    5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
5 star rating
# print reviews/scrape

    yelphtml = browser.html
    yelpsoup = BeautifulSoup(yelphtml, 'html.parser')

    allcomment = yelpsoup.find_all('span', class_='lemon--span__373c0__3997G raw__373c0__3rcx7')

    for comments in allcomment:
        print(comments.text)

    790 Baltimore Pike
Glen Mills, PA 19342
0.2 miles away from Ravanesi Pizzeria Napoletana
Your trust is our top concern, so businesses can't pay to alter or remove their reviews. Learn more.
Start your review of Ravanesi Pizzeria Napoletana.
Ravanesi serves the best pizza in the area, with the delicious dough AND cheese made in-house. We finally had our chance to try it for the first time since prior to the pandemic, they were not taking reservations or takeout, but now they are, and I am thrilled. You must call a few days or a week ahead to reserve your time slot to pick up the pizzas. When the owner found out it was my first time trying the pizzas, he sounded worried that we would be getting takeout since neopolitan style pizzas are best straight out of the oven. He reminded me of myself, because I am the same way when serving food. It's like the time my husband brought butternut squash gnocchi with sage brown butter sauce to work and did not bring the parmigiano cheese . When I arrived to pick up the pizza, he actually made me a mini white pizza to eat in the car! I couldn't believe it haha it was so kind of him. I ate the entire thing within 2 minutes-- it was excellent. I arrived home with the pizzas at the same time my husband arrived home from work, we ate them right away, and they were perfect! My husband said Ravanesi might be his favorite pizza thus far (and we have eaten A LOT of different pizzas, all over the world.) I cannot recommend Ravanesi pizza enough, and I cannot wait to eat here again. Amazing.
I love this pizza!!!  My all time favorite in this area.  The owner is so personable and recognizes you on site even if it's been awhile.  The only downside is finding a table with the limited seating area.  Even if you reserve the dough, you still have to wait to be seated.  In this case COVID-19 has been a benefit as currently they're offering takeout options.  Yum!
Always great pizza and service! Worth the wait. Ordered takeout while restaurants were closed due to COVID 2019. They had a great setup for safe pickup.
Best purist pizza I've tasted outside of Italy. The only pies in Philly's Western Burbs that remotely compare would be from Ariano in Media, but the latter create a very different dish, with ultra-cool toppings such as filet mignon and their own style of thin, delicious crust. Ravanesi is the polar opposite. If you're a crust freak like me or my splendid spouse--if you seek the freshest, thinnest, purest crust this side of the Pond, with local, very-fresh toppings, Ravanesi is il tuo abbinamento perfetto! Close your eyes, sip your BYOB Chianti Classico Riserva, take a bite and presto--you're in NaplesI do wish they offered portobello mushroom toppings and a more understandable reservation policy.  But honestly, dear friend, I had to rack my brain to come up with these two tiny whines, and the second is actually kind of charming in an old-country way. You can't book a table, but you can and should reserve your dough, because Jack makes it fresh every afternoon--enough for a hundred pies, period. Okay? This itty-bitty deuce of gripes is all that stands between Ravanesi Pizzeria and perfection.Ravanesi is AWESOME and if you crave the real deal, call ahead to reserve your dough and prepare your palate for paradise.P.S. It's BYOB! Better still, Ravenisi pizza makes an admirable match to your stash of Sangiovese, Cab, Zin, or pretty much any other zesty red you adore--and if you're looking forward to uncorking a white, whether it be Italian Soave Classico, Alsace Gewürztraminer, New Zealand Sauvignon Blanc, you're good to go as well. Sparklers work too, but nix on delicate Pinot Noir or Pinot Grigio, unless the latter is actually Alsace Pinot Gris.
This is the best "purist" pizza I've tasted outside of Italy. The only other place in the Media-Concordville area that compares is Ariano in Media, but theirs is really a different dish --with fancy toppings and its own style if delicious crust. If you seek the best crust in the area--with basic, very fresh toppings --Ravanesi is your huckleberry. I do wish they offered portobello mushrooms, but that's a minor point. This pizza is AWESOME and you owe it to yourself to try it someday.
Hands down amazing pizza.  Terrific staff and so so good.Highly recommend.
Hands down best pizza in the area. Overall, a great Neapolitan pizza. Does everything well. 8.3/10.
Hands down one of the best pizzas I have ever had. Very different than your traditional pizza or chicago or new York style.
SOOO AMAZING! I have been hearing about this place for over year now and finally went. We were there as a larger group so I got to try all the pizzas on the menu except one. The place is BYOB and you can drink while you wait which is great. This place your reservation is the dough.. so order ahead! Or you can't get in. The pizza is made perfect. The sauce, spices and everything is a blended perfection! Even the white which I have had some bland white pizza before was amazing! It had the best oil and garlic flavors. I love garlic and there was a great amount on all of them. They definitely do not skimp on any of the toppings either. One thing that you can do is on the pizzas without cheese you can add it. I definitely recommend this because then they are kinda bland. Check this place out if you get the chance it will not disappoint!
Make sure to call ahead to reserve your dough! I had to reserve a week ahead of time.This place is amazing. The man who makes the pizza asked if it was my first time and gave me a free slice of the Bianca pizza to try. It was delicious and so kind of him. All the pizzas are made on the spot in about 90 seconds.
Ravanesi pizza is some of (if not THE) best pizza I have ever had. My family introduced this place to me and I've been hooked ever since. One thing I will say is that make sure you get there early, or call ahead and reserve dough because they will sell out within 30 minutes of opening their doors. If that alone doesn't prove to you how good their pizza is then just keep reading. Before I get into the food itself I will say that the staff and Dave (the head chef/owner) are some of the friendliest and hardworking employees I've ever seen. When we come in Dave makes sure to say hi to us and ask us about our day/family. This has led us to learn a lot about his pizza making process. He hand makes all the mozzarella that goes on each pizza and makes the dough in the morning as well as fires up the oven to make sure the temp is just right by the time he opens the doors. All of the ingredients are made fresh and you can tell. I wouldn't recommend any one type of pizza as they are all very good and the type you choose is dependent on what you like but they do cover all types from the basic margarita, to spicy meat, to white with arugula. If you are with someone who doesn't like pizza I wouldn't recommend going here as the only thing on the menu is the pizzas!Dave is a dedicated owner who really pours his heart and soul into his restaurant and it shows! As for the wait, don't go here expecting to be sat immediately because there will almost always be somewhat of a wait. The wait will always be well worth it, especially if you bring some drinks because it is BYOB. Some last tips, if you're going on a Th-Sat there is no take-out and you will most likely want to reserve dough ahead of time as they run out very quickly.
Same great pizza. Better then ever. They are now 5 years old. Best pizza outside of the city. Maybe better then all the pizza in the city. It's pizza. It's all he does. He does it with real passion for perfection.
I just ate there tonight. All they do is pizza . One guy and he is a master at it. Great stuff. Crowded place . Sells out early. Super high quality. Don't free lance just get the ones on the menu. In the pizza desert of Chester County this is truly an oasis.
If you thought you needed to board a transatlantic flight to get good Italian pizza, you were wrong. Turns out there's a little piece of Italy right off of Route 1 in Glen Mills. Ravanesi Pizzeria is unequivocally the best pizza I've had since moving to Wilmington and I'm already looking forward to my next trip.Ravanesi is a bare-bones pizza place nestled in a strip mall with minimal decor and maybe a dozen tables. They do not take reservations for the tables, but I promise you, this pizza is well worth the wait. What you DO need to do is call when they open to reserve your dough. They have a limited amount of pizza dough each night, and once it's gone, it's gone. We arrived around 7 PM (they open at 4:30 PM) and the sign was already hanging that the dough was sold out. Thankfully, I had called ahead, and even though I only reserved 1 dough, they were able to make us 2 pizzas. We did wind up waiting a good 30-45 minutes for our table.The menu includes 7 different combinations of toppings that are all standard Italian options --- house-made mozzarella cheese, fresh tomatoes, Italian sausage or sopressata, roasted onions, and roasted red peppers. We ordered the Filetti di Pomodorri (cherry tomatoes, fresh mozzarella, parmesan cheese, fresh garlic) and the Salsiccia (Italian sausage, roasted onion, fresh mozzarella). Both were absolutely phenomenal and we struggled to keep from devouring both entirely. Even the leftovers were incredible. We topped off our meal with their mini Nutella pizza, which is delightful.At the end of our meal, the owner happened to be saying goodbye to some patrons he clearly knew, but also took the time to greet us, ask us about our meal, and tell us to "get home safely." We were really impressed with the friendly service and the everyone-is-family kind of vibe. Can't wait to go back and try some of the other options!
We couldn't even wait to take a picture - so here's what the wife and I couldn't finish! This place is incredible. The owner is extremely nice and makes the cheese every morning along with the fresh dough. It's a family atmosphere that is byob. The staff is very friendly. You have to call and reserve the dough and it's first come first serve but worth the wait. It's a very friendly, wholesome experience.
My husband and I have been to Italy several times...and these delish bites from Ravanesi bring us right back there! We had the standard margherita and the sopressata. Both were out of this world!! On top of the stellar food, the Ravanesi team could not be any more hospitable. We are extremely lucky to have this gem in our community! Can't wait for our next pie.
Very good pizza for the Burbs.That's a pretty big statement since pizza in this area generally is bad. This place sticks out as very good.
This is quite possibly the best pizza I've had in the Philly area. The service was excellent. Our waitress brought our two pizzas over (Margherita and Sopressata) and said the chef was worried that he overlooked mine. The attention and care put on show was dealt pleasing. I'm used to Neapolitan pizza having a little char to it and to be honest I thought my pizza was perfect....the pizzaiolo Dave came out and gave us a free 8 inch "Bianca" pizza bc he said he felt bad even tho I scarfed down my pizza in record time! He also explained the process of the dough being naturally leavened which I found verybinterstingLol. 10/10.
This is quite possibly the best pizza I've had in the Philly area. The service was excellent. Our waitress brought our two pizzas over (Margherita and Sopressata) and said the chef was worried that he overlooked mine. The attention and care put on show was dealt pleasing. I'm used to Neapolitan pizza having a little char to it and to be honest I thought my pizza was perfect....the pizzaiolo Dave came out and gave us a free 8 inch "Bianca" pizza bc he said he felt bad even tho I scarfed down my pizza in record time! He also explained the process of the dough being naturally leavened which I found very interesting! 10/10.
Our amazing friends brought us here for a relaxed night out for pizza. They called ahead to reserve dough but this place doesn't take seating reservations, which makes sense as it's a small casual place. As my very intelligent and savvy friends expected, Ravanesi ran out of dough. It's also a byob, with no care with what you bring. There's about 8 preset pizzas and all sounded good. We all got different ones and split them (you can also get halves of different types).  The soppressata was my top pick. It was awesome. You can ask for how well done you want the crust. I was in the mood for a slightly softer pizza and it was done perfectly. We chatted with the owner after and he thanked us very much for coming. We will definitely go back. Ps our friends are the best.
Ravanesi was very good! I came here early (before 4:30 when they open) because I know they always have a line. Last time I tried to come early, but there was already a line and then by the time I reached the hostess, there was a 45 minute wait. Luckily, you can put your name in and add a phone number to be called. But this time I fortunately got to be seated with my sister right away. We ordered a half Bianca, half Sopressata pizza. I very much appreciate their willingness to do a half/half pizza. I thought the pizza in general would get 4/5 stars because it was a very thin crust, but not as crispy as I expected or wanted. However, the service was so good that they deserve all five stars. Also, we tried their dessert Nutella Pizza, and it is definitely worth getting. It is definitely an exceptional dessert with fresh strawberry purée which makes it extra special. They do not do to-go orders at Ravanesi, except for if you dine in and want to take some pizza to go afterwards. Overall, I highly recommend Ravanesi!
I'm so glad we got to try the pizza at Ravanesi Pizzeria Napoletana. We were over at Kennett Square and found Ravanesi on Yelp. When we arrived, there was a sign on the door saying they're out of dough, but we walked in anyways and they had enough dough to make one last pizza for us.Ravanesi is strictly just a pizza restaurant. No salads, breadsticks, or wings. Just delicious pizza baked at 1000 degrees in an oven made in Italy. We went with half Bianca (a white pizza with garlic, oregano, and parmiggiano reggiano) and half margherita and topped it off with roasted onions. They do not have many topping options (roasted onions, arugula, roasted red peppers, sausage, and spicy sopressata), but I was impressed and really enjoyed the pizza. The ingredients were very fresh. It's BYOB!No take-out orders on Thursdays, Fridays, or Saturdays.
Great ingredients results in a premium pizza. Worth the wait. Ordered 2 of each pizza as we couldn't choose. Still can't but we have plenty of samples left to try1

</code>


# Notebook from t-hdd/econ126
Path: Lecture Notebooks/Econ126_Class_12.ipynb

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.style.use('classic')
%matplotlib inline_____no_output_____

# Class 12: Introduction to the `linearsolve` Module

In general, dynamic stochastic general equilibrium (DSGE) models do not admit analytic (i.e., pencil-and-paper) solutions and they are time-consuming to work with. The `linearsolve` module approximates, solves, and simulates DSGE models and therefore makes DSGE models easier to use.

## Installing `linearsolve`

`linearsolve` is not included in the Anaconda Python installation and so before you can import it, you need to download and install the `linearsolve` package from PyPI, the Python Package Index. In Windows, open the Anaconda Prompt and in Mac, open the Terminal and run the following commmand:

    pip install linearsolve

You only have to install the package once._____no_output_____

# Import the linearsolve under the 'ls' namespace
import linearsolve as ls_____no_output_____

## Example: A One-Equation Model of TFP

Consider the following AR(1) specification for $\log$ TFP:

\begin{align}
\log A_{t+1} & = \rho \log A_t + \epsilon_{t+1},
\end{align}

where $\epsilon_{t+1} \sim \mathcal{N}(0,\sigma^2)$. The goal is to simulate

\begin{align}
\log (A_{t}/\bar{A}) & \approx \frac{A_t - \bar{A}}{\bar{A}},
\end{align}

where $\bar{A}$ is the nonstochastic (i.e., $\epsilon_t = 0$) steady state value of $A_t$. Let's compute the model simulation with `linearsolve`. To do this we need to do several things:

1. Create a Pandas series that stores the names of the parameters of the model.
2. Define a function that returns the equilibrium conditions of the model solved for zero.
3. Initialize an instance of the `linearsolve.model` class
4. Compute and input the steady state of the model.
5. Approximate and solve the model.
6. Compute simulations of the model.

Use the following values for the simulation:

| $\rho$ | $\sigma$ |
|--------|----------|
| 0.95   | 0.01    |_____no_output_____**Step 1:** Create a variable called `parameters` that stores parameter values as a Pandas Series._____no_output_____

# Create a variable called 'parameters' that stores the model parameter values in a Pandas Series
parameters = pd.Series()
parameters['rho'] = .95

# Print stored parameter values
print(parameters)rho    0.95
dtype: float64
# Create a variable called 'sigma' that stores the value of sigma
sigma = 0.01**0.5_____no_output_____# Create variable called 'var_names' that stores the variable names in a list with state variables ordered first
var_names = ['a']

# Create variable called 'shock_names' that stores an exogenous shock name for each state variable.
shock_names = ['e_a']_____no_output_____

**Step 2:** Define a function called `equilibrium_equations` that evaluates the equilibrium
equations of model solved for zero. The function should accept three arguments:

1. `variables_forward`: Values of $t+1$-dated variables
2. `variables_current`: Values of $t$-dated variables
3. `parameters`: Pandas Series with the parameters for the model

The function should return a NumPy array of the model's equilibrium conditions solved for zero._____no_output_____

# Define a function that evaluates the equilibrium conditions of the model solved for zero. PROVIDED
def equilibrium_equations(variables_forward,variables_current,parameters):

    # Create a variable called 'p' that storres the model's parameters. PROVIDED.
    p = parameters

    # Create variable called 'cur' that stores the values of current (date t) variables. PROVIDED.
    cur = variables_current

    # Create variable called 'fwd' that stores the values of one-period-ahead (date t+1) variables. PROVIDED.
    fwd = variables_forward

    # Create variable called 'tfp_proc' that returns the law of motion for TFP solved for zero (exogenous shock excluded)
    tfp_proc = p.rho*np.log(cur.a) - np.log(fwd.a)

    # Return equilibrium conditions stacked in a numpy array. PROVIDED.
    return np.array([
        tfp_proc
        ])_____no_output_____

**Step 3:** Initialize a model instance using the `ls.model()` function. The

1. `equations`: Name of function that stores the equilibrium conditions
2. `n_states`: Number of *state* variables (i.e., variables that are *predetermined*)
3. `var_names`: List of the names of the endogenous variables
4. `shock_names`: List of the names of the exogenous shocks
5. `parameters`: Pandas Series of parameter values_____no_output_____

# Initialize the model into a variable named 'ar1_model' using the ls.model() function
ar1_model = ls.model(equations = equilibrium_equations,
                     n_states=1,
                     var_names=var_names,
                     shock_names=shock_names,
                     parameters = parameters)_____no_output_____

**Step 4:** Set the steady state of the model. Either use the `.compute_ss()` method which requires an initial guess of what the steady state is. Or set the steady state `.ss` attribute directly._____no_output_____

# Compute the steady state numerically using .compute_ss() method of ar1_model
ar1_model.compute_ss([1])
print(ar1_model.ss)a    1.0
dtype: float64
# Set the .ss attribute of ar1_model directly
ar1_model.ss = pd.Series({'a':1})_____no_output_____

**Step 5:** Compute a transform the model into a log-linear approximation around the nonstochastic steady state. Then rewrite the equilibrium conditions so that all endogenous variables are expressed as linear functions of state variables and exogenous shocks._____no_output_____

# Appproximate and solve using the .approximate_and_solve() method of ar1_model
ar1_model.approximate_and_solve()_____no_output_____

**Step 6:** Simulate the model using one of the following methods:

1. `.impulse()`: Compute impulse responses to a one-time shock to each exogenous variables. Results stored in `.irs` attribute.
2. `.stoch_sim()`: Compute stochastic simulation. Results stored in `.simulated` attribute.

First, we'll compute an impulse response simulation. Let's consider the effect of a one time shock to $\epsilon$ of 0.01 in period 5. Simulate 41 periods._____no_output_____

# Compute impulse response of a to a one-time shock to e_a
ar1_model.impulse(T=41,t0=5,shocks=[0.01])_____no_output_____

The impulse response simulations are stored in the `.irs` attribute as a dictionary with keys equal to the names of the exogenous shocks. _____no_output_____

# Print ar1_model.irs
print(ar1_model.irs){'e_a':      e_a         a
0   0.00  0.000000
1   0.00  0.000000
2   0.00  0.000000
3   0.00  0.000000
4   0.00  0.000000
5   0.01  0.010000
6   0.00  0.009500
7   0.00  0.009025
8   0.00  0.008574
9   0.00  0.008145
10  0.00  0.007738
11  0.00  0.007351
12  0.00  0.006983
13  0.00  0.006634
14  0.00  0.006302
15  0.00  0.005987
16  0.00  0.005688
17  0.00  0.005404
18  0.00  0.005133
19  0.00  0.004877
20  0.00  0.004633
21  0.00  0.004401
22  0.00  0.004181
23  0.00  0.003972
24  0.00  0.003774
25  0.00  0.003585
26  0.00  0.003406
27  0.00  0.003235
28  0.00  0.003074
29  0.00  0.002920
30  0.00  0.002774
31  0.00  0.002635
32  0.00  0.002503
33  0.00  0.002378
34  0.00  0.002259
35  0.00  0.002146
36  0.00  0.002039
37  0.00  0.001937
38  0.00  0.001840
39  0.00  0.001748
40  0.00  0.001661}

Let's look at the first 10 rows of the `'e_a'` element of `ar1_model.irs`._____no_output_____

# Print first 10 rows of the element in ar1_model.irs that corresponds to the shock to TFP
print(ar1_model.irs['e_a'].head())   e_a    a
0  0.0  0.0
1  0.0  0.0
2  0.0  0.0
3  0.0  0.0
4  0.0  0.0
# Plot simulated impulse response to e_a
ar1_model.irs['e_a'][['e_a','a']].plot(lw=3,alpha=0.5,grid=True).legend(loc='upper right',ncol=2)_____no_output_____

Next, we'll use the `.stoch_sim()` method to compute a stochastic simulation. The method takes arguments:

1. `seed`: Seed of NumPy RNG. (Optional)
2. `T`: Number of periods to simulate
3. `covMat`: Covariance matrix for exogenous shock process

The simulation should be for 201 periods._____no_output_____

# Compute stochastic simulation
ar1_model.stoch_sim(seed=126,T=201,cov_mat= [[sigma**2]])

# Print the first 10 rows of `model.simulated`
print(ar1_model.simulated.head(10))        e_a         a
0 -0.060064  0.033436
1  0.076834  0.108598
2 -0.065005  0.038163
3 -0.004405  0.031850
4 -0.008192  0.022066
5  0.030705  0.051668
6  0.062828  0.111912
7  0.014509  0.120825
8  0.029805  0.144589
9 -0.085171  0.052188

The stochastic simulations are stored in the `.stoch_sim` attribute as a Pandas `DataFrame`._____no_output_____

# Plot the stochastic simulation
ar1_model.simulated[['e_a','a']].plot(lw=3,alpha=0.5,grid=True).legend(loc='upper right',ncol=2)_____no_output_____

## Example 2: The Stochastic Solow Growth Model Revisited

Now consider the following system of equations:

\begin{align}
Y_t & = A_t K_t^{\alpha} \\
K_{t+1} & = sY_t + (1-\delta) K_t\\
\log A_{t+1} & = \rho \log A_t + \epsilon_{t+1}
\end{align}

where $\epsilon_{t+1} \sim \mathcal{N}(0,\sigma^2)$. Let's simulate the model with `linearsolve`. Before proceding, let's also go ahead and rewrite the model with all variables moved to the lefthand side of the equations:

\begin{align}
0 & = A_t K_t^{\alpha} - Y_t \\
0 & = sY_t + (1-\delta) K_t - K_{t+1}\\
0 & = \rho \log A_t + \epsilon_{t+1} - \log A_{t+1}
\end{align}

Capital and TFP are called *state variables* because they're $t+1$ values are predetermined. Output is called a *costate* or *control* variable. Note that the model as 3 endogenous variables with 2 state variables.

Use the following values for the simulation:

| $\rho$ | $\sigma$ | $s$ | $\alpha$ | $\delta $ | $T$  |
|--------|----------|-----|----------|-----------|------|
| 0.75   | 0.006    | 0.1 | 0.35     |  0.025    | 201  |_____no_output_____### Initialization, Approximation, and Solution

The next several cells initialize the model in `linearsolve` and then approximate and solve it._____no_output_____

# Create a variable called 'parameters' that stores the model parameter values in a Pandas Series
parameters = pd.Series()
parameters['rho'] = .75
parameters['alpha'] = 0.35
parameters['delta'] = 0.025
parameters['s'] = 0.1

# Print the model's parameters
print(parameters)rho      0.750
alpha    0.350
delta    0.025
s        0.100
dtype: float64
# Create a variable called 'sigma' that stores the value of sigma
sigma = 0.006_____no_output_____# Create variable called 'var_names' that stores the variable names in a list with state variables ordered first
var_names = ['a','k','y']

# Create variable called 'shock_names' that stores an exogenous shock name for each state variable.
shock_names = ['e_a','e_k']_____no_output_____# Define a function that evaluates the equilibrium conditions of the model solved for zero. PROVIDED
def equilibrium_equations(variables_forward,variables_current,parameters):

    # Parameters. PROVIDED
    p = parameters

    # Current variables. PROVIDED
    cur = variables_current

    # Forward variables. PROVIDED
    fwd = variables_forward

    # Production function
    production_function = cur.a*cur.k**p.alpha - cur.y

    # Capital evolution
    capital_evolution = p.s*cur.a*cur.k**p.alpha + (1 - p.delta)*cur.k - fwd.k

    # Exogenous tfp
    tfp_process = p.rho*np.log(cur.a) - np.log(fwd.a)

    # Stack equilibrium conditions into a numpy array
    return np.array([
        production_function,
        capital_evolution,
        tfp_process
        ])_____no_output_____

Next, initialize the model._____no_output_____

# Initialize the model into a variable named 'solow_model'
solow_model = ls.model(equations = equilibrium_equations,
                 n_states=2,
                 var_names=var_names,
                 shock_names=shock_names,
                 parameters = parameters)_____no_output_____# Compute the steady state numerically using .compute_ss() method of solow_model
guess = [1,4,1]
solow_model.compute_ss(guess)

# Print the computed steady state
print(solow_model.ss)a    1.000000
k    8.438129
y    2.109532
dtype: float64
# Find the log-linear approximation around the non-stochastic steady state and solve using .approximate_and_solve() method of solow_model
solow_model.approximate_and_solve()_____no_output_____

### A Few Details About the Approximation (Optional)

The previous step constructs a log-linear approximation of the model and then solves for the endogenous variables as functions of the state variables and exogenous shocks only.

View the approximated model by calling the `.approximated()` method._____no_output_____

# Print the log-linear approximation to the models's equilibrium conditions
print(solow_model.approximated())Log-linear equilibrium conditions:

             0 = -2.1095·a[t]-0.7383·k[t]+2.1095·y[t]

-8.4381·k[t+1] = -0.211·a[t]-8.301·k[t]

       -a[t+1] = -0.75·a[t]

Each variable represents the log-deviation from the steady state of the respective variable in our model. For example, the variable `y[t]` means $\log(Y_t) - \log(Y)$ in terms of the stochastic Solow model. But how do these equations relate to the original model?

The first equation appears to be:

\begin{align}
0 &= -2.1095\cdot a_t-0.7383\cdot k_t + 2.1095\cdot y_t
\end{align}

Note that dividing by 2.1095 and solving for $y_t$ yields:

\begin{align}
y_t &= a_t +0.3499\cdot k_t,
\end{align}

so the coefficient on $k_t$ appears to be close to $\alpha=0.35$. We can derive this linear equation directly. First, start with the production function:

\begin{align}
Y_t &= A_t K_t^{\alpha}.
\end{align}

Then divide both sides by steady state output:

\begin{align}
\frac{Y_t}{Y} &= \frac{A_t K_t^{\alpha}}{AK^{\alpha}} \, = \, \frac{A_t}{A}\frac{K_t^{\alpha}}{K^{\alpha}}.
\end{align}

Then, take the log of both sides:
\begin{align}
\log\left(\frac{Y_t}{Y} \right)&= \log\left(\frac{A_t}{A}\right) + \alpha\log\left(\frac{K_t}{K}\right).
\end{align}

finally, letting $y_t = \log(Y_t/Y)$, k_t = $\log(K_t/K)$, and $a_t = \log(A_t/A)$, we have:

\begin{align}
y_t &= a_t + \alpha k_t.
\end{align}

However, understanding this process isn't as important as being able to interpret the graphs and statistics that compute using the output of `linearsolve`.

### A Few Details About the Solution (Optional)

It's also worth seeing what it means for a model to be *solved*. After `linearsolve` computes the log-linear approximation to the model, it solves for each endogenous variable as a function of state variables only. View the solved model by calling the `.solved()` method._____no_output_____

# Print the solved model
print(solow_model.solved())Solution to the log-linear system:

a[t+1] = 0.75·a[t]+e_a[t+1]

k[t+1] = 0.025·a[t]+0.9837·k[t]+e_k[t+1]

  y[t] = a[t]+0.35·k[t]

### Impulse Responses

Compute a 41 period impulse responses of the model's variables to a 0.01 unit shock to TFP in period 5._____no_output_____

# Compute impulse responses
solow_model.impulse(T=41,t0=5,shocks=[0.01,0])

# Print the first 10 rows of the computed impulse responses.
print(solow_model.irs['e_a'].head(10))    e_a         a         k         y
0  0.00  0.000000  0.000000  0.000000
1  0.00  0.000000  0.000000  0.000000
2  0.00  0.000000  0.000000  0.000000
3  0.00  0.000000  0.000000  0.000000
4  0.00  0.000000  0.000000  0.000000
5  0.01  0.010000  0.000000  0.010000
6  0.00  0.007500  0.000250  0.007588
7  0.00  0.005625  0.000433  0.005777
8  0.00  0.004219  0.000567  0.004417
9  0.00  0.003164  0.000663  0.003396
# Plot the computed impulse responses to a TFP shock
fig = plt.figure(figsize=(12,4))

ax1 = fig.add_subplot(1,2,1)
solow_model.irs['e_a'][['a','y','k']].plot(lw=3,alpha=0.5,grid=True,ax = ax1).legend(loc='upper right',ncol=2)
ax1.set_title('Endogenous Variables')

ax2 = fig.add_subplot(1,2,2)
solow_model.irs['e_a'][['e_a']].plot(lw=3,alpha=0.5,grid=True,ax = ax2).legend(loc='upper right',ncol=2)
ax2.set_title('TFP Shock Process')

fig.tight_layout()_____no_output_____

### Stochastic Simulation

Compute a 201 period stochastic simulation of the model's variables. Set the variance of $\epsilon_t$ to $\sigma^2$ and the variance of the shock to capital to 0 so that the covariance matrix for the shock process is:

\begin{align}
\text{Covariance matrix} & = \left[\begin{array}{cc} \sigma^2 & 0\\ 0 & 0\end{array} \right]
\end{align}_____no_output_____

# Compute stochastic simulation and print the simulated values.
solow_model.stoch_sim(seed=126,T=201,cov_mat= [[sigma**2,0],[0,0]])

# Print first 10 rows of model.simulated
print(solow_model.simulated.head(10))        e_a  e_k         a         k         y
0  0.001484  0.0  0.003862  0.000649  0.004089
1  0.001427  0.0  0.004323  0.000735  0.004580
2  0.003155  0.0  0.006397  0.000831  0.006688
3  0.011305  0.0  0.016103  0.000978  0.016445
4 -0.001778  0.0  0.010299  0.001364  0.010777
5 -0.013211  0.0 -0.005486  0.001600 -0.004927
6  0.004054  0.0 -0.000060  0.001437  0.000442
7  0.006153  0.0  0.006108  0.001412  0.006602
8 -0.001336  0.0  0.003245  0.001541  0.003785
9 -0.006801  0.0 -0.004367  0.001598 -0.003808
# Plot the computed stochastic simulation
fig = plt.figure(figsize=(12,4))

ax1 = fig.add_subplot(1,2,1)
solow_model.simulated[['a','y','k']].plot(lw=3,alpha=0.5,grid=True,ax = ax1).legend(loc='upper right',ncol=3)
ax1.set_title('Endogenous Variables')

ax2 = fig.add_subplot(1,2,2)
solow_model.simulated[['e_a']].plot(lw=3,alpha=0.5,grid=True,ax = ax2).legend(loc='upper right',ncol=2)
ax2.set_title('TFP Shock Process')

fig.tight_layout()_____no_output_____# Compute standard deviations of simulated TFP, output, and capital
solow_model.simulated[['a','y','k']].std()_____no_output_____# Compute correlation coefficients of simulated TFP, output, and capital
solow_model.simulated[['a','y','k']].corr()_____no_output_____