# *Object Tracking* (Seguimiento de objetos)
El seguimiento de objetos es una tarea importante dentro del campo de *computer vision*. En este post consideramos la metodologia de *object tracking* conocida como *tracking-by-detection* donde los resultados de la detección de objetos se dan en cada *frame* como *input* y el objetivo es asociar las detecciones para encontrar las trayectorias de los objetos. No se puede esperar que se detecten todos los objetos en cada cuadro, puede haber falsas detecciones y algunos objetos pueden ser ocluidos por otros; estos factores hacen que la asociación de datos sea una tarea difícil.

Comenzamos revisando el problema *tracking-by-detection*, siguiendo de cerca la formulación utilizada en (PONER REFERENCIAS). Asumimos la existencia de $z^{(i)}_t$, que corresponde a la detección $i$ realizada en el tiempo $t$. Notese que no se especifica la forma de la detección (por ejemplo, *bounding box*, *feature vector*, trazas de *optical-flow*). Denotamos el conjunto de todas las detecciones en un video como $\mathbf{Z}$. Además, definimos una *track* $\mathbf{x}^{(k)} = \{z^{(i)}_{t_0}, \cdots, z^{(i)}_{t_{T_k}}\}$ como una lista ordenada de detecciones que contiene toda la información necesaria para rastrear un objeto incluyendo, pero no limitándose, a su ubicación actual. Estos *tracks* codifican los cambios que experimenta el objeto $k$ desde el momento de su primera detección efectiva hasta la última, proporcionando la noción de persistencia necesaria para distinguir los objetos entre sí dentro de un vídeo. Definimos el conjunto de todos los *tracks* de $K$ como $\mathbf{X} = \{ \mathbf{x}^{(1)}, \cdots, \mathbf{x}^{(K)}\}$.

Utilizando la formulación de *tracking-by-detection*, pretendemos maximizar la probabilidad posterior de $\mathbf{X}$ dado $\mathbf{Z}$, como

\begin{equation}
    \begin{aligned}
       \operatorname*{max}_\mathbf{X} p(\mathbf{X} | \mathbf{Z}) & = \operatorname*{max}_\mathbf{X} p( \mathbf{Z} | \mathbf{X}) p(\mathbf{X}) \\
        & = \operatorname*{max}_\mathbf{X} \prod_{i,t} p(\mathbf{z}^{(i)}_{t} | \mathbf{X}) \prod_{k}  p(\mathbf{x}^{(k)}),
    \end{aligned}
    \tag{1}\label{1}
\end{equation}

donde asumimos una independencia condicional entre detecciones dada una colección de *tracks*; e independencia entre los *tracks*, esto significa que el movimiento de cada objeto es independiente. Es difícil optimizar la ecuacion anterior directamente, porque el espacio de $\mathbf{X}$ es enorme, sin embargo, podemos reducir el tamaño del espacio de búsqueda utilizando la observación de que un objeto sólo puede pertenecer a una trayectoria. Esto se traduce en la restricción de que los *tracks* no puede superponerse entre sí, es decir, $\mathbf{x}^{(k)}  \cap \mathbf{x}^{(l)} = \varnothing \mbox{  }, \forall k \neq l$. Asumimos además que las transiciones de *tracks* siguen un modelo de Markov de primer orden $p(\mathbf{x}^{(k)}) = p(x^{(k)}_{t_0}) \prod_{t} p(x^{(k)}_{t_{l}} | x^{(k)}_{t_{l-1}})$.

La ecuación $\eqref{1}$ muestra que el problema de *tracking-by-detection* puede descomponerse en dos subproblemas: evaluar la probabilidad de las detecciones $p( \mathbf{Z} | \mathbf{X})$  (por ejemplo, ignorar las detecciones que muestran un movimiento improbable, evaluar la necesidad de nuevos *tracks*) y modelar el movimiento de los objetos $p(\mathbf{x}^{(k)})$.
## Tipos de *object tracking*
Partiendo de la ecuacion $\eqref{1}$ y de las asunciones anteriores, se puede modelar el problema de *tracking-by-detection* de dos formas concidas en la literatura como las formulaciones *batch* y *online*.

Metodos *online* como (PONER REFERENCIAS) asocian las detecciones del *frame* entrante inmediatamente a las trayectorias existentes y, por lo tanto, son apropiadas para aplicaciones en tiempo real, asimismo las trayectorias son modeladas como *linear state space models*, por ejemplo filtros de Kalman o filtros de particulas. La asociación a las detecciones en el *frame* actual se formula a menudo como un problema de asignacion binaria y se resuelve mediante el algoritmo húngaro.

Metodos *batch* como (PONER REFERENCIAS) consideran observaciones pasadas, presentes y futuras o incluso toda la secuencia a la vez. Aunque no es aplicable en tiempo real, la ventaja de los métodos *batch* es el contexto temporal, que permite realizar predicciones más robustas. Una solución elegante para asignar trayectorias a las detecciones es la formulación *network flow* introducida en (PONER REFERENCIAS).

### *Batch object tracking*
(IMAGE)

Los metodos *batch* se pueden representar como un grafo (Ver imagen) donde cada detección $\mathbf{z}_t$ se representa con dos nodos conectados por un borde (en rojo en la imagen). A este borde se le asigna la variable de flujo $y^{det}_{t}$. Para poder asociar dos detecciones que pertenecen a la misma trayectoria $\mathbf{X}$, se añaden al gráfico bordes dirigidos (en azul en la imagen) de todos los $\mathbf{z}_t$ a todos los $\mathbf{z}_{t'}$ tal que $t < t'$ y $\vert t - t'\vert < \tau_t$. A cada uno de estos bordes se le asigna una variable de flujo $y^{link}_{t, t'}$. El hecho de tener bordes en múltiples cuadros permite manejar oclusiones o detecciones fallidas. Para reducir el tamaño del gráfico, eliminamos bordes entre las detecciones que están espacialmente distantes, esto lo que la variable $\tau_t$ representa. Esta elección se basa en la suposición de los objetos de mueven aproximandamente con movimiento rectilineo uniforme en instantes cortos de tiempo. Para manejar el nacimiento y la muerte de las trayectorias, se añaden dos nodos especiales al gráfico. Un nodo fuente ($S$) se conecta con el primer nodo de cada detección con un borde (negro en la imagen) al que se le asigna la variable de flujo $y^{in}_t$. Asimismo, el segundo nodo de cada detección está conectado con un nodo de sumidero ($T$) y al borde correspondiente (negro) se le asigna la variable $y^{out}_t$. Cada variable del gráfico está asociada a un costo. Para cada uno de los cuatro tipos de variables definimos el costo correspondiente, es decir, $c^{in}_t = -\text{log } p(x^{(k)}_{t})$, $c^{out}_T = -\text{log } p(x^{(k)}_{T})$, $c^{det}_t = -\text{log } p( \mathbf{z}_{t} | \mathbf{X})$ y $c^{link}_{t, t'} = -\text{log }p(x^{(k)}_{t} | x^{(k)}_{t'})$, que resulta de aplicarel logaritmo a la ecuacion $\eqref{1}$ y cambiar el problema de maximizacion a uno de minimizacion introduciendo un signo menos. Esto nos deja con el siguiente problema de programacion lineal conocido como *Minimum-cost flow*:
\begin{equation}
\begin{aligned}
& {\text{min}}
&& z = \displaystyle\sum_{\mathbf{x}^{(k)} \in \mathbf{X}} \left( c^{in}_{t_0} y^{in}_{t_0}  + c^{out}_{t_{T_k}} y^{out}_{t_{T_k}} +  \sum_{l>0} c^{link}_{t_{l+1}, t_{l}} y^{link}_{t_{l+1}, t_{l}} \right)  + \sum_t c^{det}_t y^{det}_{t} \\
&&& z = \sum_{t} c^{in}_{t} y^{in}_{t} + \sum_{t} c^{out}_{t} y^{out}_{t} \sum_{t, t'} c^{link}_{t, t'} y^{link}_{t, t'} + \sum_{t} c^{det}_t y^{det}_{t} \\
&\text{s.t.}
&&  y^{in}_{t} + \sum_{t'} y^{link}_{t, t'} =  y^{det}_{t}\\
&&& y^{out}_t + \sum_{t'} y^{link}_{t, t'} = y^{det}_{t} \\
&&& y^{in}_{t}, y^{link}_{t, t'}, y^{out}_t, y^{det}_{t} \in \{0, 1\} \\
\end{aligned}
\end{equation}
Encontrar la hipótesis de asociación óptima $\mathbf{X}^\star$ es equivalente a enviar el flujo de la fuente $S$ al sumidero $T$ que minimiza el costo. Cada trayectoria de flujo puede ser interpretada como la trayectoria de un objeto, la cantidad de flujo enviada de $S$ a $T$ es igual al número de trayectorias de objetos en el video, y el costo total del flujo corresponde al *loglikelihood* de la hipótesis de asociación. Las restricciones de conservación del flujo garantizan que ningún flujo comparta un borde común y, por lo tanto, que no se superpongan las trayectorias.

### *Online object tracking*
Los metodos *online* toman un enfoque *greddy* optando por reolver el problema de *tracking-by-detection* para cada *frame*, es decir, en $t=0$ todas las detecciones $z^{(i)}_0$ se asumen correctas y correspondientes a objetos distintos, y se las asocia con un *track* para cada detecion (este paso, aunque restrictivo es necesario para inicializar la solucion), luego una variante recursiva de la ecuacion $\eqref{1}$ es resuelta:
\begin{equation}
    \begin{aligned}
          & \operatorname*{max}_{\mathbf{X}} p(\mathbf{X}_t \vert \mathbf{Z}_{t}, \mathbf{X}_{t-1}^{\star})
    \end{aligned}
    \tag{2}\label{2}
\end{equation}
La detección de objetos obtiene las detecciones $\mathbf{Z}_{t}$ aplicando un detector pre-entrenado en el *frame* $t$. Luego se  encuentran las asociaciones óptimas entre $\mathbf{Z}_{t}$ y $\mathbf{X}_{t-1}^{\star}$ para formar la estimación actual de las trayectorias $\mathbf{X}_t$. Notese que los *tracks* obtenidos utilizando el proceso definido por la ecuacion $\eqref{2}$ no son necesariamente óptimas en cuanto a la maximización de la distribución definida por la ecuacion $\eqref{1}$. Dado que el número de todas las enumeraciones posibles de $\mathbf{X}_t$ dadas las trayectorias optimas $\mathbf{X}_{t-1}^{\star}$ encontradas en $t-1$  y las detecciones $\mathbf{Z}_{t}$ es enorme, resolver directamente la ecuacion $\eqref{2}$ es intratable.
Simplificamos aun mas es problema resolviendo únicamente un problema de asociación de datos entre $\mathbf{X}_{t-1}^{\star}$ y $\mathbf{Z}_{t}$, para luego obtener las trayectorias óptimas $\mathbf{X}_t^{\star}$ actualizando $\mathbf{X}_{t-1}^{\star}$ con las detecciones asociadas. Hacemos esto definiendo una matriz de asignación $\Psi = [\Psi_{i,j}]_{n \times m}$ para representar la asociación entre $\mathbf{X}_{t-1}^{\star}$ y  $\mathbf{Z}_{t}$ , donde cada entrada $\Psi_{i,j} \in \{0,1\}$ indica si la detección $j$-esima está asociada a la trayectoria $i$-esima o no.  Luego, el problema de la asociación de datos se expresa como un problema de estimacion *MAP*

\begin{equation}
\begin{aligned}
\Psi^{\star} & = {\text{argmax}} \displaystyle\sum_{i,j} \Psi_{i,j} \text{log } p(\Psi_{i,j}=1 \vert \mathbf{Z}_{t} \mathbf{X}_{t-1}), \\
\text{s.t.} & \displaystyle\sum_{i} \Psi_{i,j} = 1 \mbox{  }, \forall j \\
& \displaystyle\sum_{j} \Psi_{i,j} = 1 \mbox{  }, \forall i
\end{aligned}
\end{equation}
donde $p(\Psi_{i,j}=1 \vert \mathbf{Z}_{t} \mathbf{X}_{t-1})$ es la probabilidad de asociación posterior cuando la detección $j$-esima se asocia a la trayectoria $i$-esima. Asumimos que las asociaciones de todos los pares de trayectoria y detección son independientes. La tarea es buscar la asignación óptima $\Psi^{\star}$ que maximiza la probabilidad de asociación posterior.
Varios tipos de *trackers* pueden existir dependiendo de como modelemos la probabilidad de asociación posterior $\text{log } p(\Psi_{i,j}=1 \vert \mathbf{Z}_{t} \mathbf{X}_{t-1})$, uno de los mas importantes es el *Kalman tracker* que aproxima la probabilidad de asociacion con la distancia de Mahalanobis $(\mathbf{z}^{(i)}_{t} - \mathbf{x}^{(j)}_{t})^{\intercal} \Sigma^{-1}_{t} (\mathbf{z}^{(i)}_{t} - \mathbf{x}^{(j)}_{t})$ o la medida de *IoU* (*Intersection over Union*) entre las detecciones y los *tracks*.
La distancia IOU entre las detecciones y los *tracks* maneja implícitamente la oclusión a corto plazo causada por los objetos que pasan dado que favorece adecuadamente las detecciones con una escala similar.  Esto permite que tanto el objeto oclusor se corrija con la detección mientras que el objeto cubierto no se ve afectado por la falta de asignación. Asimismo cuando se usa la distancia de Mahalanobis, la matriz $\Sigma$, que es la matriz de incertidumbre obtenida del filtro de Kalman, se obtienen las siguientes ventajas: (1) la asignación de *tracks* a detecciones lejanas se vuelve improbable; y (2) la oclusión a corto plazo puede ser manejada cuando la incertidumbre de movimiento es baja.

## Implementando *Online object tracking*

#### Train relational netowrk


# Double Machine Learning: Use Cases and Examples

Double Machine Learning (DML) is an algorithm that applies arbitrary machine learning methods
to fit the treatment and response, then uses a linear model to predict the response residuals
from the treatment residuals.

The EconML SDK implements the following DML classes:
* LinearDML: suitable for estimating heterogeneous treatment effects.
* SparseLinearDML: suitable for the case when $W$ is high dimensional vector and both the first stage and second stage estimate are linear.

In ths notebook, we show the performance of the DML on both synthetic data and observational data.

**Notebook contents:**

1. Example usage with single continuous treatment synthetic data
2. Example usage with single binary treatment synthetic data
3. Example usage with multiple continuous treatment synthetic data
4. Example usage with single continuous treatment observational data
5. Example usage with multiple continuous treatment, multiple outcome observational data

```python
import econml
```

```python
## Ignore warnings
import warnings
warnings.filterwarnings("ignore")
```

```python
# Main imports
from econml.dml import DML, LinearDML, SparseLinearDML, CausalForestDML

# Helper imports
import numpy as np
from itertools import product
from sklearn.linear_model import Lasso, LassoCV, LogisticRegression, LogisticRegressionCV,LinearRegression,MultiTaskElasticNet,MultiTaskElasticNetCV
from sklearn.ensemble import RandomForestRegressor,RandomForestClassifier
from sklearn.preprocessing import PolynomialFeatures
import matplotlib.pyplot as plt
import matplotlib
from sklearn.model_selection import train_test_split

%matplotlib inline
```

## 1. Example Usage with Single Continuous Treatment Synthetic Data and Model Selection

### 1.1. DGP
We use the data generating process (DGP) from [here](https://arxiv.org/abs/1806.03467). The DGP is described by the following equations:

\begin{align}
T =& \langle W, \beta\rangle + \eta, & \;\eta \sim \text{Uniform}(-1, 1)\\
Y =& T\cdot \theta(X) + \langle W, \gamma\rangle + \epsilon, &\; \epsilon \sim \text{Uniform}(-1, 1)\\
W \sim& \text{Normal}(0,\, I_{n_w})\\
X \sim& \text{Uniform}(0,1)^{n_x}
\end{align}

where $W$ is a matrix of high-dimensional confounders and $\beta, \gamma$ have high sparsity.

For this DGP,
\begin{align}
\theta(x) = \exp(2\cdot x_1).
\end{align}

```python
# Treatment effect function
def exp_te(x):
    return np.exp(2*x[0])
```

```python
# DGP constants
np.random.seed(123)
n = 2000
n_w = 30
support_size = 5
n_x = 1
# Outcome support
support_Y = np.random.choice(np.arange(n_w), size=support_size, replace=False)
coefs_Y = np.random.uniform(0, 1, size=support_size)
epsilon_sample = lambda n: np.random.uniform(-1, 1, size=n)
# Treatment support
support_T = support_Y
coefs_T = np.random.uniform(0, 1, size=support_size)
eta_sample = lambda n: np.random.uniform(-1, 1, size=n)

# Generate controls, covariates, treatments and outcomes
W = np.random.normal(0, 1, size=(n, n_w))
X = np.random.uniform(0, 1, size=(n, n_x))
# Heterogeneous treatment effects
TE = np.array([exp_te(x_i) for x_i in X])
T = np.dot(W[:, support_T], coefs_T) + eta_sample(n)
Y = TE * T + np.dot(W[:, support_Y], coefs_Y) + epsilon_sample(n)

Y_train, Y_val, T_train, T_val, X_train, X_val, W_train, W_val = train_test_split(Y, T, X, W, test_size=.2)
# Generate test data
X_test = np.array(list(product(np.arange(0, 1, 0.01), repeat=n_x)))
```

### 1.2. Train Estimator
We train models in three different ways, and compare their performance.

#### 1.2.1. Default Setting

```python
est = LinearDML(model_y=RandomForestRegressor(),
                model_t=RandomForestRegressor(),
                random_state=123)
est.fit(Y_train, T_train, X=X_train, W=W_train)
te_pred = est.effect(X_test)
```

#### 1.2.2. Polynomial Features for Heterogeneity

```python
est1 = SparseLinearDML(model_y=RandomForestRegressor(),
                       model_t=RandomForestRegressor(),
                       featurizer=PolynomialFeatures(degree=3),
                       random_state=123)
est1.fit(Y_train, T_train, X=X_train, W=W_train)
te_pred1 = est1.effect(X_test)
```

#### 1.2.3. Polynomial Features with regularization

```python
est2 = DML(model_y=RandomForestRegressor(),
           model_t=RandomForestRegressor(),
           model_final=Lasso(alpha=0.1, fit_intercept=False),
           featurizer=PolynomialFeatures(degree=10),
           random_state=123)
est2.fit(Y_train, T_train, X=X_train, W=W_train)
te_pred2 = est2.effect(X_test)
```

#### 1.2.4 Non-Parametric Heterogeneity with Causal Forest

```python
est3 = CausalForestDML(model_y=RandomForestRegressor(),
                       model_t=RandomForestRegressor(),
                       criterion='mse', n_estimators=1000,
                       min_samples_leaf=10,
                       min_impurity_decrease=0.001,
                       random_state=123)
est3.fit(Y_train, T_train, X=X_train, W=W_train)
te_pred3 = est3.effect(X_test)
```

```python
est3.feature_importances_
```

    array([1.])

### 1.3. Performance Visualization

```python
plt.figure(figsize=(10,6))
plt.plot(X_test, te_pred, label='DML default')
plt.plot(X_test, te_pred1, label='DML polynomial degree=3')
plt.plot(X_test, te_pred2, label='DML polynomial degree=10 with Lasso')
plt.plot(X_test, te_pred3, label='ForestDML')
expected_te = np.array([exp_te(x_i) for x_i in X_test])
plt.plot(X_test, expected_te, 'b--', label='True effect')
plt.ylabel('Treatment Effect')
plt.xlabel('x')
plt.legend()
plt.show()
```

### 1.4. Model selection

For the three different models above, we can use score function to estimate the final model performance. The score is the MSE of the final stage Y residual, which can be seen as a proxy of the MSE of treatment effect.

```python
score={}
score["DML default"] = est.score(Y_val, T_val, X_val, W_val)
score["DML polynomial degree=2"] = est1.score(Y_val, T_val, X_val, W_val)
score["DML polynomial degree=10 with Lasso"] = est2.score(Y_val, T_val, X_val, W_val)
score["ForestDML"] = est3.score(Y_val, T_val, X_val, W_val)
score
```

    {'DML default': 1.844539580346218,
     'DML polynomial degree=2': 1.6825898391561411,
     'DML polynomial degree=10 with Lasso': 2.2227298381949683,
     'ForestDML': 1.9356603282414195}

```python
print("best model selected by score: ",min(score,key=lambda x: score.get(x)))
```

    best model selected by score:  DML polynomial degree=2

```python
mse_te={}
mse_te["DML default"] = ((expected_te - te_pred)**2).mean()
mse_te["DML polynomial degree=2"] = ((expected_te - te_pred1)**2).mean()
mse_te["DML polynomial degree=10 with Lasso"] = ((expected_te - te_pred2)**2).mean()
mse_te["ForestDML"] = ((expected_te - te_pred3)**2).mean()
mse_te
```

    {'DML default': 0.3525689652597256,
     'DML polynomial degree=2': 0.24753714395571458,
     'DML polynomial degree=10 with Lasso': 0.2572696263250526,
     'ForestDML': 0.3483330572272164}

```python
print("best model selected by MSE of TE: ", min(mse_te, key=lambda x: mse_te.get(x)))
```

    best model selected by MSE of TE:  DML polynomial degree=2

### 1.5 Changing only Final Stage Model Specification and Refitting

It is also feasible to change the parameters of the estimator and only fit the final stage model using the existing first stage residual calculation. To enable this feature, the `fit` method should be called with the flag `cache_values=True`, so that the original fit data and their corresponding first stage estimates be stored in the model. This can be done for any estimator in the dml module. We portray an example below, using the `DML` estimator.

```python
est = DML(model_y=RandomForestRegressor(),
          model_t=RandomForestRegressor(),
          model_final=Lasso(alpha=0.1, fit_intercept=False),
          featurizer=PolynomialFeatures(degree=1, include_bias=False),
          random_state=123)
est.fit(Y_train, T_train, X=X_train, W=W_train, cache_values=True)
est.summary()
```

Coefficient Results

   	  point_estimate

  X0	      5.783

CATE Intercept Results

         	        point_estimate

  cate_intercept	      0.601

A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \Theta(X)\cdot T + g(X, W) + \epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\Theta_{ij}(X)$ has the form:
$\Theta_{ij}(X) = \phi(X)' coef_{ij} + cate\_intercept_{ij}$
where $\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\_intercept_{ij}$ parameter.

```python
from econml.sklearn_extensions.linear_model import DebiasedLasso
est.featurizer = PolynomialFeatures(degree=2, include_bias=False)
est.model_final = DebiasedLasso(fit_intercept=False)
est.refit_final()
est.summary()
```

Coefficient Results

    	   point_estimate	 stderr	 zstat	 pvalue	 ci_lower	 ci_upper

  X0	        0.645	      0.684	 0.944	  0.345	  -0.479	    1.77

  X0^2	      6.377	      0.692	 9.21	    0.0	    5.238	    7.516

CATE Intercept Results

         	        point_estimate	 stderr	 zstat	 pvalue	 ci_lower	 ci_upper

  cate_intercept	      1.246	      0.154	 8.093	   0.0	    0.993	     1.5

A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \Theta(X)\cdot T + g(X, W) + \epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\Theta_{ij}(X)$ has the form:
$\Theta_{ij}(X) = \phi(X)' coef_{ij} + cate\_intercept_{ij}$
where $\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\_intercept_{ij}$ parameter.

```python
from econml.sklearn_extensions.linear_model import StatsModelsLinearRegression
est.model_final = StatsModelsLinearRegression(fit_intercept=False)
est.refit_final()
est.summary()
```

Coefficient Results

    	   point_estimate	 stderr	 zstat	 pvalue	 ci_lower	 ci_upper

  X0	        0.632	      0.757	 0.835	  0.403	  -0.612	    1.876

  X0^2	      6.385	      0.741	 8.621	   0.0	    5.167	    7.603

CATE Intercept Results

         	        point_estimate	 stderr	 zstat	 pvalue	 ci_lower	 ci_upper

  cate_intercept	      1.247	      0.159	 7.847	   0.0	    0.986	    1.509

A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \Theta(X)\cdot T + g(X, W) + \epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\Theta_{ij}(X)$ has the form:
$\Theta_{ij}(X) = \phi(X)' coef_{ij} + cate\_intercept_{ij}$
where $\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\_intercept_{ij}$ parameter.

```python
from econml.sklearn_extensions.linear_model import StatsModelsRLM
est.model_final = StatsModelsRLM(fit_intercept=False)
est.fit_cate_intercept = False
est.refit_final()
est.summary()
```

    CATE Intercept Results:  No intercept was fitted!

Coefficient Results

    	   point_estimate	 stderr	 zstat	 pvalue	 ci_lower	 ci_upper

  X0	        6.004	      0.34	  17.67	   0.0	    5.445	    6.563

  X0^2	      1.62	       0.44	  3.682	   0.0	    0.897	    2.344

A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \Theta(X)\cdot T + g(X, W) + \epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\Theta_{ij}(X)$ has the form:
$\Theta_{ij}(X) = \phi(X)' coef_{ij} + cate\_intercept_{ij}$
where $\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\_intercept_{ij}$ parameter.

```python
est.model_final = Lasso(fit_intercept=False)
est.refit_final()
est.effect_inference(X).population_summary()
```

Uncertainty of Mean Point Estimate

  mean_point

     0.654

Distribution of Point Estimate

  std_point	 pct_point_lower	 pct_point_upper

    0.373	        0.07	            1.236

## 2. Example Usage with Single Binary Treatment Synthetic Data and Confidence Intervals

### 2.1. DGP
We use the following DGP:

\begin{align}
T \sim & \text{Bernoulli}\left(f(W)\right), &\; f(W)=\sigma(\langle W, \beta\rangle + \eta), \;\eta \sim \text{Uniform}(-1, 1)\\
Y = & T\cdot \theta(X) + \langle W, \gamma\rangle + \epsilon, & \; \epsilon \sim \text{Uniform}(-1, 1)\\
W \sim & \text{Normal}(0,\, I_{n_w}) & \\
X \sim & \text{Uniform}(0,\, 1)^{n_x}
\end{align}

where $W$ is a matrix of high-dimensional confounders, $\beta, \gamma$ have high sparsity and $\sigma$ is the sigmoid function.

For this DGP,
\begin{align}
\theta(x) = \exp( 2\cdot x_1 ).
\end{align}

```python
# Treatment effect function
def exp_te(x):
    return np.exp(2 * x[0])# DGP constants

np.random.seed(123)
n = 1000
n_w = 30
support_size = 5
n_x = 4
# Outcome support
support_Y = np.random.choice(range(n_w), size=support_size, replace=False)
coefs_Y = np.random.uniform(0, 1, size=support_size)
epsilon_sample = lambda n:np.random.uniform(-1, 1, size=n)
# Treatment support
support_T = support_Y
coefs_T = np.random.uniform(0, 1, size=support_size)
eta_sample = lambda n: np.random.uniform(-1, 1, size=n)

# Generate controls, covariates, treatments and outcomes
W = np.random.normal(0, 1, size=(n, n_w))
X = np.random.uniform(0, 1, size=(n, n_x))
# Heterogeneous treatment effects
TE = np.array([exp_te(x_i) for x_i in X])
# Define treatment
log_odds = np.dot(W[:, support_T], coefs_T) + eta_sample(n)
T_sigmoid = 1/(1 + np.exp(-log_odds))
T = np.array([np.random.binomial(1, p) for p in T_sigmoid])
# Define the outcome
Y = TE * T + np.dot(W[:, support_Y], coefs_Y) + epsilon_sample(n)

# get testing data
X_test = np.random.uniform(0, 1, size=(n, n_x))
X_test[:, 0] = np.linspace(0, 1, n)
```

### 2.2. Train Estimator

```python
est = LinearDML(model_y=RandomForestRegressor(),
                model_t=RandomForestClassifier(min_samples_leaf=10),
                discrete_treatment=True,
                linear_first_stages=False,
                cv=6)
est.fit(Y, T, X=X, W=W)
te_pred = est.effect(X_test)
lb, ub = est.effect_interval(X_test, alpha=0.01)
```

```python
est2 = SparseLinearDML(model_y=RandomForestRegressor(),
                       model_t=RandomForestClassifier(min_samples_leaf=10),
                       discrete_treatment=True,
                       featurizer=PolynomialFeatures(degree=2),
                       linear_first_stages=False,
                       cv=6)
est2.fit(Y, T, X=X, W=W)
te_pred2 = est2.effect(X_test)
lb2, ub2 = est2.effect_interval(X_test, alpha=0.01)
```

```python
est3 = CausalForestDML(model_y=RandomForestRegressor(),
                       model_t=RandomForestClassifier(min_samples_leaf=10),
                       discrete_treatment=True,
                       n_estimators=1000,
                       max_samples=.4,
                       min_samples_leaf=10,
                       min_impurity_decrease=0.001,
                       verbose=0,
                       cv=6)
est3.fit(Y, T, X=X, W=W)
te_pred3 = est3.effect(X_test)
lb3, ub3 = est3.effect_interval(X_test, alpha=0.01)
```

```python
est3.feature_importances_
```

    array([0.99552462, 0.00128984, 0.00127374, 0.0019118 ])

### 2.3. Performance Visualization

```python
expected_te=np.array([exp_te(x_i) for x_i in X_test])
plt.figure(figsize=(16,6))
plt.subplot(1, 3, 1)
plt.plot(X_test[:, 0], te_pred, label='LinearDML', alpha=.6)
plt.fill_between(X_test[:, 0], lb, ub, alpha=.4)
plt.plot(X_test[:, 0], expected_te, 'b--', label='True effect')
plt.ylabel('Treatment Effect')
plt.xlabel('x')
plt.legend()
plt.subplot(1, 3, 2)
plt.plot(X_test[:, 0], te_pred2, label='SparseLinearDML', alpha=.6)
plt.fill_between(X_test[:, 0], lb2, ub2, alpha=.4)
plt.plot(X_test[:, 0], expected_te, 'b--', label='True effect')
plt.ylabel('Treatment Effect')
plt.xlabel('x')
plt.legend()
plt.subplot(1, 3, 3)
plt.plot(X_test[:, 0], te_pred3, label='ForestDML', alpha=.6)
plt.fill_between(X_test[:, 0], lb3, ub3, alpha=.4)
plt.plot(X_test[:, 0], expected_te, 'b--', label='True effect')
plt.ylabel('Treatment Effect')
plt.xlabel('x')
plt.legend()
plt.show()
```

### 2.4 Interpretability with SHAP Values

Explain the hetergoeneity model for the constant marginal effect of the treatment using SHAP values.

```python
import shap
```

```python
shap_values = est.shap_values(X)
shap.plots.beeswarm(shap_values['Y0']['T0_1'])
```

```python
shap_values2 = est2.shap_values(X)
shap.plots.beeswarm(shap_values2['Y0']['T0_1'])
```

```python
shap_values3 = est3.shap_values(X)
shap.plots.beeswarm(shap_values3['Y0']['T0_1'])
plt.show()
```

### 2.5. Other Inferences
#### 2.5.1 Effect Inferences
Other than confidence interval, we could also output other statistical inferences of the effect include standard error, z-test score and p value given each sample $X[i]$.

```python
est.effect_inference(X_test[:10,]).summary_frame(alpha=0.1, value=0, decimals=3)
```

      point_estimate
      stderr
      zstat
      pvalue
      ci_lower
      ci_upper

      0
      0.378
      0.146
      2.587
      0.010
      0.138
      0.618

      1
      0.425
      0.163
      2.609
      0.009
      0.157
      0.693

      2
      0.303
      0.170
      1.788
      0.074
      0.024
      0.583

      3
      0.396
      0.150
      2.645
      0.008
      0.150
      0.642

      4
      0.581
      0.152
      3.810
      0.000
      0.330
      0.832

      5
      0.549
      0.139
      3.946
      0.000
      0.320
      0.778

      6
      0.427
      0.124
      3.449
      0.001
      0.223
      0.630

      7
      0.602
      0.154
      3.904
      0.000
      0.348
      0.855

      8
      0.432
      0.126
      3.421
      0.001
      0.224
      0.640

      9
      0.481
      0.162
      2.963
      0.003
      0.214
      0.747

We could also get the population inferences given sample $X$.

```python
est.effect_inference(X_test).population_summary(alpha=0.1, value=0, decimals=3, tol=0.001)
```

Uncertainty of Mean Point Estimate

  mean_point	 stderr_mean	  zstat	 pvalue	 ci_mean_lower	 ci_mean_upper

     3.37	       0.14	     24.027	   0.0	      3.139	         3.601

Distribution of Point Estimate

  std_point	 pct_point_lower	 pct_point_upper

    1.732	        0.69	            6.083

Total Variance of Point Estimate

  stderr_point	 ci_point_lower	 ci_point_upper

      1.738	         0.675	          6.08

Note: The stderr_mean is a conservative upper bound.

#### 2.5.2 Coefficient and Intercept Inferences
We could also get the coefficient and intercept inference for the final model when it's linear.

```python
est.coef__inference().summary_frame()
```

      point_estimate
      stderr
      zstat
      pvalue
      ci_lower
      ci_upper

      X0
      5.981
      0.229
      26.135
      0.000
      5.605
      6.358

      X1
      -0.080
      0.216
      -0.371
      0.711
      -0.434
      0.275

      X2
      -0.344
      0.218
      -1.579
      0.114
      -0.702
      0.014

      X3
      0.276
      0.212
      1.302
      0.193
      -0.073
      0.624

```python
est.intercept__inference().summary_frame()
```

      point_estimate
      stderr
      zstat
      pvalue
      ci_lower
      ci_upper

      cate_intercept
      0.45
      0.217
      2.074
      0.038
      0.093
      0.807

```python
est.summary()
```

Coefficient Results

   	  point_estimate	 stderr	  zstat	 pvalue	 ci_lower	 ci_upper

  X0	      5.981	      0.229	 26.135	   0.0	    5.605	    6.358

  X1	      -0.08	      0.216	 -0.371	  0.711	  -0.434	    0.275

  X2	     -0.344	      0.218	 -1.579	  0.114	  -0.702	    0.014

  X3	      0.276	      0.212	  1.302	  0.193	  -0.073	    0.624

CATE Intercept Results

         	        point_estimate	 stderr	 zstat	 pvalue	 ci_lower	 ci_upper

  cate_intercept	      0.45	       0.217	 2.074	  0.038	   0.093	    0.807

A linear parametric conditional average treatment effect (CATE) model was fitted:
$Y = \Theta(X)\cdot T + g(X, W) + \epsilon$
where for every outcome $i$ and treatment $j$ the CATE $\Theta_{ij}(X)$ has the form:
$\Theta_{ij}(X) = \phi(X)' coef_{ij} + cate\_intercept_{ij}$
where $\phi(X)$ is the output of the `featurizer` or $X$ if `featurizer`=None. Coefficient Results table portrays the $coef_{ij}$ parameter vector for each outcome $i$ and treatment $j$. Intercept Results table portrays the $cate\_intercept_{ij}$ parameter.

## 3. Example Usage with Multiple Continuous Treatment Synthetic Data

### 3.1. DGP
We use the data generating process (DGP) from [here](https://arxiv.org/abs/1806.03467), and modify the treatment to generate multiple treatments. The DGP is described by the following equations:

\begin{align}
T =& \langle W, \beta\rangle + \eta, & \;\eta \sim \text{Uniform}(-1, 1)\\
Y =& T\cdot \theta_{1}(X) + T^{2}\cdot \theta_{2}(X) + \langle W, \gamma\rangle + \epsilon, &\; \epsilon \sim \text{Uniform}(-1, 1)\\
W \sim& \text{Normal}(0,\, I_{n_w})\\
X \sim& \text{Uniform}(0,1)^{n_x}
\end{align}

where $W$ is a matrix of high-dimensional confounders and $\beta, \gamma$ have high sparsity.

For this DGP,
\begin{align}
\theta_{1}(x) = \exp(2\cdot x_1)\\
\theta_{2}(x) = x_1^{2}\\
\end{align}

```python
# DGP constants
np.random.seed(123)
n = 6000
n_w = 30
support_size = 5
n_x = 5
# Outcome support
support_Y = np.random.choice(np.arange(n_w), size=support_size, replace=False)
coefs_Y = np.random.uniform(0, 1, size=support_size)
epsilon_sample = lambda n: np.random.uniform(-1, 1, size=n)
# Treatment support
support_T = support_Y
coefs_T = np.random.uniform(0, 1, size=support_size)
eta_sample = lambda n: np.random.uniform(-1, 1, size=n)

# Generate controls, covariates, treatments and outcomes
W = np.random.normal(0, 1, size=(n, n_w))
X = np.random.uniform(0, 1, size=(n, n_x))
# Heterogeneous treatment effects
TE1 = np.array([x_i[0] for x_i in X])
TE2 = np.array([x_i[0]**2 for x_i in X]).flatten()
T = np.dot(W[:, support_T], coefs_T) + eta_sample(n)
Y = TE1 * T + TE2 * T**2 + np.dot(W[:, support_Y], coefs_Y) + epsilon_sample(n)
# Generate test data
X_test = np.random.uniform(0, 1, size=(100, n_x))
X_test[:, 0] = np.linspace(0, 1, 100)
```

### 3.2. Train Estimator

```python
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.multioutput import MultiOutputRegressor
est = LinearDML(model_y=GradientBoostingRegressor(n_estimators=100, max_depth=3, min_samples_leaf=20),
                model_t=MultiOutputRegressor(GradientBoostingRegressor(n_estimators=100,
                                                                       max_depth=3,
                                                                       min_samples_leaf=20)),
                featurizer=PolynomialFeatures(degree=2, include_bias=False),
                linear_first_stages=False,
                cv=5)
```

```python
T = T.reshape(-1,1)
est.fit(Y, np.concatenate((T, T**2), axis=1), X=X, W=W)
```

    <econml.dml.dml.LinearDML at 0x2225427e408>

```python
te_pred = est.const_marginal_effect(X_test)
```

```python
lb, ub = est.const_marginal_effect_interval(X_test, alpha=0.01)
```

```python
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.multioutput import MultiOutputRegressor
est2 = CausalForestDML(model_y=GradientBoostingRegressor(n_estimators=100, max_depth=3, min_samples_leaf=20),
                       model_t=MultiOutputRegressor(GradientBoostingRegressor(n_estimators=100,
                                                                              max_depth=3,
                                                                              min_samples_leaf=20)),
                       cv=5,
                       criterion='mse', n_estimators=1000,
                       min_samples_leaf=10,
                       min_impurity_decrease=0.001,
                       random_state=123)
T = T.reshape(-1,1)
est2.fit(Y, np.concatenate((T, T**2), axis=1), X=X, W=W)
```

    <econml.dml.causal_forest.CausalForestDML at 0x2225583c9c8>

```python
te_pred2 = est2.const_marginal_effect(X_test)
```

```python
lb2, ub2 = est2.const_marginal_effect_interval(X_test, alpha=0.01)
```

### 3.3. Performance Visualization

```python
plt.figure(figsize=(15, 6))
plt.subplot(1, 2, 1)
plt.title("LinearDML with 2d-Poly Features")
plt.plot(X_test[:, 0], te_pred[:, 0], label='DML estimate1')
plt.fill_between(X_test[:, 0], lb[:, 0], ub[:, 0], alpha=.4)
plt.plot(X_test[:, 0], te_pred[:, 1], label='DML estimate2')
plt.fill_between(X_test[:, 0], lb[:, 1], ub[:, 1], alpha=.4)
expected_te1 = np.array([x_i[0] for x_i in X_test])
expected_te2=np.array([x_i[0]**2 for x_i in X_test]).flatten()
plt.plot(X_test[:, 0], expected_te1, '--', label='True effect1')
plt.plot(X_test[:, 0], expected_te2, '--', label='True effect2')
plt.ylabel("Treatment Effect")
plt.xlabel("x")
plt.legend()
plt.subplot(1, 2, 2)
plt.title("CausalForestDML")
plt.plot(X_test[:, 0], te_pred2[:, 0], label='DML estimate1')
plt.fill_between(X_test[:, 0], lb2[:, 0], ub2[:, 0], alpha=.4)
plt.plot(X_test[:, 0], te_pred2[:, 1], label='DML estimate2')
plt.fill_between(X_test[:, 0], lb2[:, 1], ub2[:, 1], alpha=.4)
expected_te1 = np.array([x_i[0] for x_i in X_test])
expected_te2=np.array([x_i[0]**2 for x_i in X_test]).flatten()
plt.plot(X_test[:, 0], expected_te1, '--', label='True effect1')
plt.plot(X_test[:, 0], expected_te2, '--', label='True effect2')
plt.ylabel("Treatment Effect")
plt.xlabel("x")
plt.legend()
plt.show()
```

### 3.4 Tree Interpreter

Interpreting heterogeneity via a tree based rule.

```python
from econml.cate_interpreter import SingleTreeCateInterpreter

intrp = SingleTreeCateInterpreter(include_model_uncertainty=True, max_depth=2)
intrp.interpret(est, X)
```

    <econml.cate_interpreter._interpreters.SingleTreeCateInterpreter at 0x222557f8f48>

```python
plt.figure(figsize=(15, 5))
intrp.plot()
plt.show()
```

## 4. Example Usage with Single Continuous Treatment Observational Data

We applied our technique to Dominick’s dataset, a popular historical dataset of store-level orange juice prices and sales provided by University of Chicago Booth School of Business.

The dataset is comprised of a large number of covariates $W$, but researchers might only be interested in learning the elasticity of demand as a function of a few variables $x$ such
as income or education.

We applied the `LinearDML` to estimate orange juice price elasticity
as a function of income, and our results, unveil the natural phenomenon that lower income consumers are more price-sensitive.

### 4.1. Data

```python
# A few more imports
import os
import pandas as pd
import urllib.request
from sklearn.preprocessing import StandardScaler
```

```python
# Import the data
file_name = "oj_large.csv"

if not os.path.isfile(file_name):
    print("Downloading file (this might take a few seconds)...")
    urllib.request.urlretrieve("https://msalicedatapublic.blob.core.windows.net/datasets/OrangeJuice/oj_large.csv", file_name)
oj_data = pd.read_csv(file_name)
```

```python
oj_data.head()
```

      store
      brand
      week
      logmove
      feat
      price
      AGE60
      EDUC
      ETHNIC
      INCOME
      HHLARGE
      WORKWOM
      HVAL150
      SSTRDIST
      SSTRVOL
      CPDIST5
      CPWVOL5

      0
      2
      tropicana
      40
      9.018695
      0
      3.87
      0.232865
      0.248935
      0.11428
      10.553205
      0.103953
      0.303585
      0.463887
      2.110122
      1.142857
      1.92728
      0.376927

      1
      2
      tropicana
      46
      8.723231
      0
      3.87
      0.232865
      0.248935
      0.11428
      10.553205
      0.103953
      0.303585
      0.463887
      2.110122
      1.142857
      1.92728
      0.376927

      2
      2
      tropicana
      47
      8.253228
      0
      3.87
      0.232865
      0.248935
      0.11428
      10.553205
      0.103953
      0.303585
      0.463887
      2.110122
      1.142857
      1.92728
      0.376927

      3
      2
      tropicana
      48
      8.987197
      0
      3.87
      0.232865
      0.248935
      0.11428
      10.553205
      0.103953
      0.303585
      0.463887
      2.110122
      1.142857
      1.92728
      0.376927

      4
      2
      tropicana
      50
      9.093357
      0
      3.87
      0.232865
      0.248935
      0.11428
      10.553205
      0.103953
      0.303585
      0.463887
      2.110122
      1.142857
      1.92728
      0.376927

```python
# Prepare data
Y = oj_data['logmove'].values
T = np.log(oj_data["price"]).values
scaler = StandardScaler()
W1 = scaler.fit_transform(oj_data[[c for c in oj_data.columns if c not in ['price', 'logmove', 'brand', 'week', 'store','INCOME']]].values)
W2 = pd.get_dummies(oj_data[['brand']]).values
W = np.concatenate([W1, W2], axis=1)
X=scaler.fit_transform(oj_data[['INCOME']].values)
```

```python
## Generate test data
min_income = -1
max_income = 1
delta = (1 - (-1)) / 100
X_test = np.arange(min_income, max_income + delta - 0.001, delta).reshape(-1,1)
```

### 4.2. Train Estimator

```python
est = LinearDML(model_y=RandomForestRegressor(),model_t=RandomForestRegressor())
est.fit(Y, T, X=X, W=W)
te_pred=est.effect(X_test)
```

### 4.3. Performance Visualization

```python
# Plot Oranje Juice elasticity as a function of income
plt.figure(figsize=(10,6))
plt.plot(X_test, te_pred, label="OJ Elasticity")
plt.xlabel(r'Scale(Income)')
plt.ylabel('Orange Juice Elasticity')
plt.legend()
plt.title("Orange Juice Elasticity vs Income")
plt.show()
```

### 4.4. Confidence Intervals

We can also get confidence intervals around our predictions by passing an additional `inference` argument to `fit`.  All estimators support bootstrap intervals, which involves refitting the same estimator repeatedly on subsamples of the original data, but `LinearDML` also supports a more efficient approach which can be achieved by leaving inference set to the default of `'auto'` or by explicitly passing `inference='statsmodels'`.

```python
est.fit(Y, T, X=X, W=W)
te_pred=est.effect(X_test)
te_pred_interval = est.const_marginal_effect_interval(X_test, alpha=0.02)
```

```python
# Plot Oranje Juice elasticity as a function of income
plt.figure(figsize=(10,6))
plt.plot(X_test.flatten(), te_pred, label="OJ Elasticity")
plt.fill_between(X_test.flatten(), te_pred_interval[0], te_pred_interval[1], alpha=.5, label="1-99% CI")
plt.xlabel(r'Scale(Income)')
plt.ylabel('Orange Juice Elasticity')
plt.title("Orange Juice Elasticity vs Income")
plt.legend()
plt.show()
```

## 5. Example Usage with Multiple Continuous Treatment, Multiple Outcome Observational Data

We use the same data, but in this case, we want to fit the demand of multiple brand as a function of the price of each one of them, i.e. fit the matrix of cross price elasticities. It can be done, by simply setting as $Y$ to be the vector of demands and $T$ to be the vector of prices. Then we can obtain the matrix of cross price elasticities.

\begin{align}
Y=[Logmove_{tropicana},Logmove_{minute.maid},Logmove_{dominicks}] \\
T=[Logprice_{tropicana},Logprice_{minute.maid},Logprice_{dominicks}] \\
\end{align}

### 5.1. Data

```python
# Import the data
oj_data = pd.read_csv(file_name)
```

```python
# Prepare data
oj_data['price'] = np.log(oj_data["price"])
# Transform dataset.
# For each store in each week, get a vector of logmove and a vector of logprice for each brand.
# Other features are store specific, will be the same for all brands.
groupbylist = ["store", "week", "AGE60", "EDUC", "ETHNIC", "INCOME",
               "HHLARGE", "WORKWOM", "HVAL150",
               "SSTRDIST", "SSTRVOL", "CPDIST5", "CPWVOL5"]
oj_data1 = pd.pivot_table(oj_data,index=groupbylist,
                          columns=oj_data.groupby(groupbylist).cumcount(),
                          values=['logmove', 'price'],
                          aggfunc='sum').reset_index()
oj_data1.columns = oj_data1.columns.map('{0[0]}{0[1]}'.format)
oj_data1 = oj_data1.rename(index=str,
                           columns={"logmove0": "logmove_T",
                                    "logmove1": "logmove_M",
                                    "logmove2":"logmove_D",
                                    "price0":"price_T",
                                    "price1":"price_M",
                                    "price2":"price_D"})

# Define Y,T,X,W
Y = oj_data1[['logmove_T', "logmove_M", "logmove_D"]].values
T = oj_data1[['price_T', "price_M", "price_D"]].values
scaler = StandardScaler()
W=scaler.fit_transform(oj_data1[[c for c in groupbylist if c not in ['week', 'store', 'INCOME']]].values)
X=scaler.fit_transform(oj_data1[['INCOME']].values)
```

```python
## Generate test data
min_income = -1
max_income = 1
delta = (1 - (-1)) / 100
X_test = np.arange(min_income, max_income + delta - 0.001, delta).reshape(-1, 1)
```

### 5.2. Train Estimator

```python
est = LinearDML(model_y=MultiTaskElasticNetCV(cv=3, tol=1, selection='random'),
                model_t=MultiTaskElasticNetCV(cv=3),
                featurizer=PolynomialFeatures(1),
                linear_first_stages=True)
est.fit(Y, T, X=X, W=W)
te_pred = est.const_marginal_effect(X_test)
```

### 5.3. Performance Visualization

```python
# Plot Oranje Juice elasticity as a function of income
plt.figure(figsize=(18, 10))
dic={0:"Tropicana", 1:"Minute.maid", 2:"Dominicks"}
for i in range(3):
    for j in range(3):
        plt.subplot(3, 3, 3 * i + j + 1)
        plt.plot(X_test, te_pred[:, i, j],
                 color="C{}".format(str(3 * i + j)),
                 label="OJ Elasticity {} to {}".format(dic[j], dic[i]))
        plt.xlabel(r'Scale(Income)')
        plt.ylabel('Orange Juice Elasticity')
        plt.legend()
plt.suptitle("Orange Juice Elasticity vs Income", fontsize=16)
plt.show()
```

**Findings**: Look at the diagonal of the matrix, the TE of OJ prices are always negative to the sales across all the brand, but people with higher income are less price-sensitive. By contrast, for the non-diagonal of the matrix, the TE of prices for other brands are always positive to the sales for that brand, the TE is affected by income in different ways for different competitors. In addition, compare to previous plot, the negative TE of OJ prices for each brand are all larger than the TE considering all brand together, which means we would have underestimated the effect of price changes on demand.

### 5.4. Confidence Intervals

```python
te_pred_interval = est.const_marginal_effect_interval(X_test, alpha=0.02)
```

```python
# Plot Oranje Juice elasticity as a function of income
plt.figure(figsize=(18, 10))
dic={0:"Tropicana", 1:"Minute.maid", 2:"Dominicks"}
for i in range(3):
    for j in range(3):
        plt.subplot(3, 3, 3 * i + j + 1)
        plt.plot(X_test, te_pred[:, i, j],
                 color="C{}".format(str(3 * i + j)),
                 label="OJ Elasticity {} to {}".format(dic[j], dic[i]))
        plt.fill_between(X_test.flatten(), te_pred_interval[0][:, i, j],te_pred_interval[1][:, i,j], color="C{}".format(str(3*i+j)),alpha=.5, label="1-99% CI")
        plt.xlabel(r'Scale(Income)')
        plt.ylabel('Orange Juice Elasticity')
        plt.legend()
plt.suptitle("Orange Juice Elasticity vs Income",fontsize=16)
plt.show()
```

notebooks/Double Machine Learning Examples.ipynb


# Models and Pricing of Financial Derivatives HW_05

**11510691 程远星$\DeclareMathOperator*{\argmin}{argmin}
\DeclareMathOperator*{\argmax}{argmax}
\DeclareMathOperator*{\plim}{plim}
\newcommand{\ffrac}{\displaystyle \frac}
\newcommand{\d}[1]{\displaystyle{#1}}
\newcommand{\space}{\text{ }}
\newcommand{\bspace}{\;\;\;\;}
\newcommand{\bbspace}{\;\;\;\;\;\;\;\;}
\newcommand{\QQQ}{\boxed{?\:}}
\newcommand{\void}{\left.\right.}
\newcommand{\Tran}[1]{{#1}^{\mathrm{T}}}
\newcommand{\CB}[1]{\left\{ #1 \right\}}
\newcommand{\SB}[1]{\left[ #1 \right]}
\newcommand{\P}[1]{\left( #1 \right)}
\newcommand{\abs}[1]{\left| #1 \right|}
\newcommand{\norm}[1]{\left\| #1 \right\|}
\newcommand{\given}[1]{\left. #1 \right|}
\newcommand{\using}[1]{\stackrel{\mathrm{#1}}{=}}
\newcommand{\asim}{\overset{\text{a}}{\sim}}
\newcommand{\RR}{\mathbb{R}}
\newcommand{\EE}{\mathbb{E}}
\newcommand{\II}{\mathbb{I}}
\newcommand{\NN}{\mathbb{N}}
\newcommand{\ZZ}{\mathbb{Z}}
\newcommand{\QQ}{\mathbb{Q}}
\newcommand{\PP}{\mathbb{P}}
\newcommand{\AcA}{\mathcal{A}}
\newcommand{\FcF}{\mathcal{F}}
\newcommand{\AsA}{\mathscr{A}}
\newcommand{\FsF}{\mathscr{F}}
\newcommand{\dd}{\mathrm{d}}
\newcommand{\I}[1]{\mathrm{I}\left( #1 \right)}
\newcommand{\N}[1]{\mathcal{N}\left( #1 \right)}
\newcommand{\Exp}[1]{\mathrm{E}\left[ #1 \right]}
\newcommand{\Var}[1]{\mathrm{Var}\left[ #1 \right]}
\newcommand{\Avar}[1]{\mathrm{Avar}\left[ #1 \right]}
\newcommand{\Cov}[1]{\mathrm{Cov}\left( #1 \right)}
\newcommand{\Corr}[1]{\mathrm{Corr}\left( #1 \right)}
\newcommand{\ExpH}{\mathrm{E}}
\newcommand{\VarH}{\mathrm{Var}}
\newcommand{\AVarH}{\mathrm{Avar}}
\newcommand{\CovH}{\mathrm{Cov}}
\newcommand{\CorrH}{\mathrm{Corr}}
\newcommand{\ow}{\text{otherwise}}
\newcommand{\wp}{\text{with probability }}
\newcommand{\FSD}{\text{FSD}}
\newcommand{\SSD}{\text{SSD}}
\void^\dagger$**

## Question 1

$$\P{S_T -\min_{0\leq t\leq T}S_t} + \P{\max_{0\leq t\leq T}S_t - S_T} = \max_{0\leq t\leq T}S_t - \min_{0\leq t\leq T}S_t$$

## Question 2

$$\ExpH^\QQ_t\SB{e^{-r\P{T-t}} \P{S_T- K}^+ \mathbf 1_{\CB{\min\limits_{0\leq t\leq T}\;\:S_t \leq L}}} + \ExpH^\QQ_t\SB{e^{-r\P{T-t}} \P{S_T- K}^+ \mathbf 1_{\CB{\min\limits_{0\leq t\leq T}\;\:S_t > L}}} = \ExpH^\QQ_t\SB{e^{-r\P{T-t}} \P{S_T- K}^+}$$

$\bspace$The left hand side is just $c_{\text{di}} + c_{\text{do}}$ and the right hand side is the regular $c$. And surely this equation does not hold for American calls since $C=c$, $C_{\text{do}} \geq c_{\text{do}}$ and $C_{\text{di}} \geq c_{\text{di}}$.

## Question 3

$$\begin{align}
&\ExpH^\QQ\SB{e^{-r\P{T-0}}\P{1.1\cdot S_{t_{\void_1}} - S_T}^+ \mid \FcF_0}\\
=\;& \ExpH^\QQ\SB{e^{-rt_{\void_1}} \ExpH^\QQ \SB{e^{-r\P{T-t_{\void_1}}}\P{1.1\cdot S_{t_{\void_1}} - S_T}^+ \mid \FcF_{t_{\void_1}}} \mid \FcF_0}\\
=\;& \ExpH^\QQ_0\SB{p\P{t_1,S_{t_{\void_1}},T,1.1\cdot S_{t_{\void_1}}} e^{-rt_{\void_1}}}\\
=\;& \ExpH^\QQ_0\SB{ \ffrac{S_{t_{\void_1}}}{e^{rt_{\void_1}}} p\P{t_1,1,T,1.1}},\bspace \text{martingale}\\
=\;& S_{t_{\void_0}} \cdot p\P{t_1,1,T,1.1} = S_0 \cdot p\P{t_1,1,T,1.1}\\
=\;& p\P{t_1,S_0,T,1.1\cdot S_0}\\
=\;& S_0 \N{d_1'} - 1.1\cdot S_0 e^{-r\P{T-t_{\void_1}}}\N{d_2'}\\
d_{1,2} = \;& \ffrac{1}{\sigma\sqrt{T-t_1}} \P{\log \ffrac{1}{1.1} + \P{r\pm\ffrac{\sigma^2}{2}}\P{T-t_1}}
\end{align}$$

## Question 4

$\bspace$Consider this as an exchange option with $S_0^\P2 = 380$ and $S_0^\P1 = 100\times4 = 400$.

$$\begin{align}
\sigma^2 &= \sigma_1^2+\sigma^2 - \rho\sigma_1\sigma_2 = 0.2^2+0.2^2-2\times0.7\times0.2^2 = 0.024\\
\hat d_1 &= \ffrac{1}{\sigma\sqrt{T-0}} \SB{\log\P{\ffrac{S_0^{\P2}}{S_0^{\P1}}}+ \ffrac{1}{2}\sigma^2\P{T-0}} = \ffrac{1}{\sqrt{0.024}}\P{\log\ffrac{380}{400} + \ffrac{1}{2}\times 0.024 \times 1} = -0.253637\\
\hat d_2 &= \ffrac{1}{\sigma\sqrt{T-0}} \SB{\log\P{\ffrac{S_0^{\P2}}{S_0^{\P1}}}- \ffrac{1}{2}\sigma^2\P{T-0}} = \ffrac{1}{\sqrt{0.024}}\P{\log\ffrac{380}{400} - \ffrac{1}{2}\times 0.024 \times 1} = -0.408556
\end{align}$$

$$c\P{t,S_0^{\P1},S_0^{\P2}} = S_0^{\P2} \N{\hat d_1} - S_0^{\P1} \N{\hat d_2} = 380\N{-0.253637} - 400\N{-0.408556} = 15.384386$$

## Question 5

$\bspace$We'll calculate the greeks of the portfolio first.

$$\begin{align}
\Gamma&= -1000\times 2.2-500\times 0.6 - 2000\times 1.3 - 500\times 1.8 = -6000\\
\Delta&= -1000\times 0.5 - 500\times0.8-2000\times\P{-0.4}-500\times 0.7 = -450\\
N&= -1000\times 1.8 - 500 \times0.5-2000\times 0.7 - 500 \times 1.4 = -4000
\end{align}$$

$\P1$

$\bspace$Gamma Neutral can be reached by including in the portfolio a long position of $\ffrac{6000}{1.5} = 4000$ in the traded option. Then the Delta moves to $-450+4000\times 0.6 = 1950$. Thus, $1950$ units of the underlying asset must be sold from the portfolio to keep it Delta Neutral.

$\P2$

$\bspace$Vega Neutral can be reached by including in the portfolio a long position of $\ffrac{4000}{0.8} = 5000$ in the traded option. Then the Delta moves to $-450+5000\times 0.6 = 2550$. Thus, $2550$ units of the underlying asset must be sold from the portfolio to keep it Delta Neutral.

## Question 6

$\P1$

$$\mathcal{N}'\P x = f\P x = \ffrac{1}{\sqrt{2\pi}} \exp\CB{-\ffrac{x^2}{2}}$$

$\P2$

$$\begin{align}
\ffrac{\mathcal{N}'\P{d_1}}{\mathcal{N}'\P{d_2}}&= \exp\CB{-\ffrac{d_1^2 - d_2^2}{2}}\\
&= \exp\CB{-\ffrac{4\P{\ln\P{S/K}+r\P{T-t}}\ffrac{\sigma^2}{2}\P{T-t}}{2\sigma^2\P{T-t}}}\\
&= \ffrac{K}{S}e^{-r\P{T-t}}\\
\Rightarrow S\mathcal{N}'\P{d_1} &= K e^{-r\P{T-t}} \mathcal{N}'\P{d_2}
\end{align}$$

$\P3$

$$\ffrac{\partial d_1}{\partial S} = \ffrac{\partial d_2}{\partial S} = \ffrac{1}{S\sigma\sqrt{T-t}}$$

$\P4$

$$\begin{align}
\ffrac{\partial c}{\partial t} &= S\ffrac{\partial \N{d_1}}{\partial d_1}\ffrac{\partial d_1}{\partial t} - rKe^{-r\P{T-t}}\N{d_2} - Ke^{-r\P{T-t}}\ffrac{\partial \N{d_2}}{\partial d_2}\ffrac{\partial d_2}{\partial t}\\
&= \ffrac{\partial d_1}{\partial t}\cdot K e^{-r\P{T-t}} \mathcal{N}'\P{d_2} - rKe^{-r\P{T-t}}\N{d_2} - Ke^{-r\P{T-t}}\mathcal{N}'\P{d_2}\ffrac{\partial d_2}{\partial t}\\
&= - rKe^{-r\P{T-t}}\N{d_2} + \ffrac{\partial\P{ d_1 - d_2}}{\partial t}\cdot K e^{-r\P{T-t}} \mathcal{N}'\P{d_2}\\
&= - rKe^{-r\P{T-t}}\N{d_2} + S\mathcal{N}'\P{d_1} \cdot \P{-\ffrac{\sigma}{2\sqrt{T-t}}}
\end{align}$$

$\P5$

$$\begin{align}
\ffrac{\partial c}{\partial S} &= \N{d_1} + S\mathcal{N}'\P{d_1} \cdot \ffrac{\partial d_1}{\partial S} - K e^{-r\P{T-t}} \mathcal{N}'\P{d_2}\cdot \ffrac{\partial d_2}{\partial S}\\
&= \N{d_1} + S\mathcal{N}'\P{d_1} \cdot \ffrac{\partial d_1}{\partial S} - S\mathcal{N}'\P{d_1} \cdot \ffrac{\partial d_1}{\partial S}\\
&= \N{d_1}
\end{align}$$

$\P 6$

$$\begin{align}
&\ffrac{\partial c}{\partial t} + rS\ffrac{\partial c}{\partial S} + \ffrac{1}{2}\sigma^2 S^2\ffrac{\partial^2 c}{\partial S^2} - rc \\
=\;& - rKe^{-r\P{T-t}}\N{d_2} - S\mathcal{N}'\P{d_1} \cdot \ffrac{\sigma}{2\sqrt{T-t}} + rS \cdot \N{d_1}\\
&\bspace + \ffrac{1}{2}\sigma^2 S^2 \ffrac{1}{\sigma S\sqrt{T-t}}\mathcal{N}'\P{d_1} - r\P{S\N{d_1} - Ke^{-r\P{T-t}}\N{d_2}}\\
=\;& 0+0 = 0
\end{align}$$

$\P7$

$\bspace$As $t\to T$, $d_{1,2}\to\infty$ and thus $\N{d_1} \to1$ and $\N{d_2}\to 1$. Then $c = S-K$, if the call option is exercise, and $c=0$, otherwise. So the boundary condition is $c = \max\P{S-K,0}$ as $t\to T$.

## Question 7

$\P1$

$$\ffrac{\partial\P{c+Ke^{-r\P{T-t}}}}{\partial S} = \ffrac{\partial\P{p+S}}{\partial S} \Rightarrow \ffrac{\partial c}{\partial S} = \ffrac{\partial p}{\partial S} + 1 \Rightarrow \Delta_c = \Delta_p+1$$

$\P2$

$\bspace$Following the preceding one, we differentiate it again with respect to $S$ as well and obtain

$$\ffrac{\partial^2 p}{\partial S^2} = \ffrac{\partial^2 c}{\partial S^2}\Rightarrow \Gamma_c = \Gamma_p$$

$\P3$

$$\ffrac{\partial\P{c+Ke^{-r\P{T-t}}}}{\partial \sigma} = \ffrac{\partial\P{p+S}}{\partial \sigma} \Rightarrow \ffrac{\partial c}{\partial \sigma} = \ffrac{\partial p}{\partial \sigma} \Rightarrow \nu_c = \nu_p$$

$\P4$

$$\ffrac{\partial\P{c+Ke^{-r\P{T-t}}}}{\partial t} = \ffrac{\partial\P{p+S}}{\partial t} \Rightarrow \ffrac{\partial c}{\partial t} + rK^{-r\P{T-t}}= \ffrac{\partial p}{\partial t} \Rightarrow \Theta_c + rK^{-r\P{T-t}} = \Theta_p$$

***
$\\[30em]$

```python
import math
import scipy.stats as scs
```

```python
0.04+0.04-2*0.7*0.04
```

    0.024000000000000007

```python
math.pow(0.024,-0.5)*(-0.012+math.log(38/40))
```

    -0.40855645848264455

```python
380*scs.norm.cdf(math.pow(0.024,-0.5)*(0.012+math.log(38/40)))-\
400*scs.norm.cdf(math.pow(0.024,-0.5)*(-0.012+math.log(38/40)))
```

    15.384385517742544

FinMath/Models and Pricing of Financial Derivatives/HW/HW_05_11510691.ipynb